video_decode_stats_db_impl_unittest.cc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. // Copyright 2017 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 <map>
  5. #include <memory>
  6. #include "base/bind.h"
  7. #include "base/callback.h"
  8. #include "base/callback_helpers.h"
  9. #include "base/check.h"
  10. #include "base/files/file_path.h"
  11. #include "base/memory/ptr_util.h"
  12. #include "base/memory/raw_ptr.h"
  13. #include "base/run_loop.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/test/scoped_feature_list.h"
  16. #include "base/test/simple_test_clock.h"
  17. #include "base/test/task_environment.h"
  18. #include "base/time/time.h"
  19. #include "components/leveldb_proto/testing/fake_db.h"
  20. #include "media/base/media_switches.h"
  21. #include "media/base/test_data_util.h"
  22. #include "media/base/video_codecs.h"
  23. #include "media/capabilities/video_decode_stats.pb.h"
  24. #include "media/capabilities/video_decode_stats_db_impl.h"
  25. #include "testing/gmock/include/gmock/gmock.h"
  26. #include "testing/gtest/include/gtest/gtest.h"
  27. #include "ui/gfx/geometry/size.h"
  28. using leveldb_proto::test::FakeDB;
  29. using testing::_;
  30. using testing::Eq;
  31. using testing::Pointee;
  32. namespace media {
  33. class VideoDecodeStatsDBImplTest : public ::testing::Test {
  34. public:
  35. using VideoDescKey = VideoDecodeStatsDB::VideoDescKey;
  36. using DecodeStatsEntry = VideoDecodeStatsDB::DecodeStatsEntry;
  37. VideoDecodeStatsDBImplTest()
  38. : kStatsKeyVp9(VideoDescKey::MakeBucketedKey(VP9PROFILE_PROFILE3,
  39. gfx::Size(1024, 768),
  40. 60,
  41. "com.widevine.alpha",
  42. true)),
  43. kStatsKeyAvc(VideoDescKey::MakeBucketedKey(H264PROFILE_MIN,
  44. gfx::Size(1024, 768),
  45. 60,
  46. "",
  47. false)) {
  48. bool parsed_time = base::Time::FromString(
  49. VideoDecodeStatsDBImpl::kDefaultWriteTime, &kDefaultWriteTime);
  50. DCHECK(parsed_time);
  51. // Fake DB simply wraps a std::map with the LevelDB interface. We own the
  52. // map and will delete it in TearDown().
  53. fake_db_map_ = std::make_unique<FakeDB<DecodeStatsProto>::EntryMap>();
  54. // |stats_db_| will own this pointer, but we hold a reference to control
  55. // its behavior.
  56. fake_db_ = new FakeDB<DecodeStatsProto>(fake_db_map_.get());
  57. // Wrap the fake proto DB with our interface.
  58. stats_db_ = base::WrapUnique(new VideoDecodeStatsDBImpl(
  59. std::unique_ptr<FakeDB<DecodeStatsProto>>(fake_db_)));
  60. }
  61. VideoDecodeStatsDBImplTest(const VideoDecodeStatsDBImplTest&) = delete;
  62. VideoDecodeStatsDBImplTest& operator=(const VideoDecodeStatsDBImplTest&) =
  63. delete;
  64. ~VideoDecodeStatsDBImplTest() override {
  65. // Tests should always complete any pending operations
  66. VerifyNoPendingOps();
  67. }
  68. void VerifyOnePendingOp(std::string op_name) {
  69. EXPECT_EQ(stats_db_->pending_operations_.get_pending_ops_for_test().size(),
  70. 1u);
  71. PendingOperations::PendingOperation* pending_op =
  72. stats_db_->pending_operations_.get_pending_ops_for_test()
  73. .begin()
  74. ->second.get();
  75. EXPECT_EQ(pending_op->uma_str_, op_name);
  76. }
  77. void VerifyNoPendingOps() {
  78. EXPECT_TRUE(
  79. stats_db_->pending_operations_.get_pending_ops_for_test().empty());
  80. }
  81. int GetMaxFramesPerBuffer() {
  82. return VideoDecodeStatsDBImpl::GetMaxFramesPerBuffer();
  83. }
  84. int GetMaxDaysToKeepStats() {
  85. return VideoDecodeStatsDBImpl::GetMaxDaysToKeepStats();
  86. }
  87. bool GetEnableUnweightedEntries() {
  88. return VideoDecodeStatsDBImpl::GetEnableUnweightedEntries();
  89. }
  90. static base::FieldTrialParams GetFieldTrialParams() {
  91. return VideoDecodeStatsDBImpl::GetFieldTrialParams();
  92. }
  93. void SetDBClock(base::Clock* clock) {
  94. stats_db_->set_wall_clock_for_test(clock);
  95. }
  96. void InitializeDB() {
  97. stats_db_->Initialize(base::BindOnce(
  98. &VideoDecodeStatsDBImplTest::OnInitialize, base::Unretained(this)));
  99. EXPECT_CALL(*this, OnInitialize(true));
  100. fake_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
  101. testing::Mock::VerifyAndClearExpectations(this);
  102. }
  103. void AppendStats(const VideoDescKey& key, const DecodeStatsEntry& entry) {
  104. EXPECT_CALL(*this, MockAppendDecodeStatsCb(true));
  105. stats_db_->AppendDecodeStats(
  106. key, entry,
  107. base::BindOnce(&VideoDecodeStatsDBImplTest::MockAppendDecodeStatsCb,
  108. base::Unretained(this)));
  109. VerifyOnePendingOp("Read");
  110. fake_db_->GetCallback(true);
  111. VerifyOnePendingOp("Write");
  112. fake_db_->UpdateCallback(true);
  113. testing::Mock::VerifyAndClearExpectations(this);
  114. }
  115. void VerifyReadStats(const VideoDescKey& key,
  116. const DecodeStatsEntry& expected) {
  117. EXPECT_CALL(*this, MockGetDecodeStatsCb(true, Pointee(Eq(expected))));
  118. stats_db_->GetDecodeStats(
  119. key, base::BindOnce(&VideoDecodeStatsDBImplTest::GetDecodeStatsCb,
  120. base::Unretained(this)));
  121. VerifyOnePendingOp("Read");
  122. fake_db_->GetCallback(true);
  123. testing::Mock::VerifyAndClearExpectations(this);
  124. }
  125. void VerifyEmptyStats(const VideoDescKey& key) {
  126. EXPECT_CALL(*this, MockGetDecodeStatsCb(true, nullptr));
  127. stats_db_->GetDecodeStats(
  128. key, base::BindOnce(&VideoDecodeStatsDBImplTest::GetDecodeStatsCb,
  129. base::Unretained(this)));
  130. VerifyOnePendingOp("Read");
  131. fake_db_->GetCallback(true);
  132. testing::Mock::VerifyAndClearExpectations(this);
  133. }
  134. // Unwraps move-only parameters to pass to the mock function.
  135. void GetDecodeStatsCb(bool success, std::unique_ptr<DecodeStatsEntry> entry) {
  136. MockGetDecodeStatsCb(success, entry.get());
  137. }
  138. void AppendToProtoDB(const VideoDescKey& key,
  139. const DecodeStatsProto* const proto) {
  140. base::RunLoop run_loop;
  141. base::OnceCallback<void(bool)> update_done_cb = base::BindOnce(
  142. [](base::RunLoop* run_loop, bool success) {
  143. ASSERT_TRUE(success);
  144. run_loop->Quit();
  145. },
  146. Unretained(&run_loop));
  147. using DBType = leveldb_proto::ProtoDatabase<DecodeStatsProto>;
  148. std::unique_ptr<DBType::KeyEntryVector> entries =
  149. std::make_unique<DBType::KeyEntryVector>();
  150. entries->emplace_back(key.Serialize(), *proto);
  151. fake_db_->UpdateEntries(std::move(entries),
  152. std::make_unique<leveldb_proto::KeyVector>(),
  153. std::move(update_done_cb));
  154. fake_db_->UpdateCallback(true);
  155. run_loop.Run();
  156. }
  157. MOCK_METHOD1(OnInitialize, void(bool success));
  158. MOCK_METHOD2(MockGetDecodeStatsCb,
  159. void(bool success, DecodeStatsEntry* entry));
  160. MOCK_METHOD1(MockAppendDecodeStatsCb, void(bool success));
  161. MOCK_METHOD0(MockClearStatsCb, void());
  162. protected:
  163. base::test::TaskEnvironment task_environment_{
  164. base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  165. const VideoDescKey kStatsKeyVp9;
  166. const VideoDescKey kStatsKeyAvc;
  167. // Const in practice, but not marked const for compatibility with
  168. // base::Time::FromString.
  169. base::Time kDefaultWriteTime;
  170. // See documentation in SetUp()
  171. std::unique_ptr<FakeDB<DecodeStatsProto>::EntryMap> fake_db_map_;
  172. raw_ptr<FakeDB<DecodeStatsProto>> fake_db_;
  173. std::unique_ptr<VideoDecodeStatsDBImpl> stats_db_;
  174. };
  175. TEST_F(VideoDecodeStatsDBImplTest, InitializeFailed) {
  176. stats_db_->Initialize(base::BindOnce(
  177. &VideoDecodeStatsDBImplTest::OnInitialize, base::Unretained(this)));
  178. EXPECT_CALL(*this, OnInitialize(false));
  179. fake_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
  180. }
  181. TEST_F(VideoDecodeStatsDBImplTest, InitializeTimedOut) {
  182. // Queue up an Initialize.
  183. stats_db_->Initialize(base::BindOnce(
  184. &VideoDecodeStatsDBImplTest::OnInitialize, base::Unretained(this)));
  185. VerifyOnePendingOp("Initialize");
  186. // Move time forward enough to trigger timeout.
  187. EXPECT_CALL(*this, OnInitialize(_)).Times(0);
  188. task_environment_.FastForwardBy(base::Seconds(100));
  189. task_environment_.RunUntilIdle();
  190. // Verify we didn't get an init callback and task is no longer considered
  191. // pending (because it timed out).
  192. testing::Mock::VerifyAndClearExpectations(this);
  193. VerifyNoPendingOps();
  194. // Verify callback still works if init completes very late.
  195. EXPECT_CALL(*this, OnInitialize(false));
  196. fake_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
  197. }
  198. TEST_F(VideoDecodeStatsDBImplTest, ReadExpectingNothing) {
  199. InitializeDB();
  200. VerifyEmptyStats(kStatsKeyVp9);
  201. }
  202. TEST_F(VideoDecodeStatsDBImplTest, WriteReadAndClear) {
  203. InitializeDB();
  204. // Append and read back some VP9 stats.
  205. DecodeStatsEntry entry(1000, 2, 10);
  206. AppendStats(kStatsKeyVp9, entry);
  207. VerifyReadStats(kStatsKeyVp9, entry);
  208. // Reading with the wrong key (different codec) should still return nothing.
  209. VerifyEmptyStats(kStatsKeyAvc);
  210. // Appending same VP9 stats should read back as 2x the initial entry.
  211. AppendStats(kStatsKeyVp9, entry);
  212. DecodeStatsEntry aggregate_entry(2000, 4, 20);
  213. VerifyReadStats(kStatsKeyVp9, aggregate_entry);
  214. // Clear all stats from the DB.
  215. EXPECT_CALL(*this, MockClearStatsCb);
  216. stats_db_->ClearStats(base::BindOnce(
  217. &VideoDecodeStatsDBImplTest::MockClearStatsCb, base::Unretained(this)));
  218. VerifyOnePendingOp("Clear");
  219. fake_db_->UpdateCallback(true);
  220. // Database is now empty. Expect null entry.
  221. VerifyEmptyStats(kStatsKeyVp9);
  222. }
  223. TEST_F(VideoDecodeStatsDBImplTest, ConfigureMaxFramesPerBuffer) {
  224. // Setup field trial to use a tiny window of 1 decoded frame.
  225. base::test::ScopedFeatureList scoped_feature_list;
  226. std::unique_ptr<base::FieldTrialList> field_trial_list;
  227. int previous_max_frames_per_buffer = GetMaxFramesPerBuffer();
  228. int new_max_frames_per_buffer = 1;
  229. ASSERT_LT(new_max_frames_per_buffer, previous_max_frames_per_buffer);
  230. // Override field trial.
  231. base::FieldTrialParams params;
  232. params[VideoDecodeStatsDBImpl::kMaxFramesPerBufferParamName] =
  233. base::NumberToString(new_max_frames_per_buffer);
  234. scoped_feature_list.InitAndEnableFeatureWithParameters(
  235. media::kMediaCapabilitiesWithParameters, params);
  236. EXPECT_EQ(GetFieldTrialParams(), params);
  237. EXPECT_EQ(new_max_frames_per_buffer, GetMaxFramesPerBuffer());
  238. // Now verify the configured window is used by writing a frame and then
  239. // writing another.
  240. InitializeDB();
  241. // Append single frame which was, sadly, dropped and not efficient.
  242. DecodeStatsEntry entry(1, 1, 0);
  243. AppendStats(kStatsKeyVp9, entry);
  244. VerifyReadStats(kStatsKeyVp9, entry);
  245. // Appending another frame which is *not* dropped and *is* efficient.
  246. // Verify that only this last entry is still in the buffer (no aggregation).
  247. DecodeStatsEntry second_entry(1, 0, 1);
  248. AppendStats(kStatsKeyVp9, second_entry);
  249. VerifyReadStats(kStatsKeyVp9, second_entry);
  250. }
  251. TEST_F(VideoDecodeStatsDBImplTest, ConfigureExpireDays) {
  252. base::test::ScopedFeatureList scoped_feature_list;
  253. std::unique_ptr<base::FieldTrialList> field_trial_list;
  254. int previous_max_days_to_keep_stats = GetMaxDaysToKeepStats();
  255. int new_max_days_to_keep_stats = 4;
  256. ASSERT_LT(new_max_days_to_keep_stats, previous_max_days_to_keep_stats);
  257. // Override field trial.
  258. base::FieldTrialParams params;
  259. params[VideoDecodeStatsDBImpl::kMaxDaysToKeepStatsParamName] =
  260. base::NumberToString(new_max_days_to_keep_stats);
  261. scoped_feature_list.InitAndEnableFeatureWithParameters(
  262. media::kMediaCapabilitiesWithParameters, params);
  263. EXPECT_EQ(GetFieldTrialParams(), params);
  264. EXPECT_EQ(new_max_days_to_keep_stats, GetMaxDaysToKeepStats());
  265. InitializeDB();
  266. // Inject a test clock and initialize with the current time.
  267. base::SimpleTestClock clock;
  268. SetDBClock(&clock);
  269. clock.SetNow(base::Time::Now());
  270. // Append and verify read-back.
  271. AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  272. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  273. // Some simple math to avoid troubles of integer division.
  274. int half_days_to_keep_stats = new_max_days_to_keep_stats / 2;
  275. int remaining_days_to_keep_stats =
  276. new_max_days_to_keep_stats - half_days_to_keep_stats;
  277. // Advance time half way through grace period. Verify stats not expired.
  278. clock.Advance(base::Days(half_days_to_keep_stats));
  279. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  280. // Advance time 1 day beyond grace period, verify stats are expired.
  281. clock.Advance(base::Days((remaining_days_to_keep_stats) + 1));
  282. VerifyEmptyStats(kStatsKeyVp9);
  283. // Advance the clock 100 extra days. Verify stats still expired.
  284. clock.Advance(base::Days(100));
  285. VerifyEmptyStats(kStatsKeyVp9);
  286. }
  287. TEST_F(VideoDecodeStatsDBImplTest, FailedWrite) {
  288. InitializeDB();
  289. // Expect the callback to indicate success = false when the write fails.
  290. EXPECT_CALL(*this, MockAppendDecodeStatsCb(false));
  291. // Append stats, but fail the internal DB update.
  292. stats_db_->AppendDecodeStats(
  293. kStatsKeyVp9, DecodeStatsEntry(1000, 2, 10),
  294. base::BindOnce(&VideoDecodeStatsDBImplTest::MockAppendDecodeStatsCb,
  295. base::Unretained(this)));
  296. fake_db_->GetCallback(true);
  297. fake_db_->UpdateCallback(false);
  298. }
  299. TEST_F(VideoDecodeStatsDBImplTest, FillBufferInMixedIncrements) {
  300. InitializeDB();
  301. // Setup DB entry that half fills the buffer with 10% of frames dropped and
  302. // 50% of frames power efficient.
  303. const int kNumFramesEntryA = GetMaxFramesPerBuffer() / 2;
  304. DecodeStatsEntry entryA(kNumFramesEntryA, std::round(0.1 * kNumFramesEntryA),
  305. std::round(0.5 * kNumFramesEntryA));
  306. const double kDropRateA =
  307. static_cast<double>(entryA.frames_dropped) / entryA.frames_decoded;
  308. const double kEfficientRateA =
  309. static_cast<double>(entryA.frames_power_efficient) /
  310. entryA.frames_decoded;
  311. // Append entryA to half fill the buffer and verify read. Verify read.
  312. AppendStats(kStatsKeyVp9, entryA);
  313. VerifyReadStats(kStatsKeyVp9, entryA);
  314. // Append same entryA again to completely fill the buffer. Verify read gives
  315. // out aggregated stats (2x the initial entryA)
  316. AppendStats(kStatsKeyVp9, entryA);
  317. VerifyReadStats(
  318. kStatsKeyVp9,
  319. DecodeStatsEntry(GetMaxFramesPerBuffer(),
  320. std::round(kDropRateA * GetMaxFramesPerBuffer()),
  321. std::round(kEfficientRateA * GetMaxFramesPerBuffer())));
  322. // This row in the DB is now "full" (appended frames >= kMaxFramesPerBuffer)!
  323. //
  324. // Future appends will not increase the total count of decoded frames. The
  325. // ratios of dropped and power efficient frames will be a weighted average.
  326. // The weight of new appends is determined by how much of the buffer they
  327. // fill, i.e.
  328. //
  329. // new_append_weight = min(1, new_append_frame_count / kMaxFramesPerBuffer);
  330. // old_data_weight = 1 - new_append_weight;
  331. //
  332. // The calculation for dropped ratios (same for power efficient) then becomes:
  333. //
  334. // aggregate_drop_ratio = old_drop_ratio * old_drop_weight +
  335. // new_drop_ratio * new_data_weight;
  336. //
  337. // See implementation for more details.
  338. // Append same entryA a 3rd time. Verify we still only get the aggregated
  339. // stats from above (2x entryA) because the buffer is full and the ratio of
  340. // dropped and efficient frames is the same.
  341. AppendStats(kStatsKeyVp9, entryA);
  342. VerifyReadStats(
  343. kStatsKeyVp9,
  344. DecodeStatsEntry(GetMaxFramesPerBuffer(),
  345. std::round(kDropRateA * GetMaxFramesPerBuffer()),
  346. std::round(kEfficientRateA * GetMaxFramesPerBuffer())));
  347. // Append entryB that will fill just 10% of the buffer. The new entry has
  348. // different rates of dropped and power efficient frames to help verify that
  349. // it is given proper weight as it mixes with existing data in the buffer.
  350. const int kNumFramesEntryB = std::round(.1 * GetMaxFramesPerBuffer());
  351. DecodeStatsEntry entryB(kNumFramesEntryB, std::round(0.25 * kNumFramesEntryB),
  352. std::round(1 * kNumFramesEntryB));
  353. const double kDropRateB =
  354. static_cast<double>(entryB.frames_dropped) / entryB.frames_decoded;
  355. const double kEfficientRateB =
  356. static_cast<double>(entryB.frames_power_efficient) /
  357. entryB.frames_decoded;
  358. AppendStats(kStatsKeyVp9, entryB);
  359. // Verify that buffer is still full, but dropped and power efficient frame
  360. // rates are now higher according to entryB's impact (10%) on the full buffer.
  361. double mixed_drop_rate = .1 * kDropRateB + .9 * kDropRateA;
  362. double mixed_effiency_rate = .1 * kEfficientRateB + .9 * kEfficientRateA;
  363. VerifyReadStats(
  364. kStatsKeyVp9,
  365. DecodeStatsEntry(
  366. GetMaxFramesPerBuffer(),
  367. std::round(GetMaxFramesPerBuffer() * mixed_drop_rate),
  368. std::round(GetMaxFramesPerBuffer() * mixed_effiency_rate)));
  369. // After appending entryB again, verify aggregate ratios behave according to
  370. // the formula above (appending repeated entryB brings ratios closer to those
  371. // in entryB, further from entryA).
  372. AppendStats(kStatsKeyVp9, entryB);
  373. mixed_drop_rate = .1 * kDropRateB + .9 * mixed_drop_rate;
  374. mixed_effiency_rate = .1 * kEfficientRateB + .9 * mixed_effiency_rate;
  375. VerifyReadStats(
  376. kStatsKeyVp9,
  377. DecodeStatsEntry(
  378. GetMaxFramesPerBuffer(),
  379. std::round(GetMaxFramesPerBuffer() * mixed_drop_rate),
  380. std::round(GetMaxFramesPerBuffer() * mixed_effiency_rate)));
  381. // Appending entry*A* again, verify aggregate ratios behave according to
  382. // the formula above (ratio's move back in direction of those in entryA).
  383. // Since entryA fills half the buffer it gets a higher weight than entryB did
  384. // above.
  385. AppendStats(kStatsKeyVp9, entryA);
  386. mixed_drop_rate = .5 * kDropRateA + .5 * mixed_drop_rate;
  387. mixed_effiency_rate = .5 * kEfficientRateA + .5 * mixed_effiency_rate;
  388. VerifyReadStats(
  389. kStatsKeyVp9,
  390. DecodeStatsEntry(
  391. GetMaxFramesPerBuffer(),
  392. std::round(GetMaxFramesPerBuffer() * mixed_drop_rate),
  393. std::round(GetMaxFramesPerBuffer() * mixed_effiency_rate)));
  394. // Now append entryC with a frame count of 2x the buffer max. Verify entryC
  395. // gets 100% of the weight, erasing the mixed stats from earlier appends.
  396. const int kNumFramesEntryC = 2 * GetMaxFramesPerBuffer();
  397. DecodeStatsEntry entryC(kNumFramesEntryC, std::round(0.3 * kNumFramesEntryC),
  398. std::round(0.25 * kNumFramesEntryC));
  399. const double kDropRateC =
  400. static_cast<double>(entryC.frames_dropped) / entryC.frames_decoded;
  401. const double kEfficientRateC =
  402. static_cast<double>(entryC.frames_power_efficient) /
  403. entryC.frames_decoded;
  404. AppendStats(kStatsKeyVp9, entryC);
  405. VerifyReadStats(
  406. kStatsKeyVp9,
  407. DecodeStatsEntry(GetMaxFramesPerBuffer(),
  408. std::round(GetMaxFramesPerBuffer() * kDropRateC),
  409. std::round(GetMaxFramesPerBuffer() * kEfficientRateC)));
  410. }
  411. // Overfilling an empty buffer triggers the codepath to compute weighted dropped
  412. // and power efficient ratios under a circumstance where the existing counts are
  413. // all zero. This test ensures that we don't do any dividing by zero with that
  414. // empty data.
  415. TEST_F(VideoDecodeStatsDBImplTest, OverfillEmptyBuffer) {
  416. InitializeDB();
  417. // Setup DB entry that overflows the buffer max (by 1) with 10% of frames
  418. // dropped and 50% of frames power efficient.
  419. const int kNumFramesOverfill = GetMaxFramesPerBuffer() + 1;
  420. DecodeStatsEntry entryA(kNumFramesOverfill,
  421. std::round(0.1 * kNumFramesOverfill),
  422. std::round(0.5 * kNumFramesOverfill));
  423. // Append entry to completely fill the buffer and verify read.
  424. AppendStats(kStatsKeyVp9, entryA);
  425. // Read-back stats should have same ratios, but scaled such that
  426. // frames_decoded = GetMaxFramesPerBuffer().
  427. DecodeStatsEntry readBackEntryA(GetMaxFramesPerBuffer(),
  428. std::round(0.1 * GetMaxFramesPerBuffer()),
  429. std::round(0.5 * GetMaxFramesPerBuffer()));
  430. VerifyReadStats(kStatsKeyVp9, readBackEntryA);
  431. // Append another entry that again overfills with different dropped and power
  432. // efficient ratios. Verify that read-back only reflects latest entry.
  433. DecodeStatsEntry entryB(kNumFramesOverfill,
  434. std::round(0.2 * kNumFramesOverfill),
  435. std::round(0.6 * kNumFramesOverfill));
  436. AppendStats(kStatsKeyVp9, entryB);
  437. // Read-back stats should have same ratios, but scaled such that
  438. // frames_decoded = GetMaxFramesPerBuffer().
  439. DecodeStatsEntry readBackEntryB(GetMaxFramesPerBuffer(),
  440. std::round(0.2 * GetMaxFramesPerBuffer()),
  441. std::round(0.6 * GetMaxFramesPerBuffer()));
  442. VerifyReadStats(kStatsKeyVp9, readBackEntryB);
  443. }
  444. TEST_F(VideoDecodeStatsDBImplTest, NoWriteDateReadAndExpire) {
  445. InitializeDB();
  446. // Seed the fake proto DB with an old-style entry lacking a write date. This
  447. // will cause the DB to use kDefaultWriteTime.
  448. DecodeStatsProto proto_lacking_date;
  449. proto_lacking_date.set_frames_decoded(100);
  450. proto_lacking_date.set_frames_dropped(10);
  451. proto_lacking_date.set_frames_power_efficient(1);
  452. fake_db_map_->emplace(kStatsKeyVp9.Serialize(), proto_lacking_date);
  453. // Set "now" to be *before* the default write date. This will be the common
  454. // case when the proto update (adding last_write_date) first ships (i.e. we
  455. // don't want to immediately expire all the existing data).
  456. base::SimpleTestClock clock;
  457. SetDBClock(&clock);
  458. clock.SetNow(kDefaultWriteTime - base::Days(10));
  459. // Verify the stats are readable (not expired).
  460. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(100, 10, 1));
  461. // Set "now" to be in the middle of the grace period. Verify stats are still
  462. // readable (not expired).
  463. clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() / 2));
  464. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(100, 10, 1));
  465. // Set the clock 1 day beyond the expiry date. Verify stats are no longer
  466. // readable due to expiration.
  467. clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() + 1));
  468. VerifyEmptyStats(kStatsKeyVp9);
  469. // Write some stats to the entry. Verify we get back exactly what's written
  470. // without summing with the expired stats.
  471. AppendStats(kStatsKeyVp9, DecodeStatsEntry(50, 5, 0));
  472. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(50, 5, 0));
  473. }
  474. TEST_F(VideoDecodeStatsDBImplTest, NoWriteDateAppendReadAndExpire) {
  475. InitializeDB();
  476. // Seed the fake proto DB with an old-style entry lacking a write date. This
  477. // will cause the DB to use kDefaultWriteTime.
  478. DecodeStatsProto proto_lacking_date;
  479. proto_lacking_date.set_frames_decoded(100);
  480. proto_lacking_date.set_frames_dropped(10);
  481. proto_lacking_date.set_frames_power_efficient(1);
  482. fake_db_map_->emplace(kStatsKeyVp9.Serialize(), proto_lacking_date);
  483. // Set "now" to be *before* the default write date. This will be the common
  484. // case when the proto update (adding last_write_date) first ships (i.e. we
  485. // don't want to immediately expire all the existing data).
  486. base::SimpleTestClock clock;
  487. SetDBClock(&clock);
  488. clock.SetNow(kDefaultWriteTime - base::Days(10));
  489. // Verify the stats are readable (not expired).
  490. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(100, 10, 1));
  491. // Append some stats and verify the aggregate math is correct. This will
  492. // update the last_write_date to the current clock time.
  493. AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  494. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(300, 30, 3));
  495. // Set "now" to be in the middle of the grace period. Verify stats are still
  496. // readable (not expired).
  497. clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() / 2));
  498. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(300, 30, 3));
  499. // Set the clock 1 day beyond the expiry date. Verify stats are no longer
  500. // readable due to expiration.
  501. clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() + 1));
  502. VerifyEmptyStats(kStatsKeyVp9);
  503. }
  504. TEST_F(VideoDecodeStatsDBImplTest, AppendAndExpire) {
  505. InitializeDB();
  506. // Inject a test clock and initialize with the current time.
  507. base::SimpleTestClock clock;
  508. SetDBClock(&clock);
  509. clock.SetNow(base::Time::Now());
  510. // Append and verify read-back.
  511. AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  512. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  513. // Advance time half way through grace period. Verify stats not expired.
  514. clock.Advance(base::Days(GetMaxDaysToKeepStats() / 2));
  515. VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
  516. // Advance time 1 day beyond grace period, verify stats are expired.
  517. clock.Advance(base::Days((GetMaxDaysToKeepStats() / 2) + 1));
  518. VerifyEmptyStats(kStatsKeyVp9);
  519. // Advance the clock 100 days. Verify stats still expired.
  520. clock.Advance(base::Days(100));
  521. VerifyEmptyStats(kStatsKeyVp9);
  522. }
  523. TEST_F(VideoDecodeStatsDBImplTest, EnableUnweightedEntries) {
  524. base::test::ScopedFeatureList scoped_feature_list;
  525. std::unique_ptr<base::FieldTrialList> field_trial_list;
  526. // Default is false.
  527. EXPECT_FALSE(GetEnableUnweightedEntries());
  528. // Override field trial.
  529. base::FieldTrialParams params;
  530. params[VideoDecodeStatsDBImpl::kEnableUnweightedEntriesParamName] = "true";
  531. scoped_feature_list.InitAndEnableFeatureWithParameters(
  532. media::kMediaCapabilitiesWithParameters, params);
  533. EXPECT_EQ(GetFieldTrialParams(), params);
  534. // Confirm field trial overridden.
  535. EXPECT_TRUE(GetMaxDaysToKeepStats());
  536. InitializeDB();
  537. // Append 200 frames with 10% dropped, 1% efficient.
  538. AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 0.10 * 200, 0.01 * 200));
  539. // Use real doubles to keep track of these things to make sure the precision
  540. // math for repeating decimals works out with whats done internally.
  541. int num_appends = 1;
  542. double unweighted_smoothness_avg = 0.10;
  543. double unweighted_efficiency_avg = 0.01;
  544. // NOTE, the members of DecodeStatsEntry have a different meaning when using
  545. // unweighted DB entries. The denominator is 100,000 * the number of appends
  546. // and the numerator is whatever value achieves the correct unweighted ratio
  547. // for those appends. See detailed comment in
  548. // VideoDecodeStatsDBImpl::OnGotDecodeStats();
  549. const int kNumAppendScale = 100000;
  550. int expected_denominator = kNumAppendScale * num_appends;
  551. VerifyReadStats(
  552. kStatsKeyVp9,
  553. DecodeStatsEntry(expected_denominator,
  554. unweighted_smoothness_avg * expected_denominator,
  555. unweighted_efficiency_avg * expected_denominator));
  556. // Append 20K frames with 5% dropped and 10% efficient.
  557. AppendStats(kStatsKeyVp9,
  558. DecodeStatsEntry(20000, 0.05 * 20000, 0.10 * 20000));
  559. num_appends++;
  560. unweighted_smoothness_avg = (0.10 + 0.05) / num_appends;
  561. unweighted_efficiency_avg = (0.01 + 0.10) / num_appends;
  562. // While new record had 100x more frames than the previous append, the ratios
  563. // should be an unweighted average of the two records (7.5% dropped and
  564. // 5.5% efficient).
  565. expected_denominator = kNumAppendScale * num_appends;
  566. VerifyReadStats(
  567. kStatsKeyVp9,
  568. DecodeStatsEntry(expected_denominator,
  569. unweighted_smoothness_avg * expected_denominator,
  570. unweighted_efficiency_avg * expected_denominator));
  571. // Append 1M frames with 3.4567% dropped and 3.4567% efficient.
  572. AppendStats(kStatsKeyVp9, DecodeStatsEntry(1000000, 0.012345 * 1000000,
  573. 0.034567 * 1000000));
  574. num_appends++;
  575. unweighted_smoothness_avg = (0.10 + 0.05 + 0.012345) / num_appends;
  576. unweighted_efficiency_avg = (0.01 + 0.10 + 0.034567) / num_appends;
  577. // Here, the ratios should still be averaged in the unweighted fashion, but
  578. // truncated after the 3rd decimal place of the percentage (e.g. 1.234%
  579. // or the 5th decimal place when represented as a fraction of 1 (0.01234)).
  580. expected_denominator = kNumAppendScale * num_appends;
  581. VerifyReadStats(
  582. kStatsKeyVp9,
  583. DecodeStatsEntry(expected_denominator,
  584. unweighted_smoothness_avg * expected_denominator,
  585. unweighted_efficiency_avg * expected_denominator));
  586. }
  587. TEST_F(VideoDecodeStatsDBImplTest, DiscardCorruptedDBData) {
  588. InitializeDB();
  589. // Inject a test clock and initialize with the current time.
  590. base::SimpleTestClock clock;
  591. SetDBClock(&clock);
  592. clock.SetNow(base::Time::Now());
  593. // Construct several distinct key values for storing/retrieving the corrupted
  594. // data. The details of the keys are not important.
  595. const auto keyA = VideoDescKey::MakeBucketedKey(
  596. VP9PROFILE_PROFILE0, gfx::Size(1024, 768), 60, "", false);
  597. const auto keyB = VideoDescKey::MakeBucketedKey(
  598. VP9PROFILE_PROFILE1, gfx::Size(1024, 768), 60, "", false);
  599. const auto keyC = VideoDescKey::MakeBucketedKey(
  600. VP9PROFILE_PROFILE2, gfx::Size(1024, 768), 60, "", false);
  601. const auto keyD = VideoDescKey::MakeBucketedKey(
  602. VP9PROFILE_PROFILE3, gfx::Size(1024, 768), 60, "", false);
  603. const auto keyE = VideoDescKey::MakeBucketedKey(
  604. H264PROFILE_BASELINE, gfx::Size(1024, 768), 60, "", false);
  605. const auto keyF = VideoDescKey::MakeBucketedKey(
  606. H264PROFILE_MAIN, gfx::Size(1024, 768), 60, "", false);
  607. const auto keyG = VideoDescKey::MakeBucketedKey(
  608. H264PROFILE_EXTENDED, gfx::Size(1024, 768), 60, "", false);
  609. // Start with a proto that represents a valid uncorrupted and unexpired entry.
  610. DecodeStatsProto protoA;
  611. protoA.set_frames_decoded(100);
  612. protoA.set_frames_dropped(15);
  613. protoA.set_frames_power_efficient(50);
  614. protoA.set_last_write_date(clock.Now().ToJsTime());
  615. protoA.set_unweighted_average_frames_dropped(15.0 / 100);
  616. protoA.set_unweighted_average_frames_efficient(50.0 / 100);
  617. protoA.set_num_unweighted_playbacks(1);
  618. // Append it and read it back without issue.
  619. AppendToProtoDB(keyA, &protoA);
  620. VerifyReadStats(keyA, DecodeStatsEntry(100, 15, 50));
  621. // Make the valid proto invalid with more dropped frames than decoded. Verify
  622. // you can't read it back (filtered for corruption).
  623. DecodeStatsProto protoB(protoA);
  624. protoB.set_frames_dropped(150);
  625. AppendToProtoDB(keyB, &protoB);
  626. VerifyEmptyStats(keyB);
  627. // Make an invalid proto with more power efficient frames than decoded. Verify
  628. // you can't read it back (filtered for corruption).
  629. DecodeStatsProto protoC(protoA);
  630. protoC.set_frames_power_efficient(150);
  631. AppendToProtoDB(keyC, &protoC);
  632. VerifyEmptyStats(keyC);
  633. // Make an invalid proto with an unweighted average dropped ratio > 1.
  634. DecodeStatsProto protoD(protoA);
  635. protoD.set_unweighted_average_frames_dropped(2.0);
  636. AppendToProtoDB(keyD, &protoD);
  637. VerifyEmptyStats(keyD);
  638. // Make an invalid proto with an unweighted average efficient ratio > 1.
  639. DecodeStatsProto protoE(protoA);
  640. protoE.set_unweighted_average_frames_efficient(2.0);
  641. AppendToProtoDB(keyE, &protoE);
  642. VerifyEmptyStats(keyE);
  643. // Make an invalid proto with a negative last write date.
  644. DecodeStatsProto protoF(protoA);
  645. protoF.set_last_write_date(-1.0);
  646. AppendToProtoDB(keyF, &protoF);
  647. VerifyEmptyStats(keyF);
  648. // Make an invalid proto with a last write date in the future.
  649. DecodeStatsProto protoG(protoA);
  650. protoG.set_last_write_date((clock.Now() + base::Days(1)).ToJsTime());
  651. AppendToProtoDB(keyG, &protoG);
  652. VerifyEmptyStats(keyG);
  653. }
  654. } // namespace media