| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capabilities/video_decode_stats_db_impl.h" |
| |
| #include <memory> |
| #include <tuple> |
| |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/sequence_checker.h" |
| #include "base/task_scheduler/post_task.h" |
| #include "components/leveldb_proto/proto_database_impl.h" |
| #include "media/capabilities/video_decode_stats.pb.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Avoid changing client name. Used in UMA. |
| // See comments in components/leveldb_proto/leveldb_database.h |
| const char kDatabaseClientName[] = "VideoDecodeStatsDB"; |
| |
| }; // namespace |
| |
| VideoDecodeStatsDBImplFactory::VideoDecodeStatsDBImplFactory( |
| base::FilePath db_dir) |
| : db_dir_(db_dir) { |
| DVLOG(2) << __func__ << " db_dir:" << db_dir_; |
| } |
| |
| VideoDecodeStatsDBImplFactory::~VideoDecodeStatsDBImplFactory() = default; |
| |
| std::unique_ptr<VideoDecodeStatsDB> VideoDecodeStatsDBImplFactory::CreateDB() { |
| std::unique_ptr<leveldb_proto::ProtoDatabase<DecodeStatsProto>> db_; |
| |
| auto proto_db = |
| std::make_unique<leveldb_proto::ProtoDatabaseImpl<DecodeStatsProto>>( |
| base::CreateSequencedTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::BACKGROUND, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})); |
| |
| return std::make_unique<VideoDecodeStatsDBImpl>(std::move(proto_db), db_dir_); |
| } |
| |
| VideoDecodeStatsDBImpl::VideoDecodeStatsDBImpl( |
| std::unique_ptr<leveldb_proto::ProtoDatabase<DecodeStatsProto>> db, |
| const base::FilePath& db_dir) |
| : db_(std::move(db)), db_dir_(db_dir), weak_ptr_factory_(this) { |
| DCHECK(db_); |
| DCHECK(!db_dir_.empty()); |
| } |
| |
| VideoDecodeStatsDBImpl::~VideoDecodeStatsDBImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void VideoDecodeStatsDBImpl::Initialize(InitializeCB init_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(init_cb); |
| DCHECK(!IsInitialized()); |
| DCHECK(!db_destroy_pending_); |
| |
| // "Simple options" will use the default global cache of 8MB. In the worst |
| // case our whole DB will be less than 35K, so we aren't worried about |
| // spamming the cache. |
| // TODO(chcunningham): Keep an eye on the size as the table evolves. |
| db_->Init(kDatabaseClientName, db_dir_, leveldb_proto::CreateSimpleOptions(), |
| base::BindOnce(&VideoDecodeStatsDBImpl::OnInit, |
| weak_ptr_factory_.GetWeakPtr(), std::move(init_cb))); |
| } |
| |
| void VideoDecodeStatsDBImpl::OnInit(InitializeCB init_cb, bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!"); |
| UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Initialize", |
| success); |
| |
| db_init_ = true; |
| |
| // Can't use DB when initialization fails. |
| if (!success) |
| db_.reset(); |
| |
| std::move(init_cb).Run(success); |
| } |
| |
| bool VideoDecodeStatsDBImpl::IsInitialized() { |
| // |db_| will be null if Initialization failed. |
| return db_init_ && db_; |
| } |
| |
| void VideoDecodeStatsDBImpl::AppendDecodeStats( |
| const VideoDescKey& key, |
| const DecodeStatsEntry& entry, |
| AppendDecodeStatsCB append_done_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsInitialized()); |
| |
| DVLOG(3) << __func__ << " Reading key " << key.ToLogString() |
| << " from DB with intent to update with " << entry.ToLogString(); |
| |
| db_->GetEntry(key.Serialize(), |
| base::BindOnce(&VideoDecodeStatsDBImpl::WriteUpdatedEntry, |
| weak_ptr_factory_.GetWeakPtr(), key, entry, |
| std::move(append_done_cb))); |
| } |
| |
| void VideoDecodeStatsDBImpl::GetDecodeStats(const VideoDescKey& key, |
| GetDecodeStatsCB get_stats_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsInitialized()); |
| |
| DVLOG(3) << __func__ << " " << key.ToLogString(); |
| |
| db_->GetEntry( |
| key.Serialize(), |
| base::BindOnce(&VideoDecodeStatsDBImpl::OnGotDecodeStats, |
| weak_ptr_factory_.GetWeakPtr(), std::move(get_stats_cb))); |
| } |
| |
| void VideoDecodeStatsDBImpl::WriteUpdatedEntry( |
| const VideoDescKey& key, |
| const DecodeStatsEntry& entry, |
| AppendDecodeStatsCB append_done_cb, |
| bool read_success, |
| std::unique_ptr<DecodeStatsProto> prev_stats_proto) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsInitialized()); |
| |
| // Note: outcome of "Write" operation logged in OnEntryUpdated(). |
| UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Read", |
| read_success); |
| |
| if (!read_success) { |
| DVLOG(2) << __func__ << " FAILED DB read for " << key.ToLogString() |
| << "; ignoring update!"; |
| std::move(append_done_cb).Run(false); |
| return; |
| } |
| |
| if (!prev_stats_proto) { |
| prev_stats_proto.reset(new DecodeStatsProto()); |
| prev_stats_proto->set_frames_decoded(0); |
| prev_stats_proto->set_frames_dropped(0); |
| prev_stats_proto->set_frames_decoded_power_efficient(0); |
| } |
| |
| uint64_t sum_frames_decoded = |
| prev_stats_proto->frames_decoded() + entry.frames_decoded; |
| uint64_t sum_frames_dropped = |
| prev_stats_proto->frames_dropped() + entry.frames_dropped; |
| uint64_t sum_frames_decoded_power_efficient = |
| prev_stats_proto->frames_decoded_power_efficient() + |
| entry.frames_decoded_power_efficient; |
| |
| prev_stats_proto->set_frames_decoded(sum_frames_decoded); |
| prev_stats_proto->set_frames_dropped(sum_frames_dropped); |
| prev_stats_proto->set_frames_decoded_power_efficient( |
| sum_frames_decoded_power_efficient); |
| |
| DVLOG(3) << __func__ << " Updating " << key.ToLogString() << " with " |
| << entry.ToLogString() << " aggregate:" |
| << DecodeStatsEntry(sum_frames_decoded, sum_frames_dropped, |
| sum_frames_decoded_power_efficient) |
| .ToLogString(); |
| |
| using ProtoDecodeStatsEntry = leveldb_proto::ProtoDatabase<DecodeStatsProto>; |
| std::unique_ptr<ProtoDecodeStatsEntry::KeyEntryVector> entries = |
| std::make_unique<ProtoDecodeStatsEntry::KeyEntryVector>(); |
| |
| entries->emplace_back(key.Serialize(), *prev_stats_proto); |
| |
| db_->UpdateEntries(std::move(entries), |
| std::make_unique<leveldb_proto::KeyVector>(), |
| base::BindOnce(&VideoDecodeStatsDBImpl::OnEntryUpdated, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(append_done_cb))); |
| } |
| |
| void VideoDecodeStatsDBImpl::OnEntryUpdated(AppendDecodeStatsCB append_done_cb, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Write", success); |
| DVLOG(3) << __func__ << " update " << (success ? "succeeded" : "FAILED!"); |
| std::move(append_done_cb).Run(success); |
| } |
| |
| void VideoDecodeStatsDBImpl::OnGotDecodeStats( |
| GetDecodeStatsCB get_stats_cb, |
| bool success, |
| std::unique_ptr<DecodeStatsProto> stats_proto) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Read", success); |
| |
| std::unique_ptr<DecodeStatsEntry> entry; |
| if (stats_proto) { |
| DCHECK(success); |
| entry = std::make_unique<DecodeStatsEntry>( |
| stats_proto->frames_decoded(), stats_proto->frames_dropped(), |
| stats_proto->frames_decoded_power_efficient()); |
| } |
| |
| DVLOG(3) << __func__ << " read " << (success ? "succeeded" : "FAILED!") |
| << " entry: " << (entry ? entry->ToLogString() : "nullptr"); |
| |
| std::move(get_stats_cb).Run(success, std::move(entry)); |
| } |
| |
| void VideoDecodeStatsDBImpl::DestroyStats(base::OnceClosure destroy_done_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DVLOG(2) << __func__; |
| |
| // DB is no longer initialized once destruction kicks off. |
| db_init_ = false; |
| db_destroy_pending_ = true; |
| |
| db_->Destroy(base::BindOnce(&VideoDecodeStatsDBImpl::OnDestroyedStats, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(destroy_done_cb))); |
| } |
| |
| void VideoDecodeStatsDBImpl::OnDestroyedStats(base::OnceClosure destroy_done_cb, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!"); |
| |
| UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Destroy", success); |
| |
| // Allow calls to re-Intialize() now that destruction is complete. |
| DCHECK(!db_init_); |
| db_destroy_pending_ = false; |
| |
| // We don't pass success to |destroy_done_cb|. Clearing is best effort and |
| // there is no additional action for callers to take in case of failure. |
| // TODO(chcunningham): Monitor UMA and consider more aggressive action like |
| // deleting the DB directory. |
| std::move(destroy_done_cb).Run(); |
| } |
| |
| } // namespace media |