| // Copyright 2012 The ChromiumOS Authors |
| // 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/notreached.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| |
| #include "policy/adaptive_charging_controller.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/tracing.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; |
| } |
| } |
| |
| // Helper method for power.BatteryLife & power.BatteryLife.Detail metrics. |
| // We send the same data again with tighter max and min value to have more |
| // granularity with the buckets in the middle. |
| void SendCoarseAndDetailBatteryLifeMetric(std::string metrics_name, |
| std::string suffix, |
| int value) { |
| SendMetric(metrics_name + suffix, value, kBatteryLifeMin, kBatteryLifeMax, |
| kDefaultDischargeBuckets); |
| SendMetric(metrics_name + kBatteryLifeDetailSuffix + suffix, value, |
| kBatteryLifeDetailMin, kBatteryLifeDetailMax, |
| kBatteryLifeDetailBuckets); |
| } |
| |
| } // namespace |
| |
| // static |
| constexpr char MetricsCollector::kAcpiPC10ResidencyPath[]; |
| constexpr char MetricsCollector::kBigCoreS0ixResidencyPath[]; |
| constexpr char MetricsCollector::kSmallCoreS0ixResidencyPath[]; |
| constexpr base::TimeDelta MetricsCollector::KS0ixOverheadTime; |
| |
| SingleValueResidencyReader::SingleValueResidencyReader( |
| const base::FilePath& path) |
| : path_(path) {} |
| |
| base::TimeDelta SingleValueResidencyReader::ReadResidency() { |
| uint64_t value; |
| // If |path_| is empty, reading the file will fail gracefully. There is no |
| // early-exit as |IdleResidencyTracker| update functions perform that function |
| // so no point in adding extra checks. |
| const bool success = util::ReadUint64File(path_, &value); |
| |
| if (!success) { |
| PLOG(WARNING) << "Failed to read residency from " << path_.value(); |
| } |
| // base::Microseconds() will fail for INT64_MAX, however that's unlikely to |
| // ever happen. |
| return success ? base::Microseconds(value) : InvalidValue; |
| } |
| |
| IdleResidencyTracker::IdleResidencyTracker( |
| std::shared_ptr<ResidencyReader> reader) |
| : reader_(reader), |
| pre_suspend_(ResidencyReader::InvalidValue), |
| post_resume_(ResidencyReader::InvalidValue) {} |
| |
| bool IdleResidencyTracker::IsValid() { |
| return !pre_suspend_.is_negative() && !post_resume_.is_negative(); |
| } |
| |
| base::TimeDelta IdleResidencyTracker::PreSuspend() const { |
| return pre_suspend_; |
| } |
| |
| base::TimeDelta IdleResidencyTracker::PostResume() const { |
| return post_resume_; |
| } |
| |
| void IdleResidencyTracker::UpdatePreSuspend() { |
| pre_suspend_ = reader_ != nullptr ? reader_->ReadResidency() |
| : ResidencyReader::InvalidValue; |
| } |
| |
| void IdleResidencyTracker::UpdatePostResume() { |
| post_resume_ = reader_ != nullptr ? reader_->ReadResidency() |
| : ResidencyReader::InvalidValue; |
| } |
| |
| std::string MetricsCollector::AppendPowerSourceToEnumName( |
| const std::string& enum_name, PowerSource power_source) { |
| return enum_name + |
| (power_source == PowerSource::AC ? kAcSuffix : kBatterySuffix); |
| } |
| |
| std::string MetricsCollector::AppendPrivacyScreenStateToEnumName( |
| const std::string& enum_name, |
| const privacy_screen::PrivacyScreenSetting_PrivacyScreenState& state) { |
| switch (state) { |
| case privacy_screen::PrivacyScreenSetting_PrivacyScreenState_DISABLED: |
| return enum_name + kPrivacyScreenDisabled; |
| case privacy_screen::PrivacyScreenSetting_PrivacyScreenState_ENABLED: |
| return enum_name + kPrivacyScreenEnabled; |
| default: |
| NOTREACHED_IN_MIGRATION() |
| << "Will not send metrics for unhandled privacy screen state " |
| << static_cast<int>(state); |
| return enum_name; |
| } |
| } |
| |
| // static |
| int MetricsCollector::GetExpectedResidencyPercent( |
| const base::TimeDelta& reference_time, |
| const base::TimeDelta& actual_residency, |
| const base::TimeDelta& overhead) { |
| base::TimeDelta expected_delta = reference_time - overhead; |
| double expected_residency = expected_delta.InMicrosecondsF(); |
| // Sanity check to prevent divide by zero undefined behavior below. This might |
| // happen when overhead == reference_time (including == 0). Also catch cases |
| // where overhead is larger than reference_time. |
| if (expected_residency <= 0) { |
| return 0; |
| } |
| int residency_percent = static_cast<int>( |
| round((actual_residency.InMicrosecondsF() * 100.0) / expected_residency)); |
| // Guard against >100% case when |actual_residency| goes over the predicted |
| // |overhead|. |
| return std::min(100, 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, kBacklightLevelInterval, this, |
| &MetricsCollector::GenerateBacklightLevelMetrics); |
| } |
| |
| if (display_backlight_controller_) { |
| display_backlight_controller_->RegisterAmbientLightResumeMetricsHandler( |
| base::BindRepeating( |
| &MetricsCollector::GenerateAmbientLightResumeMetrics, |
| base::Unretained(this))); |
| } |
| |
| bool pref_val = false; |
| suspend_to_idle_ = prefs_->GetBool(kSuspendToIdlePref, &pref_val) && pref_val; |
| |
| base::FilePath s0ix_residency_path; |
| // 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 a |
| // little complicated. |kBigCoreS0ixResidencyPath| reports the time spent in |
| // S0ix by reading SLP_S0_RES (32 bit) register. This register increments |
| // once for every *_PMC_SLP_S0_RES_COUNTER_STEP microseconds spent in S0ix |
| // (see drivers/platform/x86/intel/pmc/core.h in Linux kernel sources for |
| // exact resolution). The value read from this 32 bit register is first cast |
| // to u64 and then multiplied by the counter resolution to get a microsecond |
| // granularity. For |kBigCoreS0ixResidencyPath| a resolution upper bound of |
| // 100 microseconds is used to discard samples on counter roll-over. |
| if (s0ix_residency_path == |
| GetPrefixedFilePath(base::FilePath(kBigCoreS0ixResidencyPath))) { |
| max_s0ix_residency_ = base::Microseconds(100 * (uint64_t)UINT32_MAX); |
| } |
| |
| base::FilePath pc10_residency_path; |
| // PC10 residency related configuration. |
| if (base::PathExists( |
| GetPrefixedFilePath(base::FilePath(kAcpiPC10ResidencyPath)))) { |
| pc10_residency_path = |
| GetPrefixedFilePath(base::FilePath(kAcpiPC10ResidencyPath)); |
| } |
| |
| // Finally create residency trackers for accessible counters. |
| // For unavailable counters, leave the tracker uninitialized (with a nullptr |
| // ResidencyReader). |
| if (!s0ix_residency_path.empty()) { |
| residency_trackers_[IdleState::S0ix] = IdleResidencyTracker( |
| std::make_shared<SingleValueResidencyReader>(s0ix_residency_path)); |
| } |
| if (!pc10_residency_path.empty()) { |
| residency_trackers_[IdleState::PC10] = IdleResidencyTracker( |
| std::make_shared<SingleValueResidencyReader>(pc10_residency_path)); |
| } |
| } |
| |
| base::TimeTicks MetricsCollector::GetLastKernelResumedTime() { |
| if (!base::PathExists( |
| GetPrefixedFilePath(base::FilePath(kLastSuccessResumeTimePath)))) { |
| return base::TimeTicks(); |
| } |
| base::FilePath last_resume_time_path = |
| base::FilePath(kLastSuccessResumeTimePath); |
| |
| // This file records the end of succeeded kernel resumed time |
| // in MONOTONIC clock base. |
| std::string last_resume_time; |
| if (!base::ReadFileToString(last_resume_time_path, &last_resume_time)) { |
| return base::TimeTicks(); |
| } |
| // The timestamp tails a linefeed for human readability, trim it. |
| base::TrimWhitespaceASCII(last_resume_time, base::TRIM_TRAILING, |
| &last_resume_time); |
| double resume_time_sec; |
| if (!base::StringToDouble(last_resume_time, &resume_time_sec)) { |
| return base::TimeTicks(); |
| } |
| return base::TimeTicks() + base::Seconds(resume_time_sec); |
| } |
| |
| void MetricsCollector::GenerateDisplayAfterResumeDurationMsMetric() { |
| // Prohibit sending this metrics unless suspend sets the |
| // time_before_suspend_monotonic_, |
| if (time_before_suspend_monotonic_ == base::TimeTicks()) { |
| return; |
| } |
| |
| base::TimeTicks now = clock_.GetCurrentTime(); |
| |
| base::TimeTicks last_kernel_resumed_timestamp = GetLastKernelResumedTime(); |
| // Check if the /sys/power/suspend_stats/last_success_resume_time is not |
| // updated yet. |
| if (last_kernel_resumed_timestamp < time_before_suspend_monotonic_) { |
| return; |
| } |
| |
| base::TimeDelta duration = now - last_kernel_resumed_timestamp; |
| |
| // Reset time_before_suspend_monotonic_, which will be set again |
| // when the next suspend starts. |
| time_before_suspend_monotonic_ = base::TimeTicks(); |
| |
| LOG(INFO) << "Display after resume duration is " << duration; |
| SendMetricWithPowerSource(kDisplayAfterResumeDurationMsName, |
| duration.InMilliseconds(), |
| kDisplayAfterResumeDurationMsMin, |
| kDisplayAfterResumeDurationMsMax, kDefaultBuckets); |
| } |
| |
| void MetricsCollector::SendSuspendJourneyResult(SuspendJourneyResult result) { |
| VLOG(1) << "Reporting suspend journey result: " << static_cast<int>(result); |
| SendEnumMetric(kSuspendJourneyResultName, static_cast<int>(result), |
| static_cast<int>(SuspendJourneyResult::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); |
| |
| std::string metric_name = kBatteryCapacityName; |
| SendMetric(metric_name + kBatteryCapacityActualSuffix, |
| static_cast<int>(round(1000.0 * status.battery_energy_full)), |
| kBatteryCapacityMin, kBatteryCapacityMax, kDefaultBuckets); |
| |
| SendMetric( |
| metric_name + kBatteryCapacityDesignSuffix, |
| static_cast<int>(round(1000.0 * status.battery_energy_full_design)), |
| kBatteryCapacityMin, kBatteryCapacityMax, kDefaultBuckets); |
| } |
| } 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(); |
| } |
| |
| void MetricsCollector::HandleShutdown(ShutdownReason reason, |
| bool in_dark_resume) { |
| SendEnumMetric(kShutdownReasonName, static_cast<int>(reason), |
| static_cast<int>(ShutdownReason::MAX)); |
| if (reason == ShutdownReason::SUSPEND_FAILED) { |
| SendSuspendJourneyResult(SuspendJourneyResult::SHUTDOWN); |
| } else if (reason == ShutdownReason::SHUTDOWN_FROM_SUSPEND) { |
| SendSuspendJourneyResult(SuspendJourneyResult::SHUTDOWN_AFTER_X); |
| } else if (reason == ShutdownReason::LOW_BATTERY && in_dark_resume) { |
| SendSuspendJourneyResult(SuspendJourneyResult::LOW_POWER_SHUTDOWN); |
| } |
| } |
| |
| void MetricsCollector::HandlePrivacyScreenStateChange( |
| const privacy_screen::PrivacyScreenSetting_PrivacyScreenState& state) { |
| if (state == privacy_screen_state_) { |
| return; |
| } |
| |
| privacy_screen_state_ = state; |
| } |
| |
| void MetricsCollector::PrepareForSuspend() { |
| battery_energy_before_suspend_ = last_power_status_.battery_energy; |
| battery_percent_before_suspend_ = |
| static_cast<int>(round(last_power_status_.battery_percentage)), |
| on_line_power_before_suspend_ = last_power_status_.line_power_on; |
| time_before_suspend_ = clock_.GetCurrentBootTime(); |
| time_before_suspend_monotonic_ = clock_.GetCurrentTime(); |
| for (auto& tracker : residency_trackers_) { |
| tracker.UpdatePreSuspend(); |
| } |
| GenerateRuntimeS0ixMetrics(); |
| } |
| |
| void MetricsCollector::HandleResume(int num_suspend_attempts) { |
| SendMetric(kSuspendAttemptsBeforeSuccessName, num_suspend_attempts, |
| kSuspendAttemptsMin, kSuspendAttemptsMax, kSuspendAttemptsBuckets); |
| SendSuspendJourneyResult(SuspendJourneyResult::RESUME); |
| |
| // Report the discharge rate in response to the next |
| // OnPowerStatusUpdate() call. |
| report_battery_discharge_rate_while_suspended_ = true; |
| time_after_resume_ = clock_.GetCurrentBootTime(); |
| for (auto& tracker : residency_trackers_) { |
| tracker.UpdatePostResume(); |
| } |
| if (suspend_to_idle_) { |
| GenerateS2IdleS0ixMetrics(); |
| } |
| } |
| |
| 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() { |
| TRACE_EVENT("power", "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); |
| SendEnumMetricWithPrivacyScreenStatePowerSource( |
| 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::GenerateDimEventMetrics(const DimEvent sample) { |
| SendEnumMetricWithPowerSource(kDimEvent, static_cast<int>(sample), |
| static_cast<int>(DimEvent::MAX)); |
| } |
| |
| void MetricsCollector::GenerateLockEventMetrics(const LockEvent sample) { |
| SendEnumMetricWithPowerSource(kLockEvent, static_cast<int>(sample), |
| static_cast<int>(LockEvent::MAX)); |
| } |
| |
| void MetricsCollector::GenerateHpsEventDurationMetrics( |
| const std::string& event_name, base::TimeDelta duration) { |
| SendMetric(event_name, duration.InSeconds(), kHpsEventDurationMin, |
| kHpsEventDurationMax, kDefaultBuckets); |
| } |
| |
| 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 { |
| last_power_button_down_timestamp_ = base::TimeTicks(); |
| } |
| 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); |
| } |
| |
| bool MetricsCollector::SendEnumMetricWithPrivacyScreenStatePowerSource( |
| const std::string& name, int sample, int max) { |
| privacy_screen::PrivacyScreenSetting_PrivacyScreenState state = |
| privacy_screen_state_; |
| switch (state) { |
| case privacy_screen::PrivacyScreenSetting_PrivacyScreenState_DISABLED: |
| case privacy_screen::PrivacyScreenSetting_PrivacyScreenState_ENABLED: |
| return SendEnumMetricWithPowerSource( |
| AppendPrivacyScreenStateToEnumName(name, state), sample, max); |
| default: |
| return true; |
| } |
| } |
| |
| void MetricsCollector::GenerateBatteryDischargeRateMetric() { |
| // The battery discharge rate metric is relevant and collected only |
| // when running on battery. |
| if (!last_power_status_.battery_is_present) { |
| return; |
| } |
| if (last_power_status_.line_power_on) { |
| // Reset the rolling average dequeues if switched to non-battery sources. |
| rolling_average_actual_.clear(); |
| rolling_average_design_.clear(); |
| 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_) < |
| kBatteryDischargeRateInterval) { |
| return; |
| } |
| |
| // Reset rolling average dequeues after suspend. |
| if (time_before_suspend_ > last_battery_discharge_rate_metric_timestamp_) { |
| rolling_average_actual_.clear(); |
| rolling_average_design_.clear(); |
| } |
| |
| if (SendMetric(kBatteryDischargeRateName, rate, kBatteryDischargeRateMin, |
| kBatteryDischargeRateMax, kDefaultDischargeBuckets)) { |
| last_battery_discharge_rate_metric_timestamp_ = clock_.GetCurrentTime(); |
| } |
| |
| double low_battery_shutdown_percent = 0.0; |
| prefs_->GetDouble(kLowBatteryShutdownPercentPref, |
| &low_battery_shutdown_percent); |
| |
| double battery_life_actual = (60 * last_power_status_.battery_energy_full / |
| last_power_status_.battery_energy_rate * |
| (100 - low_battery_shutdown_percent) / 100.0); |
| rolling_average_actual_.push_back(battery_life_actual); |
| |
| double battery_life_design = |
| (60 * last_power_status_.battery_energy_full_design / |
| last_power_status_.battery_energy_rate * |
| (100 - low_battery_shutdown_percent) / 100.0); |
| rolling_average_design_.push_back(battery_life_design); |
| |
| std::string metrics_name = kBatteryLifeName; |
| SendCoarseAndDetailBatteryLifeMetric( |
| kBatteryLifeName, kBatteryCapacityActualSuffix, |
| static_cast<int>(round(battery_life_actual))); |
| SendCoarseAndDetailBatteryLifeMetric( |
| kBatteryLifeName, kBatteryCapacityDesignSuffix, |
| static_cast<int>(round(battery_life_design))); |
| |
| if (rolling_average_actual_.size() == kBatteryLifeRollingAverageSampleSize) { |
| double average = CalculateRollingAverage(rolling_average_actual_); |
| SendCoarseAndDetailBatteryLifeMetric( |
| kBatteryLifeName, |
| std::string(kBatteryLifeRollingAverageSuffix) + |
| kBatteryCapacityActualSuffix, |
| static_cast<int>(average)); |
| rolling_average_actual_.pop_front(); |
| } |
| |
| if (rolling_average_design_.size() == kBatteryLifeRollingAverageSampleSize) { |
| double average = CalculateRollingAverage(rolling_average_design_); |
| SendCoarseAndDetailBatteryLifeMetric( |
| kBatteryLifeName, |
| std::string(kBatteryLifeRollingAverageSuffix) + |
| kBatteryCapacityDesignSuffix, |
| static_cast<int>(average)); |
| rolling_average_design_.pop_front(); |
| } |
| } |
| |
| double MetricsCollector::CalculateRollingAverage( |
| const std::deque<double>& battery_lives) { |
| double average = 0; |
| for (double number : battery_lives) { |
| average += number; |
| } |
| average /= static_cast<double>(kBatteryLifeRollingAverageSampleSize); |
| return average; |
| } |
| |
| 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 < kBatteryDischargeRateWhileSuspendedMinSuspend) { |
| 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); |
| |
| if (discharge_rate_watts <= 0.0) { |
| return; |
| } |
| |
| std::string metrics_name = kBatteryLifeWhileSuspendedName; |
| SendMetric(metrics_name + kBatteryCapacityActualSuffix, |
| static_cast<int>(round(last_power_status_.battery_energy_full / |
| discharge_rate_watts)), |
| kBatteryLifeWhileSuspendedMin, kBatteryLifeWhileSuspendedMax, |
| kDefaultDischargeBuckets); |
| SendMetric( |
| metrics_name + kBatteryCapacityDesignSuffix, |
| static_cast<int>(round(last_power_status_.battery_energy_full_design / |
| discharge_rate_watts)), |
| kBatteryLifeWhileSuspendedMin, kBatteryLifeWhileSuspendedMax, |
| kDefaultDischargeBuckets); |
| } |
| |
| void MetricsCollector::GenerateAdaptiveChargingUnplugMetrics( |
| const AdaptiveChargingState state, |
| const base::TimeTicks& target_time, |
| const base::TimeTicks& hold_start_time, |
| const base::TimeTicks& hold_end_time, |
| const base::TimeTicks& charge_finished_time, |
| const base::TimeDelta& time_spent_slow_charging, |
| double display_battery_percentage) { |
| base::TimeTicks now = clock_.GetCurrentBootTime(); |
| std::string metric_name = kAdaptiveChargingMinutesDeltaName; |
| std::string state_suffix = ""; |
| std::string time_suffix = ""; |
| std::string type_suffix = ""; |
| bool report_active_metrics = false; |
| |
| switch (state) { |
| case AdaptiveChargingState::ACTIVE: |
| case AdaptiveChargingState::SLOWCHARGE: |
| case AdaptiveChargingState::INACTIVE: |
| state_suffix = kAdaptiveChargingStateActiveSuffix; |
| report_active_metrics = true; |
| break; |
| case AdaptiveChargingState::HEURISTIC_DISABLED: |
| state_suffix = kAdaptiveChargingStateHeuristicDisabledSuffix; |
| break; |
| case AdaptiveChargingState::USER_CANCELED: |
| state_suffix = kAdaptiveChargingStateUserCanceledSuffix; |
| break; |
| case AdaptiveChargingState::USER_DISABLED: |
| state_suffix = kAdaptiveChargingStateUserDisabledSuffix; |
| break; |
| case AdaptiveChargingState::SHUTDOWN: |
| state_suffix = kAdaptiveChargingStateShutdownSuffix; |
| break; |
| case AdaptiveChargingState::NOT_SUPPORTED: |
| state_suffix = kAdaptiveChargingStateNotSupportedSuffix; |
| break; |
| default: |
| LOG(ERROR) << "Invalid Adaptive Charging State for reporting to UMA: " |
| << static_cast<int>(state); |
| } |
| |
| base::TimeDelta delta = now - target_time; |
| if (delta.is_negative()) { |
| time_suffix = kAdaptiveChargingLateSuffix; |
| delta = delta.magnitude(); |
| } else { |
| time_suffix = kAdaptiveChargingEarlySuffix; |
| } |
| |
| SendMetric(metric_name + state_suffix + time_suffix, delta.InMinutes(), |
| kAdaptiveChargingDeltaMin, kAdaptiveChargingDeltaMax, |
| kDefaultBuckets); |
| |
| base::TimeDelta total_charge_time = charge_finished_time - hold_end_time; |
| if (time_spent_slow_charging == base::TimeDelta()) { |
| type_suffix = kAdaptiveChargingTypeNormalChargingSuffix; |
| } else if (total_charge_time - time_spent_slow_charging > base::Seconds(1)) { |
| type_suffix = kAdaptiveChargingTypeMixedChargingSuffix; |
| } else { |
| type_suffix = kAdaptiveChargingTypeSlowChargingSuffix; |
| } |
| |
| SendEnumMetric(kAdaptiveChargingBatteryPercentageOnUnplugName + type_suffix, |
| lround(display_battery_percentage), kMaxPercent); |
| |
| if (charge_finished_time != base::TimeTicks()) { |
| SendMetric(kAdaptiveChargingMinutesToFullName + type_suffix, |
| (charge_finished_time - hold_end_time).InMinutes(), |
| kAdaptiveChargingMinutesToFullMin, |
| kAdaptiveChargingMinutesToFullMax, kDefaultBuckets); |
| } |
| |
| base::TimeDelta delay_time = hold_start_time == base::TimeTicks() |
| ? base::TimeDelta() |
| : hold_end_time - hold_start_time; |
| SendMetric(kAdaptiveChargingMinutesDelayName, delay_time.InMinutes(), |
| kAdaptiveChargingMinutesMin, kAdaptiveChargingMinutesMax, |
| kAdaptiveChargingMinutesBuckets); |
| |
| base::TimeDelta available_time = hold_start_time == base::TimeTicks() |
| ? base::TimeDelta() |
| : now - hold_start_time; |
| SendMetric(kAdaptiveChargingMinutesAvailableName, available_time.InMinutes(), |
| kAdaptiveChargingMinutesMin, kAdaptiveChargingMinutesMax, |
| kAdaptiveChargingMinutesBuckets); |
| |
| if (report_active_metrics) { |
| AdaptiveChargingBatteryState battery_state = |
| AdaptiveChargingBatteryState::MAX; |
| // Treat anything over 99% for the display battery percent as full. |
| if (display_battery_percentage >= 99.0) { |
| if (delay_time != base::TimeDelta()) { |
| battery_state = AdaptiveChargingBatteryState::FULL_CHARGE_WITH_DELAY; |
| } else { |
| battery_state = AdaptiveChargingBatteryState::FULL_CHARGE_WITHOUT_DELAY; |
| } |
| } else { |
| if (delay_time != base::TimeDelta()) { |
| battery_state = AdaptiveChargingBatteryState::PARTIAL_CHARGE_WITH_DELAY; |
| } else { |
| battery_state = |
| AdaptiveChargingBatteryState::PARTIAL_CHARGE_WITHOUT_DELAY; |
| } |
| } |
| SendEnumMetric(kAdaptiveChargingBatteryStateName, |
| static_cast<int>(battery_state), |
| static_cast<int>(AdaptiveChargingBatteryState::MAX)); |
| } |
| |
| metric_name = kAdaptiveChargingDelayDeltaName; |
| |
| // Compute the available time minus the time reserved for charging first. If |
| // this is negative, the available hold time is 0. |
| base::TimeDelta slow_charging_delay = |
| policy::AdaptiveChargingController::kFinishSlowChargingDelay; |
| base::TimeDelta normal_charging_delay = |
| policy::AdaptiveChargingController::kFinishChargingDelay; |
| if (available_time >= slow_charging_delay) { |
| delta = available_time - slow_charging_delay; |
| } else { |
| delta = available_time - normal_charging_delay; |
| } |
| |
| if (delta.is_negative()) { |
| delta = base::TimeDelta(); |
| } |
| |
| delta -= delay_time; |
| if (delta.is_negative()) { |
| time_suffix = kAdaptiveChargingLateSuffix; |
| delta = delta.magnitude(); |
| } else { |
| time_suffix = kAdaptiveChargingEarlySuffix; |
| } |
| |
| SendMetric(metric_name + state_suffix + time_suffix, delta.InMinutes(), |
| kAdaptiveChargingDeltaMin, kAdaptiveChargingDeltaMax, |
| kDefaultBuckets); |
| |
| metric_name = kAdaptiveChargingMinutesFullOnACName; |
| delta = charge_finished_time == base::TimeTicks() |
| ? base::TimeDelta() |
| : now - charge_finished_time; |
| SendMetric(metric_name + state_suffix, delta.InMinutes(), |
| kAdaptiveChargingMinutesMin, kAdaptiveChargingMinutesMax, |
| kAdaptiveChargingMinutesBuckets); |
| } |
| |
| 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::GenerateAmbientLightResumeMetrics(int lux) { |
| SendMetric(kAmbientLightOnResumeName, lux, kAmbientLightOnResumeMin, |
| kAmbientLightOnResumeMax, kDefaultBuckets); |
| } |
| |
| void MetricsCollector::GenerateS2IdleS0ixMetrics() { |
| // This method should be invoked only when suspend to idle is enabled. |
| CHECK(suspend_to_idle_); |
| |
| IdleResidencyTracker& s0ix = residency_trackers_[IdleState::S0ix]; |
| |
| // If S0ix residency reading was not successful, we have no way to track the |
| // residency during suspend. |
| if (!s0ix.IsValid()) { |
| return; |
| } |
| |
| const base::TimeDelta s0ix_residency = s0ix.PostResume() - s0ix.PreSuspend(); |
| |
| // If the counter overflowed during suspend, then residency delta is not |
| // useful anymore because we have no way to read the precise residency counter |
| // resolution. |
| // We'll loose a single sample per |max_s0ix_residency_| which is at minimum |
| // ~5 days. |
| if (s0ix_residency.is_negative()) { |
| 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. |
| // At most we'll loose a single sample per |max_s0ix_residency_| which is |
| // at minimum ~5 days. |
| 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; |
| } |
| |
| int s0ix_residency_percent = |
| GetExpectedResidencyPercent(time_in_suspend, s0ix_residency); |
| // 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.InSeconds() |
| << " secs in S0ix"; |
| } |
| |
| // Enum to avoid exponential histogram's varyingly-sized buckets. |
| SendEnumMetric(kS0ixResidencyRateName, s0ix_residency_percent, kMaxPercent); |
| } |
| |
| void MetricsCollector::GenerateRuntimeS0ixMetrics() { |
| IdleResidencyTracker& s0ix = residency_trackers_[IdleState::S0ix]; |
| IdleResidencyTracker& pc10 = residency_trackers_[IdleState::PC10]; |
| |
| // If either S0ix or PC10 residency reading was not successful, we have no way |
| // to track the runtime residency. |
| if (!s0ix.IsValid() || !pc10.IsValid()) { |
| return; |
| } |
| |
| const base::TimeDelta s0ix_residency = s0ix.PreSuspend() - s0ix.PostResume(); |
| const base::TimeDelta pc10_residency = pc10.PreSuspend() - pc10.PostResume(); |
| |
| // If the counter overflowed during suspend, then residency delta is not |
| // useful anymore because we have no way to read the precise residency counter |
| // resolution. |
| // We'll loose a single sample per counter. For S0ix it is |
| // |max_s0ix_residency_| which is at minimum ~5 days. |
| if (s0ix_residency.is_negative() || pc10_residency.is_negative()) { |
| return; |
| } |
| |
| // Calculate the time between |HandleResume| and |PrepareForSuspend|. This |
| // includes one round of |residency_trackers_| update which will be taken into |
| // account by |kRuntimeS0ixOverheadTime| later. |
| const base::TimeDelta time_in_resume = |
| time_before_suspend_ - time_after_resume_; |
| |
| // If user initiated suspend less than |kRuntimeS0ixOverheadTime| after resume |
| // don't report such metric. This is fine as the overhead time is in range of |
| // microseconds which would mean that user didn't want to resume the system |
| // anyway. |
| if (time_in_resume <= kRuntimeS0ixOverheadTime) { |
| return; |
| } |
| |
| // Guard against a very unlikely case of a counter roll-over using |
| // |max_s0ix_residency_| as a safety lower-bound which gives us a minimum of |
| // ~5 days in runtime. |
| if (time_in_resume > max_s0ix_residency_) { |
| return; |
| } |
| |
| // Additionally measure how much time is spent in PC10 compared to the |
| // runtime. This should give us an estimate on potential power-savings if S0ix |
| // is enabled. |
| // Take the expected overhead into account. Worst case (if there wasn't any), |
| // the impact should be negligible (runtime is usually several orders of |
| // magnitude longer). |
| int pc10_residency_percent = GetExpectedResidencyPercent( |
| time_in_resume, pc10_residency, kRuntimeS0ixOverheadTime); |
| |
| // Enum to avoid exponential histogram's varyingly-sized buckets. |
| SendEnumMetric(kPC10RuntimeResidencyRateName, pc10_residency_percent, |
| kMaxPercent); |
| |
| // Report time spent in S0ix only if device actually spent some time in PC10. |
| // Otherwise there would be no difference between devices that spent no time |
| // in S0ix and ones which couldn't have (due to PC10 residency being 0). |
| if (pc10_residency_percent > 0) { |
| // Since runtime may last quite long compared to time spent in idle, |
| // calculate instead how much time SoC spent in S0ix compared to the PC10 |
| // (which is a pre-requisite for runtime S0ix). |
| // Do not apply overhead because it's safer to round-down this metric when |
| // PC10 residency is small. |
| int pc10_in_s0ix_percent = GetExpectedResidencyPercent( |
| pc10_residency, s0ix_residency, base::Microseconds(0)); |
| |
| // Enum to avoid exponential histogram's varyingly-sized buckets. |
| SendEnumMetric(kPC10inS0ixRuntimeResidencyRateName, pc10_in_s0ix_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 |