1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/power/process_power_collector.h"
6
7#include "base/process/process_handle.h"
8#include "base/process/process_metrics.h"
9#include "chrome/browser/browser_process.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/profiles/profile_manager.h"
12#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
13#include "components/power/origin_power_map.h"
14#include "components/power/origin_power_map_factory.h"
15#include "content/public/browser/browser_context.h"
16#include "content/public/browser/render_process_host.h"
17#include "content/public/browser/web_contents.h"
18#include "extensions/browser/app_window/app_window.h"
19#include "extensions/browser/app_window/app_window_registry.h"
20#include "url/gurl.h"
21
22#if defined(OS_CHROMEOS)
23#include "chromeos/dbus/dbus_thread_manager.h"
24#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
25#endif
26
27namespace {
28const int kSecondsPerSample = 30;
29}
30
31ProcessPowerCollector::PerProcessData::PerProcessData(
32    scoped_ptr<base::ProcessMetrics> metrics,
33    const GURL& origin,
34    Profile* profile)
35    : metrics_(metrics.Pass()),
36      profile_(profile),
37      last_origin_(origin),
38      last_cpu_percent_(0),
39      seen_this_cycle_(true) {
40}
41
42ProcessPowerCollector::PerProcessData::PerProcessData()
43    : profile_(NULL),
44      last_cpu_percent_(0.0),
45      seen_this_cycle_(false) {
46}
47
48ProcessPowerCollector::PerProcessData::~PerProcessData() {
49}
50
51ProcessPowerCollector::ProcessPowerCollector()
52    : scale_factor_(1.0) {
53#if defined(OS_CHROMEOS)
54  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(
55      this);
56#endif
57}
58
59ProcessPowerCollector::~ProcessPowerCollector() {
60#if defined(OS_CHROMEOS)
61  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(
62      this);
63#endif
64}
65
66#if defined(OS_CHROMEOS)
67void ProcessPowerCollector::PowerChanged(
68    const power_manager::PowerSupplyProperties& prop) {
69  if (prop.battery_state() ==
70      power_manager::PowerSupplyProperties::DISCHARGING) {
71    if (!timer_.IsRunning())
72      StartTimer();
73    scale_factor_ = prop.battery_discharge_rate();
74  } else {
75    timer_.Stop();
76  }
77}
78#endif
79
80void ProcessPowerCollector::Initialize() {
81  StartTimer();
82}
83
84double ProcessPowerCollector::UpdatePowerConsumptionForTesting() {
85  return UpdatePowerConsumption();
86}
87
88void ProcessPowerCollector::StartTimer() {
89  DCHECK(!timer_.IsRunning());
90  timer_.Start(FROM_HERE,
91               base::TimeDelta::FromSeconds(kSecondsPerSample),
92               this,
93               &ProcessPowerCollector::HandleUpdateTimeout);
94}
95
96double ProcessPowerCollector::UpdatePowerConsumption() {
97  double total_cpu_percent = SynchronizeProcesses();
98
99  for (ProcessMetricsMap::iterator it = metrics_map_.begin();
100       it != metrics_map_.end();
101       ++it) {
102    // Invalidate the process for the next cycle.
103    it->second->set_seen_this_cycle(false);
104  }
105
106  RecordCpuUsageByOrigin(total_cpu_percent);
107  return total_cpu_percent;
108}
109
110void ProcessPowerCollector::HandleUpdateTimeout() {
111  UpdatePowerConsumption();
112}
113
114double ProcessPowerCollector::SynchronizeProcesses() {
115  // Update all tabs.
116  for (TabContentsIterator it; !it.done(); it.Next()) {
117    content::RenderProcessHost* render_process = it->GetRenderProcessHost();
118    // Skip incognito web contents.
119    if (render_process->GetBrowserContext()->IsOffTheRecord())
120      continue;
121    UpdateProcessInMap(render_process, it->GetLastCommittedURL().GetOrigin());
122  }
123
124  // Iterate over all profiles to find all app windows to attribute all apps.
125  ProfileManager* pm = g_browser_process->profile_manager();
126  std::vector<Profile*> open_profiles = pm->GetLoadedProfiles();
127  for (std::vector<Profile*>::const_iterator it = open_profiles.begin();
128       it != open_profiles.end();
129       ++it) {
130    const extensions::AppWindowRegistry::AppWindowList& app_windows =
131        extensions::AppWindowRegistry::Get(*it)->app_windows();
132    for (extensions::AppWindowRegistry::AppWindowList::const_iterator itr =
133             app_windows.begin();
134         itr != app_windows.end();
135         ++itr) {
136      content::WebContents* web_contents = (*itr)->web_contents();
137
138      UpdateProcessInMap(web_contents->GetRenderProcessHost(),
139                         web_contents->GetLastCommittedURL().GetOrigin());
140    }
141  }
142
143  // Remove invalid processes and sum up the cpu cycle.
144  double total_cpu_percent = 0.0;
145  ProcessMetricsMap::iterator it = metrics_map_.begin();
146  while (it != metrics_map_.end()) {
147    if (!it->second->seen_this_cycle()) {
148      metrics_map_.erase(it++);
149      continue;
150    }
151
152    total_cpu_percent += it->second->last_cpu_percent();
153    ++it;
154  }
155
156  return total_cpu_percent;
157}
158
159void ProcessPowerCollector::RecordCpuUsageByOrigin(double total_cpu_percent) {
160  DCHECK_GE(total_cpu_percent, 0);
161  if (total_cpu_percent == 0)
162    return;
163
164  for (ProcessMetricsMap::iterator it = metrics_map_.begin();
165       it != metrics_map_.end();
166       ++it) {
167    double last_process_power_usage = it->second->last_cpu_percent();
168    last_process_power_usage *= scale_factor_ / total_cpu_percent;
169
170    GURL origin = it->second->last_origin();
171    power::OriginPowerMap* origin_power_map =
172        power::OriginPowerMapFactory::GetForBrowserContext(
173            it->second->profile());
174    // |origin_power_map| can be NULL, if the profile is a guest profile in
175    // Chrome OS.
176    if (!origin_power_map)
177      continue;
178    origin_power_map->AddPowerForOrigin(origin, last_process_power_usage);
179  }
180
181  // Iterate over all profiles to let them know we've finished updating.
182  ProfileManager* pm = g_browser_process->profile_manager();
183  std::vector<Profile*> open_profiles = pm->GetLoadedProfiles();
184  for (std::vector<Profile*>::const_iterator it = open_profiles.begin();
185       it != open_profiles.end();
186       ++it) {
187    power::OriginPowerMap* origin_power_map =
188        power::OriginPowerMapFactory::GetForBrowserContext(*it);
189    if (!origin_power_map)
190      continue;
191    origin_power_map->OnAllOriginsUpdated();
192  }
193}
194
195void ProcessPowerCollector::UpdateProcessInMap(
196    const content::RenderProcessHost* rph,
197    const GURL& origin) {
198  base::ProcessHandle handle = rph->GetHandle();
199  if (metrics_map_.find(handle) == metrics_map_.end()) {
200    metrics_map_[handle] = linked_ptr<PerProcessData>(new PerProcessData(
201#if defined(OS_MACOSX)
202        scoped_ptr<base::ProcessMetrics>(
203            base::ProcessMetrics::CreateProcessMetrics(handle, NULL)),
204#else
205        scoped_ptr<base::ProcessMetrics>(
206            base::ProcessMetrics::CreateProcessMetrics(handle)),
207#endif
208        origin,
209        Profile::FromBrowserContext(rph->GetBrowserContext())));
210  }
211
212  linked_ptr<PerProcessData>& process_data = metrics_map_[handle];
213  process_data->set_last_cpu_percent(std::max(0.0,
214      cpu_usage_callback_.is_null() ? process_data->metrics()->GetCPUUsage()
215                                    : cpu_usage_callback_.Run(handle)));
216  process_data->set_seen_this_cycle(true);
217}
218