blob: 64119a90a9e14296380c40a3c47f02832c533f67 [file] [log] [blame]
// Copyright 2021 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/metrics/power/power_metrics_reporter.h"
#include <optional>
#include <vector>
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/trace_event/named_trigger.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/metrics/power/power_metrics.h"
#include "chrome/browser/metrics/power/power_metrics_constants.h"
#include "chrome/browser/metrics/power/process_metrics_recorder_util.h"
#include "chrome/browser/metrics/power/process_monitor.h"
#include "chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace {
// Calculates the UKM bucket |value| falls in and returns it. This uses an
// exponential bucketing approach with an exponent base of 1.3, resulting in
// 17 buckets for an interval of 120 seconds.
int64_t GetBucketForSample(base::TimeDelta value) {
const float kBucketSpacing = 1.3;
// Put all the abnormal values in an overflow bucket. The default interval
// length is 120 seconds, with an exponent base of 1.3 the bucket for this
// value includes all values in the [113, 146] range.
constexpr int64_t kOverflowBucket = 147;
DCHECK_EQ(kOverflowBucket,
ukm::GetExponentialBucketMin(kOverflowBucket, kBucketSpacing));
return std::min(
ukm::GetExponentialBucketMin(value.InSeconds(), kBucketSpacing),
kOverflowBucket);
}
// Returns the histogram suffix to be used given the MonitoredProcessType.
const char* GetMetricSuffixFromProcessType(MonitoredProcessType type) {
switch (type) {
case MonitoredProcessType::kBrowser:
return "BrowserProcess";
case MonitoredProcessType::kRenderer:
return "RendererProcess";
case MonitoredProcessType::kExtensionPersistent:
return "RendererExtensionPersistentProcess";
case MonitoredProcessType::kExtensionEvent:
return "RendererExtensionEventProcess";
case MonitoredProcessType::kGpu:
return "GPUProcess";
case MonitoredProcessType::kUtility:
return "UtilityProcess";
case MonitoredProcessType::kNetwork:
return "NetworkProcess";
case MonitoredProcessType::kOther:
return "OtherProcess";
default:
NOTREACHED();
}
}
} // namespace
PowerMetricsReporter::PowerMetricsReporter(
ProcessMonitor* process_monitor,
UsageScenarioDataStore* long_usage_scenario_data_store,
std::unique_ptr<base::BatteryLevelProvider> battery_level_provider)
: process_monitor_(process_monitor),
long_usage_scenario_data_store_(long_usage_scenario_data_store),
battery_level_provider_(std::move(battery_level_provider)) {
if (!long_usage_scenario_data_store_) {
long_usage_scenario_tracker_ = std::make_unique<UsageScenarioTracker>();
long_usage_scenario_data_store_ =
long_usage_scenario_tracker_->data_store();
}
interval_begin_ = base::TimeTicks::Now();
// `battery_level_provider_` may be null on platforms that do not have an
// implementation.
if (battery_level_provider_) {
// Unretained() is safe here because |this| outlive
// |battery_level_provider_|.
battery_level_provider_->GetBatteryState(
base::BindOnce(&PowerMetricsReporter::OnFirstBatteryStateSampled,
base::Unretained(this)));
}
StartNextLongInterval();
}
PowerMetricsReporter::~PowerMetricsReporter() = default;
// static
int64_t PowerMetricsReporter::GetBucketForSampleForTesting(
base::TimeDelta value) {
return GetBucketForSample(value);
}
void PowerMetricsReporter::OnFirstBatteryStateSampled(
const std::optional<base::BatteryLevelProvider::BatteryState>&
battery_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(battery_level_provider_);
battery_state_ = battery_state;
}
void PowerMetricsReporter::StartNextLongInterval() {
// TODO(fdoray): Remove when no longer referenced by server-side trace
// configs, planned for 06/2024.
base::trace_event::EmitNamedTrigger("power-metrics-interval-start");
interval_timer_.Start(FROM_HERE, kLongPowerMetricsIntervalDuration,
base::BindOnce(&PowerMetricsReporter::OnLongIntervalEnd,
base::Unretained(this)));
}
void PowerMetricsReporter::OnLongIntervalEnd() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Sample the metrics for all processes. This will call back into
// OnAggregatedMetricsSampled() once done (synchronously).
process_monitor_->SampleAllProcesses(this);
}
void PowerMetricsReporter::OnMetricsSampled(
MonitoredProcessType type,
const ProcessMonitor::Metrics& metrics) {
RecordProcessHistograms(GetMetricSuffixFromProcessType(type), metrics);
}
void PowerMetricsReporter::OnAggregatedMetricsSampled(
const ProcessMonitor::Metrics& metrics) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Evaluate the interval duration.
const base::TimeTicks now = base::TimeTicks::Now();
const base::TimeDelta interval_duration = now - interval_begin_;
interval_begin_ = now;
// Finally, retrieve the battery state before reporting the metrics. On
// platform without a BatteryLevelProvider implementation, skip straight to
// reporting the metrics.
if (battery_level_provider_) {
// Note: The use of `Unretained()` is safe here because |this| outlives
// |battery_level_provider_|.
battery_level_provider_->GetBatteryState(base::BindOnce(
&PowerMetricsReporter::OnBatteryAndAggregatedProcessMetricsSampled,
base::Unretained(this), metrics, interval_duration));
} else {
// Get usage scenario data.
auto long_interval_data =
long_usage_scenario_data_store_->ResetIntervalData();
ReportMetrics(long_interval_data, interval_duration, metrics);
}
}
void PowerMetricsReporter::OnBatteryAndAggregatedProcessMetricsSampled(
const ProcessMonitor::Metrics& aggregated_process_metrics,
base::TimeDelta interval_duration,
const std::optional<base::BatteryLevelProvider::BatteryState>&
new_battery_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(battery_level_provider_);
// Evaluate battery discharge mode and rate.
auto previous_battery_state =
std::exchange(battery_state_, new_battery_state);
auto battery_discharge = GetBatteryDischargeDuringInterval(
previous_battery_state, new_battery_state, interval_duration);
// Get usage scenario data.
auto long_interval_data =
long_usage_scenario_data_store_->ResetIntervalData();
ReportMetrics(long_interval_data, interval_duration,
aggregated_process_metrics);
ReportBatterySpecificMetrics(long_interval_data, interval_duration,
aggregated_process_metrics, battery_discharge);
}
void PowerMetricsReporter::ReportMetrics(
const UsageScenarioDataStore::IntervalData& long_interval_data,
base::TimeDelta interval_duration,
const ProcessMonitor::Metrics& aggregated_process_metrics) {
// Get scenario data.
const auto long_interval_scenario_params =
GetLongIntervalScenario(long_interval_data);
// Histograms are recorded without suffix and with a scenario-specific
// suffix.
const std::vector<const char*> long_interval_suffixes{
"", long_interval_scenario_params.histogram_suffix};
// Report process metrics histograms.
ReportAggregatedProcessMetricsHistograms(aggregated_process_metrics,
long_interval_suffixes);
base::UmaHistogramEnumeration("PerformanceMonitor.UsageScenario.LongInterval",
long_interval_scenario_params.scenario);
StartNextLongInterval();
}
void PowerMetricsReporter::ReportBatterySpecificMetrics(
const UsageScenarioDataStore::IntervalData& long_interval_data,
base::TimeDelta interval_duration,
const ProcessMonitor::Metrics& aggregated_process_metrics,
BatteryDischarge battery_discharge) {
DCHECK(battery_level_provider_);
// Report UKMs.
ReportBatteryUKMs(long_interval_data, aggregated_process_metrics,
interval_duration, battery_discharge);
}
void PowerMetricsReporter::ReportBatteryUKMs(
const UsageScenarioDataStore::IntervalData& interval_data,
const ProcessMonitor::Metrics& metrics,
base::TimeDelta interval_duration,
BatteryDischarge battery_discharge) {
// UKM may be unavailable in content_shell or other non-chrome/ builds; it
// may also be unavailable if browser shutdown has started; so this may be a
// nullptr. If it's unavailable, UKM reporting will be skipped.
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
if (!ukm_recorder)
return;
auto source_id = interval_data.source_id_for_longest_visible_origin;
// Only navigation SourceIds should be associated with this UKM.
if (source_id != ukm::kInvalidSourceId) {
// TODO(crbug.com/40158987): Change to a DCHECK in August 2021, after we've
// validated that the condition is always met in production.
CHECK_EQ(ukm::GetSourceIdType(source_id), ukm::SourceIdType::NAVIGATION_ID);
}
ukm::builders::PowerUsageScenariosIntervalData builder(source_id);
builder.SetURLVisibilityTimeSeconds(GetBucketForSample(
interval_data.source_id_for_longest_visible_origin_duration));
builder.SetIntervalDurationSeconds(interval_duration.InSeconds());
// An exponential bucket is fine here as this value isn't limited to the
// interval duration.
builder.SetUptimeSeconds(ukm::GetExponentialBucketMinForUserTiming(
interval_data.uptime_at_interval_end.InSeconds()));
builder.SetBatteryDischargeMode(static_cast<int64_t>(battery_discharge.mode));
if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
DCHECK(battery_discharge.rate_relative.has_value());
builder.SetBatteryDischargeRate(*battery_discharge.rate_relative);
}
if (metrics.cpu_usage.has_value()) {
builder.SetCPUTimeMs(metrics.cpu_usage.value() *
interval_duration.InMilliseconds());
}
#if BUILDFLAG(IS_MAC)
builder.SetIdleWakeUps(metrics.idle_wakeups);
builder.SetPackageExits(metrics.package_idle_wakeups);
#endif
builder.SetMaxTabCount(
ukm::GetExponentialBucketMinForCounts1000(interval_data.max_tab_count));
// The number of windows is usually relatively low, use a small bucket
// spacing.
builder.SetMaxVisibleWindowCount(ukm::GetExponentialBucketMin(
interval_data.max_visible_window_count, 1.05));
builder.SetTabClosed(ukm::GetExponentialBucketMinForCounts1000(
interval_data.tabs_closed_during_interval));
builder.SetTimePlayingVideoInVisibleTab(
GetBucketForSample(interval_data.time_playing_video_in_visible_tab));
builder.SetTopLevelNavigationEvents(ukm::GetExponentialBucketMinForCounts1000(
interval_data.top_level_navigation_count));
builder.SetUserInteractionCount(ukm::GetExponentialBucketMinForCounts1000(
interval_data.user_interaction_count));
builder.SetFullscreenVideoSingleMonitorSeconds(GetBucketForSample(
interval_data.time_playing_video_full_screen_single_monitor));
builder.SetTimeWithOpenWebRTCConnectionSeconds(
GetBucketForSample(interval_data.time_with_open_webrtc_connection));
builder.SetTimeSinceInteractionWithBrowserSeconds(GetBucketForSample(
interval_data.time_since_last_user_interaction_with_browser));
builder.SetVideoCaptureSeconds(
GetBucketForSample(interval_data.time_capturing_video));
builder.SetBrowserShuttingDown(browser_shutdown::HasShutdownStarted());
builder.SetPlayingAudioSeconds(
GetBucketForSample(interval_data.time_playing_audio));
builder.SetOriginVisibilityTimeSeconds(
GetBucketForSample(interval_data.longest_visible_origin_duration));
builder.SetDeviceSleptDuringInterval(interval_data.sleep_events);
builder.Record(ukm_recorder);
}