blob: 989378de9438287340e77eb844193c8b5634c5b9 [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"
#include "chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h"
namespace {
constexpr const char* kBatteryDischargeRateMilliwattsHistogramName =
"Power.BatteryDischargeRateMilliwatts6";
constexpr const char* kBatteryDischargeRateRelativeHistogramName =
"Power.BatteryDischargeRateRelative5";
constexpr const char* kBatteryDischargeModeHistogramName =
"Power.BatteryDischargeMode5";
#if BUILDFLAG(IS_WIN)
constexpr const char* kHasPreciseBatteryDischargeGranularity =
"Power.HasPreciseBatteryDischargeGranularity";
constexpr const char* kBatteryDischargeRatePreciseMilliwattsHistogramName =
"Power.BatteryDischargeRatePreciseMilliwatts";
constexpr const char* kBatteryDischargeRateMilliwattsTenMinutesHistogramName =
"Power.BatteryDischargeRateMilliwatts6.TenMinutes";
constexpr const char* kBatteryDischargeModeTenMinutesHistogramName =
"Power.BatteryDischargeMode5.TenMinutes";
#endif // BUILDFLAG(IS_WIN)
} // 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(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 std::optional<base::BatteryLevelProvider::BatteryState>&
previous_battery_state,
const std::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, std::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, std::nullopt};
}
if (new_battery_state->battery_count == 0) {
return {BatteryDischargeMode::kNoBattery, std::nullopt};
}
if (new_battery_state->is_external_power_connected) {
return {BatteryDischargeMode::kPluggedIn, std::nullopt};
}
if (new_battery_state->battery_count > 1) {
return {BatteryDischargeMode::kMultipleBatteries, std::nullopt};
}
if ((previous_battery_state->charge_unit ==
base::BatteryLevelProvider::BatteryLevelUnit::kRelative) ||
(new_battery_state->charge_unit ==
base::BatteryLevelProvider::BatteryLevelUnit::kRelative)) {
return {BatteryDischargeMode::kInsufficientResolution, std::nullopt};
}
// TODO(crbug.com/40756364): 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, std::nullopt};
}
#endif
if (previous_battery_state->full_charged_capacity.value() == 0 ||
new_battery_state->full_charged_capacity.value() == 0) {
return {BatteryDischargeMode::kFullChargedCapacityIsZero, std::nullopt};
}
const auto discharge_rate_mw = CalculateDischargeRateMilliwatts(
*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;
std::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 = 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, std::nullopt};
}
return {
.mode = BatteryDischargeMode::kDischarging,
.rate_milliwatts = 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)
bool battery_saver_enabled =
performance_manager::user_tuning::BatterySaverModeManager::
GetInstance() &&
performance_manager::user_tuning::BatterySaverModeManager::GetInstance()
->IsBatterySaverActive();
const char* interval_type_suffixes[] = {
"", is_initial_interval ? ".Initial" : ".Periodic"};
const char* battery_saver_suffixes[] = {"", battery_saver_enabled
? ".BatterySaverEnabled"
: ".BatterySaverDisabled"};
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);
for (const char* battery_saver_suffix : battery_saver_suffixes) {
if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
DCHECK(battery_discharge.rate_milliwatts.has_value());
base::UmaHistogramCounts100000(
base::StrCat({kBatteryDischargeRateMilliwattsHistogramName,
scenario_suffix, interval_type_suffix,
battery_saver_suffix}),
*battery_discharge.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_saver_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_saver_suffix}),
*battery_discharge.rate_relative);
}
}
}
}
}
#if BUILDFLAG(IS_WIN)
void ReportBatteryHistogramsTenMinutesInterval(
base::TimeDelta interval_duration,
BatteryDischarge battery_discharge) {
base::UmaHistogramEnumeration(kBatteryDischargeModeTenMinutesHistogramName,
battery_discharge.mode);
if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
DCHECK(battery_discharge.rate_milliwatts.has_value());
base::UmaHistogramCounts100000(
kBatteryDischargeRateMilliwattsTenMinutesHistogramName,
*battery_discharge.rate_milliwatts);
}
}
#endif // BUILDFLAG(IS_WIN)