blob: eb8a819a4f43c7da0105226b81348db9b1a496d8 [file] [log] [blame]
// Copyright 2022 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.h"
#include <string>
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "chrome/browser/metrics/power/process_metrics_recorder_util.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
namespace {
constexpr const char* kBatteryDischargeRateMilliwattsHistogramName =
"Power.BatteryDischargeRateMilliwatts5";
constexpr const char* kAltBatteryDischargeRateMilliwattsHistogramName =
"Power.AltBatteryDischargeRateMilliwatts5";
#if BUILDFLAG(IS_WIN)
constexpr const char* kHasPreciseBatteryDischargeGranularity =
"Power.HasPreciseBatteryDischargeGranularity";
constexpr const char* kBatteryDischargeRatePreciseMilliwattsHistogramName =
"Power.BatteryDischargeRatePreciseMilliwatts";
#endif // BUILDFLAG(IS_WIN)
constexpr const char* kBatteryDischargeRateRelativeHistogramName =
"Power.BatteryDischargeRateRelative5";
constexpr const char* kBatteryDischargeModeHistogramName =
"Power.BatteryDischargeMode5";
#if BUILDFLAG(IS_MAC)
// Reports `proportion` of a time used to a histogram in permyriad (1/100 %).
// `proportion` is 0.5 if half a CPU core or half total GPU time is used. It can
// be above 1.0 if more than 1 CPU core is used. CPU and GPU usage is often
// below 1% so it's useful to report with 1/10000 granularity (otherwise most
// samples end up in the same bucket).
void UsageTimeHistogram(const std::string& histogram_name,
double proportion,
int max_proportion) {
// Multiplicator to convert `proportion` to permyriad (1/100 %).
// For example, 1.0 * kScaleFactor = 10000 1/100 % = 100 %.
constexpr int kScaleFactor = 100 * 100;
base::UmaHistogramCustomCounts(
histogram_name, std::lroundl(proportion * kScaleFactor),
/* min=*/1, /* exclusive_max=*/max_proportion * kScaleFactor,
/* buckets=*/50);
}
// Max proportion for CPU time histograms. This used to be 64 but was reduced to
// 2 because data shows that less than 0.2% of samples are above that.
constexpr int kMaxCPUProportion = 2;
// Max proportion for GPU time histograms. It's not possible to use more than
// 100% of total GPU time.
constexpr int kMaxGPUProportion = 1;
#endif // BUILDFLAG(IS_MAC)
// Returns the current capacity of |battery_state| in milliwatt-hours.
uint64_t GetBatteryCapacityinMWh(
const base::BatteryLevelProvider::BatteryState& battery_state) {
if (battery_state.charge_unit ==
base::BatteryLevelProvider::BatteryLevelUnit::kMWh) {
return battery_state.current_capacity.value();
}
DCHECK_EQ(battery_state.charge_unit.value(),
base::BatteryLevelProvider::BatteryLevelUnit::kMAh);
DCHECK(battery_state.voltage_mv.has_value());
return battery_state.current_capacity.value() *
battery_state.voltage_mv.value() / 1000;
}
} // namespace
void ReportAggregatedProcessMetricsHistograms(
const ProcessMonitor::Metrics& aggregated_process_metrics,
const std::vector<const char*>& suffixes) {
for (const char* suffix : suffixes) {
std::string complete_suffix = base::StrCat({"Total", suffix});
RecordProcessHistograms(complete_suffix.c_str(),
aggregated_process_metrics);
}
}
int64_t CalculateDischargeRateMilliwatts(
const base::BatteryLevelProvider::BatteryState& previous_battery_state,
const base::BatteryLevelProvider::BatteryState& new_battery_state,
base::TimeDelta interval_duration) {
DCHECK_EQ(previous_battery_state.charge_unit.value(),
new_battery_state.charge_unit.value());
const uint64_t previous_capacity =
GetBatteryCapacityinMWh(previous_battery_state);
const uint64_t new_capacity = GetBatteryCapacityinMWh(new_battery_state);
// The capacity is in mWh. Divide by hours to get mW. Note that there is no
// InHoursF() method.
const double interval_duration_in_hours =
interval_duration.InSecondsF() / base::Time::kSecondsPerHour;
return (previous_capacity - new_capacity) / interval_duration_in_hours;
}
int64_t CalculateAltDischargeRateMilliwatts(
const base::BatteryLevelProvider::BatteryState& previous_battery_state,
const base::BatteryLevelProvider::BatteryState& new_battery_state,
base::TimeDelta interval_duration) {
DCHECK(previous_battery_state.charge_unit.has_value());
DCHECK(new_battery_state.charge_unit.has_value());
DCHECK_EQ(previous_battery_state.charge_unit.value(),
new_battery_state.charge_unit.value());
const int64_t discharge_capacity =
(new_battery_state.full_charged_capacity.value() -
new_battery_state.current_capacity.value()) -
(previous_battery_state.full_charged_capacity.value() -
previous_battery_state.current_capacity.value());
const int64_t discharge_capacity_mwh = [&]() -> int64_t {
if (new_battery_state.charge_unit.value() ==
base::BatteryLevelProvider::BatteryLevelUnit::kMWh) {
return discharge_capacity;
}
DCHECK_EQ(new_battery_state.charge_unit.value(),
base::BatteryLevelProvider::BatteryLevelUnit::kMAh);
const uint64_t average_mv = (previous_battery_state.voltage_mv.value() +
new_battery_state.voltage_mv.value()) /
2;
return discharge_capacity * average_mv / 1000;
}();
// The capacity is in mWh. Divide by hours to get mW. Note that there is no
// InHoursF() method.
const double interval_duration_in_hours =
interval_duration.InSecondsF() / base::Time::kSecondsPerHour;
return discharge_capacity_mwh / interval_duration_in_hours;
}
int64_t CalculateDischargeRateRelative(
const base::BatteryLevelProvider::BatteryState& previous_battery_state,
const base::BatteryLevelProvider::BatteryState& new_battery_state,
base::TimeDelta interval_duration) {
// The battery discharge rate is reported per minute with 1/10000 of full
// charge resolution.
static constexpr int64_t kDischargeRateFactor = 10000;
const double previous_level =
static_cast<double>(previous_battery_state.current_capacity.value()) /
previous_battery_state.full_charged_capacity.value();
const double new_level =
static_cast<double>(new_battery_state.current_capacity.value()) /
new_battery_state.full_charged_capacity.value();
const double interval_duration_in_minutes =
interval_duration.InSecondsF() / base::Time::kSecondsPerMinute;
return (previous_level - new_level) * kDischargeRateFactor /
interval_duration_in_minutes;
}
BatteryDischarge GetBatteryDischargeDuringInterval(
const absl::optional<base::BatteryLevelProvider::BatteryState>&
previous_battery_state,
const absl::optional<base::BatteryLevelProvider::BatteryState>&
new_battery_state,
base::TimeDelta interval_duration) {
if (!previous_battery_state.has_value() || !new_battery_state.has_value()) {
return {BatteryDischargeMode::kRetrievalError, absl::nullopt};
}
if (previous_battery_state->is_external_power_connected !=
new_battery_state->is_external_power_connected ||
previous_battery_state->battery_count !=
new_battery_state->battery_count) {
return {BatteryDischargeMode::kStateChanged, absl::nullopt};
}
if (new_battery_state->battery_count == 0) {
return {BatteryDischargeMode::kNoBattery, absl::nullopt};
}
if (new_battery_state->is_external_power_connected) {
return {BatteryDischargeMode::kPluggedIn, absl::nullopt};
}
if (new_battery_state->battery_count > 1) {
return {BatteryDischargeMode::kMultipleBatteries, absl::nullopt};
}
if ((previous_battery_state->charge_unit ==
base::BatteryLevelProvider::BatteryLevelUnit::kRelative) ||
(new_battery_state->charge_unit ==
base::BatteryLevelProvider::BatteryLevelUnit::kRelative)) {
return {BatteryDischargeMode::kInsufficientResolution, absl::nullopt};
}
// TODO(crbug.com/1191045): Change CHECK to DCHECK in October 2022 after
// verifying that there are no crash reports.
CHECK(previous_battery_state->current_capacity.has_value());
CHECK(previous_battery_state->full_charged_capacity.has_value());
CHECK(new_battery_state->current_capacity.has_value());
CHECK(new_battery_state->full_charged_capacity.has_value());
#if BUILDFLAG(IS_MAC)
// On MacOS, empirical evidence has shown that right after a full charge, the
// current capacity stays equal to the maximum capacity for several minutes,
// despite the fact that power was definitely consumed. Reporting a zero
// discharge rate for this duration would be misleading.
if (previous_battery_state->current_capacity ==
previous_battery_state->full_charged_capacity) {
return {BatteryDischargeMode::kMacFullyCharged, absl::nullopt};
}
#endif
if (previous_battery_state->full_charged_capacity.value() == 0 ||
new_battery_state->full_charged_capacity.value() == 0) {
return {BatteryDischargeMode::kFullChargedCapacityIsZero, absl::nullopt};
}
const auto discharge_rate_mw = CalculateDischargeRateMilliwatts(
*previous_battery_state, *new_battery_state, interval_duration);
const auto alt_discharge_rate_mw = CalculateAltDischargeRateMilliwatts(
*previous_battery_state, *new_battery_state, interval_duration);
#if BUILDFLAG(IS_WIN)
// The maximum granularity allowed for the following battery discharge value.
// The bell curve of the battery discharge rate starts at 1000 mW. This
// correspond to a discharge amount of 1000/60 ~ 17 mWh every 1 minute
// interval.
static const int64_t kMaximumGranularityInMilliwattHours = 17;
absl::optional<int64_t> discharge_rate_with_precise_granularity;
if (previous_battery_state->battery_discharge_granularity.has_value() &&
previous_battery_state->battery_discharge_granularity.value() <=
kMaximumGranularityInMilliwattHours &&
new_battery_state->battery_discharge_granularity.has_value() &&
new_battery_state->battery_discharge_granularity.value() <=
kMaximumGranularityInMilliwattHours) {
discharge_rate_with_precise_granularity = alt_discharge_rate_mw;
}
#endif // BUILDFLAG(IS_WIN)
const auto discharge_rate_relative = CalculateDischargeRateRelative(
*previous_battery_state, *new_battery_state, interval_duration);
if (discharge_rate_relative < 0 || discharge_rate_mw < 0) {
return {BatteryDischargeMode::kBatteryLevelIncreased, absl::nullopt};
}
return {
.mode = BatteryDischargeMode::kDischarging,
.rate_milliwatts = discharge_rate_mw,
.alt_rate_milliwatts = alt_discharge_rate_mw,
#if BUILDFLAG(IS_WIN)
.rate_milliwatts_with_precise_granularity =
discharge_rate_with_precise_granularity,
#endif
.rate_relative = discharge_rate_relative
};
}
void ReportBatteryHistograms(
base::TimeDelta interval_duration,
BatteryDischarge battery_discharge,
bool is_initial_interval,
const std::vector<const char*>& scenario_suffixes) {
#if BUILDFLAG(IS_WIN)
base::UmaHistogramBoolean(
kHasPreciseBatteryDischargeGranularity,
battery_discharge.rate_milliwatts_with_precise_granularity.has_value());
#endif // BUILDFLAG(IS_WIN)
const char* interval_type_suffixes[] = {
"", is_initial_interval ? ".Initial" : ".Periodic"};
for (const char* scenario_suffix : scenario_suffixes) {
for (const char* interval_type_suffix : interval_type_suffixes) {
base::UmaHistogramEnumeration(
base::StrCat({kBatteryDischargeModeHistogramName, scenario_suffix,
interval_type_suffix}),
battery_discharge.mode);
if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
DCHECK(battery_discharge.rate_milliwatts.has_value());
base::UmaHistogramCounts100000(
base::StrCat({kBatteryDischargeRateMilliwattsHistogramName,
scenario_suffix, interval_type_suffix}),
*battery_discharge.rate_milliwatts);
DCHECK(battery_discharge.alt_rate_milliwatts.has_value());
base::UmaHistogramCounts100000(
base::StrCat({kAltBatteryDischargeRateMilliwattsHistogramName,
scenario_suffix, interval_type_suffix}),
*battery_discharge.alt_rate_milliwatts);
#if BUILDFLAG(IS_WIN)
if (battery_discharge.rate_milliwatts_with_precise_granularity) {
base::UmaHistogramCounts100000(
base::StrCat({kBatteryDischargeRatePreciseMilliwattsHistogramName,
scenario_suffix, interval_type_suffix}),
*battery_discharge.rate_milliwatts_with_precise_granularity);
}
#endif // BUILDFLAG(IS_WIN)
DCHECK(battery_discharge.rate_relative.has_value());
base::UmaHistogramCounts1000(
base::StrCat({kBatteryDischargeRateRelativeHistogramName,
scenario_suffix, interval_type_suffix}),
*battery_discharge.rate_relative);
}
}
}
}
#if BUILDFLAG(IS_MAC)
void ReportShortIntervalHistograms(
const char* scenario_suffix,
const power_metrics::CoalitionResourceUsageRate&
coalition_resource_usage_rate) {
for (const char* suffix : {"", scenario_suffix}) {
UsageTimeHistogram(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.CPUTime2_10sec", suffix}),
coalition_resource_usage_rate.cpu_time_per_second, kMaxCPUProportion);
}
}
void ReportResourceCoalitionHistograms(
const power_metrics::CoalitionResourceUsageRate& rate,
const std::vector<const char*>& suffixes) {
// Calling this function with an empty suffix list is probably a mistake.
DCHECK(!suffixes.empty());
// TODO(crbug.com/1229884): Review the units and buckets once we have
// sufficient data from the field.
for (const char* scenario_suffix : suffixes) {
// Suffixes are expected to be empty or starting by a period.
DCHECK(::strlen(scenario_suffix) == 0U || scenario_suffix[0] == '.');
UsageTimeHistogram(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.CPUTime2", scenario_suffix}),
rate.cpu_time_per_second, kMaxCPUProportion);
UsageTimeHistogram(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.GPUTime2", scenario_suffix}),
rate.gpu_time_per_second, kMaxGPUProportion);
// Report the metrics based on a count (e.g. wakeups) with a millievent/sec
// granularity. In theory it doesn't make much sense to talk about a
// milliwakeups but the wakeup rate should ideally be lower than one per
// second in some scenarios and this will provide more granularity.
constexpr int kMilliFactor = 1000;
auto scale_sample = [](double sample) -> int {
// Round the sample to the nearest integer value.
return std::roundl(sample * kMilliFactor);
};
base::UmaHistogramCounts1M(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond",
scenario_suffix}),
scale_sample(rate.interrupt_wakeups_per_second));
base::UmaHistogramCounts1M(
base::StrCat({"PerformanceMonitor.ResourceCoalition."
"PlatformIdleWakeupsPerSecond",
scenario_suffix}),
scale_sample(rate.platform_idle_wakeups_per_second));
base::UmaHistogramCounts10M(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.BytesReadPerSecond2",
scenario_suffix}),
rate.bytesread_per_second);
base::UmaHistogramCounts10M(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond2",
scenario_suffix}),
rate.byteswritten_per_second);
// EnergyImpact is reported in centi-EI, so scaled up by a factor of 100
// for the histogram recording.
if (rate.energy_impact_per_second.has_value()) {
constexpr double kEnergyImpactScalingFactor = 100.0;
base::UmaHistogramCounts100000(
base::StrCat({"PerformanceMonitor.ResourceCoalition.EnergyImpact",
scenario_suffix}),
std::roundl(rate.energy_impact_per_second.value() *
kEnergyImpactScalingFactor));
}
// As of Feb 2, 2022, the value of `rate->power_nw` is always zero on Intel.
// Don't report it to avoid polluting the data.
if (base::mac::GetCPUType() == base::mac::CPUType::kArm) {
constexpr int kMilliWattPerWatt = 1000;
constexpr int kNanoWattPerMilliWatt = 1000 * 1000;
// The maximum is 10 watts, which is larger than the 99.99th percentile
// as of Feb 2, 2022.
base::UmaHistogramCustomCounts(
base::StrCat(
{"PerformanceMonitor.ResourceCoalition.Power2", scenario_suffix}),
std::roundl(rate.power_nw / kNanoWattPerMilliWatt),
/* min=*/1, /* exclusive_max=*/10 * kMilliWattPerWatt,
/* buckets=*/50);
}
auto record_qos_level = [&](size_t index, const char* qos_suffix) {
UsageTimeHistogram(
base::StrCat({"PerformanceMonitor.ResourceCoalition.QoSLevel.",
qos_suffix, scenario_suffix}),
rate.qos_time_per_second[index], kMaxCPUProportion);
};
record_qos_level(THREAD_QOS_DEFAULT, "Default");
record_qos_level(THREAD_QOS_MAINTENANCE, "Maintenance");
record_qos_level(THREAD_QOS_BACKGROUND, "Background");
record_qos_level(THREAD_QOS_UTILITY, "Utility");
record_qos_level(THREAD_QOS_LEGACY, "Legacy");
record_qos_level(THREAD_QOS_USER_INITIATED, "UserInitiated");
record_qos_level(THREAD_QOS_USER_INTERACTIVE, "UserInteractive");
}
}
#endif // BUILDFLAG(IS_MAC)