1// Copyright (c) 2010 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/memory_details.h"
6
7#include "base/file_version_info.h"
8#include "base/metrics/histogram.h"
9#include "base/process_util.h"
10#include "base/string_util.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/common/extensions/extension.h"
15#include "chrome/common/url_constants.h"
16#include "content/browser/browser_child_process_host.h"
17#include "content/browser/browser_thread.h"
18#include "content/browser/renderer_host/backing_store_manager.h"
19#include "content/browser/renderer_host/render_process_host.h"
20#include "content/browser/renderer_host/render_view_host.h"
21#include "content/browser/tab_contents/navigation_entry.h"
22#include "content/browser/tab_contents/tab_contents.h"
23#include "content/common/bindings_policy.h"
24#include "grit/chromium_strings.h"
25#include "grit/generated_resources.h"
26#include "ui/base/l10n/l10n_util.h"
27
28#if defined(OS_LINUX)
29#include "content/browser/zygote_host_linux.h"
30#include "content/browser/renderer_host/render_sandbox_host_linux.h"
31#endif
32
33ProcessMemoryInformation::ProcessMemoryInformation()
34    : pid(0),
35      num_processes(0),
36      is_diagnostics(false),
37      type(ChildProcessInfo::UNKNOWN_PROCESS),
38      renderer_type(ChildProcessInfo::RENDERER_UNKNOWN) {
39}
40
41ProcessMemoryInformation::~ProcessMemoryInformation() {}
42
43ProcessData::ProcessData() {}
44
45ProcessData::ProcessData(const ProcessData& rhs)
46    : name(rhs.name),
47      process_name(rhs.process_name),
48      processes(rhs.processes) {
49}
50
51ProcessData::~ProcessData() {}
52
53ProcessData& ProcessData::operator=(const ProcessData& rhs) {
54  name = rhs.name;
55  process_name = rhs.process_name;
56  processes = rhs.processes;
57  return *this;
58}
59
60// About threading:
61//
62// This operation will hit no fewer than 3 threads.
63//
64// The ChildProcessInfo::Iterator can only be accessed from the IO thread.
65//
66// The RenderProcessHostIterator can only be accessed from the UI thread.
67//
68// This operation can take 30-100ms to complete.  We never want to have
69// one task run for that long on the UI or IO threads.  So, we run the
70// expensive parts of this operation over on the file thread.
71//
72void MemoryDetails::StartFetch() {
73  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
74  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
75
76  // In order to process this request, we need to use the plugin information.
77  // However, plugin process information is only available from the IO thread.
78  BrowserThread::PostTask(
79      BrowserThread::IO, FROM_HERE,
80      NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnIOThread));
81}
82
83MemoryDetails::~MemoryDetails() {}
84
85void MemoryDetails::CollectChildInfoOnIOThread() {
86  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
87
88  std::vector<ProcessMemoryInformation> child_info;
89
90  // Collect the list of child processes.
91  for (BrowserChildProcessHost::Iterator iter; !iter.Done(); ++iter) {
92    ProcessMemoryInformation info;
93    info.pid = base::GetProcId(iter->handle());
94    if (!info.pid)
95      continue;
96
97    info.type = iter->type();
98    info.renderer_type = iter->renderer_type();
99    info.titles.push_back(WideToUTF16Hack(iter->name()));
100    child_info.push_back(info);
101  }
102
103  // Now go do expensive memory lookups from the file thread.
104  BrowserThread::PostTask(
105      BrowserThread::FILE, FROM_HERE,
106      NewRunnableMethod(this, &MemoryDetails::CollectProcessData, child_info));
107}
108
109void MemoryDetails::CollectChildInfoOnUIThread() {
110  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
111
112#if defined(OS_LINUX)
113  const pid_t zygote_pid = ZygoteHost::GetInstance()->pid();
114  const pid_t sandbox_helper_pid = RenderSandboxHostLinux::GetInstance()->pid();
115#endif
116
117  ProcessData* const chrome_browser = ChromeBrowser();
118  // Get more information about the process.
119  for (size_t index = 0; index < chrome_browser->processes.size();
120      index++) {
121    // Check if it's a renderer, if so get the list of page titles in it and
122    // check if it's a diagnostics-related process.  We skip about:memory pages.
123    // Iterate the RenderProcessHosts to find the tab contents.
124    ProcessMemoryInformation& process =
125        chrome_browser->processes[index];
126
127    for (RenderProcessHost::iterator renderer_iter(
128         RenderProcessHost::AllHostsIterator()); !renderer_iter.IsAtEnd();
129         renderer_iter.Advance()) {
130      RenderProcessHost* render_process_host = renderer_iter.GetCurrentValue();
131      DCHECK(render_process_host);
132      // Ignore processes that don't have a connection, such as crashed tabs.
133      if (!render_process_host->HasConnection() ||
134          process.pid != base::GetProcId(render_process_host->GetHandle())) {
135        continue;
136      }
137      process.type = ChildProcessInfo::RENDER_PROCESS;
138      Profile* profile = render_process_host->profile();
139      ExtensionService* extension_service = profile->GetExtensionService();
140
141      // The RenderProcessHost may host multiple TabContents.  Any
142      // of them which contain diagnostics information make the whole
143      // process be considered a diagnostics process.
144      //
145      // NOTE: This is a bit dangerous.  We know that for now, listeners
146      //       are always RenderWidgetHosts.  But in theory, they don't
147      //       have to be.
148      RenderProcessHost::listeners_iterator iter(
149          render_process_host->ListenersIterator());
150      for (; !iter.IsAtEnd(); iter.Advance()) {
151        const RenderWidgetHost* widget =
152            static_cast<const RenderWidgetHost*>(iter.GetCurrentValue());
153        DCHECK(widget);
154        if (!widget || !widget->IsRenderView())
155          continue;
156
157        const RenderViewHost* host = static_cast<const RenderViewHost*>(widget);
158        RenderViewHostDelegate* host_delegate = host->delegate();
159        DCHECK(host_delegate);
160        GURL url = host_delegate->GetURL();
161        ViewType::Type type = host_delegate->GetRenderViewType();
162        if (host->enabled_bindings() & BindingsPolicy::WEB_UI) {
163          // TODO(erikkay) the type for devtools doesn't actually appear to
164          // be set.
165          if (type == ViewType::DEV_TOOLS_UI)
166            process.renderer_type = ChildProcessInfo::RENDERER_DEVTOOLS;
167          else
168            process.renderer_type = ChildProcessInfo::RENDERER_CHROME;
169        } else if (host->enabled_bindings() & BindingsPolicy::EXTENSION) {
170          process.renderer_type = ChildProcessInfo::RENDERER_EXTENSION;
171        }
172        TabContents* contents = host_delegate->GetAsTabContents();
173        if (!contents) {
174          if (host->is_extension_process()) {
175            const Extension* extension =
176                extension_service->GetExtensionByURL(url);
177            if (extension) {
178              string16 title = UTF8ToUTF16(extension->name());
179              process.titles.push_back(title);
180            }
181          } else if (process.renderer_type ==
182                     ChildProcessInfo::RENDERER_UNKNOWN) {
183            process.titles.push_back(UTF8ToUTF16(url.spec()));
184            switch (type) {
185              case ViewType::BACKGROUND_CONTENTS:
186                process.renderer_type =
187                    ChildProcessInfo::RENDERER_BACKGROUND_APP;
188                break;
189              case ViewType::INTERSTITIAL_PAGE:
190                process.renderer_type = ChildProcessInfo::RENDERER_INTERSTITIAL;
191                break;
192              case ViewType::NOTIFICATION:
193                process.renderer_type = ChildProcessInfo::RENDERER_NOTIFICATION;
194                break;
195              default:
196                process.renderer_type = ChildProcessInfo::RENDERER_UNKNOWN;
197                break;
198            }
199          }
200          continue;
201        }
202
203        // Since We have a TabContents and and the renderer type hasn't been
204        // set yet, it must be a normal tabbed renderer.
205        if (process.renderer_type == ChildProcessInfo::RENDERER_UNKNOWN)
206          process.renderer_type = ChildProcessInfo::RENDERER_NORMAL;
207
208        string16 title = contents->GetTitle();
209        if (!title.length())
210          title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE);
211        process.titles.push_back(title);
212
213        // We need to check the pending entry as well as the virtual_url to
214        // see if it's an about:memory URL (we don't want to count these in the
215        // total memory usage of the browser).
216        //
217        // When we reach here, about:memory will be the pending entry since we
218        // haven't responded with any data such that it would be committed. If
219        // you have another about:memory tab open (which would be committed),
220        // we don't want to count it either, so we also check the last committed
221        // entry.
222        //
223        // Either the pending or last committed entries can be NULL.
224        const NavigationEntry* pending_entry =
225            contents->controller().pending_entry();
226        const NavigationEntry* last_committed_entry =
227            contents->controller().GetLastCommittedEntry();
228        if ((last_committed_entry &&
229             LowerCaseEqualsASCII(last_committed_entry->virtual_url().spec(),
230                                  chrome::kAboutMemoryURL)) ||
231            (pending_entry &&
232             LowerCaseEqualsASCII(pending_entry->virtual_url().spec(),
233                                  chrome::kAboutMemoryURL)))
234          process.is_diagnostics = true;
235      }
236    }
237
238#if defined(OS_LINUX)
239    if (process.pid == zygote_pid) {
240      process.type = ChildProcessInfo::ZYGOTE_PROCESS;
241    } else if (process.pid == sandbox_helper_pid) {
242      process.type = ChildProcessInfo::SANDBOX_HELPER_PROCESS;
243    }
244#endif
245  }
246
247  // Get rid of other Chrome processes that are from a different profile.
248  for (size_t index = 0; index < chrome_browser->processes.size();
249      index++) {
250    if (chrome_browser->processes[index].type ==
251        ChildProcessInfo::UNKNOWN_PROCESS) {
252      chrome_browser->processes.erase(
253          chrome_browser->processes.begin() + index);
254      index--;
255    }
256  }
257
258  UpdateHistograms();
259
260  OnDetailsAvailable();
261}
262
263void MemoryDetails::UpdateHistograms() {
264  // Reports a set of memory metrics to UMA.
265  // Memory is measured in KB.
266
267  const ProcessData& browser = *ChromeBrowser();
268  size_t aggregate_memory = 0;
269  int chrome_count = 0;
270  int extension_count = 0;
271  int plugin_count = 0;
272  int renderer_count = 0;
273  int other_count = 0;
274  int worker_count = 0;
275  for (size_t index = 0; index < browser.processes.size(); index++) {
276    int sample = static_cast<int>(browser.processes[index].working_set.priv);
277    aggregate_memory += sample;
278    switch (browser.processes[index].type) {
279      case ChildProcessInfo::BROWSER_PROCESS:
280        UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample);
281        break;
282      case ChildProcessInfo::RENDER_PROCESS: {
283        ChildProcessInfo::RendererProcessType renderer_type =
284            browser.processes[index].renderer_type;
285        switch (renderer_type) {
286          case ChildProcessInfo::RENDERER_EXTENSION:
287            UMA_HISTOGRAM_MEMORY_KB("Memory.Extension", sample);
288            extension_count++;
289            break;
290          case ChildProcessInfo::RENDERER_CHROME:
291            UMA_HISTOGRAM_MEMORY_KB("Memory.Chrome", sample);
292            chrome_count++;
293            break;
294          case ChildProcessInfo::RENDERER_UNKNOWN:
295            NOTREACHED() << "Unknown renderer process type.";
296            break;
297          case ChildProcessInfo::RENDERER_NORMAL:
298          default:
299            // TODO(erikkay): Should we bother splitting out the other subtypes?
300            UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample);
301            renderer_count++;
302            break;
303        }
304        break;
305      }
306      case ChildProcessInfo::PLUGIN_PROCESS:
307        UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample);
308        plugin_count++;
309        break;
310      case ChildProcessInfo::WORKER_PROCESS:
311        UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample);
312        worker_count++;
313        break;
314      case ChildProcessInfo::UTILITY_PROCESS:
315        UMA_HISTOGRAM_MEMORY_KB("Memory.Utility", sample);
316        other_count++;
317        break;
318      case ChildProcessInfo::ZYGOTE_PROCESS:
319        UMA_HISTOGRAM_MEMORY_KB("Memory.Zygote", sample);
320        other_count++;
321        break;
322      case ChildProcessInfo::SANDBOX_HELPER_PROCESS:
323        UMA_HISTOGRAM_MEMORY_KB("Memory.SandboxHelper", sample);
324        other_count++;
325        break;
326      case ChildProcessInfo::NACL_LOADER_PROCESS:
327        UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClient", sample);
328        other_count++;
329        break;
330      case ChildProcessInfo::NACL_BROKER_PROCESS:
331        UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClientBroker", sample);
332        other_count++;
333        break;
334      case ChildProcessInfo::GPU_PROCESS:
335        UMA_HISTOGRAM_MEMORY_KB("Memory.Gpu", sample);
336        other_count++;
337        break;
338      default:
339        NOTREACHED();
340    }
341  }
342  UMA_HISTOGRAM_MEMORY_KB("Memory.BackingStore",
343                          BackingStoreManager::MemorySize() / 1024);
344
345  UMA_HISTOGRAM_COUNTS_100("Memory.ProcessCount",
346      static_cast<int>(browser.processes.size()));
347  UMA_HISTOGRAM_COUNTS_100("Memory.ChromeProcessCount", chrome_count);
348  UMA_HISTOGRAM_COUNTS_100("Memory.ExtensionProcessCount", extension_count);
349  UMA_HISTOGRAM_COUNTS_100("Memory.OtherProcessCount", other_count);
350  UMA_HISTOGRAM_COUNTS_100("Memory.PluginProcessCount", plugin_count);
351  UMA_HISTOGRAM_COUNTS_100("Memory.RendererProcessCount", renderer_count);
352  UMA_HISTOGRAM_COUNTS_100("Memory.WorkerProcessCount", worker_count);
353  // TODO(viettrungluu): Do we want separate counts for the other
354  // (platform-specific) process types?
355
356  int total_sample = static_cast<int>(aggregate_memory / 1000);
357  UMA_HISTOGRAM_MEMORY_MB("Memory.Total", total_sample);
358}
359