webrtc_video_stats_db_impl.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "media/capabilities/webrtc_video_stats_db_impl.h"
  5. #include <memory>
  6. #include <string>
  7. #include <tuple>
  8. #include "base/bind.h"
  9. #include "base/debug/alias.h"
  10. #include "base/files/file_path.h"
  11. #include "base/logging.h"
  12. #include "base/memory/ptr_util.h"
  13. #include "base/metrics/field_trial_params.h"
  14. #include "base/metrics/histogram_functions.h"
  15. #include "base/metrics/histogram_macros.h"
  16. #include "base/sequence_checker.h"
  17. #include "base/strings/string_util.h"
  18. #include "base/task/thread_pool.h"
  19. #include "base/time/default_clock.h"
  20. #include "components/leveldb_proto/public/proto_database_provider.h"
  21. #include "media/base/media_switches.h"
  22. #include "media/capabilities/webrtc_video_stats.pb.h"
  23. namespace media {
  24. using ProtoVideoStatsEntry =
  25. leveldb_proto::ProtoDatabase<WebrtcVideoStatsEntryProto>;
  26. // static
  27. std::unique_ptr<WebrtcVideoStatsDBImpl> WebrtcVideoStatsDBImpl::Create(
  28. base::FilePath db_dir,
  29. leveldb_proto::ProtoDatabaseProvider* db_provider) {
  30. DVLOG(2) << __func__ << " db_dir:" << db_dir;
  31. auto proto_db = db_provider->GetDB<WebrtcVideoStatsEntryProto>(
  32. leveldb_proto::ProtoDbType::WEBRTC_VIDEO_STATS_DB, db_dir,
  33. base::ThreadPool::CreateSequencedTaskRunner(
  34. {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
  35. base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
  36. return base::WrapUnique(new WebrtcVideoStatsDBImpl(std::move(proto_db)));
  37. }
  38. WebrtcVideoStatsDBImpl::WebrtcVideoStatsDBImpl(
  39. std::unique_ptr<leveldb_proto::ProtoDatabase<WebrtcVideoStatsEntryProto>>
  40. db)
  41. : pending_operations_(/*uma_prefix=*/"Media.WebrtcVideoStatsDB.OpTiming."),
  42. db_(std::move(db)),
  43. wall_clock_(base::DefaultClock::GetInstance()) {
  44. DCHECK(db_);
  45. }
  46. WebrtcVideoStatsDBImpl::~WebrtcVideoStatsDBImpl() {
  47. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  48. }
  49. void WebrtcVideoStatsDBImpl::Initialize(InitializeCB init_cb) {
  50. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  51. DCHECK(init_cb);
  52. DCHECK(!IsInitialized());
  53. db_->Init(base::BindOnce(
  54. &WebrtcVideoStatsDBImpl::OnInit, weak_ptr_factory_.GetWeakPtr(),
  55. pending_operations_.Start("Initialize"), std::move(init_cb)));
  56. }
  57. void WebrtcVideoStatsDBImpl::OnInit(PendingOperations::Id op_id,
  58. InitializeCB init_cb,
  59. leveldb_proto::Enums::InitStatus status) {
  60. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  61. DCHECK_NE(status, leveldb_proto::Enums::InitStatus::kInvalidOperation);
  62. bool success = status == leveldb_proto::Enums::InitStatus::kOK;
  63. DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!");
  64. pending_operations_.Complete(op_id);
  65. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Initialize",
  66. success);
  67. db_init_ = true;
  68. // Can't use DB when initialization fails.
  69. if (!success)
  70. db_.reset();
  71. std::move(init_cb).Run(success);
  72. }
  73. bool WebrtcVideoStatsDBImpl::IsInitialized() {
  74. // `db_` will be null if Initialization failed.
  75. return db_init_ && db_;
  76. }
  77. void WebrtcVideoStatsDBImpl::AppendVideoStats(
  78. const VideoDescKey& key,
  79. const VideoStats& video_stats,
  80. AppendVideoStatsCB append_done_cb) {
  81. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  82. DCHECK(IsInitialized());
  83. DVLOG(3) << __func__ << " Reading " << key.ToLogStringForDebug()
  84. << " from DB with intent to update with "
  85. << video_stats.ToLogString();
  86. db_->GetEntry(key.Serialize(),
  87. base::BindOnce(&WebrtcVideoStatsDBImpl::WriteUpdatedEntry,
  88. weak_ptr_factory_.GetWeakPtr(),
  89. pending_operations_.Start("Read"), key,
  90. video_stats, std::move(append_done_cb)));
  91. }
  92. void WebrtcVideoStatsDBImpl::GetVideoStats(const VideoDescKey& key,
  93. GetVideoStatsCB get_stats_cb) {
  94. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  95. DCHECK(IsInitialized());
  96. DVLOG(3) << __func__ << " " << key.ToLogStringForDebug();
  97. db_->GetEntry(key.Serialize(),
  98. base::BindOnce(&WebrtcVideoStatsDBImpl::OnGotVideoStats,
  99. weak_ptr_factory_.GetWeakPtr(),
  100. pending_operations_.Start("Read"),
  101. std::move(get_stats_cb)));
  102. }
  103. void WebrtcVideoStatsDBImpl::GetVideoStatsCollection(
  104. const VideoDescKey& key,
  105. GetVideoStatsCollectionCB get_stats_cb) {
  106. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  107. DCHECK(IsInitialized());
  108. DVLOG(3) << __func__ << " " << key.ToLogStringForDebug();
  109. // Filter out all entries starting as the serialized key without pixels. This
  110. // corresponds to all entries with the same codec profile, hardware
  111. // accelerate, and decode/encode.
  112. std::string key_without_pixels = key.SerializeWithoutPixels();
  113. auto key_iterator_controller = base::BindRepeating(
  114. [](const std::string& key_filter, const std::string& key) {
  115. if (base::StartsWith(key, key_filter)) {
  116. // Include this entry and continue the search if the key has the
  117. // same beginning as `key_without_pixels`.
  118. return leveldb_proto::Enums::kLoadAndContinue;
  119. } else {
  120. // Cancel otherwise.
  121. return leveldb_proto::Enums::kSkipAndStop;
  122. }
  123. },
  124. key_without_pixels);
  125. db_->LoadKeysAndEntriesWhile(
  126. key_without_pixels, key_iterator_controller,
  127. base::BindOnce(&WebrtcVideoStatsDBImpl::OnGotVideoStatsCollection,
  128. weak_ptr_factory_.GetWeakPtr(),
  129. pending_operations_.Start("Read"),
  130. std::move(get_stats_cb)));
  131. }
  132. bool WebrtcVideoStatsDBImpl::AreStatsValid(
  133. const WebrtcVideoStatsEntryProto* const stats_proto) {
  134. // Check for corruption.
  135. bool are_stats_valid = stats_proto->stats_size() > 0 &&
  136. stats_proto->stats_size() <= GetMaxEntriesPerConfig();
  137. // Verify each entry.
  138. double previous_timestamp = std::numeric_limits<double>::max();
  139. for (auto const& stats_entry : stats_proto->stats()) {
  140. // The stats are ordered with the latest entry first.
  141. are_stats_valid &= previous_timestamp > stats_entry.timestamp();
  142. are_stats_valid &=
  143. stats_entry.frames_processed() >= kFramesProcessedMinValue &&
  144. stats_entry.frames_processed() <= kFramesProcessedMaxValue;
  145. are_stats_valid &=
  146. stats_entry.frames_processed() >= stats_entry.key_frames_processed();
  147. are_stats_valid &=
  148. stats_entry.p99_processing_time_ms() >= kP99ProcessingTimeMinValueMs &&
  149. stats_entry.p99_processing_time_ms() <= kP99ProcessingTimeMaxValueMs;
  150. previous_timestamp = stats_entry.timestamp();
  151. }
  152. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Validate",
  153. are_stats_valid);
  154. return are_stats_valid;
  155. }
  156. void WebrtcVideoStatsDBImpl::WriteUpdatedEntry(
  157. PendingOperations::Id op_id,
  158. const VideoDescKey& key,
  159. const VideoStats& new_video_stats,
  160. AppendVideoStatsCB append_done_cb,
  161. bool read_success,
  162. std::unique_ptr<WebrtcVideoStatsEntryProto> existing_entry_proto) {
  163. DVLOG(3) << __func__;
  164. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  165. DCHECK(IsInitialized());
  166. pending_operations_.Complete(op_id);
  167. // Note: outcome of "Write" operation logged in OnEntryUpdated().
  168. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Read",
  169. read_success);
  170. if (!read_success) {
  171. DVLOG(2) << __func__ << " FAILED DB read for " << key.ToLogStringForDebug()
  172. << "; ignoring update!";
  173. std::move(append_done_cb).Run(false);
  174. return;
  175. }
  176. if (!existing_entry_proto || !AreStatsValid(existing_entry_proto.get())) {
  177. // Default instance will not have any stats entries.
  178. existing_entry_proto = std::make_unique<WebrtcVideoStatsEntryProto>();
  179. }
  180. // Create a new entry, with new stats in the front and copy any existing stats
  181. // to the back.
  182. WebrtcVideoStatsEntryProto new_entry_proto;
  183. media::WebrtcVideoStatsProto* new_stats = new_entry_proto.add_stats();
  184. DCHECK(new_stats);
  185. new_stats->set_frames_processed(new_video_stats.frames_processed);
  186. new_stats->set_key_frames_processed(new_video_stats.key_frames_processed);
  187. new_stats->set_p99_processing_time_ms(new_video_stats.p99_processing_time_ms);
  188. new_stats->set_timestamp(wall_clock_->Now().ToJsTimeIgnoringNull());
  189. DVLOG(3) << "Adding new stats entry:" << new_stats->timestamp() << ", "
  190. << new_stats->frames_processed() << ", "
  191. << new_stats->key_frames_processed() << ", "
  192. << new_stats->p99_processing_time_ms();
  193. // Append existing entries.
  194. const base::TimeDelta max_time_to_keep_stats = GetMaxTimeToKeepStats();
  195. const int max_entries_per_config = GetMaxEntriesPerConfig();
  196. DCHECK_GT(max_time_to_keep_stats, base::Days(0));
  197. double previous_timestamp = new_stats->timestamp();
  198. for (auto const& existing_stats : existing_entry_proto->stats()) {
  199. // Discard existing stats that have expired, if the entry is full, or if the
  200. // timestamps come in the wrong order.
  201. if (wall_clock_->Now() -
  202. base::Time::FromJsTime(existing_stats.timestamp()) <=
  203. max_time_to_keep_stats &&
  204. new_entry_proto.stats_size() < max_entries_per_config &&
  205. existing_stats.timestamp() < previous_timestamp) {
  206. previous_timestamp = existing_stats.timestamp();
  207. media::WebrtcVideoStatsProto* stats = new_entry_proto.add_stats();
  208. DCHECK(stats);
  209. *stats = existing_stats;
  210. DVLOG(3) << " appending existing stats:" << existing_stats.timestamp()
  211. << ", " << existing_stats.frames_processed() << ", "
  212. << existing_stats.key_frames_processed() << ", "
  213. << existing_stats.p99_processing_time_ms();
  214. }
  215. }
  216. // Make sure we never write bogus stats into the DB! While its possible the DB
  217. // may experience some corruption (disk), we should have detected that above
  218. // and discarded any bad data prior to this upcoming save.
  219. DCHECK(AreStatsValid(&new_entry_proto));
  220. // Push the update to the DB.
  221. using DBType = leveldb_proto::ProtoDatabase<WebrtcVideoStatsEntryProto>;
  222. std::unique_ptr<DBType::KeyEntryVector> entries =
  223. std::make_unique<DBType::KeyEntryVector>();
  224. entries->emplace_back(key.Serialize(), new_entry_proto);
  225. db_->UpdateEntries(std::move(entries),
  226. std::make_unique<leveldb_proto::KeyVector>(),
  227. base::BindOnce(&WebrtcVideoStatsDBImpl::OnEntryUpdated,
  228. weak_ptr_factory_.GetWeakPtr(),
  229. pending_operations_.Start("Write"),
  230. std::move(append_done_cb)));
  231. }
  232. void WebrtcVideoStatsDBImpl::OnEntryUpdated(PendingOperations::Id op_id,
  233. AppendVideoStatsCB append_done_cb,
  234. bool success) {
  235. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  236. DVLOG(3) << __func__ << " update " << (success ? "succeeded" : "FAILED!");
  237. pending_operations_.Complete(op_id);
  238. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Write", success);
  239. std::move(append_done_cb).Run(success);
  240. }
  241. void WebrtcVideoStatsDBImpl::OnGotVideoStats(
  242. PendingOperations::Id op_id,
  243. GetVideoStatsCB get_stats_cb,
  244. bool success,
  245. std::unique_ptr<WebrtcVideoStatsEntryProto> stats_proto) {
  246. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  247. DVLOG(3) << __func__ << " get " << (success ? "succeeded" : "FAILED!");
  248. pending_operations_.Complete(op_id);
  249. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Read", success);
  250. // Convert from WebrtcVideoStatsEntryProto to VideoStatsEntry.
  251. absl::optional<VideoStatsEntry> entry;
  252. if (stats_proto && AreStatsValid(stats_proto.get())) {
  253. DCHECK(success);
  254. const base::TimeDelta max_time_to_keep_stats = GetMaxTimeToKeepStats();
  255. entry.emplace();
  256. for (auto const& stats : stats_proto->stats()) {
  257. if (wall_clock_->Now() - base::Time::FromJsTime(stats.timestamp()) <=
  258. max_time_to_keep_stats) {
  259. entry->emplace_back(stats.timestamp(), stats.frames_processed(),
  260. stats.key_frames_processed(),
  261. stats.p99_processing_time_ms());
  262. }
  263. }
  264. // Clear the pointer if all stats were expired.
  265. if (entry->size() == 0) {
  266. entry.reset();
  267. }
  268. }
  269. std::move(get_stats_cb).Run(success, std::move(entry));
  270. }
  271. void WebrtcVideoStatsDBImpl::OnGotVideoStatsCollection(
  272. PendingOperations::Id op_id,
  273. GetVideoStatsCollectionCB get_stats_cb,
  274. bool success,
  275. std::unique_ptr<std::map<std::string, WebrtcVideoStatsEntryProto>>
  276. stats_proto_collection) {
  277. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  278. DVLOG(3) << __func__ << " get " << (success ? "succeeded" : "FAILED!");
  279. pending_operations_.Complete(op_id);
  280. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Read", success);
  281. // Convert from map of WebrtcVideoStatsEntryProto to VideoStatsCollection.
  282. absl::optional<VideoStatsCollection> collection;
  283. if (stats_proto_collection) {
  284. DCHECK(success);
  285. collection.emplace();
  286. const base::TimeDelta max_time_to_keep_stats = GetMaxTimeToKeepStats();
  287. for (auto const& [pixel_key, video_stats_entry] : *stats_proto_collection) {
  288. if (AreStatsValid(&video_stats_entry)) {
  289. VideoStatsEntry entry;
  290. for (auto const& stats : video_stats_entry.stats()) {
  291. if (wall_clock_->Now() - base::Time::FromJsTime(stats.timestamp()) <=
  292. max_time_to_keep_stats) {
  293. entry.emplace_back(stats.timestamp(), stats.frames_processed(),
  294. stats.key_frames_processed(),
  295. stats.p99_processing_time_ms());
  296. }
  297. }
  298. if (!entry.empty()) {
  299. absl::optional<int> pixels =
  300. VideoDescKey::ParsePixelsFromKey(pixel_key);
  301. if (pixels) {
  302. collection->insert({*pixels, std::move(entry)});
  303. }
  304. }
  305. }
  306. }
  307. if (collection->empty()) {
  308. collection.reset();
  309. }
  310. }
  311. std::move(get_stats_cb).Run(success, std::move(collection));
  312. }
  313. void WebrtcVideoStatsDBImpl::ClearStats(base::OnceClosure clear_done_cb) {
  314. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  315. DVLOG(2) << __func__;
  316. db_->UpdateEntriesWithRemoveFilter(
  317. std::make_unique<ProtoVideoStatsEntry::KeyEntryVector>(),
  318. base::BindRepeating([](const std::string& key) { return true; }),
  319. base::BindOnce(&WebrtcVideoStatsDBImpl::OnStatsCleared,
  320. weak_ptr_factory_.GetWeakPtr(),
  321. pending_operations_.Start("Clear"),
  322. std::move(clear_done_cb)));
  323. }
  324. void WebrtcVideoStatsDBImpl::OnStatsCleared(PendingOperations::Id op_id,
  325. base::OnceClosure clear_done_cb,
  326. bool success) {
  327. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  328. DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!");
  329. pending_operations_.Complete(op_id);
  330. UMA_HISTOGRAM_BOOLEAN("Media.WebrtcVideoStatsDB.OpSuccess.Clear", success);
  331. // We don't pass success to `clear_done_cb`. Clearing is best effort and
  332. // there is no additional action for callers to take in case of failure.
  333. std::move(clear_done_cb).Run();
  334. }
  335. } // namespace media