blob: ac4324ef9ab4bb2a23b24ab3995e1867b2843301 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/performance_manager/metrics/metrics_provider_desktop.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/process_metrics.h"
#include "base/system/sys_info.h"
#include "base/timer/timer.h"
#include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
#include "components/performance_manager/public/user_tuning/prefs.h"
#include "components/prefs/pref_service.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/platform/ax_platform_node.h"
using performance_manager::user_tuning::prefs::HighEfficiencyModeState;
using performance_manager::user_tuning::prefs::kHighEfficiencyModeState;
namespace performance_manager {
namespace {
MetricsProviderDesktop* g_metrics_provider = nullptr;
uint64_t kBytesPerMb = 1024 * 1024;
#if BUILDFLAG(IS_MAC)
uint64_t kKilobytesPerMb = 1024;
#endif
} // namespace
// Tracks the proportion of time a specific mode was enabled during this
// object's entire lifetime, and records it to a specified histogram on
// destruction.
class ScopedTimeInModeTracker {
public:
ScopedTimeInModeTracker(bool enabled, const std::string& histogram_name)
: currently_enabled_(enabled),
current_interval_start_(base::LiveTicks::Now()),
start_(current_interval_start_),
histogram_name_(histogram_name) {}
~ScopedTimeInModeTracker() {
// Ensure `time_spent_enabled_` is updated if the mode was currently
// enabled. This doesn't call `ModeChanged` directly to ensure the value of
// `now` used for the total time computation is the same as was used to
// close the interval.
base::LiveTicks now = base::LiveTicks::Now();
CHECK(current_interval_start_ <= now);
CHECK(start_ <= now);
if (currently_enabled_) {
time_spent_enabled_ += now - current_interval_start_;
}
base::TimeDelta total_time = now - start_;
// Time spent enabled should be lower or equal to the total time this was
// active.
CHECK_LE(time_spent_enabled_, total_time);
// Check that the `time_spent_enabled_ * 100` operation can't overflow.
CHECK_LE(time_spent_enabled_.InMicroseconds(),
std::numeric_limits<int64_t>::max() / 100);
// `total_time` being 0 would mean the object was constructed and destructed
// without the clock advancing a single microsecond. This shouldn't happen
// in production but can happen in tests that use mock time. Treat this as
// an interval that has only been in the current state.
unsigned int percent_enabled = currently_enabled_ ? 100 : 0;
if (total_time.is_positive()) {
// Do the computation in microseconds to avoid prior truncation since it's
// `TimeDelta`'s internal representation.
int64_t checked_percent =
(base::CheckMul(time_spent_enabled_.InMicroseconds(), 100) /
total_time.InMicroseconds())
.ValueOrDie();
CHECK(base::IsValueInRangeForNumericType<unsigned int>(checked_percent));
percent_enabled = checked_percent;
}
CHECK_LE(percent_enabled, 100U);
base::UmaHistogramPercentage(histogram_name_, percent_enabled);
}
void ModeChanged(bool enabled) {
if (currently_enabled_ == enabled) {
// It's possible for the pref to be notified as "changed" even if it's
// "changing" to the same state it's already in when going to/from
// "enabled with heuristic mode" to/from "enabled on timer mode".
return;
}
base::LiveTicks now = base::LiveTicks::Now();
CHECK(current_interval_start_ <= now);
if (currently_enabled_) {
time_spent_enabled_ += now - current_interval_start_;
}
currently_enabled_ = enabled;
current_interval_start_ = now;
}
private:
bool currently_enabled_;
base::TimeDelta time_spent_enabled_;
base::LiveTicks current_interval_start_;
base::LiveTicks start_;
std::string histogram_name_;
};
// static
MetricsProviderDesktop* MetricsProviderDesktop::GetInstance() {
DCHECK(g_metrics_provider);
return g_metrics_provider;
}
MetricsProviderDesktop::~MetricsProviderDesktop() {
DCHECK_EQ(this, g_metrics_provider);
g_metrics_provider = nullptr;
}
void MetricsProviderDesktop::Initialize() {
DCHECK(!initialized_);
pref_change_registrar_.Init(local_state_);
pref_change_registrar_.Add(
kHighEfficiencyModeState,
base::BindRepeating(&MetricsProviderDesktop::OnHighEfficiencyPrefChanged,
base::Unretained(this)));
performance_manager::user_tuning::BatterySaverModeManager::GetInstance()
->AddObserver(this);
battery_saver_enabled_ =
performance_manager::user_tuning::BatterySaverModeManager::GetInstance()
->IsBatterySaverActive();
initialized_ = true;
current_mode_ = ComputeCurrentMode();
ResetTrackers();
}
void MetricsProviderDesktop::ProvideCurrentSessionData(
metrics::ChromeUserMetricsExtension* uma_proto) {
// It's valid for this to be called when `initialized_` is false if the finch
// features controlling battery saver and high efficiency are disabled.
// TODO(crbug.com/1348590): CHECK(initialized_) when the features are enabled
// and removed.
base::UmaHistogramEnumeration("PerformanceManager.UserTuning.EfficiencyMode",
current_mode_);
// Resetting the trackers will cause the existing ones to record their
// histogram.
ResetTrackers();
// Set `current_mode_` to represent the state of the modes as they are now, so
// that this mode is what is adequately reported at the next report, unless it
// changes in the meantime.
current_mode_ = ComputeCurrentMode();
}
MetricsProviderDesktop::MetricsProviderDesktop(PrefService* local_state)
: local_state_(local_state) {
DCHECK(!g_metrics_provider);
g_metrics_provider = this;
available_memory_metrics_timer_.Start(
FROM_HERE, base::Minutes(2),
base::BindRepeating(&MetricsProviderDesktop::RecordAvailableMemoryMetrics,
base::Unretained(this)));
}
void MetricsProviderDesktop::OnBatterySaverActiveChanged(bool is_active) {
battery_saver_enabled_ = is_active;
battery_saver_mode_tracker_->ModeChanged(battery_saver_enabled_);
OnTuningModesChanged();
}
void MetricsProviderDesktop::OnHighEfficiencyPrefChanged() {
high_efficiency_mode_tracker_->ModeChanged(IsHighEfficiencyEnabled());
OnTuningModesChanged();
}
void MetricsProviderDesktop::OnTuningModesChanged() {
EfficiencyMode new_mode = ComputeCurrentMode();
// If the mode changes between UMA reports, mark it as Mixed for this
// interval.
if (current_mode_ != new_mode) {
current_mode_ = EfficiencyMode::kMixed;
}
}
MetricsProviderDesktop::EfficiencyMode
MetricsProviderDesktop::ComputeCurrentMode() const {
// It's valid for this to be uninitialized if the battery saver/high
// efficiency modes are unavailable. In that case, the browser is running in
// normal mode, so return kNormal.
// TODO(crbug.com/1348590): Change this to a DCHECK when the features are
// enabled and removed.
if (!initialized_) {
return EfficiencyMode::kNormal;
}
// It's possible for this function to be called during shutdown, after
// BatterySaverModeManager is destroyed. Do not access UPTM directly from
// here.
bool high_efficiency_enabled = IsHighEfficiencyEnabled();
if (high_efficiency_enabled && battery_saver_enabled_) {
return EfficiencyMode::kBoth;
}
if (high_efficiency_enabled) {
return EfficiencyMode::kHighEfficiency;
}
if (battery_saver_enabled_) {
return EfficiencyMode::kBatterySaver;
}
return EfficiencyMode::kNormal;
}
bool MetricsProviderDesktop::IsHighEfficiencyEnabled() const {
return local_state_->GetInteger(kHighEfficiencyModeState) !=
static_cast<int>(HighEfficiencyModeState::kDisabled);
}
void MetricsProviderDesktop::RecordAvailableMemoryMetrics() {
auto available_bytes = base::SysInfo::AmountOfAvailablePhysicalMemory();
auto total_bytes = base::SysInfo::AmountOfPhysicalMemory();
base::UmaHistogramMemoryLargeMB("Memory.Experimental.AvailableMemoryMB",
available_bytes / kBytesPerMb);
base::UmaHistogramPercentage("Memory.Experimental.AvailableMemoryPercent",
available_bytes * 100 / total_bytes);
#if BUILDFLAG(IS_MAC)
base::SystemMemoryInfoKB info;
if (base::GetSystemMemoryInfo(&info)) {
base::UmaHistogramMemoryLargeMB(
"Memory.Experimental.MacFileBackedMemoryMB2",
info.file_backed / kKilobytesPerMb);
// `info.file_backed` is in kb, so multiply it by 1024 to get the amount of
// bytes
base::UmaHistogramPercentage(
"Memory.Experimental.MacAvailableMemoryPercentFreePageCache2",
(available_bytes + (info.file_backed * 1024)) * 100 / total_bytes);
}
#endif
}
void MetricsProviderDesktop::ResetTrackers() {
battery_saver_mode_tracker_ = std::make_unique<ScopedTimeInModeTracker>(
battery_saver_enabled_,
"PerformanceManager.UserTuning.BatterySaverModeEnabledPercent");
high_efficiency_mode_tracker_ = std::make_unique<ScopedTimeInModeTracker>(
IsHighEfficiencyEnabled(),
"PerformanceManager.UserTuning.MemorySaverModeEnabledPercent");
}
} // namespace performance_manager