123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785 |
- // 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 <map>
- #include <memory>
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/callback_helpers.h"
- #include "base/check.h"
- #include "base/files/file_path.h"
- #include "base/memory/ptr_util.h"
- #include "base/memory/raw_ptr.h"
- #include "base/run_loop.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/test/scoped_feature_list.h"
- #include "base/test/simple_test_clock.h"
- #include "base/test/task_environment.h"
- #include "base/time/time.h"
- #include "components/leveldb_proto/testing/fake_db.h"
- #include "media/base/media_switches.h"
- #include "media/base/test_data_util.h"
- #include "media/base/video_codecs.h"
- #include "media/capabilities/video_decode_stats.pb.h"
- #include "media/capabilities/video_decode_stats_db_impl.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "ui/gfx/geometry/size.h"
- using leveldb_proto::test::FakeDB;
- using testing::_;
- using testing::Eq;
- using testing::Pointee;
- namespace media {
- class VideoDecodeStatsDBImplTest : public ::testing::Test {
- public:
- using VideoDescKey = VideoDecodeStatsDB::VideoDescKey;
- using DecodeStatsEntry = VideoDecodeStatsDB::DecodeStatsEntry;
- VideoDecodeStatsDBImplTest()
- : kStatsKeyVp9(VideoDescKey::MakeBucketedKey(VP9PROFILE_PROFILE3,
- gfx::Size(1024, 768),
- 60,
- "com.widevine.alpha",
- true)),
- kStatsKeyAvc(VideoDescKey::MakeBucketedKey(H264PROFILE_MIN,
- gfx::Size(1024, 768),
- 60,
- "",
- false)) {
- bool parsed_time = base::Time::FromString(
- VideoDecodeStatsDBImpl::kDefaultWriteTime, &kDefaultWriteTime);
- DCHECK(parsed_time);
- // Fake DB simply wraps a std::map with the LevelDB interface. We own the
- // map and will delete it in TearDown().
- fake_db_map_ = std::make_unique<FakeDB<DecodeStatsProto>::EntryMap>();
- // |stats_db_| will own this pointer, but we hold a reference to control
- // its behavior.
- fake_db_ = new FakeDB<DecodeStatsProto>(fake_db_map_.get());
- // Wrap the fake proto DB with our interface.
- stats_db_ = base::WrapUnique(new VideoDecodeStatsDBImpl(
- std::unique_ptr<FakeDB<DecodeStatsProto>>(fake_db_)));
- }
- VideoDecodeStatsDBImplTest(const VideoDecodeStatsDBImplTest&) = delete;
- VideoDecodeStatsDBImplTest& operator=(const VideoDecodeStatsDBImplTest&) =
- delete;
- ~VideoDecodeStatsDBImplTest() override {
- // Tests should always complete any pending operations
- VerifyNoPendingOps();
- }
- void VerifyOnePendingOp(std::string op_name) {
- EXPECT_EQ(stats_db_->pending_operations_.get_pending_ops_for_test().size(),
- 1u);
- PendingOperations::PendingOperation* pending_op =
- stats_db_->pending_operations_.get_pending_ops_for_test()
- .begin()
- ->second.get();
- EXPECT_EQ(pending_op->uma_str_, op_name);
- }
- void VerifyNoPendingOps() {
- EXPECT_TRUE(
- stats_db_->pending_operations_.get_pending_ops_for_test().empty());
- }
- int GetMaxFramesPerBuffer() {
- return VideoDecodeStatsDBImpl::GetMaxFramesPerBuffer();
- }
- int GetMaxDaysToKeepStats() {
- return VideoDecodeStatsDBImpl::GetMaxDaysToKeepStats();
- }
- bool GetEnableUnweightedEntries() {
- return VideoDecodeStatsDBImpl::GetEnableUnweightedEntries();
- }
- static base::FieldTrialParams GetFieldTrialParams() {
- return VideoDecodeStatsDBImpl::GetFieldTrialParams();
- }
- void SetDBClock(base::Clock* clock) {
- stats_db_->set_wall_clock_for_test(clock);
- }
- void InitializeDB() {
- stats_db_->Initialize(base::BindOnce(
- &VideoDecodeStatsDBImplTest::OnInitialize, base::Unretained(this)));
- EXPECT_CALL(*this, OnInitialize(true));
- fake_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
- testing::Mock::VerifyAndClearExpectations(this);
- }
- void AppendStats(const VideoDescKey& key, const DecodeStatsEntry& entry) {
- EXPECT_CALL(*this, MockAppendDecodeStatsCb(true));
- stats_db_->AppendDecodeStats(
- key, entry,
- base::BindOnce(&VideoDecodeStatsDBImplTest::MockAppendDecodeStatsCb,
- base::Unretained(this)));
- VerifyOnePendingOp("Read");
- fake_db_->GetCallback(true);
- VerifyOnePendingOp("Write");
- fake_db_->UpdateCallback(true);
- testing::Mock::VerifyAndClearExpectations(this);
- }
- void VerifyReadStats(const VideoDescKey& key,
- const DecodeStatsEntry& expected) {
- EXPECT_CALL(*this, MockGetDecodeStatsCb(true, Pointee(Eq(expected))));
- stats_db_->GetDecodeStats(
- key, base::BindOnce(&VideoDecodeStatsDBImplTest::GetDecodeStatsCb,
- base::Unretained(this)));
- VerifyOnePendingOp("Read");
- fake_db_->GetCallback(true);
- testing::Mock::VerifyAndClearExpectations(this);
- }
- void VerifyEmptyStats(const VideoDescKey& key) {
- EXPECT_CALL(*this, MockGetDecodeStatsCb(true, nullptr));
- stats_db_->GetDecodeStats(
- key, base::BindOnce(&VideoDecodeStatsDBImplTest::GetDecodeStatsCb,
- base::Unretained(this)));
- VerifyOnePendingOp("Read");
- fake_db_->GetCallback(true);
- testing::Mock::VerifyAndClearExpectations(this);
- }
- // Unwraps move-only parameters to pass to the mock function.
- void GetDecodeStatsCb(bool success, std::unique_ptr<DecodeStatsEntry> entry) {
- MockGetDecodeStatsCb(success, entry.get());
- }
- void AppendToProtoDB(const VideoDescKey& key,
- const DecodeStatsProto* const proto) {
- base::RunLoop run_loop;
- base::OnceCallback<void(bool)> update_done_cb = base::BindOnce(
- [](base::RunLoop* run_loop, bool success) {
- ASSERT_TRUE(success);
- run_loop->Quit();
- },
- Unretained(&run_loop));
- using DBType = leveldb_proto::ProtoDatabase<DecodeStatsProto>;
- std::unique_ptr<DBType::KeyEntryVector> entries =
- std::make_unique<DBType::KeyEntryVector>();
- entries->emplace_back(key.Serialize(), *proto);
- fake_db_->UpdateEntries(std::move(entries),
- std::make_unique<leveldb_proto::KeyVector>(),
- std::move(update_done_cb));
- fake_db_->UpdateCallback(true);
- run_loop.Run();
- }
- MOCK_METHOD1(OnInitialize, void(bool success));
- MOCK_METHOD2(MockGetDecodeStatsCb,
- void(bool success, DecodeStatsEntry* entry));
- MOCK_METHOD1(MockAppendDecodeStatsCb, void(bool success));
- MOCK_METHOD0(MockClearStatsCb, void());
- protected:
- base::test::TaskEnvironment task_environment_{
- base::test::TaskEnvironment::TimeSource::MOCK_TIME};
- const VideoDescKey kStatsKeyVp9;
- const VideoDescKey kStatsKeyAvc;
- // Const in practice, but not marked const for compatibility with
- // base::Time::FromString.
- base::Time kDefaultWriteTime;
- // See documentation in SetUp()
- std::unique_ptr<FakeDB<DecodeStatsProto>::EntryMap> fake_db_map_;
- raw_ptr<FakeDB<DecodeStatsProto>> fake_db_;
- std::unique_ptr<VideoDecodeStatsDBImpl> stats_db_;
- };
- TEST_F(VideoDecodeStatsDBImplTest, InitializeFailed) {
- stats_db_->Initialize(base::BindOnce(
- &VideoDecodeStatsDBImplTest::OnInitialize, base::Unretained(this)));
- EXPECT_CALL(*this, OnInitialize(false));
- fake_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
- }
- TEST_F(VideoDecodeStatsDBImplTest, InitializeTimedOut) {
- // Queue up an Initialize.
- stats_db_->Initialize(base::BindOnce(
- &VideoDecodeStatsDBImplTest::OnInitialize, base::Unretained(this)));
- VerifyOnePendingOp("Initialize");
- // Move time forward enough to trigger timeout.
- EXPECT_CALL(*this, OnInitialize(_)).Times(0);
- task_environment_.FastForwardBy(base::Seconds(100));
- task_environment_.RunUntilIdle();
- // Verify we didn't get an init callback and task is no longer considered
- // pending (because it timed out).
- testing::Mock::VerifyAndClearExpectations(this);
- VerifyNoPendingOps();
- // Verify callback still works if init completes very late.
- EXPECT_CALL(*this, OnInitialize(false));
- fake_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
- }
- TEST_F(VideoDecodeStatsDBImplTest, ReadExpectingNothing) {
- InitializeDB();
- VerifyEmptyStats(kStatsKeyVp9);
- }
- TEST_F(VideoDecodeStatsDBImplTest, WriteReadAndClear) {
- InitializeDB();
- // Append and read back some VP9 stats.
- DecodeStatsEntry entry(1000, 2, 10);
- AppendStats(kStatsKeyVp9, entry);
- VerifyReadStats(kStatsKeyVp9, entry);
- // Reading with the wrong key (different codec) should still return nothing.
- VerifyEmptyStats(kStatsKeyAvc);
- // Appending same VP9 stats should read back as 2x the initial entry.
- AppendStats(kStatsKeyVp9, entry);
- DecodeStatsEntry aggregate_entry(2000, 4, 20);
- VerifyReadStats(kStatsKeyVp9, aggregate_entry);
- // Clear all stats from the DB.
- EXPECT_CALL(*this, MockClearStatsCb);
- stats_db_->ClearStats(base::BindOnce(
- &VideoDecodeStatsDBImplTest::MockClearStatsCb, base::Unretained(this)));
- VerifyOnePendingOp("Clear");
- fake_db_->UpdateCallback(true);
- // Database is now empty. Expect null entry.
- VerifyEmptyStats(kStatsKeyVp9);
- }
- TEST_F(VideoDecodeStatsDBImplTest, ConfigureMaxFramesPerBuffer) {
- // Setup field trial to use a tiny window of 1 decoded frame.
- base::test::ScopedFeatureList scoped_feature_list;
- std::unique_ptr<base::FieldTrialList> field_trial_list;
- int previous_max_frames_per_buffer = GetMaxFramesPerBuffer();
- int new_max_frames_per_buffer = 1;
- ASSERT_LT(new_max_frames_per_buffer, previous_max_frames_per_buffer);
- // Override field trial.
- base::FieldTrialParams params;
- params[VideoDecodeStatsDBImpl::kMaxFramesPerBufferParamName] =
- base::NumberToString(new_max_frames_per_buffer);
- scoped_feature_list.InitAndEnableFeatureWithParameters(
- media::kMediaCapabilitiesWithParameters, params);
- EXPECT_EQ(GetFieldTrialParams(), params);
- EXPECT_EQ(new_max_frames_per_buffer, GetMaxFramesPerBuffer());
- // Now verify the configured window is used by writing a frame and then
- // writing another.
- InitializeDB();
- // Append single frame which was, sadly, dropped and not efficient.
- DecodeStatsEntry entry(1, 1, 0);
- AppendStats(kStatsKeyVp9, entry);
- VerifyReadStats(kStatsKeyVp9, entry);
- // Appending another frame which is *not* dropped and *is* efficient.
- // Verify that only this last entry is still in the buffer (no aggregation).
- DecodeStatsEntry second_entry(1, 0, 1);
- AppendStats(kStatsKeyVp9, second_entry);
- VerifyReadStats(kStatsKeyVp9, second_entry);
- }
- TEST_F(VideoDecodeStatsDBImplTest, ConfigureExpireDays) {
- base::test::ScopedFeatureList scoped_feature_list;
- std::unique_ptr<base::FieldTrialList> field_trial_list;
- int previous_max_days_to_keep_stats = GetMaxDaysToKeepStats();
- int new_max_days_to_keep_stats = 4;
- ASSERT_LT(new_max_days_to_keep_stats, previous_max_days_to_keep_stats);
- // Override field trial.
- base::FieldTrialParams params;
- params[VideoDecodeStatsDBImpl::kMaxDaysToKeepStatsParamName] =
- base::NumberToString(new_max_days_to_keep_stats);
- scoped_feature_list.InitAndEnableFeatureWithParameters(
- media::kMediaCapabilitiesWithParameters, params);
- EXPECT_EQ(GetFieldTrialParams(), params);
- EXPECT_EQ(new_max_days_to_keep_stats, GetMaxDaysToKeepStats());
- InitializeDB();
- // Inject a test clock and initialize with the current time.
- base::SimpleTestClock clock;
- SetDBClock(&clock);
- clock.SetNow(base::Time::Now());
- // Append and verify read-back.
- AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- // Some simple math to avoid troubles of integer division.
- int half_days_to_keep_stats = new_max_days_to_keep_stats / 2;
- int remaining_days_to_keep_stats =
- new_max_days_to_keep_stats - half_days_to_keep_stats;
- // Advance time half way through grace period. Verify stats not expired.
- clock.Advance(base::Days(half_days_to_keep_stats));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- // Advance time 1 day beyond grace period, verify stats are expired.
- clock.Advance(base::Days((remaining_days_to_keep_stats) + 1));
- VerifyEmptyStats(kStatsKeyVp9);
- // Advance the clock 100 extra days. Verify stats still expired.
- clock.Advance(base::Days(100));
- VerifyEmptyStats(kStatsKeyVp9);
- }
- TEST_F(VideoDecodeStatsDBImplTest, FailedWrite) {
- InitializeDB();
- // Expect the callback to indicate success = false when the write fails.
- EXPECT_CALL(*this, MockAppendDecodeStatsCb(false));
- // Append stats, but fail the internal DB update.
- stats_db_->AppendDecodeStats(
- kStatsKeyVp9, DecodeStatsEntry(1000, 2, 10),
- base::BindOnce(&VideoDecodeStatsDBImplTest::MockAppendDecodeStatsCb,
- base::Unretained(this)));
- fake_db_->GetCallback(true);
- fake_db_->UpdateCallback(false);
- }
- TEST_F(VideoDecodeStatsDBImplTest, FillBufferInMixedIncrements) {
- InitializeDB();
- // Setup DB entry that half fills the buffer with 10% of frames dropped and
- // 50% of frames power efficient.
- const int kNumFramesEntryA = GetMaxFramesPerBuffer() / 2;
- DecodeStatsEntry entryA(kNumFramesEntryA, std::round(0.1 * kNumFramesEntryA),
- std::round(0.5 * kNumFramesEntryA));
- const double kDropRateA =
- static_cast<double>(entryA.frames_dropped) / entryA.frames_decoded;
- const double kEfficientRateA =
- static_cast<double>(entryA.frames_power_efficient) /
- entryA.frames_decoded;
- // Append entryA to half fill the buffer and verify read. Verify read.
- AppendStats(kStatsKeyVp9, entryA);
- VerifyReadStats(kStatsKeyVp9, entryA);
- // Append same entryA again to completely fill the buffer. Verify read gives
- // out aggregated stats (2x the initial entryA)
- AppendStats(kStatsKeyVp9, entryA);
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(GetMaxFramesPerBuffer(),
- std::round(kDropRateA * GetMaxFramesPerBuffer()),
- std::round(kEfficientRateA * GetMaxFramesPerBuffer())));
- // This row in the DB is now "full" (appended frames >= kMaxFramesPerBuffer)!
- //
- // Future appends will not increase the total count of decoded frames. The
- // ratios of dropped and power efficient frames will be a weighted average.
- // The weight of new appends is determined by how much of the buffer they
- // fill, i.e.
- //
- // new_append_weight = min(1, new_append_frame_count / kMaxFramesPerBuffer);
- // old_data_weight = 1 - new_append_weight;
- //
- // The calculation for dropped ratios (same for power efficient) then becomes:
- //
- // aggregate_drop_ratio = old_drop_ratio * old_drop_weight +
- // new_drop_ratio * new_data_weight;
- //
- // See implementation for more details.
- // Append same entryA a 3rd time. Verify we still only get the aggregated
- // stats from above (2x entryA) because the buffer is full and the ratio of
- // dropped and efficient frames is the same.
- AppendStats(kStatsKeyVp9, entryA);
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(GetMaxFramesPerBuffer(),
- std::round(kDropRateA * GetMaxFramesPerBuffer()),
- std::round(kEfficientRateA * GetMaxFramesPerBuffer())));
- // Append entryB that will fill just 10% of the buffer. The new entry has
- // different rates of dropped and power efficient frames to help verify that
- // it is given proper weight as it mixes with existing data in the buffer.
- const int kNumFramesEntryB = std::round(.1 * GetMaxFramesPerBuffer());
- DecodeStatsEntry entryB(kNumFramesEntryB, std::round(0.25 * kNumFramesEntryB),
- std::round(1 * kNumFramesEntryB));
- const double kDropRateB =
- static_cast<double>(entryB.frames_dropped) / entryB.frames_decoded;
- const double kEfficientRateB =
- static_cast<double>(entryB.frames_power_efficient) /
- entryB.frames_decoded;
- AppendStats(kStatsKeyVp9, entryB);
- // Verify that buffer is still full, but dropped and power efficient frame
- // rates are now higher according to entryB's impact (10%) on the full buffer.
- double mixed_drop_rate = .1 * kDropRateB + .9 * kDropRateA;
- double mixed_effiency_rate = .1 * kEfficientRateB + .9 * kEfficientRateA;
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(
- GetMaxFramesPerBuffer(),
- std::round(GetMaxFramesPerBuffer() * mixed_drop_rate),
- std::round(GetMaxFramesPerBuffer() * mixed_effiency_rate)));
- // After appending entryB again, verify aggregate ratios behave according to
- // the formula above (appending repeated entryB brings ratios closer to those
- // in entryB, further from entryA).
- AppendStats(kStatsKeyVp9, entryB);
- mixed_drop_rate = .1 * kDropRateB + .9 * mixed_drop_rate;
- mixed_effiency_rate = .1 * kEfficientRateB + .9 * mixed_effiency_rate;
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(
- GetMaxFramesPerBuffer(),
- std::round(GetMaxFramesPerBuffer() * mixed_drop_rate),
- std::round(GetMaxFramesPerBuffer() * mixed_effiency_rate)));
- // Appending entry*A* again, verify aggregate ratios behave according to
- // the formula above (ratio's move back in direction of those in entryA).
- // Since entryA fills half the buffer it gets a higher weight than entryB did
- // above.
- AppendStats(kStatsKeyVp9, entryA);
- mixed_drop_rate = .5 * kDropRateA + .5 * mixed_drop_rate;
- mixed_effiency_rate = .5 * kEfficientRateA + .5 * mixed_effiency_rate;
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(
- GetMaxFramesPerBuffer(),
- std::round(GetMaxFramesPerBuffer() * mixed_drop_rate),
- std::round(GetMaxFramesPerBuffer() * mixed_effiency_rate)));
- // Now append entryC with a frame count of 2x the buffer max. Verify entryC
- // gets 100% of the weight, erasing the mixed stats from earlier appends.
- const int kNumFramesEntryC = 2 * GetMaxFramesPerBuffer();
- DecodeStatsEntry entryC(kNumFramesEntryC, std::round(0.3 * kNumFramesEntryC),
- std::round(0.25 * kNumFramesEntryC));
- const double kDropRateC =
- static_cast<double>(entryC.frames_dropped) / entryC.frames_decoded;
- const double kEfficientRateC =
- static_cast<double>(entryC.frames_power_efficient) /
- entryC.frames_decoded;
- AppendStats(kStatsKeyVp9, entryC);
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(GetMaxFramesPerBuffer(),
- std::round(GetMaxFramesPerBuffer() * kDropRateC),
- std::round(GetMaxFramesPerBuffer() * kEfficientRateC)));
- }
- // Overfilling an empty buffer triggers the codepath to compute weighted dropped
- // and power efficient ratios under a circumstance where the existing counts are
- // all zero. This test ensures that we don't do any dividing by zero with that
- // empty data.
- TEST_F(VideoDecodeStatsDBImplTest, OverfillEmptyBuffer) {
- InitializeDB();
- // Setup DB entry that overflows the buffer max (by 1) with 10% of frames
- // dropped and 50% of frames power efficient.
- const int kNumFramesOverfill = GetMaxFramesPerBuffer() + 1;
- DecodeStatsEntry entryA(kNumFramesOverfill,
- std::round(0.1 * kNumFramesOverfill),
- std::round(0.5 * kNumFramesOverfill));
- // Append entry to completely fill the buffer and verify read.
- AppendStats(kStatsKeyVp9, entryA);
- // Read-back stats should have same ratios, but scaled such that
- // frames_decoded = GetMaxFramesPerBuffer().
- DecodeStatsEntry readBackEntryA(GetMaxFramesPerBuffer(),
- std::round(0.1 * GetMaxFramesPerBuffer()),
- std::round(0.5 * GetMaxFramesPerBuffer()));
- VerifyReadStats(kStatsKeyVp9, readBackEntryA);
- // Append another entry that again overfills with different dropped and power
- // efficient ratios. Verify that read-back only reflects latest entry.
- DecodeStatsEntry entryB(kNumFramesOverfill,
- std::round(0.2 * kNumFramesOverfill),
- std::round(0.6 * kNumFramesOverfill));
- AppendStats(kStatsKeyVp9, entryB);
- // Read-back stats should have same ratios, but scaled such that
- // frames_decoded = GetMaxFramesPerBuffer().
- DecodeStatsEntry readBackEntryB(GetMaxFramesPerBuffer(),
- std::round(0.2 * GetMaxFramesPerBuffer()),
- std::round(0.6 * GetMaxFramesPerBuffer()));
- VerifyReadStats(kStatsKeyVp9, readBackEntryB);
- }
- TEST_F(VideoDecodeStatsDBImplTest, NoWriteDateReadAndExpire) {
- InitializeDB();
- // Seed the fake proto DB with an old-style entry lacking a write date. This
- // will cause the DB to use kDefaultWriteTime.
- DecodeStatsProto proto_lacking_date;
- proto_lacking_date.set_frames_decoded(100);
- proto_lacking_date.set_frames_dropped(10);
- proto_lacking_date.set_frames_power_efficient(1);
- fake_db_map_->emplace(kStatsKeyVp9.Serialize(), proto_lacking_date);
- // Set "now" to be *before* the default write date. This will be the common
- // case when the proto update (adding last_write_date) first ships (i.e. we
- // don't want to immediately expire all the existing data).
- base::SimpleTestClock clock;
- SetDBClock(&clock);
- clock.SetNow(kDefaultWriteTime - base::Days(10));
- // Verify the stats are readable (not expired).
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(100, 10, 1));
- // Set "now" to be in the middle of the grace period. Verify stats are still
- // readable (not expired).
- clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() / 2));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(100, 10, 1));
- // Set the clock 1 day beyond the expiry date. Verify stats are no longer
- // readable due to expiration.
- clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() + 1));
- VerifyEmptyStats(kStatsKeyVp9);
- // Write some stats to the entry. Verify we get back exactly what's written
- // without summing with the expired stats.
- AppendStats(kStatsKeyVp9, DecodeStatsEntry(50, 5, 0));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(50, 5, 0));
- }
- TEST_F(VideoDecodeStatsDBImplTest, NoWriteDateAppendReadAndExpire) {
- InitializeDB();
- // Seed the fake proto DB with an old-style entry lacking a write date. This
- // will cause the DB to use kDefaultWriteTime.
- DecodeStatsProto proto_lacking_date;
- proto_lacking_date.set_frames_decoded(100);
- proto_lacking_date.set_frames_dropped(10);
- proto_lacking_date.set_frames_power_efficient(1);
- fake_db_map_->emplace(kStatsKeyVp9.Serialize(), proto_lacking_date);
- // Set "now" to be *before* the default write date. This will be the common
- // case when the proto update (adding last_write_date) first ships (i.e. we
- // don't want to immediately expire all the existing data).
- base::SimpleTestClock clock;
- SetDBClock(&clock);
- clock.SetNow(kDefaultWriteTime - base::Days(10));
- // Verify the stats are readable (not expired).
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(100, 10, 1));
- // Append some stats and verify the aggregate math is correct. This will
- // update the last_write_date to the current clock time.
- AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(300, 30, 3));
- // Set "now" to be in the middle of the grace period. Verify stats are still
- // readable (not expired).
- clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() / 2));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(300, 30, 3));
- // Set the clock 1 day beyond the expiry date. Verify stats are no longer
- // readable due to expiration.
- clock.SetNow(kDefaultWriteTime + base::Days(GetMaxDaysToKeepStats() + 1));
- VerifyEmptyStats(kStatsKeyVp9);
- }
- TEST_F(VideoDecodeStatsDBImplTest, AppendAndExpire) {
- InitializeDB();
- // Inject a test clock and initialize with the current time.
- base::SimpleTestClock clock;
- SetDBClock(&clock);
- clock.SetNow(base::Time::Now());
- // Append and verify read-back.
- AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- // Advance time half way through grace period. Verify stats not expired.
- clock.Advance(base::Days(GetMaxDaysToKeepStats() / 2));
- VerifyReadStats(kStatsKeyVp9, DecodeStatsEntry(200, 20, 2));
- // Advance time 1 day beyond grace period, verify stats are expired.
- clock.Advance(base::Days((GetMaxDaysToKeepStats() / 2) + 1));
- VerifyEmptyStats(kStatsKeyVp9);
- // Advance the clock 100 days. Verify stats still expired.
- clock.Advance(base::Days(100));
- VerifyEmptyStats(kStatsKeyVp9);
- }
- TEST_F(VideoDecodeStatsDBImplTest, EnableUnweightedEntries) {
- base::test::ScopedFeatureList scoped_feature_list;
- std::unique_ptr<base::FieldTrialList> field_trial_list;
- // Default is false.
- EXPECT_FALSE(GetEnableUnweightedEntries());
- // Override field trial.
- base::FieldTrialParams params;
- params[VideoDecodeStatsDBImpl::kEnableUnweightedEntriesParamName] = "true";
- scoped_feature_list.InitAndEnableFeatureWithParameters(
- media::kMediaCapabilitiesWithParameters, params);
- EXPECT_EQ(GetFieldTrialParams(), params);
- // Confirm field trial overridden.
- EXPECT_TRUE(GetMaxDaysToKeepStats());
- InitializeDB();
- // Append 200 frames with 10% dropped, 1% efficient.
- AppendStats(kStatsKeyVp9, DecodeStatsEntry(200, 0.10 * 200, 0.01 * 200));
- // Use real doubles to keep track of these things to make sure the precision
- // math for repeating decimals works out with whats done internally.
- int num_appends = 1;
- double unweighted_smoothness_avg = 0.10;
- double unweighted_efficiency_avg = 0.01;
- // NOTE, the members of DecodeStatsEntry have a different meaning when using
- // unweighted DB entries. The denominator is 100,000 * the number of appends
- // and the numerator is whatever value achieves the correct unweighted ratio
- // for those appends. See detailed comment in
- // VideoDecodeStatsDBImpl::OnGotDecodeStats();
- const int kNumAppendScale = 100000;
- int expected_denominator = kNumAppendScale * num_appends;
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(expected_denominator,
- unweighted_smoothness_avg * expected_denominator,
- unweighted_efficiency_avg * expected_denominator));
- // Append 20K frames with 5% dropped and 10% efficient.
- AppendStats(kStatsKeyVp9,
- DecodeStatsEntry(20000, 0.05 * 20000, 0.10 * 20000));
- num_appends++;
- unweighted_smoothness_avg = (0.10 + 0.05) / num_appends;
- unweighted_efficiency_avg = (0.01 + 0.10) / num_appends;
- // While new record had 100x more frames than the previous append, the ratios
- // should be an unweighted average of the two records (7.5% dropped and
- // 5.5% efficient).
- expected_denominator = kNumAppendScale * num_appends;
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(expected_denominator,
- unweighted_smoothness_avg * expected_denominator,
- unweighted_efficiency_avg * expected_denominator));
- // Append 1M frames with 3.4567% dropped and 3.4567% efficient.
- AppendStats(kStatsKeyVp9, DecodeStatsEntry(1000000, 0.012345 * 1000000,
- 0.034567 * 1000000));
- num_appends++;
- unweighted_smoothness_avg = (0.10 + 0.05 + 0.012345) / num_appends;
- unweighted_efficiency_avg = (0.01 + 0.10 + 0.034567) / num_appends;
- // Here, the ratios should still be averaged in the unweighted fashion, but
- // truncated after the 3rd decimal place of the percentage (e.g. 1.234%
- // or the 5th decimal place when represented as a fraction of 1 (0.01234)).
- expected_denominator = kNumAppendScale * num_appends;
- VerifyReadStats(
- kStatsKeyVp9,
- DecodeStatsEntry(expected_denominator,
- unweighted_smoothness_avg * expected_denominator,
- unweighted_efficiency_avg * expected_denominator));
- }
- TEST_F(VideoDecodeStatsDBImplTest, DiscardCorruptedDBData) {
- InitializeDB();
- // Inject a test clock and initialize with the current time.
- base::SimpleTestClock clock;
- SetDBClock(&clock);
- clock.SetNow(base::Time::Now());
- // Construct several distinct key values for storing/retrieving the corrupted
- // data. The details of the keys are not important.
- const auto keyA = VideoDescKey::MakeBucketedKey(
- VP9PROFILE_PROFILE0, gfx::Size(1024, 768), 60, "", false);
- const auto keyB = VideoDescKey::MakeBucketedKey(
- VP9PROFILE_PROFILE1, gfx::Size(1024, 768), 60, "", false);
- const auto keyC = VideoDescKey::MakeBucketedKey(
- VP9PROFILE_PROFILE2, gfx::Size(1024, 768), 60, "", false);
- const auto keyD = VideoDescKey::MakeBucketedKey(
- VP9PROFILE_PROFILE3, gfx::Size(1024, 768), 60, "", false);
- const auto keyE = VideoDescKey::MakeBucketedKey(
- H264PROFILE_BASELINE, gfx::Size(1024, 768), 60, "", false);
- const auto keyF = VideoDescKey::MakeBucketedKey(
- H264PROFILE_MAIN, gfx::Size(1024, 768), 60, "", false);
- const auto keyG = VideoDescKey::MakeBucketedKey(
- H264PROFILE_EXTENDED, gfx::Size(1024, 768), 60, "", false);
- // Start with a proto that represents a valid uncorrupted and unexpired entry.
- DecodeStatsProto protoA;
- protoA.set_frames_decoded(100);
- protoA.set_frames_dropped(15);
- protoA.set_frames_power_efficient(50);
- protoA.set_last_write_date(clock.Now().ToJsTime());
- protoA.set_unweighted_average_frames_dropped(15.0 / 100);
- protoA.set_unweighted_average_frames_efficient(50.0 / 100);
- protoA.set_num_unweighted_playbacks(1);
- // Append it and read it back without issue.
- AppendToProtoDB(keyA, &protoA);
- VerifyReadStats(keyA, DecodeStatsEntry(100, 15, 50));
- // Make the valid proto invalid with more dropped frames than decoded. Verify
- // you can't read it back (filtered for corruption).
- DecodeStatsProto protoB(protoA);
- protoB.set_frames_dropped(150);
- AppendToProtoDB(keyB, &protoB);
- VerifyEmptyStats(keyB);
- // Make an invalid proto with more power efficient frames than decoded. Verify
- // you can't read it back (filtered for corruption).
- DecodeStatsProto protoC(protoA);
- protoC.set_frames_power_efficient(150);
- AppendToProtoDB(keyC, &protoC);
- VerifyEmptyStats(keyC);
- // Make an invalid proto with an unweighted average dropped ratio > 1.
- DecodeStatsProto protoD(protoA);
- protoD.set_unweighted_average_frames_dropped(2.0);
- AppendToProtoDB(keyD, &protoD);
- VerifyEmptyStats(keyD);
- // Make an invalid proto with an unweighted average efficient ratio > 1.
- DecodeStatsProto protoE(protoA);
- protoE.set_unweighted_average_frames_efficient(2.0);
- AppendToProtoDB(keyE, &protoE);
- VerifyEmptyStats(keyE);
- // Make an invalid proto with a negative last write date.
- DecodeStatsProto protoF(protoA);
- protoF.set_last_write_date(-1.0);
- AppendToProtoDB(keyF, &protoF);
- VerifyEmptyStats(keyF);
- // Make an invalid proto with a last write date in the future.
- DecodeStatsProto protoG(protoA);
- protoG.set_last_write_date((clock.Now() + base::Days(1)).ToJsTime());
- AppendToProtoDB(keyG, &protoG);
- VerifyEmptyStats(keyG);
- }
- } // namespace media
|