memory_details_mac.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
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 <set>
8#include <string>
9
10#include "app/l10n_util.h"
11#include "base/basictypes.h"
12#include "base/file_path.h"
13#include "base/file_version_info.h"
14#include "base/mac_util.h"
15#include "base/string_util.h"
16#include "base/process_util.h"
17#include "base/thread.h"
18#include "chrome/browser/browser_child_process_host.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/browser/chrome_thread.h"
21#include "chrome/browser/process_info_snapshot.h"
22#include "chrome/browser/renderer_host/backing_store_manager.h"
23#include "chrome/browser/renderer_host/render_process_host.h"
24#include "chrome/browser/tab_contents/navigation_entry.h"
25#include "chrome/browser/tab_contents/tab_contents.h"
26#include "chrome/common/chrome_constants.h"
27#include "chrome/common/chrome_version_info.h"
28#include "chrome/common/url_constants.h"
29#include "grit/chromium_strings.h"
30
31// TODO(viettrungluu): Many of the TODOs below are subsumed by a general need to
32// refactor the about:memory code (not just on Mac, but probably on other
33// platforms as well). I've filed crbug.com/25456.
34
35class RenderViewHostDelegate;
36
37// Known browsers which we collect details for. |CHROME_BROWSER| *must* be the
38// first browser listed. The order here must match those in |process_template|
39// (in |MemoryDetails::MemoryDetails()| below).
40// TODO(viettrungluu): In the big refactoring (see above), get rid of this order
41// dependence.
42enum BrowserType {
43  // TODO(viettrungluu): possibly add more?
44  CHROME_BROWSER = 0,
45  SAFARI_BROWSER,
46  FIREFOX_BROWSER,
47  CAMINO_BROWSER,
48  OPERA_BROWSER,
49  OMNIWEB_BROWSER,
50  MAX_BROWSERS
51} BrowserProcess;
52
53
54MemoryDetails::MemoryDetails() {
55  static const std::wstring google_browser_name =
56      l10n_util::GetString(IDS_PRODUCT_NAME);
57  // (Human and process) names of browsers; should match the ordering for
58  // |BrowserProcess| (i.e., |BrowserType|).
59  // TODO(viettrungluu): The current setup means that we can't detect both
60  // Chrome and Chromium at the same time!
61  // TODO(viettrungluu): Get localized browser names for other browsers
62  // (crbug.com/25779).
63  ProcessData process_template[MAX_BROWSERS] = {
64    { google_browser_name.c_str(), chrome::kBrowserProcessExecutableName, },
65    { L"Safari", L"Safari", },
66    { L"Firefox", L"firefox-bin", },
67    { L"Camino", L"Camino", },
68    { L"Opera", L"Opera", },
69    { L"OmniWeb", L"OmniWeb", },
70  };
71
72  for (size_t index = 0; index < arraysize(process_template); ++index) {
73    ProcessData process;
74    process.name = process_template[index].name;
75    process.process_name = process_template[index].process_name;
76    process_data_.push_back(process);
77  }
78}
79
80ProcessData* MemoryDetails::ChromeBrowser() {
81  return &process_data_[CHROME_BROWSER];
82}
83
84void MemoryDetails::CollectProcessData(
85    std::vector<ProcessMemoryInformation> child_info) {
86  // This must be run on the file thread to avoid jank (|ProcessInfoSnapshot|
87  // runs /bin/ps, which isn't instantaneous).
88  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
89
90  // Clear old data.
91  for (size_t index = 0; index < MAX_BROWSERS; index++)
92    process_data_[index].processes.clear();
93
94  // First, we use |NamedProcessIterator| to get the PIDs of the processes we're
95  // interested in; we save our results to avoid extra calls to
96  // |NamedProcessIterator| (for performance reasons) and to avoid additional
97  // inconsistencies caused by racing. Then we run |/bin/ps| *once* to get
98  // information on those PIDs. Then we used our saved information to iterate
99  // over browsers, then over PIDs.
100
101  // Get PIDs of main browser processes.
102  std::vector<base::ProcessId> pids_by_browser[MAX_BROWSERS];
103  std::vector<base::ProcessId> all_pids;
104  for (size_t index = CHROME_BROWSER; index < MAX_BROWSERS; index++) {
105    base::NamedProcessIterator process_it(process_data_[index].process_name,
106                                          NULL);
107
108    while (const base::ProcessEntry* entry = process_it.NextProcessEntry()) {
109      pids_by_browser[index].push_back(entry->pid());
110      all_pids.push_back(entry->pid());
111    }
112  }
113
114  // Get PIDs of helpers.
115  std::vector<base::ProcessId> helper_pids;
116  {
117    base::NamedProcessIterator helper_it(chrome::kHelperProcessExecutableName,
118                                         NULL);
119    while (const base::ProcessEntry* entry = helper_it.NextProcessEntry()) {
120      helper_pids.push_back(entry->pid());
121      all_pids.push_back(entry->pid());
122    }
123  }
124
125  // Capture information about the processes we're interested in.
126  ProcessInfoSnapshot process_info;
127  process_info.Sample(all_pids);
128
129  // Handle the other processes first.
130  for (size_t index = CHROME_BROWSER + 1; index < MAX_BROWSERS; index++) {
131    for (std::vector<base::ProcessId>::const_iterator it =
132         pids_by_browser[index].begin();
133         it != pids_by_browser[index].end(); ++it) {
134      ProcessMemoryInformation info;
135      info.pid = *it;
136      info.type = ChildProcessInfo::UNKNOWN_PROCESS;
137
138      // Try to get version information. To do this, we need first to get the
139      // executable's name (we can only believe |proc_info.command| if it looks
140      // like an absolute path). Then we need strip the executable's name back
141      // to the bundle's name. And only then can we try to get the version.
142      scoped_ptr<FileVersionInfo> version_info;
143      ProcessInfoSnapshot::ProcInfoEntry proc_info;
144      if (process_info.GetProcInfo(info.pid, &proc_info)) {
145        if (proc_info.command.length() > 1 && proc_info.command[0] == '/') {
146          FilePath bundle_name =
147              mac_util::GetAppBundlePath(FilePath(proc_info.command));
148          if (!bundle_name.empty()) {
149            version_info.reset(FileVersionInfo::CreateFileVersionInfo(
150                bundle_name));
151          }
152        }
153      }
154      if (version_info.get()) {
155        info.product_name = version_info->product_name();
156        info.version = version_info->product_version();
157      } else {
158        info.product_name = process_data_[index].name;
159        info.version = L"";
160      }
161
162      // Memory info.
163      process_info.GetCommittedKBytesOfPID(info.pid, &info.committed);
164      process_info.GetWorkingSetKBytesOfPID(info.pid, &info.working_set);
165
166      // Add the process info to our list.
167      process_data_[index].processes.push_back(info);
168    }
169  }
170
171  // Collect data about Chrome/Chromium.
172  for (std::vector<base::ProcessId>::const_iterator it =
173       pids_by_browser[CHROME_BROWSER].begin();
174       it != pids_by_browser[CHROME_BROWSER].end(); ++it) {
175    CollectProcessDataChrome(child_info, *it, process_info);
176  }
177
178  // And collect data about the helpers.
179  for (std::vector<base::ProcessId>::const_iterator it = helper_pids.begin();
180       it != helper_pids.end(); ++it) {
181    CollectProcessDataChrome(child_info, *it, process_info);
182  }
183
184  // Finally return to the browser thread.
185  ChromeThread::PostTask(
186      ChromeThread::UI, FROM_HERE,
187      NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnUIThread));
188}
189
190void MemoryDetails::CollectProcessDataChrome(
191    const std::vector<ProcessMemoryInformation>& child_info,
192    base::ProcessId pid,
193    const ProcessInfoSnapshot& process_info) {
194  ProcessMemoryInformation info;
195  info.pid = pid;
196  if (info.pid == base::GetCurrentProcId())
197    info.type = ChildProcessInfo::BROWSER_PROCESS;
198  else
199    info.type = ChildProcessInfo::UNKNOWN_PROCESS;
200
201  scoped_ptr<FileVersionInfo> version_info(chrome::GetChromeVersionInfo());
202  if (version_info.get()) {
203    info.product_name = version_info->product_name();
204    info.version = version_info->product_version();
205  } else {
206    info.product_name = process_data_[CHROME_BROWSER].name;
207    info.version = L"";
208  }
209
210  // Check if this is one of the child processes whose data we collected
211  // on the IO thread, and if so copy over that data.
212  for (size_t child = 0; child < child_info.size(); child++) {
213    if (child_info[child].pid == info.pid) {
214      info.titles = child_info[child].titles;
215      info.type = child_info[child].type;
216      break;
217    }
218  }
219
220  // Memory info.
221  process_info.GetCommittedKBytesOfPID(info.pid, &info.committed);
222  process_info.GetWorkingSetKBytesOfPID(info.pid, &info.working_set);
223
224  // Add the process info to our list.
225  process_data_[CHROME_BROWSER].processes.push_back(info);
226}
227