blob: b1f425a13d1436a4710c7217288699b684a75374 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "power_manager/powerd/metrics_collector.h"
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include "power_manager/common/metrics_constants.h"
#include "power_manager/common/metrics_sender.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
#include "power_manager/powerd/policy/backlight_controller.h"
namespace power_manager {
using system::PowerStatus;
namespace metrics {
namespace {
// Generates the histogram name under which dark resume wake duration metrics
// are logged for the dark resume triggered by |wake_reason|.
std::string WakeReasonToHistogramName(const std::string& wake_reason) {
return std::string("Power.DarkResumeWakeDurationMs.").append(wake_reason);
}
// Returns true if port |index| exists in |status| and has a connected dedicated
// source or dual-role device.
bool ChargingPortConnected(const PowerStatus& status, size_t index) {
if (index >= status.ports.size())
return false;
const PowerStatus::Port::Role role = status.ports[index].role;
return role == PowerStatus::Port::Role::DEDICATED_SOURCE ||
role == PowerStatus::Port::Role::DUAL_ROLE;
}
// Returns a value describing which power ports are connected.
ConnectedChargingPorts GetConnectedChargingPorts(const PowerStatus& status) {
// More values should be added here if we ship systems with more than two
// ports.
if (status.ports.size() > 2u)
return ConnectedChargingPorts::TOO_MANY_PORTS;
const bool port1_connected = ChargingPortConnected(status, 0);
const bool port2_connected = ChargingPortConnected(status, 1);
if (port1_connected && port2_connected)
return ConnectedChargingPorts::PORT1_PORT2;
else if (port1_connected)
return ConnectedChargingPorts::PORT1;
else if (port2_connected)
return ConnectedChargingPorts::PORT2;
else
return ConnectedChargingPorts::NONE;
}
} // namespace
// static
constexpr char MetricsCollector::kBigCoreS0ixResidencyPath[];
constexpr char MetricsCollector::kSmallCoreS0ixResidencyPath[];
constexpr base::TimeDelta MetricsCollector::KS0ixOverheadTime;
std::string MetricsCollector::AppendPowerSourceToEnumName(
const std::string& enum_name, PowerSource power_source) {
return enum_name +
(power_source == PowerSource::AC ? kAcSuffix : kBatterySuffix);
}
// static
int MetricsCollector::GetExpectedS0ixResidencyPercent(
const base::TimeDelta& suspend_time,
const base::TimeDelta& actual_residency) {
base::TimeDelta expected_residency =
suspend_time - MetricsCollector::KS0ixOverheadTime;
int s0ix_residency_percent =
static_cast<int>(round((actual_residency * 100.0) / expected_residency));
return std::min(100, s0ix_residency_percent);
}
MetricsCollector::MetricsCollector() = default;
MetricsCollector::~MetricsCollector() = default;
void MetricsCollector::Init(
PrefsInterface* prefs,
policy::BacklightController* display_backlight_controller,
policy::BacklightController* keyboard_backlight_controller,
const PowerStatus& power_status,
bool first_run_after_boot) {
prefs_ = prefs;
display_backlight_controller_ = display_backlight_controller;
keyboard_backlight_controller_ = keyboard_backlight_controller;
last_power_status_ = power_status;
if (first_run_after_boot) {
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetricWithPowerSource(
kBatteryRemainingAtBootName,
static_cast<int>(round(last_power_status_.battery_percentage)),
kMaxPercent);
}
if (display_backlight_controller_ || keyboard_backlight_controller_) {
generate_backlight_metrics_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(kBacklightLevelIntervalMs),
this, &MetricsCollector::GenerateBacklightLevelMetrics);
}
bool pref_val = false;
suspend_to_idle_ = prefs_->GetBool(kSuspendToIdlePref, &pref_val) && pref_val;
if (suspend_to_idle_) {
// S0ix residency related configuration.
if (base::PathExists(
GetPrefixedFilePath(base::FilePath(kBigCoreS0ixResidencyPath)))) {
s0ix_residency_path_ =
GetPrefixedFilePath(base::FilePath(kBigCoreS0ixResidencyPath));
} else if (base::PathExists(GetPrefixedFilePath(
base::FilePath(kSmallCoreS0ixResidencyPath)))) {
s0ix_residency_path_ =
GetPrefixedFilePath(base::FilePath(kSmallCoreS0ixResidencyPath));
}
// For devices with |kBigCoreS0ixResidencyPath|, the default range is little
// complicated. |kBigCoreS0ixResidencyPath| reports the time spent in S0ix
// by reading SLP_S0_RES (32 bit) register. This register increments once
// for every 100 micro seconds spent in S0ix. The value read from this 32
// bit register is first casted to u64 and then multiplied by 100 to get
// micro second granularity. Thus the range of |kBigCoreS0ixResidencyPath|
// is 100 * UINT32_MAX.
if (s0ix_residency_path_ ==
GetPrefixedFilePath(base::FilePath(kBigCoreS0ixResidencyPath))) {
max_s0ix_residency_ =
base::TimeDelta::FromMicroseconds(100 * (uint64_t)UINT32_MAX);
}
}
}
void MetricsCollector::HandleScreenDimmedChange(
bool dimmed, base::TimeTicks last_user_activity_time) {
if (dimmed) {
base::TimeTicks now = clock_.GetCurrentTime();
screen_dim_timestamp_ = now;
last_idle_event_timestamp_ = now;
last_idle_timedelta_ = now - last_user_activity_time;
} else {
screen_dim_timestamp_ = base::TimeTicks();
}
}
void MetricsCollector::HandleScreenOffChange(
bool off, base::TimeTicks last_user_activity_time) {
if (off) {
base::TimeTicks now = clock_.GetCurrentTime();
screen_off_timestamp_ = now;
last_idle_event_timestamp_ = now;
last_idle_timedelta_ = now - last_user_activity_time;
} else {
screen_off_timestamp_ = base::TimeTicks();
}
}
void MetricsCollector::HandleSessionStateChange(SessionState state) {
if (state == session_state_)
return;
session_state_ = state;
switch (state) {
case SessionState::STARTED:
session_start_time_ = clock_.GetCurrentTime();
if (!last_power_status_.line_power_on)
IncrementNumOfSessionsPerChargeMetric();
if (last_power_status_.battery_is_present) {
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetricWithPowerSource(
kBatteryRemainingAtStartOfSessionName,
static_cast<int>(round(last_power_status_.battery_percentage)),
kMaxPercent);
}
break;
case SessionState::STOPPED: {
if (last_power_status_.battery_is_present) {
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetricWithPowerSource(
kBatteryRemainingAtEndOfSessionName,
static_cast<int>(round(last_power_status_.battery_percentage)),
kMaxPercent);
}
SendMetric(kLengthOfSessionName,
(clock_.GetCurrentTime() - session_start_time_).InSeconds(),
kLengthOfSessionMin, kLengthOfSessionMax, kDefaultBuckets);
if (display_backlight_controller_) {
SendMetric(kNumberOfAlsAdjustmentsPerSessionName,
display_backlight_controller_
->GetNumAmbientLightSensorAdjustments(),
kNumberOfAlsAdjustmentsPerSessionMin,
kNumberOfAlsAdjustmentsPerSessionMax, kDefaultBuckets);
SendMetricWithPowerSource(
kUserBrightnessAdjustmentsPerSessionName,
display_backlight_controller_->GetNumUserAdjustments(),
kUserBrightnessAdjustmentsPerSessionMin,
kUserBrightnessAdjustmentsPerSessionMax, kDefaultBuckets);
}
break;
}
}
}
void MetricsCollector::HandlePowerStatusUpdate(const PowerStatus& status) {
const bool previously_on_line_power = last_power_status_.line_power_on;
const bool previously_using_unknown_type =
previously_on_line_power &&
system::GetPowerSupplyTypeMetric(last_power_status_.line_power_type) ==
PowerSupplyType::OTHER;
last_power_status_ = status;
// Charge stats.
if (status.line_power_on && !previously_on_line_power) {
GenerateNumOfSessionsPerChargeMetric();
if (status.battery_is_present) {
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetric(kBatteryRemainingWhenChargeStartsName,
static_cast<int>(round(status.battery_percentage)),
kMaxPercent);
SendEnumMetric(kBatteryChargeHealthName,
static_cast<int>(round(100.0 * status.battery_charge_full /
status.battery_charge_full_design)),
kBatteryChargeHealthMax);
}
} else if (!status.line_power_on && previously_on_line_power) {
if (session_state_ == SessionState::STARTED)
IncrementNumOfSessionsPerChargeMetric();
}
// Power supply details.
if (status.line_power_on) {
const PowerSupplyType type =
system::GetPowerSupplyTypeMetric(status.line_power_type);
if (type == PowerSupplyType::OTHER && !previously_using_unknown_type)
LOG(WARNING) << "Unknown power supply type " << status.line_power_type;
SendEnumMetric(kPowerSupplyTypeName, static_cast<int>(type),
static_cast<int>(PowerSupplyType::MAX));
// Sent as enums to avoid exponential histogram's exponentially-sized
// buckets.
SendEnumMetric(kPowerSupplyMaxVoltageName,
static_cast<int>(round(status.line_power_max_voltage)),
kPowerSupplyMaxVoltageMax);
SendEnumMetric(kPowerSupplyMaxPowerName,
static_cast<int>(round(status.line_power_max_voltage *
status.line_power_max_current)),
kPowerSupplyMaxPowerMax);
}
SendEnumMetric(kConnectedChargingPortsName,
static_cast<int>(GetConnectedChargingPorts(status)),
static_cast<int>(ConnectedChargingPorts::MAX));
GenerateBatteryDischargeRateMetric();
GenerateBatteryDischargeRateWhileSuspendedMetric();
SendEnumMetric(kBatteryInfoSampleName,
static_cast<int>(BatteryInfoSampleResult::READ),
static_cast<int>(BatteryInfoSampleResult::MAX));
// TODO(derat): Continue sending BAD in some situations? Remove this metric
// entirely?
SendEnumMetric(kBatteryInfoSampleName,
static_cast<int>(BatteryInfoSampleResult::GOOD),
static_cast<int>(BatteryInfoSampleResult::MAX));
}
void MetricsCollector::HandleShutdown(ShutdownReason reason) {
SendEnumMetric(kShutdownReasonName, static_cast<int>(reason),
static_cast<int>(kShutdownReasonMax));
}
void MetricsCollector::PrepareForSuspend() {
battery_energy_before_suspend_ = last_power_status_.battery_energy;
on_line_power_before_suspend_ = last_power_status_.line_power_on;
time_before_suspend_ = clock_.GetCurrentBootTime();
if (suspend_to_idle_)
TrackS0ixResidency(true);
}
void MetricsCollector::HandleResume(int num_suspend_attempts) {
SendMetric(kSuspendAttemptsBeforeSuccessName, num_suspend_attempts,
kSuspendAttemptsMin, kSuspendAttemptsMax, kSuspendAttemptsBuckets);
// Report the discharge rate in response to the next
// OnPowerStatusUpdate() call.
report_battery_discharge_rate_while_suspended_ = true;
if (suspend_to_idle_)
TrackS0ixResidency(false);
}
void MetricsCollector::HandleCanceledSuspendRequest(int num_suspend_attempts) {
SendMetric(kSuspendAttemptsBeforeCancelName, num_suspend_attempts,
kSuspendAttemptsMin, kSuspendAttemptsMax, kSuspendAttemptsBuckets);
}
void MetricsCollector::GenerateDarkResumeMetrics(
const std::vector<policy::Suspender::DarkResumeInfo>& wake_durations,
base::TimeDelta suspend_duration) {
if (suspend_duration.InSeconds() <= 0)
return;
// We want to get metrics even if the system suspended for less than an hour
// so we scale the number of wakes up.
static const int kSecondsPerHour = 60 * 60;
const int64_t wakeups_per_hour =
wake_durations.size() * kSecondsPerHour / suspend_duration.InSeconds();
SendMetric(kDarkResumeWakeupsPerHourName, wakeups_per_hour,
kDarkResumeWakeupsPerHourMin, kDarkResumeWakeupsPerHourMax,
kDefaultBuckets);
for (const auto& pair : wake_durations) {
// Send aggregated dark resume duration metric.
SendMetric(kDarkResumeWakeDurationMsName, pair.second.InMilliseconds(),
kDarkResumeWakeDurationMsMin, kDarkResumeWakeDurationMsMax,
kDefaultBuckets);
// Send wake reason-specific dark resume duration metric.
SendMetric(WakeReasonToHistogramName(pair.first),
pair.second.InMilliseconds(), kDarkResumeWakeDurationMsMin,
kDarkResumeWakeDurationMsMax, kDefaultBuckets);
}
}
void MetricsCollector::GenerateUserActivityMetrics() {
if (last_idle_event_timestamp_.is_null())
return;
base::TimeTicks current_time = clock_.GetCurrentTime();
base::TimeDelta event_delta = current_time - last_idle_event_timestamp_;
base::TimeDelta total_delta = event_delta + last_idle_timedelta_;
last_idle_event_timestamp_ = base::TimeTicks();
SendMetricWithPowerSource(kIdleName, total_delta.InMilliseconds(), kIdleMin,
kIdleMax, kDefaultBuckets);
if (!screen_dim_timestamp_.is_null()) {
base::TimeDelta dim_event_delta = current_time - screen_dim_timestamp_;
SendMetricWithPowerSource(
kIdleAfterDimName, dim_event_delta.InMilliseconds(), kIdleAfterDimMin,
kIdleAfterDimMax, kDefaultBuckets);
screen_dim_timestamp_ = base::TimeTicks();
}
if (!screen_off_timestamp_.is_null()) {
base::TimeDelta screen_off_event_delta =
current_time - screen_off_timestamp_;
SendMetricWithPowerSource(
kIdleAfterScreenOffName, screen_off_event_delta.InMilliseconds(),
kIdleAfterScreenOffMin, kIdleAfterScreenOffMax, kDefaultBuckets);
screen_off_timestamp_ = base::TimeTicks();
}
}
void MetricsCollector::GenerateBacklightLevelMetrics() {
if (!screen_dim_timestamp_.is_null() || !screen_off_timestamp_.is_null())
return;
double percent = 0.0;
if (display_backlight_controller_ &&
display_backlight_controller_->GetBrightnessPercent(&percent)) {
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetricWithPowerSource(kBacklightLevelName, lround(percent),
kMaxPercent);
}
if (keyboard_backlight_controller_ &&
keyboard_backlight_controller_->GetBrightnessPercent(&percent)) {
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetric(kKeyboardBacklightLevelName, lround(percent), kMaxPercent);
}
}
void MetricsCollector::HandlePowerButtonEvent(ButtonState state) {
switch (state) {
case ButtonState::DOWN:
// Just keep track of the time when the button was pressed.
if (!last_power_button_down_timestamp_.is_null()) {
LOG(ERROR) << "Got power-button-down event while button was already "
<< "down";
}
last_power_button_down_timestamp_ = clock_.GetCurrentTime();
break;
case ButtonState::UP: {
// Metrics are sent after the button is released.
if (last_power_button_down_timestamp_.is_null()) {
LOG(ERROR) << "Got power-button-up event while button was already up";
} else {
base::TimeDelta delta =
clock_.GetCurrentTime() - last_power_button_down_timestamp_;
last_power_button_down_timestamp_ = base::TimeTicks();
SendMetric(kPowerButtonDownTimeName, delta.InMilliseconds(),
kPowerButtonDownTimeMin, kPowerButtonDownTimeMax,
kDefaultBuckets);
}
break;
}
case ButtonState::REPEAT:
// Ignore repeat events if we get them.
break;
}
}
void MetricsCollector::SendPowerButtonAcknowledgmentDelayMetric(
base::TimeDelta delay) {
SendMetric(kPowerButtonAcknowledgmentDelayName, delay.InMilliseconds(),
kPowerButtonAcknowledgmentDelayMin,
kPowerButtonAcknowledgmentDelayMax, kDefaultBuckets);
}
bool MetricsCollector::SendMetricWithPowerSource(
const std::string& name, int sample, int min, int max, int num_buckets) {
const std::string full_name = AppendPowerSourceToEnumName(
name, last_power_status_.line_power_on ? PowerSource::AC
: PowerSource::BATTERY);
return SendMetric(full_name, sample, min, max, num_buckets);
}
bool MetricsCollector::SendEnumMetricWithPowerSource(const std::string& name,
int sample,
int max) {
const std::string full_name = AppendPowerSourceToEnumName(
name, last_power_status_.line_power_on ? PowerSource::AC
: PowerSource::BATTERY);
return SendEnumMetric(full_name, sample, max);
}
void MetricsCollector::GenerateBatteryDischargeRateMetric() {
// The battery discharge rate metric is relevant and collected only
// when running on battery.
if (!last_power_status_.battery_is_present ||
last_power_status_.line_power_on)
return;
// Converts the discharge rate from W to mW.
int rate =
static_cast<int>(round(last_power_status_.battery_energy_rate * 1000));
if (rate <= 0)
return;
// Ensures that the metric is not generated too frequently.
if (!last_battery_discharge_rate_metric_timestamp_.is_null() &&
(clock_.GetCurrentTime() - last_battery_discharge_rate_metric_timestamp_)
.InSeconds() < kBatteryDischargeRateIntervalSec) {
return;
}
if (SendMetric(kBatteryDischargeRateName, rate, kBatteryDischargeRateMin,
kBatteryDischargeRateMax, kDefaultDischargeBuckets))
last_battery_discharge_rate_metric_timestamp_ = clock_.GetCurrentTime();
}
void MetricsCollector::GenerateBatteryDischargeRateWhileSuspendedMetric() {
// Do nothing unless this is the first time we're called after resuming.
if (!report_battery_discharge_rate_while_suspended_)
return;
report_battery_discharge_rate_while_suspended_ = false;
if (!last_power_status_.battery_is_present || on_line_power_before_suspend_ ||
last_power_status_.line_power_on)
return;
base::TimeDelta elapsed_time =
clock_.GetCurrentBootTime() - time_before_suspend_;
if (elapsed_time.InSeconds() <
kBatteryDischargeRateWhileSuspendedMinSuspendSec)
return;
double discharged_watt_hours =
battery_energy_before_suspend_ - last_power_status_.battery_energy;
double discharge_rate_watts =
discharged_watt_hours / (elapsed_time.InSecondsF() / 3600);
// Maybe the charger was connected while the system was suspended but
// disconnected before it resumed.
if (discharge_rate_watts < 0.0)
return;
SendMetric(kBatteryDischargeRateWhileSuspendedName,
static_cast<int>(round(discharge_rate_watts * 1000)),
kBatteryDischargeRateWhileSuspendedMin,
kBatteryDischargeRateWhileSuspendedMax, kDefaultDischargeBuckets);
}
void MetricsCollector::IncrementNumOfSessionsPerChargeMetric() {
int64_t num = 0;
prefs_->GetInt64(kNumSessionsOnCurrentChargePref, &num);
num = std::max(num, static_cast<int64_t>(0));
prefs_->SetInt64(kNumSessionsOnCurrentChargePref, num + 1);
}
void MetricsCollector::GenerateNumOfSessionsPerChargeMetric() {
int64_t sample = 0;
prefs_->GetInt64(kNumSessionsOnCurrentChargePref, &sample);
if (sample <= 0)
return;
sample = std::min(sample, static_cast<int64_t>(kNumOfSessionsPerChargeMax));
prefs_->SetInt64(kNumSessionsOnCurrentChargePref, 0);
SendMetric(kNumOfSessionsPerChargeName, sample, kNumOfSessionsPerChargeMin,
kNumOfSessionsPerChargeMax, kDefaultBuckets);
}
void MetricsCollector::TrackS0ixResidency(bool pre_suspend) {
// This method should be invoked only when suspend to idle is enabled.
DCHECK(suspend_to_idle_);
// If S0ix residency read before suspend was not successful, we have no way
// to track the residency during suspend.
if (!pre_suspend && !pre_suspend_s0ix_read_successful_)
return;
// If we cannot find any residency related files, nothing to track.
if (s0ix_residency_path_.empty())
return;
uint64_t residency_usecs = 0;
const bool success =
util::ReadUint64File(s0ix_residency_path_, &residency_usecs);
if (pre_suspend)
pre_suspend_s0ix_read_successful_ = success;
if (!success) {
PLOG(WARNING) << "Failed to read residency from "
<< s0ix_residency_path_.value();
return;
}
if (pre_suspend) {
s0ix_residency_usecs_before_suspend_ = residency_usecs;
return;
}
// We reach here only on post-suspend.
// If the counter overflowed during suspend, then residency delta is not
// useful anymore.
if (residency_usecs < s0ix_residency_usecs_before_suspend_)
return;
const base::TimeDelta time_in_suspend =
clock_.GetCurrentBootTime() - time_before_suspend_;
// If we spent more time in suspend than the max residency that
// |s0ix_residency_path_| can report, then the residency counter is
// not reliable anymore.
if (time_in_suspend > max_s0ix_residency_)
return;
// If the device woke from suspend before |KS0ixOverheadTime|, then the
// CPUs might not have entered S0ix. Let us not complain nor generate UMA
// metrics.
if (time_in_suspend <= KS0ixOverheadTime)
return;
const base::TimeDelta s0ix_residency_time = base::TimeDelta::FromMicroseconds(
residency_usecs - s0ix_residency_usecs_before_suspend_);
int s0ix_residency_percent =
GetExpectedS0ixResidencyPercent(time_in_suspend, s0ix_residency_time);
// If we spent less than 90% of time in S0ix, log a warning. This can help
// debugging feedback reports that complain about low battery life.
if (s0ix_residency_percent < 90) {
LOG(WARNING) << "Device spent around " << time_in_suspend.InSeconds()
<< " secs in suspend, but only "
<< s0ix_residency_time.InSeconds() << " secs in S0ix";
}
// Enum to avoid exponential histogram's varyingly-sized buckets.
SendEnumMetric(kS0ixResidencyRateName, s0ix_residency_percent, kMaxPercent);
}
base::FilePath MetricsCollector::GetPrefixedFilePath(
const base::FilePath& file_path) const {
if (prefix_path_for_testing_.empty())
return file_path;
DCHECK(file_path.IsAbsolute());
return prefix_path_for_testing_.Append(file_path.value().substr(1));
}
} // namespace metrics
} // namespace power_manager