| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/android/battery_metrics.h" |
| |
| #include "base/android/application_status_listener.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/power_monitor/energy_monitor_android.h" |
| #include "base/power_monitor/power_monitor.h" |
| #include "base/strings/strcat.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/application_state_proto_android.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "base/tracing/protos/chrome_track_event.pbzero.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h" |
| |
| namespace content { |
| namespace { |
| |
| using ScenarioScope = performance_scenarios::ScenarioScope; |
| using LoadingScenario = performance_scenarios::LoadingScenario; |
| using InputScenario = performance_scenarios::InputScenario; |
| using Subsystem = AndroidBatteryMetrics::EnergyConsumedTracker::Subsystem; |
| |
| perfetto::protos::pbzero::DeviceThermalState ToTraceEnum( |
| base::PowerThermalObserver::DeviceThermalState state) { |
| switch (state) { |
| case base::PowerThermalObserver::DeviceThermalState::kUnknown: |
| return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_UNKNOWN; |
| case base::PowerThermalObserver::DeviceThermalState::kNominal: |
| return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_NOMINAL; |
| case base::PowerThermalObserver::DeviceThermalState::kFair: |
| return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_FAIR; |
| case base::PowerThermalObserver::DeviceThermalState::kSerious: |
| return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_SERIOUS; |
| case base::PowerThermalObserver::DeviceThermalState::kCritical: |
| return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_CRITICAL; |
| } |
| } |
| |
| std::string GetLoadingScenarioSuffix(std::optional<LoadingScenario> scenario) { |
| // LINT.IfChange(LoadingScenarioSuffix) |
| if (!scenario.has_value()) { |
| return "UnknownLoadingScenario"; |
| } |
| switch (*scenario) { |
| case LoadingScenario::kFocusedPageLoading: |
| return "FocusedPageLoading"; |
| case LoadingScenario::kVisiblePageLoading: |
| return "VisiblePageLoading"; |
| case LoadingScenario::kBackgroundPageLoading: |
| return "BackgroundPageLoading"; |
| case LoadingScenario::kNoPageLoading: |
| return "NoPageLoading"; |
| } |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/power/histograms.xml:LoadingScenarioSuffix) |
| NOTREACHED(); |
| } |
| |
| std::string GetInputScenarioSuffix(std::optional<InputScenario> scenario) { |
| // LINT.IfChange(InputScenarioSuffix) |
| if (!scenario.has_value()) { |
| return "UnknownInputScenario"; |
| } |
| switch (*scenario) { |
| case InputScenario::kScroll: |
| return "Scroll"; |
| case InputScenario::kTap: |
| return "Tap"; |
| case InputScenario::kTyping: |
| return "Typing"; |
| case InputScenario::kNoInput: |
| return "NoInput"; |
| } |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/power/histograms.xml:InputScenarioSuffix) |
| NOTREACHED(); |
| } |
| |
| std::string GetSubsystemSuffix(Subsystem subsystem) { |
| // LINT.IfChange(SubsystemSuffix) |
| switch (subsystem) { |
| case Subsystem::kCpu: |
| return "Cpu"; |
| case Subsystem::kGpu: |
| return "Gpu"; |
| case Subsystem::kDisplay: |
| return "Display"; |
| case Subsystem::kOther: |
| return "Other"; |
| } |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/power/histograms.xml:SubsystemSuffix) |
| NOTREACHED(); |
| } |
| |
| Subsystem ClassifyConsumer(std::string_view consumer) { |
| if (consumer.find("CPU") != std::string::npos) { |
| return Subsystem::kCpu; |
| } |
| if (consumer.find("GPU") != std::string::npos) { |
| return Subsystem::kGpu; |
| } |
| if (consumer.find("DISPLAY") != std::string::npos) { |
| return Subsystem::kDisplay; |
| } |
| return Subsystem::kOther; |
| } |
| |
| base::flat_map<Subsystem, int64_t> AttributePowerMonitorReadingsToSubsystems( |
| const std::vector<base::android::PowerMonitorReading>& readings) { |
| base::flat_map<Subsystem, int64_t> energy_per_subsystem; |
| for (const auto& reading : readings) { |
| energy_per_subsystem[ClassifyConsumer(reading.consumer)] += |
| reading.total_energy; |
| } |
| return energy_per_subsystem; |
| } |
| |
| void ReportPerSubsystemEnergyDeltas( |
| const std::vector<AndroidBatteryMetrics::EnergyConsumedTracker::Delta>& |
| energy_deltas, |
| const std::string& base_histogram_name) { |
| // If there're no energy deltas for any subsystem, there's an error in the |
| // latest or the previous reading. Do not report any metrics. |
| if (energy_deltas.empty()) { |
| return; |
| } |
| |
| int64_t total_energy_consumed_mwh = 0; |
| for (const auto& delta : energy_deltas) { |
| total_energy_consumed_mwh += delta.energy_consumed_mwh; |
| base::UmaHistogramCounts100000( |
| base::StrCat( |
| {base_histogram_name, ".", GetSubsystemSuffix(delta.subsystem)}), |
| delta.energy_consumed_mwh); |
| } |
| base::UmaHistogramCounts100000(base_histogram_name, |
| total_energy_consumed_mwh); |
| } |
| |
| void Report30SecondDrain( |
| int capacity_consumed, |
| const std::vector<AndroidBatteryMetrics::EnergyConsumedTracker::Delta>& |
| energy_deltas, |
| bool is_exclusive_measurement, |
| const std::string& scenario) { |
| // Drain over the last 30 seconds in uAh. We assume a max current of 10A which |
| // translates to a little under 100mAh capacity drain over 30 seconds. |
| UMA_HISTOGRAM_COUNTS_100000("Power.ForegroundBatteryDrain.30Seconds", |
| capacity_consumed); |
| base::UmaHistogramCounts100000( |
| std::string("Power.ForegroundBatteryDrainPerScenario.30Seconds.") + |
| scenario, |
| capacity_consumed); |
| ReportPerSubsystemEnergyDeltas( |
| energy_deltas, |
| std::string("Power.ForegroundEnergyConsumedPerScenario.30Seconds.") + |
| scenario); |
| |
| // Record a separate metric for power drain that was completely observed while |
| // we were the foreground app. This avoids attributing power draw from other |
| // apps to us. |
| if (is_exclusive_measurement) { |
| UMA_HISTOGRAM_COUNTS_100000( |
| "Power.ForegroundBatteryDrain.30Seconds.Exclusive", capacity_consumed); |
| base::UmaHistogramCounts100000( |
| std::string( |
| "Power.ForegroundBatteryDrainPerScenario.30Seconds.Exclusive.") + |
| scenario, |
| capacity_consumed); |
| ReportPerSubsystemEnergyDeltas( |
| energy_deltas, |
| std::string( |
| "Power.ForegroundEnergyConsumedPerScenario.30Seconds.Exclusive.") + |
| scenario); |
| } |
| } |
| |
| base::HistogramBase* GetAvgBatteryDrainHistogram(const char* suffix) { |
| static constexpr char kAvgDrainHistogramPrefix[] = |
| "Power.ForegroundBatteryDrain.30SecondsAvg2"; |
| return base::Histogram::FactoryGet( |
| std::string(kAvgDrainHistogramPrefix) + suffix, 1, 100000, 50, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| } |
| |
| // Dark mode histograms are reported on the UI thread, because they depend on |
| // the current darkening state of the web contents -- which we can only inspect |
| // on the UI thread. |
| void ReportDarkModeDrains(int capacity_consumed_avg, |
| bool is_exclusive_measurement, |
| int num_sampling_periods) { |
| size_t no_darkening_count = 0, user_agent_darkening_count = 0, |
| web_page_or_user_agent_darkening_count = 0, |
| web_page_darkening_count = 0; |
| auto all_webcontents = WebContentsImpl::GetAllWebContents(); |
| auto total_webcontents_count = all_webcontents.size(); |
| for (WebContentsImpl* wc : all_webcontents) { |
| auto dark_theme = wc->GetOrCreateWebPreferences().preferred_color_scheme == |
| blink::mojom::PreferredColorScheme::kDark; |
| auto force_dark = wc->GetOrCreateWebPreferences().force_dark_mode_enabled; |
| |
| if (force_dark) { |
| if (dark_theme) { |
| web_page_or_user_agent_darkening_count++; |
| } else { |
| user_agent_darkening_count++; |
| } |
| } else { |
| if (dark_theme) { |
| web_page_darkening_count++; |
| } else { |
| no_darkening_count++; |
| } |
| } |
| } |
| |
| base::HistogramBase* dark_mode_histogram = nullptr; |
| base::HistogramBase* exclusive_dark_mode_histogram = nullptr; |
| |
| if (user_agent_darkening_count + web_page_or_user_agent_darkening_count == |
| total_webcontents_count) { |
| // All WebContents have at least user-agent darkening. |
| dark_mode_histogram = GetAvgBatteryDrainHistogram(".ForcedDarkMode"); |
| exclusive_dark_mode_histogram = |
| GetAvgBatteryDrainHistogram(".Exclusive.ForcedDarkMode"); |
| } else if (web_page_darkening_count == total_webcontents_count) { |
| // All WebContents have only web page darkening. |
| dark_mode_histogram = GetAvgBatteryDrainHistogram(".DarkMode"); |
| exclusive_dark_mode_histogram = |
| GetAvgBatteryDrainHistogram(".Exclusive.DarkMode"); |
| } else if (no_darkening_count == total_webcontents_count) { |
| // None of the WebContents have any darkening. |
| dark_mode_histogram = GetAvgBatteryDrainHistogram(".LightMode"); |
| exclusive_dark_mode_histogram = |
| GetAvgBatteryDrainHistogram(".Exclusive.LightMode"); |
| } else { |
| // Some WebContents have some kind of darkening and some might not have any. |
| dark_mode_histogram = GetAvgBatteryDrainHistogram(".MixedMode"); |
| exclusive_dark_mode_histogram = |
| GetAvgBatteryDrainHistogram(".Exclusive.MixedMode"); |
| } |
| DCHECK(dark_mode_histogram); |
| DCHECK(exclusive_dark_mode_histogram); |
| |
| dark_mode_histogram->AddCount(capacity_consumed_avg, num_sampling_periods); |
| if (is_exclusive_measurement) { |
| exclusive_dark_mode_histogram->AddCount(capacity_consumed_avg, |
| num_sampling_periods); |
| } |
| } |
| |
| void ReportAveragedDrain(int capacity_consumed, |
| bool is_exclusive_measurement, |
| int num_sampling_periods) { |
| // Averaged drain over 30 second intervals in uAh. We assume a max current of |
| // 10A which translates to a little under 100mAh capacity drain over 30 |
| // seconds. |
| auto capacity_consumed_avg = capacity_consumed / num_sampling_periods; |
| |
| GetAvgBatteryDrainHistogram("")->AddCount(capacity_consumed_avg, |
| num_sampling_periods); |
| if (is_exclusive_measurement) { |
| GetAvgBatteryDrainHistogram(".Exclusive") |
| ->AddCount(capacity_consumed_avg, num_sampling_periods); |
| } |
| |
| GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) |
| ->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ReportDarkModeDrains, capacity_consumed_avg, |
| is_exclusive_measurement, num_sampling_periods)); |
| } |
| |
| } // namespace |
| |
| // static |
| constexpr base::TimeDelta AndroidBatteryMetrics::kMetricsInterval; |
| |
| // static |
| void AndroidBatteryMetrics::CreateInstance() { |
| static base::NoDestructor<AndroidBatteryMetrics> instance; |
| } |
| |
| AndroidBatteryMetrics::AndroidBatteryMetrics() |
| : task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&AndroidBatteryMetrics::InitializeOnSequence, |
| base::Unretained(this))); |
| } |
| |
| AndroidBatteryMetrics::~AndroidBatteryMetrics() { |
| // Never called, this is a no-destruct singleton. |
| NOTREACHED(); |
| } |
| |
| void AndroidBatteryMetrics::InitializeOnSequence() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto* power_monitor = base::PowerMonitor::GetInstance(); |
| battery_power_status_ = |
| power_monitor->AddPowerStateObserverAndReturnBatteryPowerStatus(this); |
| power_monitor->AddPowerThermalObserver(this); |
| // The observer is never removed because this class uses base::NoDestructor. |
| content::ProcessVisibilityTracker::GetInstance()->AddObserver(this); |
| // TODO(b/339859756): Update this call to take into account the unknown battery |
| // status. |
| UpdateMetricsEnabled(); |
| } |
| |
| void AndroidBatteryMetrics::TryObservePerformanceScenarios() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (is_observing_performance_scenarios_) { |
| return; |
| } |
| // We're using ScenarioScope::kGlobal because we're interested in the overall |
| // app state, not just the current process. |
| if (auto observer_list = |
| performance_scenarios::PerformanceScenarioObserverList::GetForScope( |
| ScenarioScope::kGlobal)) { |
| is_observing_performance_scenarios_ = true; |
| performance_scenario_tracker_.UpdateLoadingScenario( |
| performance_scenarios::GetLoadingScenario(ScenarioScope::kGlobal) |
| ->load(std::memory_order_relaxed)); |
| performance_scenario_tracker_.UpdateInputScenario( |
| performance_scenarios::GetInputScenario(ScenarioScope::kGlobal) |
| ->load(std::memory_order_relaxed)); |
| observer_list->AddObserver(this); |
| } |
| } |
| |
| void AndroidBatteryMetrics::OnVisibilityChanged(bool visible) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| app_visible_ = visible; |
| UpdateMetricsEnabled(); |
| } |
| |
| void AndroidBatteryMetrics::OnBatteryPowerStatusChange( |
| base::PowerStateObserver::BatteryPowerStatus battery_power_status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| battery_power_status_ = battery_power_status; |
| UpdateMetricsEnabled(); |
| } |
| |
| void AndroidBatteryMetrics::OnLoadingScenarioChanged( |
| ScenarioScope scope, |
| LoadingScenario old_scenario, |
| LoadingScenario new_scenario) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| performance_scenario_tracker_.UpdateLoadingScenario(new_scenario); |
| } |
| |
| void AndroidBatteryMetrics::OnInputScenarioChanged(ScenarioScope scope, |
| InputScenario old_scenario, |
| InputScenario new_scenario) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| performance_scenario_tracker_.UpdateInputScenario(new_scenario); |
| } |
| |
| void AndroidBatteryMetrics::OnThermalStateChange(DeviceThermalState new_state) { |
| TRACE_EVENT_INSTANT( |
| "base.power", "OnThermalStateChange", perfetto::Track::Global(0), |
| [&](perfetto::EventContext ctx) { |
| auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); |
| event->set_chrome_application_state_info()->set_application_state( |
| base::trace_event::ApplicationStateToTraceEnum( |
| base::android::ApplicationStatusListener::GetState())); |
| event->set_device_thermal_state(ToTraceEnum(new_state)); |
| }); |
| } |
| |
| void AndroidBatteryMetrics::OnSpeedLimitChange(int speed_limit) {} |
| |
| void AndroidBatteryMetrics::UpdateMetricsEnabled() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| TryObservePerformanceScenarios(); |
| |
| // We want to attribute battery drain to chromium while the embedding app is |
| // visible. Battery drain will only be reflected in remaining battery capacity |
| // when the device is not on a charger. |
| bool should_be_enabled = |
| app_visible_ && (battery_power_status_ == |
| PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| |
| if (should_be_enabled && !metrics_timer_.IsRunning()) { |
| // Capture first capacity measurement and enable the repeating timer. |
| last_remaining_capacity_uah_ = base::android::GetRemainingBatteryCapacity(); |
| energy_consumed_tracker_.UpdatePowerMonitorReadings( |
| base::android::GetTotalEnergyConsumed()); |
| |
| skipped_timers_ = 0; |
| observed_capacity_drops_ = 0; |
| performance_scenario_tracker_.UseLatestScenarios(); |
| |
| metrics_timer_.Start( |
| FROM_HERE, kMetricsInterval, |
| base::BindRepeating(&AndroidBatteryMetrics::CaptureAndReportMetrics, |
| base::Unretained(this), |
| /*disabling=*/false)); |
| } else if (!should_be_enabled && metrics_timer_.IsRunning()) { |
| // Capture one last measurement before disabling the timer. |
| CaptureAndReportMetrics(/*disabling=*/true); |
| metrics_timer_.Stop(); |
| } |
| } |
| |
| void AndroidBatteryMetrics::CaptureAndReportMetrics(bool disabling) { |
| int remaining_capacity_uah = base::android::GetRemainingBatteryCapacity(); |
| const auto power_monitor_readings = base::android::GetTotalEnergyConsumed(); |
| std::vector<EnergyConsumedTracker::Delta> energy_deltas; |
| // The underlying API has throttling (in which case the old value is reported |
| // again, but no errors), so if we call this method because the battery state |
| // is changing rather than by timer, the deltas are likely to be |
| // (misleadingly) 0. Let's not report such values. |
| if (!disabling) { |
| energy_deltas = energy_consumed_tracker_.GetDeltas(power_monitor_readings); |
| } |
| energy_consumed_tracker_.UpdatePowerMonitorReadings(power_monitor_readings); |
| |
| const std::string scenario = performance_scenario_tracker_.GetMetricSuffix(); |
| performance_scenario_tracker_.UseLatestScenarios(); |
| |
| |
| if (remaining_capacity_uah >= last_remaining_capacity_uah_) { |
| // No change in battery capacity, or it increased. The latter could happen |
| // if we detected the switch off battery power to a charger late, or if the |
| // device reports bogus values. We don't change last_remaining_capacity_uah_ |
| // here to avoid overreporting in case of fluctuating values. |
| skipped_timers_++; |
| Report30SecondDrain(0, energy_deltas, IsMeasuringDrainExclusively(), |
| scenario); |
| |
| if (disabling) { |
| // Disabling the timer, but without a change in capacity counter -- We |
| // should still emit values for the elapsed time intervals into the |
| // average histograms. We exclude exclusive metrics here, because these |
| // metrics exclude the measurements before the first capacity drop and |
| // after the last drop. Member fields will be reset when tracking |
| // is resumed after foregrounding again later. |
| ReportAveragedDrain(0, /*is_exclusive_measurement=*/false, |
| skipped_timers_); |
| } |
| |
| return; |
| } |
| observed_capacity_drops_++; |
| |
| // Report the consumed capacity delta over the last 30 seconds. |
| int capacity_consumed = last_remaining_capacity_uah_ - remaining_capacity_uah; |
| Report30SecondDrain(capacity_consumed, energy_deltas, |
| IsMeasuringDrainExclusively(), scenario); |
| |
| // Also record drain over 30 second intervals, but averaged since the last |
| // time we recorded an increase (or started recording samples). Because the |
| // underlying battery capacity counter is often low-resolution (usually |
| // between .5 and 50 mAh), it may only increment after multiple sampling |
| // points. For example, a 20 mAh drop over two successive periods of 30 |
| // seconds will be reported as two samples of 10 mAh. |
| ReportAveragedDrain(capacity_consumed, IsMeasuringDrainExclusively(), |
| skipped_timers_ + 1); |
| |
| // Also track the total capacity consumed in a single-bucket-histogram, |
| // emitting one sample for every 100 uAh drained. |
| static constexpr base::Histogram::Sample32 kSampleFactor = 100; |
| UMA_HISTOGRAM_SCALED_EXACT_LINEAR("Power.ForegroundBatteryDrain", |
| /*sample=*/1, capacity_consumed, |
| /*sample_max=*/1, kSampleFactor); |
| if (IsMeasuringDrainExclusively()) { |
| UMA_HISTOGRAM_SCALED_EXACT_LINEAR("Power.ForegroundBatteryDrain.Exclusive", |
| /*sample=*/1, capacity_consumed, |
| /*sample_max=*/1, kSampleFactor); |
| } |
| |
| last_remaining_capacity_uah_ = remaining_capacity_uah; |
| skipped_timers_ = 0; |
| } |
| |
| bool AndroidBatteryMetrics::IsMeasuringDrainExclusively() const { |
| return observed_capacity_drops_ >= 2; |
| } |
| |
| void AndroidBatteryMetrics::PerformanceScenarioTracker::UpdateLoadingScenario( |
| performance_scenarios::LoadingScenario new_scenario) { |
| latest_loading_scenario_ = new_scenario; |
| if (loading_scenario_to_report_.value_or(LoadingScenario::kNoPageLoading) <= |
| new_scenario) { |
| loading_scenario_to_report_ = new_scenario; |
| } |
| } |
| |
| void AndroidBatteryMetrics::PerformanceScenarioTracker::UpdateInputScenario( |
| performance_scenarios::InputScenario new_scenario) { |
| latest_input_scenario_ = new_scenario; |
| if (input_scenario_to_report_.value_or(InputScenario::kNoInput) <= |
| new_scenario) { |
| input_scenario_to_report_ = new_scenario; |
| } |
| } |
| |
| std::string AndroidBatteryMetrics::PerformanceScenarioTracker::GetMetricSuffix() |
| const { |
| return GetLoadingScenarioSuffix(loading_scenario_to_report_) + "_" + |
| GetInputScenarioSuffix(input_scenario_to_report_); |
| } |
| |
| void AndroidBatteryMetrics::PerformanceScenarioTracker::UseLatestScenarios() { |
| loading_scenario_to_report_ = latest_loading_scenario_; |
| input_scenario_to_report_ = latest_input_scenario_; |
| } |
| |
| AndroidBatteryMetrics::EnergyConsumedTracker::EnergyConsumedTracker() = default; |
| AndroidBatteryMetrics::EnergyConsumedTracker::~EnergyConsumedTracker() = |
| default; |
| |
| void AndroidBatteryMetrics::EnergyConsumedTracker::UpdatePowerMonitorReadings( |
| const std::vector<base::android::PowerMonitorReading>& readings) { |
| last_total_energy_uws_ = AttributePowerMonitorReadingsToSubsystems(readings); |
| } |
| |
| std::vector<AndroidBatteryMetrics::EnergyConsumedTracker::Delta> |
| AndroidBatteryMetrics::EnergyConsumedTracker::GetDeltas( |
| const std::vector<base::android::PowerMonitorReading>& readings) const { |
| base::flat_map<Subsystem, int64_t> total_energy_uws = |
| AttributePowerMonitorReadingsToSubsystems(readings); |
| std::vector<Delta> deltas; |
| for (const auto& [subsystem, energy_uws] : total_energy_uws) { |
| // 0 total energy means an error, in which case we can't report the detla. |
| if (energy_uws == 0) { |
| continue; |
| } |
| auto it = last_total_energy_uws_.find(subsystem); |
| if (it == last_total_energy_uws_.end() || it->second == 0) { |
| continue; |
| } |
| int64_t delta_mwh = |
| std::max(static_cast<int64_t>(0), (energy_uws - it->second) / 3600); |
| deltas.push_back({subsystem, delta_mwh}); |
| } |
| return deltas; |
| } |
| |
| } // namespace content |