123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- // Copyright 2022 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/webrtc_video_stats_db_impl.h"
- #include <memory>
- #include <string>
- #include <tuple>
- #include "base/bind.h"
- #include "base/debug/alias.h"
- #include "base/files/file_path.h"
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/metrics/field_trial_params.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/sequence_checker.h"
- #include "base/strings/string_util.h"
- #include "base/task/thread_pool.h"
- #include "base/time/default_clock.h"
- #include "components/leveldb_proto/public/proto_database_provider.h"
- #include "media/base/media_switches.h"
- #include "media/capabilities/webrtc_video_stats.pb.h"
- namespace media {
- using ProtoVideoStatsEntry =
- leveldb_proto::ProtoDatabase<WebrtcVideoStatsEntryProto>;
- // static
- std::unique_ptr<WebrtcVideoStatsDBImpl> WebrtcVideoStatsDBImpl::Create(
- base::FilePath db_dir,
- leveldb_proto::ProtoDatabaseProvider* db_provider) {
- DVLOG(2) << __func__ << " db_dir:" << db_dir;
- auto proto_db = db_provider->GetDB<WebrtcVideoStatsEntryProto>(
- leveldb_proto::ProtoDbType::WEBRTC_VIDEO_STATS_DB, db_dir,
- base::ThreadPool::CreateSequencedTaskRunner(
- {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
- base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
- return base::WrapUnique(new WebrtcVideoStatsDBImpl(std::move(proto_db)));
- }
- WebrtcVideoStatsDBImpl::WebrtcVideoStatsDBImpl(
- std::unique_ptr<leveldb_proto::ProtoDatabase<WebrtcVideoStatsEntryProto>>
- db)
- : pending_operations_(/*uma_prefix=*/"Media.WebrtcVideoStatsDB.OpTiming."),
- db_(std::move(db)),
- wall_clock_(base::DefaultClock::GetInstance()) {
- DCHECK(db_);
- }
- WebrtcVideoStatsDBImpl::~WebrtcVideoStatsDBImpl() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- }
- void WebrtcVideoStatsDBImpl::Initialize(InitializeCB init_cb) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(init_cb);
- DCHECK(!IsInitialized());
- db_->Init(base::BindOnce(
- &WebrtcVideoStatsDBImpl::OnInit, weak_ptr_factory_.GetWeakPtr(),
- pending_operations_.Start("Initialize"), std::move(init_cb)));
- }
- void WebrtcVideoStatsDBImpl::OnInit(PendingOperations::Id op_id,
- InitializeCB init_cb,
- leveldb_proto::Enums::InitStatus status) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_NE(status, leveldb_proto::Enums::InitStatus::kInvalidOperation);
- bool success = status == leveldb_proto::Enums::InitStatus::kOK;
- DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!");
- pending_operations_.Complete(op_id);
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Initialize",
- success);
- db_init_ = true;
- // Can't use DB when initialization fails.
- if (!success)
- db_.reset();
- std::move(init_cb).Run(success);
- }
- bool WebrtcVideoStatsDBImpl::IsInitialized() {
- // `db_` will be null if Initialization failed.
- return db_init_ && db_;
- }
- void WebrtcVideoStatsDBImpl::AppendVideoStats(
- const VideoDescKey& key,
- const VideoStats& video_stats,
- AppendVideoStatsCB append_done_cb) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(IsInitialized());
- DVLOG(3) << __func__ << " Reading " << key.ToLogStringForDebug()
- << " from DB with intent to update with "
- << video_stats.ToLogString();
- db_->GetEntry(key.Serialize(),
- base::BindOnce(&WebrtcVideoStatsDBImpl::WriteUpdatedEntry,
- weak_ptr_factory_.GetWeakPtr(),
- pending_operations_.Start("Read"), key,
- video_stats, std::move(append_done_cb)));
- }
- void WebrtcVideoStatsDBImpl::GetVideoStats(const VideoDescKey& key,
- GetVideoStatsCB get_stats_cb) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(IsInitialized());
- DVLOG(3) << __func__ << " " << key.ToLogStringForDebug();
- db_->GetEntry(key.Serialize(),
- base::BindOnce(&WebrtcVideoStatsDBImpl::OnGotVideoStats,
- weak_ptr_factory_.GetWeakPtr(),
- pending_operations_.Start("Read"),
- std::move(get_stats_cb)));
- }
- void WebrtcVideoStatsDBImpl::GetVideoStatsCollection(
- const VideoDescKey& key,
- GetVideoStatsCollectionCB get_stats_cb) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(IsInitialized());
- DVLOG(3) << __func__ << " " << key.ToLogStringForDebug();
- // Filter out all entries starting as the serialized key without pixels. This
- // corresponds to all entries with the same codec profile, hardware
- // accelerate, and decode/encode.
- std::string key_without_pixels = key.SerializeWithoutPixels();
- auto key_iterator_controller = base::BindRepeating(
- [](const std::string& key_filter, const std::string& key) {
- if (base::StartsWith(key, key_filter)) {
- // Include this entry and continue the search if the key has the
- // same beginning as `key_without_pixels`.
- return leveldb_proto::Enums::kLoadAndContinue;
- } else {
- // Cancel otherwise.
- return leveldb_proto::Enums::kSkipAndStop;
- }
- },
- key_without_pixels);
- db_->LoadKeysAndEntriesWhile(
- key_without_pixels, key_iterator_controller,
- base::BindOnce(&WebrtcVideoStatsDBImpl::OnGotVideoStatsCollection,
- weak_ptr_factory_.GetWeakPtr(),
- pending_operations_.Start("Read"),
- std::move(get_stats_cb)));
- }
- bool WebrtcVideoStatsDBImpl::AreStatsValid(
- const WebrtcVideoStatsEntryProto* const stats_proto) {
- // Check for corruption.
- bool are_stats_valid = stats_proto->stats_size() > 0 &&
- stats_proto->stats_size() <= GetMaxEntriesPerConfig();
- // Verify each entry.
- double previous_timestamp = std::numeric_limits<double>::max();
- for (auto const& stats_entry : stats_proto->stats()) {
- // The stats are ordered with the latest entry first.
- are_stats_valid &= previous_timestamp > stats_entry.timestamp();
- are_stats_valid &=
- stats_entry.frames_processed() >= kFramesProcessedMinValue &&
- stats_entry.frames_processed() <= kFramesProcessedMaxValue;
- are_stats_valid &=
- stats_entry.frames_processed() >= stats_entry.key_frames_processed();
- are_stats_valid &=
- stats_entry.p99_processing_time_ms() >= kP99ProcessingTimeMinValueMs &&
- stats_entry.p99_processing_time_ms() <= kP99ProcessingTimeMaxValueMs;
- previous_timestamp = stats_entry.timestamp();
- }
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Validate",
- are_stats_valid);
- return are_stats_valid;
- }
- void WebrtcVideoStatsDBImpl::WriteUpdatedEntry(
- PendingOperations::Id op_id,
- const VideoDescKey& key,
- const VideoStats& new_video_stats,
- AppendVideoStatsCB append_done_cb,
- bool read_success,
- std::unique_ptr<WebrtcVideoStatsEntryProto> existing_entry_proto) {
- DVLOG(3) << __func__;
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(IsInitialized());
- pending_operations_.Complete(op_id);
- // Note: outcome of "Write" operation logged in OnEntryUpdated().
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Read",
- read_success);
- if (!read_success) {
- DVLOG(2) << __func__ << " FAILED DB read for " << key.ToLogStringForDebug()
- << "; ignoring update!";
- std::move(append_done_cb).Run(false);
- return;
- }
- if (!existing_entry_proto || !AreStatsValid(existing_entry_proto.get())) {
- // Default instance will not have any stats entries.
- existing_entry_proto = std::make_unique<WebrtcVideoStatsEntryProto>();
- }
- // Create a new entry, with new stats in the front and copy any existing stats
- // to the back.
- WebrtcVideoStatsEntryProto new_entry_proto;
- media::WebrtcVideoStatsProto* new_stats = new_entry_proto.add_stats();
- DCHECK(new_stats);
- new_stats->set_frames_processed(new_video_stats.frames_processed);
- new_stats->set_key_frames_processed(new_video_stats.key_frames_processed);
- new_stats->set_p99_processing_time_ms(new_video_stats.p99_processing_time_ms);
- new_stats->set_timestamp(wall_clock_->Now().ToJsTimeIgnoringNull());
- DVLOG(3) << "Adding new stats entry:" << new_stats->timestamp() << ", "
- << new_stats->frames_processed() << ", "
- << new_stats->key_frames_processed() << ", "
- << new_stats->p99_processing_time_ms();
- // Append existing entries.
- const base::TimeDelta max_time_to_keep_stats = GetMaxTimeToKeepStats();
- const int max_entries_per_config = GetMaxEntriesPerConfig();
- DCHECK_GT(max_time_to_keep_stats, base::Days(0));
- double previous_timestamp = new_stats->timestamp();
- for (auto const& existing_stats : existing_entry_proto->stats()) {
- // Discard existing stats that have expired, if the entry is full, or if the
- // timestamps come in the wrong order.
- if (wall_clock_->Now() -
- base::Time::FromJsTime(existing_stats.timestamp()) <=
- max_time_to_keep_stats &&
- new_entry_proto.stats_size() < max_entries_per_config &&
- existing_stats.timestamp() < previous_timestamp) {
- previous_timestamp = existing_stats.timestamp();
- media::WebrtcVideoStatsProto* stats = new_entry_proto.add_stats();
- DCHECK(stats);
- *stats = existing_stats;
- DVLOG(3) << " appending existing stats:" << existing_stats.timestamp()
- << ", " << existing_stats.frames_processed() << ", "
- << existing_stats.key_frames_processed() << ", "
- << existing_stats.p99_processing_time_ms();
- }
- }
- // Make sure we never write bogus stats into the DB! While its possible the DB
- // may experience some corruption (disk), we should have detected that above
- // and discarded any bad data prior to this upcoming save.
- DCHECK(AreStatsValid(&new_entry_proto));
- // Push the update to the DB.
- using DBType = leveldb_proto::ProtoDatabase<WebrtcVideoStatsEntryProto>;
- std::unique_ptr<DBType::KeyEntryVector> entries =
- std::make_unique<DBType::KeyEntryVector>();
- entries->emplace_back(key.Serialize(), new_entry_proto);
- db_->UpdateEntries(std::move(entries),
- std::make_unique<leveldb_proto::KeyVector>(),
- base::BindOnce(&WebrtcVideoStatsDBImpl::OnEntryUpdated,
- weak_ptr_factory_.GetWeakPtr(),
- pending_operations_.Start("Write"),
- std::move(append_done_cb)));
- }
- void WebrtcVideoStatsDBImpl::OnEntryUpdated(PendingOperations::Id op_id,
- AppendVideoStatsCB append_done_cb,
- bool success) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DVLOG(3) << __func__ << " update " << (success ? "succeeded" : "FAILED!");
- pending_operations_.Complete(op_id);
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Write", success);
- std::move(append_done_cb).Run(success);
- }
- void WebrtcVideoStatsDBImpl::OnGotVideoStats(
- PendingOperations::Id op_id,
- GetVideoStatsCB get_stats_cb,
- bool success,
- std::unique_ptr<WebrtcVideoStatsEntryProto> stats_proto) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DVLOG(3) << __func__ << " get " << (success ? "succeeded" : "FAILED!");
- pending_operations_.Complete(op_id);
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Read", success);
- // Convert from WebrtcVideoStatsEntryProto to VideoStatsEntry.
- absl::optional<VideoStatsEntry> entry;
- if (stats_proto && AreStatsValid(stats_proto.get())) {
- DCHECK(success);
- const base::TimeDelta max_time_to_keep_stats = GetMaxTimeToKeepStats();
- entry.emplace();
- for (auto const& stats : stats_proto->stats()) {
- if (wall_clock_->Now() - base::Time::FromJsTime(stats.timestamp()) <=
- max_time_to_keep_stats) {
- entry->emplace_back(stats.timestamp(), stats.frames_processed(),
- stats.key_frames_processed(),
- stats.p99_processing_time_ms());
- }
- }
- // Clear the pointer if all stats were expired.
- if (entry->size() == 0) {
- entry.reset();
- }
- }
- std::move(get_stats_cb).Run(success, std::move(entry));
- }
- void WebrtcVideoStatsDBImpl::OnGotVideoStatsCollection(
- PendingOperations::Id op_id,
- GetVideoStatsCollectionCB get_stats_cb,
- bool success,
- std::unique_ptr<std::map<std::string, WebrtcVideoStatsEntryProto>>
- stats_proto_collection) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DVLOG(3) << __func__ << " get " << (success ? "succeeded" : "FAILED!");
- pending_operations_.Complete(op_id);
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Read", success);
- // Convert from map of WebrtcVideoStatsEntryProto to VideoStatsCollection.
- absl::optional<VideoStatsCollection> collection;
- if (stats_proto_collection) {
- DCHECK(success);
- collection.emplace();
- const base::TimeDelta max_time_to_keep_stats = GetMaxTimeToKeepStats();
- for (auto const& [pixel_key, video_stats_entry] : *stats_proto_collection) {
- if (AreStatsValid(&video_stats_entry)) {
- VideoStatsEntry entry;
- for (auto const& stats : video_stats_entry.stats()) {
- if (wall_clock_->Now() - base::Time::FromJsTime(stats.timestamp()) <=
- max_time_to_keep_stats) {
- entry.emplace_back(stats.timestamp(), stats.frames_processed(),
- stats.key_frames_processed(),
- stats.p99_processing_time_ms());
- }
- }
- if (!entry.empty()) {
- absl::optional<int> pixels =
- VideoDescKey::ParsePixelsFromKey(pixel_key);
- if (pixels) {
- collection->insert({*pixels, std::move(entry)});
- }
- }
- }
- }
- if (collection->empty()) {
- collection.reset();
- }
- }
- std::move(get_stats_cb).Run(success, std::move(collection));
- }
- void WebrtcVideoStatsDBImpl::ClearStats(base::OnceClosure clear_done_cb) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DVLOG(2) << __func__;
- db_->UpdateEntriesWithRemoveFilter(
- std::make_unique<ProtoVideoStatsEntry::KeyEntryVector>(),
- base::BindRepeating([](const std::string& key) { return true; }),
- base::BindOnce(&WebrtcVideoStatsDBImpl::OnStatsCleared,
- weak_ptr_factory_.GetWeakPtr(),
- pending_operations_.Start("Clear"),
- std::move(clear_done_cb)));
- }
- void WebrtcVideoStatsDBImpl::OnStatsCleared(PendingOperations::Id op_id,
- base::OnceClosure clear_done_cb,
- bool success) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!");
- pending_operations_.Complete(op_id);
- UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Clear", success);
- // We don't pass success to `clear_done_cb`. Clearing is best effort and
- // there is no additional action for callers to take in case of failure.
- std::move(clear_done_cb).Run();
- }
- } // namespace media
|