#include "media/capabilities/video_decode_stats_db_impl.h"
#include <memory>
#include <string>
#include <tuple>
#include "base/debug/alias.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequence_checker.h"
#include "base/task/thread_pool.h"
#include "base/time/default_clock.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
#include "media/base/media_switches.h"
#include "media/capabilities/video_decode_stats.pb.h"
namespace media {
using ProtoDecodeStatsEntry = leveldb_proto::ProtoDatabase<DecodeStatsProto>;
namespace {
const int kMaxFramesPerBufferDefault = 2500;
const int kMaxDaysToKeepStatsDefault = 30;
const bool kEnableUnweightedEntriesDefault = false;
}
const char VideoDecodeStatsDBImpl::kMaxFramesPerBufferParamName[] =
"db_frames_buffer_size";
const char VideoDecodeStatsDBImpl::kMaxDaysToKeepStatsParamName[] =
"db_days_to_keep_stats";
const char VideoDecodeStatsDBImpl::kEnableUnweightedEntriesParamName[] =
"db_enable_unweighted_entries";
int VideoDecodeStatsDBImpl::GetMaxFramesPerBuffer() {
return base::GetFieldTrialParamByFeatureAsDouble(
kMediaCapabilitiesWithParameters, kMaxFramesPerBufferParamName,
kMaxFramesPerBufferDefault);
}
int VideoDecodeStatsDBImpl::GetMaxDaysToKeepStats() {
return base::GetFieldTrialParamByFeatureAsDouble(
kMediaCapabilitiesWithParameters, kMaxDaysToKeepStatsParamName,
kMaxDaysToKeepStatsDefault);
}
bool VideoDecodeStatsDBImpl::GetEnableUnweightedEntries() {
return base::GetFieldTrialParamByFeatureAsBool(
kMediaCapabilitiesWithParameters, kEnableUnweightedEntriesParamName,
kEnableUnweightedEntriesDefault);
}
base::FieldTrialParams VideoDecodeStatsDBImpl::GetFieldTrialParams() {
base::FieldTrialParams actual_trial_params;
const bool result = base::GetFieldTrialParamsByFeature(
kMediaCapabilitiesWithParameters, &actual_trial_params);
DCHECK(result);
return actual_trial_params;
}
std::unique_ptr<VideoDecodeStatsDBImpl> VideoDecodeStatsDBImpl::Create(
base::FilePath db_dir,
leveldb_proto::ProtoDatabaseProvider* db_provider) {
DVLOG(2) << __func__ << " db_dir:" << db_dir;
auto proto_db = db_provider->GetDB<DecodeStatsProto>(
leveldb_proto::ProtoDbType::VIDEO_DECODE_STATS_DB, db_dir,
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
return base::WrapUnique(new VideoDecodeStatsDBImpl(std::move(proto_db)));
}
constexpr char VideoDecodeStatsDBImpl::kDefaultWriteTime[];
VideoDecodeStatsDBImpl::VideoDecodeStatsDBImpl(
std::unique_ptr<leveldb_proto::ProtoDatabase<DecodeStatsProto>> db)
: pending_operations_("Media.VideoDecodeStatsDB.OpTiming."),
db_(std::move(db)),
wall_clock_(base::DefaultClock::GetInstance()) {
bool time_parsed =
base::Time::FromString(kDefaultWriteTime, &default_write_time_);
DCHECK(time_parsed);
DCHECK(db_);
}
VideoDecodeStatsDBImpl::~VideoDecodeStatsDBImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void VideoDecodeStatsDBImpl::Initialize(InitializeCB init_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(init_cb);
DCHECK(!IsInitialized());
db_->Init(base::BindOnce(
&VideoDecodeStatsDBImpl::OnInit, weak_ptr_factory_.GetWeakPtr(),
pending_operations_.Start("Initialize"), std::move(init_cb)));
}
void VideoDecodeStatsDBImpl::OnInit(PendingOperations::Id op_id,
InitializeCB init_cb,
leveldb_proto::Enums::InitStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(status, leveldb_proto::Enums::InitStatus::kInvalidOperation);
bool success = status == leveldb_proto::Enums::InitStatus::kOK;
DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!");
pending_operations_.Complete(op_id);
UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Initialize",
success);
db_init_ = true;
if (!success)
db_.reset();
std::move(init_cb).Run(success);
}
bool VideoDecodeStatsDBImpl::IsInitialized() {
return db_init_ && db_;
}
void VideoDecodeStatsDBImpl::AppendDecodeStats(
const VideoDescKey& key,
const DecodeStatsEntry& entry,
AppendDecodeStatsCB append_done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsInitialized());
DVLOG(3) << __func__ << " Reading key " << key.ToLogString()
<< " from DB with intent to update with " << entry.ToLogString();
db_->GetEntry(key.Serialize(),
base::BindOnce(&VideoDecodeStatsDBImpl::WriteUpdatedEntry,
weak_ptr_factory_.GetWeakPtr(),
pending_operations_.Start("Read"), key, entry,
std::move(append_done_cb)));
}
void VideoDecodeStatsDBImpl::GetDecodeStats(const VideoDescKey& key,
GetDecodeStatsCB get_stats_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsInitialized());
DVLOG(3) << __func__ << " " << key.ToLogString();
db_->GetEntry(key.Serialize(),
base::BindOnce(&VideoDecodeStatsDBImpl::OnGotDecodeStats,
weak_ptr_factory_.GetWeakPtr(),
pending_operations_.Start("Read"),
std::move(get_stats_cb)));
}
bool VideoDecodeStatsDBImpl::AreStatsUsable(
const DecodeStatsProto* const stats_proto) {
bool are_stats_valid =
stats_proto->frames_dropped() <= stats_proto->frames_decoded() &&
stats_proto->frames_power_efficient() <= stats_proto->frames_decoded() &&
stats_proto->unweighted_average_frames_dropped() <= 1 &&
stats_proto->unweighted_average_frames_efficient() <= 1 &&
stats_proto->last_write_date() >= 0 &&
base::Time::FromMillisecondsSinceUnixEpoch(
stats_proto->last_write_date()) <= wall_clock_->Now();
UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Validate",
are_stats_valid);
if (!are_stats_valid)
return false;
double last_write_date = stats_proto->last_write_date();
if (last_write_date == 0) {
last_write_date = default_write_time_.InMillisecondsFSinceUnixEpoch();
}
const int kMaxDaysToKeepStats = GetMaxDaysToKeepStats();
DCHECK_GT(kMaxDaysToKeepStats, 0);
return wall_clock_->Now() -
base::Time::FromMillisecondsSinceUnixEpoch(last_write_date) <=
base::Days(kMaxDaysToKeepStats);
}
void VideoDecodeStatsDBImpl::WriteUpdatedEntry(
PendingOperations::Id op_id,
const VideoDescKey& key,
const DecodeStatsEntry& new_entry,
AppendDecodeStatsCB append_done_cb,
bool read_success,
std::unique_ptr<DecodeStatsProto> stats_proto) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsInitialized());
pending_operations_.Complete(op_id);
UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Read",
read_success);
if (!read_success) {
DVLOG(2) << __func__ << " FAILED DB read for " << key.ToLogString()
<< "; ignoring update!";
std::move(append_done_cb).Run(false);
return;
}
if (!stats_proto || !AreStatsUsable(stats_proto.get())) {
stats_proto = std::make_unique<DecodeStatsProto>();
}
uint64_t old_frames_decoded = stats_proto->frames_decoded();
uint64_t old_frames_dropped = stats_proto->frames_dropped();
uint64_t old_frames_power_efficient = stats_proto->frames_power_efficient();
uint64_t new_frames_decoded = new_entry.frames_decoded;
uint64_t new_frames_dropped = new_entry.frames_dropped;
uint64_t new_frames_power_efficient = new_entry.frames_power_efficient;
base::debug::Alias(&old_frames_decoded);
base::debug::Alias(&old_frames_dropped);
base::debug::Alias(&old_frames_power_efficient);
base::debug::Alias(&new_frames_decoded);
base::debug::Alias(&new_frames_dropped);
base::debug::Alias(&new_frames_power_efficient);
const uint64_t kMaxFramesPerBuffer = GetMaxFramesPerBuffer();
DCHECK_GT(kMaxFramesPerBuffer, 0UL);
double new_entry_dropped_ratio = 0;
double new_entry_efficient_ratio = 0;
if (new_entry.frames_decoded) {
new_entry_dropped_ratio = static_cast<double>(new_entry.frames_dropped) /
new_entry.frames_decoded;
new_entry_efficient_ratio =
static_cast<double>(new_entry.frames_power_efficient) /
new_entry.frames_decoded;
} else {
NOTREACHED() << "Saving empty stats record.";
}
if (old_frames_decoded + new_entry.frames_decoded > kMaxFramesPerBuffer) {
double fill_ratio = std::min(
static_cast<double>(new_entry.frames_decoded) / kMaxFramesPerBuffer,
1.0);
double old_dropped_ratio = 0;
double old_efficient_ratio = 0;
if (old_frames_decoded) {
old_dropped_ratio =
static_cast<double>(old_frames_dropped) / old_frames_decoded;
old_efficient_ratio =
static_cast<double>(old_frames_power_efficient) / old_frames_decoded;
}
double agg_dropped_ratio = fill_ratio * new_entry_dropped_ratio +
(1 - fill_ratio) * old_dropped_ratio;
double agg_efficient_ratio = fill_ratio * new_entry_efficient_ratio +
(1 - fill_ratio) * old_efficient_ratio;
base::debug::Alias(&fill_ratio);
base::debug::Alias(&old_dropped_ratio);
base::debug::Alias(&old_efficient_ratio);
base::debug::Alias(&agg_dropped_ratio);
base::debug::Alias(&agg_efficient_ratio);
stats_proto->set_frames_decoded(kMaxFramesPerBuffer);
stats_proto->set_frames_dropped(
std::round(agg_dropped_ratio * kMaxFramesPerBuffer));
stats_proto->set_frames_power_efficient(
std::round(agg_efficient_ratio * kMaxFramesPerBuffer));
} else {
stats_proto->set_frames_decoded(new_entry.frames_decoded +
old_frames_decoded);
stats_proto->set_frames_dropped(new_entry.frames_dropped +
old_frames_dropped);
stats_proto->set_frames_power_efficient(new_entry.frames_power_efficient +
old_frames_power_efficient);
}
if (GetEnableUnweightedEntries()) {
uint64_t old_num_unweighted_playbacks =
stats_proto->num_unweighted_playbacks();
double old_unweighted_drop_avg =
stats_proto->unweighted_average_frames_dropped();
double old_unweighted_efficient_avg =
stats_proto->unweighted_average_frames_efficient();
uint64_t new_num_unweighted_playbacks = old_num_unweighted_playbacks + 1;
double new_unweighted_drop_avg =
((old_unweighted_drop_avg * old_num_unweighted_playbacks) +
new_entry_dropped_ratio) /
new_num_unweighted_playbacks;
double new_unweighted_efficient_avg =
((old_unweighted_efficient_avg * old_num_unweighted_playbacks) +
new_entry_efficient_ratio) /
new_num_unweighted_playbacks;
stats_proto->set_num_unweighted_playbacks(new_num_unweighted_playbacks);
stats_proto->set_unweighted_average_frames_dropped(new_unweighted_drop_avg);
stats_proto->set_unweighted_average_frames_efficient(
new_unweighted_efficient_avg);
DVLOG(2) << __func__ << " Updating unweighted averages. dropped:"
<< new_unweighted_drop_avg
<< " efficient:" << new_unweighted_efficient_avg
<< " num_playbacks:" << new_num_unweighted_playbacks;
}
stats_proto->set_last_write_date(
wall_clock_->Now().InMillisecondsFSinceUnixEpoch());
DCHECK(AreStatsUsable(stats_proto.get()));
using DBType = leveldb_proto::ProtoDatabase<DecodeStatsProto>;
std::unique_ptr<DBType::KeyEntryVector> entries =
std::make_unique<DBType::KeyEntryVector>();
entries->emplace_back(key.Serialize(), *stats_proto);
db_->UpdateEntries(std::move(entries),
std::make_unique<leveldb_proto::KeyVector>(),
base::BindOnce(&VideoDecodeStatsDBImpl::OnEntryUpdated,
weak_ptr_factory_.GetWeakPtr(),
pending_operations_.Start("Write"),
std::move(append_done_cb)));
}
void VideoDecodeStatsDBImpl::OnEntryUpdated(PendingOperations::Id op_id,
AppendDecodeStatsCB append_done_cb,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << " update " << (success ? "succeeded" : "FAILED!");
pending_operations_.Complete(op_id);
UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Write", success);
std::move(append_done_cb).Run(success);
}
void VideoDecodeStatsDBImpl::OnGotDecodeStats(
PendingOperations::Id op_id,
GetDecodeStatsCB get_stats_cb,
bool success,
std::unique_ptr<DecodeStatsProto> stats_proto) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << " get " << (success ? "succeeded" : "FAILED!");
pending_operations_.Complete(op_id);
UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Read", success);
std::unique_ptr<DecodeStatsEntry> entry;
if (stats_proto && AreStatsUsable(stats_proto.get())) {
DCHECK(success);
if (GetEnableUnweightedEntries()) {
DCHECK_GE(stats_proto->unweighted_average_frames_dropped(), 0);
DCHECK_LE(stats_proto->unweighted_average_frames_dropped(), 1);
DCHECK_GE(stats_proto->unweighted_average_frames_efficient(), 0);
DCHECK_LE(stats_proto->unweighted_average_frames_efficient(), 1);
DVLOG(2) << __func__ << " Using unweighted averages. dropped:"
<< stats_proto->unweighted_average_frames_dropped()
<< " efficient:"
<< stats_proto->unweighted_average_frames_efficient()
<< " num_playbacks:" << stats_proto->num_unweighted_playbacks();
uint64_t frames_decoded_lie =
100000 * stats_proto->num_unweighted_playbacks();
entry = std::make_unique<DecodeStatsEntry>(
frames_decoded_lie,
frames_decoded_lie * stats_proto->unweighted_average_frames_dropped(),
frames_decoded_lie *
stats_proto->unweighted_average_frames_efficient());
} else {
entry = std::make_unique<DecodeStatsEntry>(
stats_proto->frames_decoded(), stats_proto->frames_dropped(),
stats_proto->frames_power_efficient());
}
}
DVLOG(3) << __func__ << " read " << (success ? "succeeded" : "FAILED!")
<< " entry: " << (entry ? entry->ToLogString() : "nullptr");
std::move(get_stats_cb).Run(success, std::move(entry));
}
void VideoDecodeStatsDBImpl::ClearStats(base::OnceClosure clear_done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << __func__;
db_->UpdateEntriesWithRemoveFilter(
std::make_unique<ProtoDecodeStatsEntry::KeyEntryVector>(),
base::BindRepeating([](const std::string&) { return true; }),
base::BindOnce(&VideoDecodeStatsDBImpl::OnStatsCleared,
weak_ptr_factory_.GetWeakPtr(),
pending_operations_.Start("Clear"),
std::move(clear_done_cb)));
}
void VideoDecodeStatsDBImpl::OnStatsCleared(PendingOperations::Id op_id,
base::OnceClosure clear_done_cb,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << __func__ << (success ? " succeeded" : " FAILED!");
pending_operations_.Complete(op_id);
UMA_HISTOGRAM_BOOLEAN("Media.VideoDecodeStatsDB.OpSuccess.Clear", success);
std::move(clear_done_cb).Run();
}
}