blob: 4a64dfa76c954a9a72efe9a75c52945ce2900fb4 [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/byte_count.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/power_monitor/cpu_frequency_utils.h"
#include "base/process/process_metrics.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/performance_manager/public/user_tuning/prefs.h"
#include "components/prefs/pref_service.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/registry.h"
#endif
using performance_manager::user_tuning::prefs::kMemorySaverModeState;
using performance_manager::user_tuning::prefs::MemorySaverModeState;
namespace performance_manager {
namespace {
MetricsProviderDesktop* g_metrics_provider = nullptr;
#if SHOULD_COLLECT_CPU_FREQUENCY_METRICS()
enum class CpuThroughputEstimatedStatus {
kNormal,
kUnknown,
kThrottled,
kDescheduled,
kMigrated,
};
CpuThroughputEstimatedStatus EstimateCpuThroughputStatus(
bool migrated,
std::optional<double> cpu_frequency_percent,
std::optional<double> thread_time_percent,
base::TimeDelta queued_time) {
if (migrated) {
// If the task migrated from one CPU to the other, report the status as
// such. It's not relevant to check the thread time % in this instance,
// since a migrated task has by definition been descheduled. Likewise,
// checking the estimate vs nominal frequencies is also irrelevant, since
// the frequency of the original and migrated cores might be different.
return CpuThroughputEstimatedStatus::kMigrated;
}
if (!thread_time_percent) {
return CpuThroughputEstimatedStatus::kUnknown;
} else if (*thread_time_percent < 75.0) {
// If the task is actually running for only 75% of its wall time or less, we
// report it as having been descheduled
return CpuThroughputEstimatedStatus::kDescheduled;
}
if (!cpu_frequency_percent) {
return CpuThroughputEstimatedStatus::kUnknown;
} else if (*cpu_frequency_percent < 75.0) {
// If the task had a thread time close to its wall time, but we estimate its
// CPU frequency as 75% of nominal or less, we report the CPU as being
// throttled.
return CpuThroughputEstimatedStatus::kThrottled;
}
return CpuThroughputEstimatedStatus::kNormal;
}
constexpr char kCpuEstimationEventCategory[] =
"performance_manager.cpu_metrics";
constexpr char kCpuEstimationEvent[] = "CpuStatusSampling";
constexpr char kCpuEstimationStatusNormalEvent[] =
"CpuStatusSampling.Status.Normal";
constexpr char kCpuEstimationStatusUnknownEvent[] =
"CpuStatusSampling.Status.Unknown";
constexpr char kCpuEstimationStatusThrottledEvent[] =
"CpuStatusSampling.Status.Throttled";
constexpr char kCpuEstimationStatusDescheduledEvent[] =
"CpuStatusSampling.Status.Descheduled";
constexpr char kCpuEstimationStatusMigratedEvent[] =
"CpuStatusSampling.Status.Migrated";
constexpr char kCpuEstimationQueuedEvent[] = "CpuStatusSampling.Queued";
constexpr char kCpuEstimationRunningEvent[] = "CpuStatusSampling.Running";
constexpr char kCpuEstimationThreadTimeEvent[] = "CpuStatusSampling.ThreadTime";
constexpr char kCpuEstimationDescheduledEvent[] =
"CpuStatusSampling.Descheduled";
// This function emits trace events related to the status of the CPU throughput
// estimation task. In a trace, these events might look like this (where CSS is
// CpuStatusSampling):
//
// +-----------------------------------------------+
// | CpuStatusSampling |
// +-----------------------------------------------+
// | CSS.Status.{estimated_status} |
// +------------+----------------------------------+
// | CSS.Queued | CSS.Running |
// +------------+----------------+-----------------+
// | CSS.ThreadTime | CSS.Descheduled |
// +----------------+-----------------+
//
// CpuStatusSampling.Status.{estimated_status} will reflect what the estimation
// code thinks the status of the CPU is, between Normal, Unknown, Throttled,
// Descheduled, Migrated.
//
// CpuStatusSampling.Queued is the time between when the estimation task was
// posted and when it started running.
//
// CpuStatusSampling.Running is the time between when the task started running
// and when it finished
//
// CpuStatusSampling.ThreadTime is the CPU time of the running task
//
// CpuStatusSampling.Descheduled is the wall time the task spent off-cpu
void EmitCpuStatusSamplingTraceEvents(base::TimeTicks posted_at_time,
base::TimeTicks started_running_time,
base::TimeDelta thread_time,
base::TimeDelta wall_time,
CpuThroughputEstimatedStatus status) {
void* id = g_metrics_provider;
base::TimeTicks end_time = started_running_time + wall_time;
TRACE_EVENT_BEGIN(kCpuEstimationEventCategory, kCpuEstimationEvent,
perfetto::Track::FromPointer(id), posted_at_time);
TRACE_EVENT_END(kCpuEstimationEventCategory, perfetto::Track::FromPointer(id),
end_time);
const char* selected;
switch (status) {
case CpuThroughputEstimatedStatus::kNormal:
selected = kCpuEstimationStatusNormalEvent;
break;
case CpuThroughputEstimatedStatus::kUnknown:
selected = kCpuEstimationStatusUnknownEvent;
break;
case CpuThroughputEstimatedStatus::kThrottled:
selected = kCpuEstimationStatusThrottledEvent;
break;
case CpuThroughputEstimatedStatus::kDescheduled:
selected = kCpuEstimationStatusDescheduledEvent;
break;
case CpuThroughputEstimatedStatus::kMigrated:
selected = kCpuEstimationStatusMigratedEvent;
break;
}
TRACE_EVENT_BEGIN(kCpuEstimationEventCategory,
perfetto::StaticString(selected),
perfetto::Track::FromPointer(id), posted_at_time);
TRACE_EVENT_END(kCpuEstimationEventCategory, perfetto::Track::FromPointer(id),
end_time);
TRACE_EVENT_BEGIN(kCpuEstimationEventCategory, kCpuEstimationQueuedEvent,
perfetto::Track::FromPointer(id), posted_at_time);
TRACE_EVENT_END(kCpuEstimationEventCategory, perfetto::Track::FromPointer(id),
started_running_time);
TRACE_EVENT_BEGIN(kCpuEstimationEventCategory, kCpuEstimationRunningEvent,
perfetto::Track::FromPointer(id), started_running_time);
TRACE_EVENT_END(kCpuEstimationEventCategory, perfetto::Track::FromPointer(id),
end_time);
// Emit a block for the running thread time
TRACE_EVENT_BEGIN(kCpuEstimationEventCategory, kCpuEstimationThreadTimeEvent,
perfetto::Track::FromPointer(id), started_running_time);
TRACE_EVENT_END(kCpuEstimationEventCategory, perfetto::Track::FromPointer(id),
started_running_time + thread_time);
// And then one of the wall time spent descheduled
TRACE_EVENT_BEGIN(kCpuEstimationEventCategory, kCpuEstimationDescheduledEvent,
perfetto::Track::FromPointer(id),
started_running_time + thread_time);
TRACE_EVENT_END(kCpuEstimationEventCategory, perfetto::Track::FromPointer(id),
started_running_time + wall_time);
}
#endif // SHOULD_COLLECT_CPU_FREQUENCY_METRICS()
#if BUILDFLAG(IS_WIN)
// Reports histograms describing the value of the HKEY_LOCAL_MACHINE ->
// Software\Microsoft\Windows NT\CurrentVersion\Image File ->
// FrontEndHeapDebugOptions registry key. We observed locally that the 0x10 bit
// activates stack collection on heap allocation, which results in unacceptable
// performance. We want to be sure that this isn't used widely in the field.
void RecordFrontEndHeapDebugOptionsHistogram() {
// Outcome of reading the registry key. These values are persisted to logs.
// Entries should not be renumbered and numeric values should never be reused.
// LINT.IfChange(FrontEndHeapDebugOptionsOutcome)
enum class FrontEndHeapDebugOptionsOutcome {
kCannotOpenKey = 0,
kCannotReadValue = 1,
kSuccess = 2,
kMaxValue = kSuccess,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/performance_manager/enums.xml:FrontEndHeapDebugOptionsOutcome)
std::optional<FrontEndHeapDebugOptionsOutcome> outcome;
base::win::RegKey key;
if (key.Open(HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File "
L"Execution Options\\chrome.exe",
KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
DWORD value = 0;
if (key.ReadValueDW(L"FrontEndHeapDebugOptions", &value) == ERROR_SUCCESS) {
base::UmaHistogramSparse(
"PerformanceManager.RegistryStats.FrontEndHeapDebugOptionsValue",
// Limit the number of distinct values recorded to this histogram, as
// recommended by `base::UmaHistogramSparse()` documentation. The
// highest bit observed being set in practice is 0x10 (for stack
// collection on heap allocation). We set the maximum a little bit
// above that, to be aware if higher bits are used in the field.
std::clamp(base::saturated_cast<int>(value), 0, 0xff));
outcome = FrontEndHeapDebugOptionsOutcome::kSuccess;
} else {
outcome = FrontEndHeapDebugOptionsOutcome::kCannotReadValue;
}
} else {
outcome = FrontEndHeapDebugOptionsOutcome::kCannotOpenKey;
}
CHECK(outcome.has_value());
base::UmaHistogramEnumeration(
"PerformanceManager.RegistryStats.FrontEndHeapDebugOptionsOutcome",
outcome.value());
}
#endif // BUILDFLAG(IS_WIN)
} // 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(
kMemorySaverModeState,
base::BindRepeating(&MetricsProviderDesktop::OnMemorySaverPrefChanged,
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();
PostDiskMetricsTask();
}
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 memory saver are disabled.
// TODO(crbug.com/40233418): 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();
RecordDiskMetrics();
#if BUILDFLAG(IS_WIN)
RecordFrontEndHeapDebugOptionsHistogram();
#endif // BUILDFLAG(IS_WIN)
// Request a disk measurement so it's ready for the next interval
PostDiskMetricsTask();
}
MetricsProviderDesktop::MetricsProviderDesktop(PrefService* local_state)
: local_state_(local_state),
disk_metrics_getter_(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) {
DCHECK(!g_metrics_provider);
g_metrics_provider = this;
#if SHOULD_COLLECT_CPU_FREQUENCY_METRICS()
ScheduleCpuFrequencyTask();
#endif // SHOULD_COLLECT_CPU_FREQUENCY_METRICS()
}
void MetricsProviderDesktop::OnBatterySaverActiveChanged(bool is_active) {
battery_saver_enabled_ = is_active;
battery_saver_mode_tracker_->ModeChanged(battery_saver_enabled_);
OnTuningModesChanged();
}
void MetricsProviderDesktop::OnMemorySaverPrefChanged() {
memory_saver_mode_tracker_->ModeChanged(IsMemorySaverEnabled());
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/40233418): 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 = IsMemorySaverEnabled();
if (high_efficiency_enabled && battery_saver_enabled_) {
return EfficiencyMode::kBoth;
}
if (high_efficiency_enabled) {
return EfficiencyMode::kMemorySaver;
}
if (battery_saver_enabled_) {
return EfficiencyMode::kBatterySaver;
}
return EfficiencyMode::kNormal;
}
bool MetricsProviderDesktop::IsMemorySaverEnabled() const {
return local_state_->GetInteger(kMemorySaverModeState) !=
static_cast<int>(MemorySaverModeState::kDisabled);
}
void MetricsProviderDesktop::ResetTrackers() {
battery_saver_mode_tracker_ = std::make_unique<ScopedTimeInModeTracker>(
battery_saver_enabled_,
"PerformanceManager.UserTuning.BatterySaverModeEnabledPercent");
memory_saver_mode_tracker_ = std::make_unique<ScopedTimeInModeTracker>(
IsMemorySaverEnabled(),
"PerformanceManager.UserTuning.MemorySaverModeEnabledPercent");
}
#if SHOULD_COLLECT_CPU_FREQUENCY_METRICS()
// static
void MetricsProviderDesktop::RecordCpuFrequencyMetrics(
base::TimeTicks posted_at_time) {
auto started_running_time = base::TimeTicks::Now();
auto queued_time = started_running_time - posted_at_time;
static const double kHzInMhz = 1000 * 1000;
std::optional<base::CpuThroughputEstimationResult> cpu_throughput =
base::EstimateCpuThroughput();
base::CpuFrequencyInfo cpu_frequency_info = base::GetCpuFrequencyInfo();
if (!cpu_throughput) {
return;
}
std::string_view core_type_suffix = "Performance";
if (cpu_frequency_info.type == base::CpuFrequencyInfo::CoreType::kBalanced) {
core_type_suffix = "Balanced";
} else if (cpu_frequency_info.type ==
base::CpuFrequencyInfo::CoreType::kEfficiency) {
core_type_suffix = "Efficiency";
}
base::UmaHistogramCustomMicrosecondsTimes(
base::StrCat(
{"CPU.Experimental.CpuEstimationTaskQueuedTime.", core_type_suffix}),
queued_time, base::Microseconds(1), base::Seconds(1), 50);
base::UmaHistogramCustomMicrosecondsTimes(
base::StrCat(
{"CPU.Experimental.CpuEstimationTaskTotalTime.", core_type_suffix}),
queued_time + cpu_throughput->wall_time, base::Microseconds(1),
base::Seconds(1), 50);
base::UmaHistogramCustomMicrosecondsTimes(
base::StrCat(
{"CPU.Experimental.CpuEstimationTaskThreadTime.", core_type_suffix}),
cpu_throughput->thread_time, base::Microseconds(1), base::Seconds(1), 50);
base::UmaHistogramCustomMicrosecondsTimes(
base::StrCat(
{"CPU.Experimental.CpuEstimationTaskWallTime.", core_type_suffix}),
cpu_throughput->wall_time, base::Microseconds(1), base::Seconds(1), 50);
base::UmaHistogramBoolean("CPU.Experimental.CpuEstimationTaskMigrated",
cpu_throughput->migrated);
std::optional<double> cpu_frequency_percent = std::nullopt;
if (!cpu_throughput->migrated) {
// Don't record frequency metrics if the code migrated from one CPU to
// another in the middle of the estimation loop. This is because the nominal
// frequency of the start and end cores might be different.
double estimated_mhz = cpu_throughput->estimated_frequency / kHzInMhz;
// Max/Limit can (rarely) be 0 in the field, perhaps in virtualized or
// sandboxed environments.
if (cpu_frequency_info.max_mhz > 0UL) {
cpu_frequency_percent = estimated_mhz * 100.0 /
static_cast<double>(cpu_frequency_info.max_mhz);
base::UmaHistogramPercentage(
base::StrCat({"CPU.Experimental.EstimatedFrequencyAsPercentOfMax.",
core_type_suffix}),
static_cast<int>(*cpu_frequency_percent));
}
if (cpu_frequency_info.mhz_limit > 0UL) {
base::UmaHistogramPercentage(
base::StrCat({"CPU.Experimental.EstimatedFrequencyAsPercentOfLimit.",
core_type_suffix}),
static_cast<int>(estimated_mhz * 100.0 /
static_cast<double>(cpu_frequency_info.mhz_limit)));
}
}
// These can be 0 in tests
if (!cpu_throughput->thread_time.is_zero() &&
!cpu_throughput->wall_time.is_zero()) {
std::optional<double> thread_time_percent =
cpu_throughput->thread_time / cpu_throughput->wall_time * 100.0;
base::UmaHistogramPercentage(
base::StrCat({"CPU.Experimental.CpuEstimationThreadTimePercent.",
core_type_suffix}),
static_cast<int>(*thread_time_percent));
CpuThroughputEstimatedStatus status = EstimateCpuThroughputStatus(
cpu_throughput->migrated, cpu_frequency_percent, thread_time_percent,
queued_time);
EmitCpuStatusSamplingTraceEvents(posted_at_time, started_running_time,
cpu_throughput->thread_time,
cpu_throughput->wall_time, status);
}
ScheduleCpuFrequencyTask();
}
// static
void MetricsProviderDesktop::ScheduleCpuFrequencyTask() {
static constexpr base::TimeDelta kCpuThroughputSamplingInterval =
base::Minutes(5);
base::ThreadPool::PostDelayedTask(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&MetricsProviderDesktop::PostCpuFrequencyEstimation),
kCpuThroughputSamplingInterval);
}
// static
void MetricsProviderDesktop::PostCpuFrequencyEstimation() {
base::ThreadPool::PostTask(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&MetricsProviderDesktop::RecordCpuFrequencyMetrics,
base::TimeTicks::Now()));
}
#endif // SHOULD_COLLECT_CPU_FREQUENCY_METRICS()
void MetricsProviderDesktop::RecordDiskMetrics() {
if (!pending_disk_metrics_) {
// The measurements aren't ready yet, don't report anything.
return;
}
if (pending_disk_metrics_->free_bytes.is_negative() ||
pending_disk_metrics_->total_bytes.is_negative()) {
return;
}
base::UmaHistogramCustomCounts(
"PerformanceManager.DiskStats.UserDataDirFreeSpaceMb",
pending_disk_metrics_->free_bytes.InMiB(), 0,
base::GiB(10)
.InMiB(), // It's fine to bucket everything >10Gb as "large enough"
100);
// Also report as a percentage of capacity
base::UmaHistogramPercentage(
"PerformanceManager.DiskStats.UserDataDirFreeSpacePercent",
pending_disk_metrics_->free_bytes.InBytes() * 100 /
pending_disk_metrics_->total_bytes.InBytes());
pending_disk_metrics_ = std::nullopt;
}
void MetricsProviderDesktop::PostDiskMetricsTask() {
if (!g_browser_process || !g_browser_process->profile_manager()) {
// It's possible to have a null browser process or a null profile manager in
// unit tests.
return;
}
// Records the free/available space on the disk that hosts the user data dir.
ProfileManager* profile_manager = g_browser_process->profile_manager();
const base::FilePath& user_data_dir = profile_manager->user_data_dir();
disk_metrics_getter_
.AsyncCall(&MetricsProviderDesktop::DiskMetricsThreadPoolGetter::
ComputeDiskMetrics)
.WithArgs(user_data_dir)
.Then(base::BindOnce(&MetricsProviderDesktop::SavePendingDiskMetrics,
base::Unretained(this)));
}
MetricsProviderDesktop::DiskMetrics
MetricsProviderDesktop::DiskMetricsThreadPoolGetter::ComputeDiskMetrics(
const base::FilePath& user_data_dir) {
return {
.free_bytes =
base::ByteCount(base::SysInfo::AmountOfFreeDiskSpace(user_data_dir)),
.total_bytes =
base::ByteCount(base::SysInfo::AmountOfTotalDiskSpace(user_data_dir)),
};
}
void MetricsProviderDesktop::SavePendingDiskMetrics(DiskMetrics metrics) {
pending_disk_metrics_ = metrics;
}
} // namespace performance_manager