blob: 30f1effadb08b2888c3f762b2524b6e7fbc176de [file] [log] [blame]
// 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 <string>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "components/ukm/test_ukm_recorder.h"
#include "media/base/key_systems.h"
#include "media/base/media_switches.h"
#include "media/capabilities/video_decode_stats_db.h"
#include "media/mojo/mojom/media_types.mojom.h"
#include "media/mojo/services/test_helpers.h"
#include "media/mojo/services/video_decode_perf_history.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
using UkmEntry = ukm::builders::Media_VideoDecodePerfRecord;
using testing::IsNull;
using testing::_;
namespace {
// Aliases for readability.
const bool kIsSmooth = true;
const bool kIsNotSmooth = false;
const bool kIsPowerEfficient = true;
const bool kIsNotPowerEfficient = false;
const url::Origin kOrigin = url::Origin::Create(GURL("http://example.com"));
const bool kIsTopFrame = true;
const uint64_t kPlayerId = 1234u;
} // namespace
namespace media {
class FakeVideoDecodeStatsDB : public VideoDecodeStatsDB {
public:
FakeVideoDecodeStatsDB() = default;
~FakeVideoDecodeStatsDB() override = default;
// Call CompleteInitialize(...) to run |init_cb| callback.
void Initialize(base::OnceCallback<void(bool)> init_cb) override {
pendnding_init_cb_ = std::move(init_cb);
}
// Completes fake initialization, running |init_cb| with the supplied value
// for success.
void CompleteInitialize(bool success) {
DVLOG(2) << __func__ << " running with success = " << success;
EXPECT_FALSE(!pendnding_init_cb_);
std::move(pendnding_init_cb_).Run(success);
}
// Simple hooks to fail the next calls to AppendDecodeStats() and
// GetDecodeStats(). Will be reset to false after the call.
void set_fail_next_append(bool fail_append) {
fail_next_append_ = fail_append;
}
void set_fail_next_get(bool fail_get) { fail_next_get_ = fail_get; }
void AppendDecodeStats(const VideoDescKey& key,
const DecodeStatsEntry& new_entry,
AppendDecodeStatsCB append_done_cb) override {
if (fail_next_append_) {
fail_next_append_ = false;
std::move(append_done_cb).Run(false);
return;
}
std::string key_str = key.Serialize();
if (entries_.find(key_str) == entries_.end()) {
entries_.emplace(std::make_pair(key_str, new_entry));
} else {
const DecodeStatsEntry& known_entry = entries_.at(key_str);
entries_.at(key_str) = DecodeStatsEntry(
known_entry.frames_decoded + new_entry.frames_decoded,
known_entry.frames_dropped + new_entry.frames_dropped,
known_entry.frames_power_efficient +
new_entry.frames_power_efficient);
}
std::move(append_done_cb).Run(true);
}
void GetDecodeStats(const VideoDescKey& key,
GetDecodeStatsCB get_stats_cb) override {
if (fail_next_get_) {
fail_next_get_ = false;
std::move(get_stats_cb).Run(false, nullptr);
return;
}
auto entry_it = entries_.find(key.Serialize());
if (entry_it == entries_.end()) {
std::move(get_stats_cb).Run(true, nullptr);
} else {
std::move(get_stats_cb)
.Run(true, std::make_unique<DecodeStatsEntry>(entry_it->second));
}
}
void ClearStats(base::OnceClosure clear_done_cb) override {
entries_.clear();
std::move(clear_done_cb).Run();
}
private:
friend class VideoDecodePerfHistoryTest;
// Private method for immediate retrieval of stats for test helpers.
std::unique_ptr<DecodeStatsEntry> GetDecodeStatsSync(
const VideoDescKey& key) {
auto entry_it = entries_.find(key.Serialize());
if (entry_it == entries_.end()) {
return nullptr;
} else {
return std::make_unique<DecodeStatsEntry>(entry_it->second);
}
}
bool fail_next_append_ = false;
bool fail_next_get_ = false;
std::map<std::string, DecodeStatsEntry> entries_;
base::OnceCallback<void(bool)> pendnding_init_cb_;
};
class VideoDecodePerfHistoryTest : public testing::Test {
public:
// Indicates what type of UKM verification should be performed upon saving
// new stats to the perf history.
enum class UkmVerifcation {
kNoUkmsAdded,
kSaveTriggersUkm,
};
void SetUp() override {
test_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
perf_history_ = std::make_unique<VideoDecodePerfHistory>(
std::make_unique<FakeVideoDecodeStatsDB>());
}
void TearDown() override { perf_history_.reset(); }
FakeVideoDecodeStatsDB* GetFakeDB() {
return static_cast<FakeVideoDecodeStatsDB*>(perf_history_->db_.get());
}
void PreInitializeDB(bool initialize_success) {
// Invoke private method to start initialization. Usually invoked by first
// API call requiring DB access.
perf_history_->InitDatabase();
// Complete initialization by firing callback from our fake DB.
GetFakeDB()->CompleteInitialize(initialize_success);
}
double GetMaxSmoothDroppedFramesPercent() {
return VideoDecodePerfHistory::GetMaxSmoothDroppedFramesPercent();
}
// Tests may set this as the callback for VideoDecodePerfHistory::GetPerfInfo
// to check the results of the call.
MOCK_METHOD2(MockGetPerfInfoCB,
void(bool is_smooth, bool is_power_efficient));
// Tests should EXPECT_CALL this method prior to ClearHistory() to know that
// the operation has completed.
MOCK_METHOD0(MockOnClearedHistory, void());
MOCK_METHOD1(MockGetVideoDecodeStatsDBCB, void(VideoDecodeStatsDB* db));
// Internal check that the UKM verification is complete before the test exits.
MOCK_METHOD0(UkmVerifyDoneCb, void());
void SavePerfRecord(UkmVerifcation ukm_verification,
const url::Origin& origin,
bool is_top_frame,
mojom::PredictionFeatures features,
mojom::PredictionTargets targets,
uint64_t player_id) {
// Manually associate URL with |source_id|. In production this happens
// externally at a higher layer.
const ukm::SourceId source_id = test_recorder_->GetNewSourceID();
test_recorder_->UpdateSourceURL(source_id, origin.GetURL());
// Use save callback to verify UKM reporting.
base::OnceClosure save_done_cb;
switch (ukm_verification) {
case UkmVerifcation::kSaveTriggersUkm: {
// Expect UKM report with given properties upon successful save. Use
// old stats values from DB to set expectations about what API would
// have claimed pre-save.
std::unique_ptr<DecodeStatsEntry> old_stats =
GetStatsForFeatures(features);
save_done_cb =
base::BindOnce(&VideoDecodePerfHistoryTest::VerifyLastUkmReport,
base::Unretained(this), origin, is_top_frame,
features, targets, player_id, std::move(old_stats));
break;
}
case UkmVerifcation::kNoUkmsAdded: {
// Expect no additional UKM entries upon failing to save. Capture the
// current entry count before save to verify no new entries are added.
size_t current_num_entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName).size();
save_done_cb = base::BindOnce(
&VideoDecodePerfHistoryTest::VerifyNoUkmReportForFailedSave,
base::Unretained(this), current_num_entries);
break;
}
}
// Check that the UKM verification is complete before the test exits.
EXPECT_CALL(*this, UkmVerifyDoneCb());
perf_history_->GetSaveCallback().Run(
source_id, learning::FeatureValue(kOrigin.host()), is_top_frame,
features, targets, player_id, std::move(save_done_cb));
}
protected:
using VideoDescKey = VideoDecodeStatsDB::VideoDescKey;
using DecodeStatsEntry = VideoDecodeStatsDB::DecodeStatsEntry;
// Private helper to public test methods. This bypasses VPH::GetPerfInfo()
// to synchronously grab stats from the fake DB. Tests should instead use
// the VPH::GetPerfInfo().
std::unique_ptr<DecodeStatsEntry> GetStatsForFeatures(
mojom::PredictionFeatures features) {
VideoDecodeStatsDB::VideoDescKey video_key =
VideoDecodeStatsDB::VideoDescKey::MakeBucketedKey(
features.profile, features.video_size, features.frames_per_sec,
features.key_system, features.use_hw_secure_codecs);
return GetFakeDB()->GetDecodeStatsSync(video_key);
}
// Lookup up the most recent recorded entry and verify its properties against
// method arguments.
void VerifyLastUkmReport(const url::Origin& origin,
bool is_top_frame,
mojom::PredictionFeatures features,
mojom::PredictionTargets new_targets,
uint64_t player_id,
std::unique_ptr<DecodeStatsEntry> old_stats) {
#define EXPECT_UKM(name, value) \
test_recorder_->ExpectEntryMetric(entry, name, value)
#define EXPECT_NO_UKM(name) \
EXPECT_FALSE(test_recorder_->EntryHasMetric(entry, name))
const auto& entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
ASSERT_GE(entries.size(), 1U);
auto* entry = entries.back();
// Verify stream properties. Make a key to ensure we check bucketed values.
VideoDecodeStatsDB::VideoDescKey key =
VideoDecodeStatsDB::VideoDescKey::MakeBucketedKey(
features.profile, features.video_size, features.frames_per_sec,
features.key_system, features.use_hw_secure_codecs);
test_recorder_->ExpectEntrySourceHasUrl(entry, origin.GetURL());
EXPECT_UKM(UkmEntry::kVideo_InTopFrameName, is_top_frame);
EXPECT_UKM(UkmEntry::kVideo_PlayerIDName, player_id);
EXPECT_UKM(UkmEntry::kVideo_CodecProfileName, key.codec_profile);
EXPECT_UKM(UkmEntry::kVideo_FramesPerSecondName, key.frame_rate);
EXPECT_UKM(UkmEntry::kVideo_NaturalHeightName, key.size.height());
EXPECT_UKM(UkmEntry::kVideo_NaturalWidthName, key.size.width());
if (key.key_system.empty()) {
EXPECT_NO_UKM(UkmEntry::kVideo_EME_KeySystemName);
EXPECT_NO_UKM(UkmEntry::kVideo_EME_UseHwSecureCodecsName);
} else {
EXPECT_UKM(UkmEntry::kVideo_EME_KeySystemName,
GetKeySystemIntForUKM(key.key_system));
EXPECT_UKM(UkmEntry::kVideo_EME_UseHwSecureCodecsName,
key.use_hw_secure_codecs);
}
// Verify past stats.
bool past_is_smooth = false;
bool past_is_efficient = false;
perf_history_->AssessStats(old_stats.get(), &past_is_smooth,
&past_is_efficient);
EXPECT_UKM(UkmEntry::kPerf_ApiWouldClaimIsSmoothName, past_is_smooth);
EXPECT_UKM(UkmEntry::kPerf_ApiWouldClaimIsPowerEfficientName,
past_is_efficient);
// Zero it out to make verification readable.
if (!old_stats)
old_stats.reset(new DecodeStatsEntry(0, 0, 0));
EXPECT_UKM(UkmEntry::kPerf_PastVideoFramesDecodedName,
old_stats->frames_decoded);
EXPECT_UKM(UkmEntry::kPerf_PastVideoFramesDroppedName,
old_stats->frames_dropped);
EXPECT_UKM(UkmEntry::kPerf_PastVideoFramesPowerEfficientName,
old_stats->frames_power_efficient);
// Verify latest stats.
VideoDecodeStatsDB::DecodeStatsEntry new_stats(
new_targets.frames_decoded, new_targets.frames_dropped,
new_targets.frames_power_efficient);
bool new_is_smooth = false;
bool new_is_efficient = false;
perf_history_->AssessStats(&new_stats, &new_is_smooth, &new_is_efficient);
EXPECT_UKM(UkmEntry::kPerf_RecordIsSmoothName, new_is_smooth);
EXPECT_UKM(UkmEntry::kPerf_RecordIsPowerEfficientName, new_is_efficient);
EXPECT_UKM(UkmEntry::kPerf_VideoFramesDecodedName,
new_stats.frames_decoded);
EXPECT_UKM(UkmEntry::kPerf_VideoFramesDroppedName,
new_stats.frames_dropped);
EXPECT_UKM(UkmEntry::kPerf_VideoFramesPowerEfficientName,
new_stats.frames_power_efficient);
#undef EXPECT_UKM
#undef EXPECT_NO_UKM
UkmVerifyDoneCb();
}
void VerifyNoUkmReportForFailedSave(size_t expected_num_ukm_entries) {
// Verify no new UKM entries appear after failed save.
const auto& entries =
test_recorder_->GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(expected_num_ukm_entries, entries.size());
UkmVerifyDoneCb();
}
static constexpr double kMinPowerEfficientDecodedFramePercent =
VideoDecodePerfHistory::kMinPowerEfficientDecodedFramePercent;
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_recorder_;
// The VideoDecodeStatsReporter being tested.
std::unique_ptr<VideoDecodePerfHistory> perf_history_;
};
struct PerfHistoryTestParams {
const bool defer_initialize;
const std::string key_system;
const bool use_hw_secure_codecs;
};
// When bool param is true, tests should wait until the end to run
// GetFakeDB()->CompleteInitialize(). Otherwise run PreInitializeDB() at the
// test start.
class VideoDecodePerfHistoryParamTest
: public testing::WithParamInterface<PerfHistoryTestParams>,
public VideoDecodePerfHistoryTest {};
TEST_P(VideoDecodePerfHistoryParamTest, GetPerfInfo_Smooth) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
testing::InSequence dummy;
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// First add 2 records to the history. The second record has a higher frame
// rate and a higher number of dropped frames such that it is "not smooth".
const VideoCodecProfile kKnownProfile = VP9PROFILE_PROFILE0;
const gfx::Size kKownSize(100, 200);
const int kSmoothFrameRate = 30;
const int kNotSmoothFrameRate = 90;
const int kFramesDecoded = 1000;
const int kNotPowerEfficientFramesDecoded = 0;
// Sets the ratio of dropped frames to barely qualify as smooth.
const int kSmoothFramesDropped =
kFramesDecoded * GetMaxSmoothDroppedFramesPercent();
// Set the ratio of dropped frames to barely qualify as NOT smooth.
const int kNotSmoothFramesDropped =
kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
// Add the entries.
SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kSmoothFramesDropped,
kNotPowerEfficientFramesDecoded),
kPlayerId);
SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kKnownProfile, kKownSize, kNotSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kNotSmoothFramesDropped,
kNotPowerEfficientFramesDecoded),
kPlayerId);
// Verify perf history returns is_smooth = true for the smooth entry.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Verify perf history returns is_smooth = false for the NOT smooth entry.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsNotPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kKnownProfile, kKownSize, kNotSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Verify perf history optimistically returns is_smooth = true when no entry
// can be found with the given configuration.
const VideoCodecProfile kUnknownProfile = VP9PROFILE_PROFILE2;
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kUnknownProfile, kKownSize, kNotSmoothFrameRate),
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest, GetPerfInfo_PowerEfficient) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
testing::InSequence dummy;
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// First add 3 records to the history:
// - the first has a high number of power efficiently decoded frames;
// - the second has a low number of power efficiently decoded frames;
// - the third is similar to the first with a high number of dropped frames.
const VideoCodecProfile kPowerEfficientProfile = VP9PROFILE_PROFILE0;
const VideoCodecProfile kNotPowerEfficientProfile = VP8PROFILE_ANY;
const gfx::Size kKownSize(100, 200);
const int kSmoothFrameRate = 30;
const int kNotSmoothFrameRate = 90;
const int kFramesDecoded = 1000;
const int kPowerEfficientFramesDecoded =
kFramesDecoded * kMinPowerEfficientDecodedFramePercent;
const int kNotPowerEfficientFramesDecoded =
kFramesDecoded * kMinPowerEfficientDecodedFramePercent - 1;
// Sets the ratio of dropped frames to barely qualify as smooth.
const int kSmoothFramesDropped =
kFramesDecoded * GetMaxSmoothDroppedFramesPercent();
// Set the ratio of dropped frames to barely qualify as NOT smooth.
const int kNotSmoothFramesDropped =
kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
// Add the entries.
SavePerfRecord(
UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kPowerEfficientProfile, kKownSize, kSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kSmoothFramesDropped,
kPowerEfficientFramesDecoded),
kPlayerId);
SavePerfRecord(
UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kNotPowerEfficientProfile, kKownSize, kSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kSmoothFramesDropped,
kNotPowerEfficientFramesDecoded),
kPlayerId);
SavePerfRecord(
UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kPowerEfficientProfile, kKownSize, kNotSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kNotSmoothFramesDropped,
kPowerEfficientFramesDecoded),
kPlayerId);
// Verify perf history returns is_smooth = true, is_power_efficient = true.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kPowerEfficientProfile, kKownSize, kSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Verify perf history returns is_smooth = true, is_power_efficient = false.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kNotPowerEfficientProfile, kKownSize, kSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Verify perf history returns is_smooth = false, is_power_efficient = true.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kPowerEfficientProfile, kKownSize, kNotSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Verify perf history optimistically returns is_smooth = true and
// is_power_efficient = true when no entry can be found with the given
// configuration.
const VideoCodecProfile kUnknownProfile = VP9PROFILE_PROFILE2;
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kUnknownProfile, kKownSize, kNotSmoothFrameRate,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest, GetPerfInfo_FailedInitialize) {
PerfHistoryTestParams params = GetParam();
// Fail initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ false);
const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
const gfx::Size kSize(100, 200);
const int kFrameRate = 30;
// When initialization fails, callback should optimistically claim both smooth
// and power efficient performance.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Fail deferred DB initialization (see comment at top of test).
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(false);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest, AppendAndDestroyStats) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
testing::InSequence dummy;
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// Add a simple record to the history.
const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
const gfx::Size kSize(100, 200);
const int kFrameRate = 30;
const int kFramesDecoded = 1000;
const int kManyFramesDropped = kFramesDecoded / 2;
const int kFramesPowerEfficient = kFramesDecoded;
SavePerfRecord(
UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kManyFramesDropped, kFramesPowerEfficient),
kPlayerId);
// Verify its there before we ClearHistory(). Note that perf is NOT smooth.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Initiate async clearing of history.
EXPECT_CALL(*this, MockOnClearedHistory());
perf_history_->ClearHistory(
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockOnClearedHistory,
base::Unretained(this)));
// Verify record we added above is no longer present.
// SUBTLE: The PerfHistory will optimistically respond kIsSmooth when no data
// is found. So the signal that the entry was removed is the CB now claims
// "smooth" when it claimed NOT smooth just moments before.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest, GetVideoDecodeStatsDB) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
testing::InSequence dummy;
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// Request a pointer to VideoDecodeStatsDB and verify the callback.
EXPECT_CALL(*this, MockGetVideoDecodeStatsDBCB(_))
.WillOnce([&](const auto* db_ptr) {
// Not able to simply use a matcher because the DB does not exist at the
// time we setup the EXPECT_CALL.
EXPECT_EQ(GetFakeDB(), db_ptr);
});
perf_history_->GetVideoDecodeStatsDB(
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetVideoDecodeStatsDBCB,
base::Unretained(this)));
scoped_task_environment_.RunUntilIdle();
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest,
GetVideoDecodeStatsDB_FailedInitialize) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
testing::InSequence dummy;
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ false);
// Request a pointer to VideoDecodeStatsDB and verify the callback provides
// a nullptr due to failed initialization.
EXPECT_CALL(*this, MockGetVideoDecodeStatsDBCB(IsNull()));
perf_history_->GetVideoDecodeStatsDB(
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetVideoDecodeStatsDBCB,
base::Unretained(this)));
scoped_task_environment_.RunUntilIdle();
// Complete failed deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(false);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest, FailedDatabaseGetForAppend) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// Create a record that is neither smooth nor power efficient. After we fail
// to save this record we should find smooth = power_efficient = true (the
// default for no-data-found).
const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
const gfx::Size kSize(100, 200);
const int kFrameRate = 30;
const int kFramesDecoded = 1000;
const int kFramesDropped =
kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
const int kFramesPowerEfficient = 0;
// Fail the "get" step of the save (we always get existing stats prior to
// save for UKM reporting).
GetFakeDB()->set_fail_next_get(true);
// Attempt (and fail) the save. UKM report depends on successful retrieval
// of stats from the DB, so no UKM reporting should occur here.
SavePerfRecord(
UkmVerifcation::kNoUkmsAdded, kOrigin, kIsTopFrame,
MakeFeatures(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kFramesDropped, kFramesPowerEfficient),
kPlayerId);
// Verify perf history still returns is_smooth = power_efficient = true since
// no data was successfully saved for the given configuration.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_P(VideoDecodePerfHistoryParamTest, FailedDatabaseAppend) {
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// Force the DB to fail on the next append.
GetFakeDB()->set_fail_next_append(true);
// Create a record that is neither smooth nor power efficient. After we fail
// to save this record we should find smooth = power_efficient = true (the
// default for no-data-found).
const VideoCodecProfile kProfile = VP9PROFILE_PROFILE0;
const gfx::Size kSize(100, 200);
const int kFrameRate = 30;
const int kFramesDecoded = 1000;
const int kFramesDropped =
kFramesDecoded * GetMaxSmoothDroppedFramesPercent() + 1;
const int kFramesPowerEfficient = 0;
// Attempt (and fail) the save. Note that we still expect UKM to be reported
// because we successfully retrieved stats from the DB, we just fail to append
// the new stats. UKM reporting occurs between retrieval and appending.
SavePerfRecord(
UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kFramesDropped, kFramesPowerEfficient),
kPlayerId);
// Verify perf history still returns is_smooth = power_efficient = true since
// no data was successfully saved for the given configuration.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kProfile, kSize, kFrameRate, params.key_system,
params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
// Tests that the feature parameters are used to override constants for the
// Media Capabilities feature.
// To avoid race conditions when setting the parameter, the test sets it when
// starting and make sure the values recorded to the DB wouldn't be smooth per
// the default value.
TEST_P(VideoDecodePerfHistoryParamTest, SmoothThresholdFinchOverride) {
base::test::ScopedFeatureList scoped_feature_list;
double previous_smooth_dropped_frames_threshold =
GetMaxSmoothDroppedFramesPercent();
double new_smooth_dropped_frames_threshold =
previous_smooth_dropped_frames_threshold / 2;
ASSERT_LT(new_smooth_dropped_frames_threshold,
previous_smooth_dropped_frames_threshold);
// Override field trial.
base::FieldTrialParams trial_params;
trial_params
[VideoDecodePerfHistory::kMaxSmoothDroppedFramesPercentParamName] =
base::NumberToString(new_smooth_dropped_frames_threshold);
scoped_feature_list.InitAndEnableFeatureWithParameters(
media::kMediaCapabilitiesWithParameters, trial_params);
base::FieldTrialParams actual_trial_params;
EXPECT_TRUE(base::GetFieldTrialParamsByFeature(
media::kMediaCapabilitiesWithParameters, &actual_trial_params));
EXPECT_EQ(trial_params, actual_trial_params);
EXPECT_EQ(new_smooth_dropped_frames_threshold,
GetMaxSmoothDroppedFramesPercent());
// NOTE: The when the DB initialization is deferred, All EXPECT_CALLs are then
// delayed until we db_->CompleteInitialize(). testing::InSequence enforces
// that EXPECT_CALLs arrive in top-to-bottom order.
PerfHistoryTestParams params = GetParam();
testing::InSequence dummy;
// Complete initialization in advance of API calls when not asked to defer.
if (!params.defer_initialize)
PreInitializeDB(/* success */ true);
// First add 2 records to the history. The second record has a higher frame
// rate and a higher number of dropped frames such that it is "not smooth".
const VideoCodecProfile kKnownProfile = VP9PROFILE_PROFILE0;
const gfx::Size kKownSize(100, 200);
const int kSmoothFrameRatePrevious = 30;
const int kSmoothFrameRateNew = 90;
const int kFramesDecoded = 1000;
const int kNotPowerEfficientFramesDecoded = 0;
// Sets the ratio of dropped frames to qualify as smooth per the default
// threshold.
const int kSmoothFramesDroppedPrevious =
kFramesDecoded * previous_smooth_dropped_frames_threshold;
// Sets the ratio of dropped frames to quality as smooth per the new
// threshold.
const int kSmoothFramesDroppedNew =
kFramesDecoded * new_smooth_dropped_frames_threshold;
// Add the entry.
SavePerfRecord(
UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRatePrevious,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kSmoothFramesDroppedPrevious,
kNotPowerEfficientFramesDecoded),
kPlayerId);
SavePerfRecord(UkmVerifcation::kSaveTriggersUkm, kOrigin, kIsTopFrame,
MakeFeatures(kKnownProfile, kKownSize, kSmoothFrameRateNew,
params.key_system, params.use_hw_secure_codecs),
MakeTargets(kFramesDecoded, kSmoothFramesDroppedNew,
kNotPowerEfficientFramesDecoded),
kPlayerId);
// Verify perf history returns is_smooth = false for entry that would be
// smooth per previous smooth threshold.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsNotSmooth, kIsNotPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRatePrevious,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Verify perf history returns is_smooth = true for entry that would be
// smooth per new smooth theshold.
EXPECT_CALL(*this, MockGetPerfInfoCB(kIsSmooth, kIsNotPowerEfficient));
perf_history_->GetPerfInfo(
MakeFeaturesPtr(kKnownProfile, kKownSize, kSmoothFrameRateNew,
params.key_system, params.use_hw_secure_codecs),
base::BindOnce(&VideoDecodePerfHistoryParamTest::MockGetPerfInfoCB,
base::Unretained(this)));
// Complete successful deferred DB initialization (see comment at top of test)
if (params.defer_initialize) {
GetFakeDB()->CompleteInitialize(true);
// Allow initialize-deferred API calls to complete.
scoped_task_environment_.RunUntilIdle();
}
}
const PerfHistoryTestParams kPerfHistoryTestParams[] = {
{true, "", false},
{false, "", false},
{true, "com.widevine.alpha", false},
{true, "com.widevine.alpha", true},
};
INSTANTIATE_TEST_SUITE_P(VaryDBInitTiming,
VideoDecodePerfHistoryParamTest,
::testing::ValuesIn(kPerfHistoryTestParams));
} // namespace media