// Copyright 2014 The Chromium 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 "content/browser/power_usage_monitor_impl.h" #include "base/bind.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/macros.h" #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" #include "base/sys_info.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/power_usage_monitor.h" #include "content/public/browser/render_process_host.h" namespace content { namespace { // Wait this long after power on before enabling power usage monitoring. const int kMinUptimeMinutes = 30; // Minimum discharge time after which we collect the discharge rate. const int kMinDischargeMinutes = 30; class PowerUsageMonitorSystemInterface : public PowerUsageMonitor::SystemInterface { public: explicit PowerUsageMonitorSystemInterface(PowerUsageMonitor* owner) : power_usage_monitor_(owner), weak_ptr_factory_(this) {} ~PowerUsageMonitorSystemInterface() override {} void ScheduleHistogramReport(base::TimeDelta delay) override { BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, base::Bind( &PowerUsageMonitorSystemInterface::ReportBatteryLevelHistogram, weak_ptr_factory_.GetWeakPtr(), Now(), delay), delay); } void CancelPendingHistogramReports() override { weak_ptr_factory_.InvalidateWeakPtrs(); } void RecordDischargePercentPerHour(int percent_per_hour) override { UMA_HISTOGRAM_PERCENTAGE("Power.BatteryDischargePercentPerHour", percent_per_hour); } base::Time Now() override { return base::Time::Now(); } protected: void ReportBatteryLevelHistogram(base::Time start_time, base::TimeDelta discharge_time) { // It's conceivable that the code to cancel pending histogram reports on // system suspend, will only get called after the system has woken up. // To mitigage this, check whether more time has passed than expected and // abort histogram recording in this case. // Delayed tasks are subject to timer coalescing and can fire anywhere from // delay -> delay * 1.5) . In most cases, the OS should fire the task // at the next wakeup and not as late as it can. // A threshold of 2 minutes is used, since that should be large enough to // take the slop factor due to coalescing into account. base::TimeDelta threshold = discharge_time + base::TimeDelta::FromMinutes(2); if ((Now() - start_time) > threshold) { return; } const std::string histogram_name = base::StringPrintf( "Power.BatteryDischarge_%d", discharge_time.InMinutes()); base::HistogramBase* histogram = base::Histogram::FactoryGet(histogram_name, 1, 100, 101, base::Histogram::kUmaTargetedHistogramFlag); double discharge_amount = power_usage_monitor_->discharge_amount(); histogram->Add(discharge_amount * 100); } private: PowerUsageMonitor* power_usage_monitor_; // Not owned. // Used to cancel in progress delayed tasks. base::WeakPtrFactory weak_ptr_factory_; }; } // namespace void StartPowerUsageMonitor() { static base::LazyInstance::Leaky monitor = LAZY_INSTANCE_INITIALIZER; monitor.Get().Start(); } PowerUsageMonitor::PowerUsageMonitor() : callback_(base::Bind(&PowerUsageMonitor::OnBatteryStatusUpdate, base::Unretained(this))), system_interface_(new PowerUsageMonitorSystemInterface(this)), started_(false), tracking_discharge_(false), on_battery_power_(false), initial_battery_level_(0), current_battery_level_(0) { } PowerUsageMonitor::~PowerUsageMonitor() { if (started_) base::PowerMonitor::Get()->RemoveObserver(this); } void PowerUsageMonitor::Start() { // Power monitoring may be delayed based on uptime, but renderer process // lifetime tracking needs to start immediately so processes created before // then are accounted for. registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CREATED, NotificationService::AllBrowserContextsAndSources()); registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED, NotificationService::AllBrowserContextsAndSources()); subscription_ = device::BatteryStatusService::GetInstance()->AddCallback(callback_); // Delay initialization until the system has been up for a while. // This is to mitigate the effect of increased power draw during system start. base::TimeDelta uptime = base::TimeDelta::FromMilliseconds(base::SysInfo::Uptime()); base::TimeDelta min_uptime = base::TimeDelta::FromMinutes(kMinUptimeMinutes); if (uptime < min_uptime) { base::TimeDelta delay = min_uptime - uptime; BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, base::Bind(&PowerUsageMonitor::StartInternal, base::Unretained(this)), delay); } else { StartInternal(); } } void PowerUsageMonitor::StartInternal() { DCHECK(!started_); started_ = true; // PowerMonitor is used to get suspend/resume notifications. base::PowerMonitor::Get()->AddObserver(this); } void PowerUsageMonitor::DischargeStarted(double battery_level) { on_battery_power_ = true; // If all browser windows are closed, don't report power metrics since // Chrome's power draw is likely not significant. if (live_renderer_ids_.empty()) return; // Cancel any in-progress ReportBatteryLevelHistogram() calls. system_interface_->CancelPendingHistogramReports(); tracking_discharge_ = true; start_discharge_time_ = system_interface_->Now(); initial_battery_level_ = battery_level; current_battery_level_ = battery_level; const int kBatteryReportingIntervalMinutes[] = {5, 15, 30}; for (auto reporting_interval : kBatteryReportingIntervalMinutes) { base::TimeDelta delay = base::TimeDelta::FromMinutes(reporting_interval); system_interface_->ScheduleHistogramReport(delay); } } void PowerUsageMonitor::WallPowerConnected(double battery_level) { on_battery_power_ = false; if (tracking_discharge_) { DCHECK(!start_discharge_time_.is_null()); base::TimeDelta discharge_time = system_interface_->Now() - start_discharge_time_; if (discharge_time.InMinutes() > kMinDischargeMinutes) { // Record the rate at which the battery discharged over the entire period // the system was on battery power. double discharge_hours = discharge_time.InSecondsF() / 3600.0; int percent_per_hour = floor(((discharge_amount() / discharge_hours) * 100.0) + 0.5); system_interface_->RecordDischargePercentPerHour(percent_per_hour); } } // Cancel any in-progress ReportBatteryLevelHistogram() calls. system_interface_->CancelPendingHistogramReports(); initial_battery_level_ = 0; current_battery_level_ = 0; start_discharge_time_ = base::Time(); tracking_discharge_ = false; } void PowerUsageMonitor::OnBatteryStatusUpdate( const device::BatteryStatus& status) { bool now_on_battery_power = (status.charging == 0); bool was_on_battery_power = on_battery_power_; double battery_level = status.level; if (now_on_battery_power == was_on_battery_power) { if (now_on_battery_power) current_battery_level_ = battery_level; return; } else if (now_on_battery_power) { // Wall power disconnected. DischargeStarted(battery_level); } else { // Wall power connected. WallPowerConnected(battery_level); } } void PowerUsageMonitor::OnRenderProcessNotification(int type, int rph_id) { size_t previous_num_live_renderers = live_renderer_ids_.size(); if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) { live_renderer_ids_.insert(rph_id); } else if (type == NOTIFICATION_RENDERER_PROCESS_CLOSED) { live_renderer_ids_.erase(rph_id); } else { NOTREACHED() << "Unexpected notification type: " << type; } if (live_renderer_ids_.empty() && previous_num_live_renderers != 0) { // All render processes have died. CancelPendingHistogramReporting(); tracking_discharge_ = false; } } void PowerUsageMonitor::SetSystemInterfaceForTest( scoped_ptr interface) { system_interface_ = interface.Pass(); } void PowerUsageMonitor::OnPowerStateChange(bool on_battery_power) { } void PowerUsageMonitor::OnResume() { } void PowerUsageMonitor::OnSuspend() { CancelPendingHistogramReporting(); } void PowerUsageMonitor::Observe(int type, const NotificationSource& source, const NotificationDetails& details) { RenderProcessHost* rph = Source(source).ptr(); OnRenderProcessNotification(type, rph->GetID()); } void PowerUsageMonitor::CancelPendingHistogramReporting() { // Cancel any in-progress histogram reports and reporting of discharge UMA. system_interface_->CancelPendingHistogramReports(); } } // namespace content