blob: ba27f89ec839689548dcb622e2e095dd35eb54c8 [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// Copyright (c) 2006-2008 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.
initial.commit09911bf2008-07-26 23:55:294
5#include "chrome/browser/memory_details.h"
6#include <psapi.h>
7
8#include "base/file_version_info.h"
[email protected]4c4d8d22009-03-04 05:29:279#include "base/string_util.h"
initial.commit09911bf2008-07-26 23:55:2910#include "chrome/browser/browser_process.h"
[email protected]dcccb942009-02-01 18:23:0011#include "chrome/browser/chrome_thread.h"
[email protected]8c8657d62009-01-16 18:31:2612#include "chrome/browser/renderer_host/render_process_host.h"
[email protected]cd3d7892009-03-04 23:55:0613#include "chrome/browser/tab_contents/navigation_entry.h"
[email protected]f3ec7742009-01-15 00:59:1614#include "chrome/browser/tab_contents/web_contents.h"
[email protected]6dffde322009-02-18 03:47:4815#include "chrome/common/child_process_host.h"
[email protected]cd3d7892009-03-04 23:55:0616#include "chrome/common/url_constants.h"
initial.commit09911bf2008-07-26 23:55:2917
18class RenderViewHostDelegate;
19
20// Template of static data we use for finding browser process information.
21// These entries must match the ordering for MemoryDetails::BrowserProcess.
22static ProcessData g_process_template[] = {
[email protected]1e1402c12008-08-28 00:58:4123 { L"Chromium", L"chrome.exe", },
initial.commit09911bf2008-07-26 23:55:2924 { L"IE", L"iexplore.exe", },
25 { L"Firefox", L"firefox.exe", },
26 { L"Opera", L"opera.exe", },
27 { L"Safari", L"safari.exe", },
28 };
29
30
31// About threading:
32//
33// This operation will hit no fewer than 3 threads.
34//
[email protected]a436d922009-02-13 23:16:4235// The ChildProcessInfo::Iterator can only be accessed from the IO thread.
initial.commit09911bf2008-07-26 23:55:2936//
37// The RenderProcessHostIterator can only be accessed from the UI thread.
38//
39// This operation can take 30-100ms to complete. We never want to have
40// one task run for that long on the UI or IO threads. So, we run the
41// expensive parts of this operation over on the file thread.
42//
43
44MemoryDetails::MemoryDetails()
45 : ui_loop_(NULL) {
46 for (int index = 0; index < arraysize(g_process_template); ++index) {
47 process_data_[index].name = g_process_template[index].name;
48 process_data_[index].process_name = g_process_template[index].process_name;
49 }
50}
51
52void MemoryDetails::StartFetch() {
53 ui_loop_ = MessageLoop::current();
54
55 DCHECK(ui_loop_ != g_browser_process->io_thread()->message_loop());
56 DCHECK(ui_loop_ != g_browser_process->file_thread()->message_loop());
57
58 // In order to process this request, we need to use the plugin information.
59 // However, plugin process information is only available from the IO thread.
60 g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE,
[email protected]a27a9382009-02-11 23:55:1061 NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnIOThread));
initial.commit09911bf2008-07-26 23:55:2962}
63
[email protected]a27a9382009-02-11 23:55:1064void MemoryDetails::CollectChildInfoOnIOThread() {
initial.commit09911bf2008-07-26 23:55:2965 DCHECK(MessageLoop::current() ==
66 ChromeThread::GetMessageLoop(ChromeThread::IO));
67
[email protected]a27a9382009-02-11 23:55:1068 std::vector<ProcessMemoryInformation> child_info;
69
[email protected]a436d922009-02-13 23:16:4270 // Collect the list of child processes.
[email protected]6dffde322009-02-18 03:47:4871 for (ChildProcessHost::Iterator iter; !iter.Done(); ++iter) {
[email protected]a27a9382009-02-11 23:55:1072 ProcessMemoryInformation info;
[email protected]6dffde322009-02-18 03:47:4873 info.pid = iter->pid();
[email protected]a27a9382009-02-11 23:55:1074 if (!info.pid)
75 continue;
76
[email protected]a436d922009-02-13 23:16:4277 info.type = iter->type();
78 info.titles.push_back(iter->name());
[email protected]a27a9382009-02-11 23:55:1079 child_info.push_back(info);
initial.commit09911bf2008-07-26 23:55:2980 }
81
82 // Now go do expensive memory lookups from the file thread.
83 ChromeThread::GetMessageLoop(ChromeThread::FILE)->PostTask(FROM_HERE,
[email protected]a27a9382009-02-11 23:55:1084 NewRunnableMethod(this, &MemoryDetails::CollectProcessData, child_info));
initial.commit09911bf2008-07-26 23:55:2985}
86
[email protected]a27a9382009-02-11 23:55:1087void MemoryDetails::CollectProcessData(
88 std::vector<ProcessMemoryInformation> child_info) {
initial.commit09911bf2008-07-26 23:55:2989 DCHECK(MessageLoop::current() ==
90 ChromeThread::GetMessageLoop(ChromeThread::FILE));
91
92 int array_size = 32;
[email protected]a27a9382009-02-11 23:55:1093 scoped_ptr_malloc<DWORD> process_list;
initial.commit09911bf2008-07-26 23:55:2994 DWORD bytes_used = 0;
95 do {
96 array_size *= 2;
[email protected]a27a9382009-02-11 23:55:1097 process_list.reset(static_cast<DWORD*>(
98 realloc(process_list.release(), sizeof(DWORD) * array_size)));
initial.commit09911bf2008-07-26 23:55:2999 // EnumProcesses doesn't return an error if the array is too small.
100 // We have to check if the return buffer is full, and if so, call it
101 // again. See msdn docs for more info.
[email protected]a27a9382009-02-11 23:55:10102 if (!EnumProcesses(process_list.get(), array_size * sizeof(DWORD),
initial.commit09911bf2008-07-26 23:55:29103 &bytes_used)) {
104 LOG(ERROR) << "EnumProcesses failed: " << GetLastError();
105 return;
106 }
[email protected]a27a9382009-02-11 23:55:10107 } while (bytes_used == (array_size * sizeof(DWORD)));
initial.commit09911bf2008-07-26 23:55:29108
[email protected]a27a9382009-02-11 23:55:10109 int num_processes = bytes_used / sizeof(DWORD);
initial.commit09911bf2008-07-26 23:55:29110
111 // Clear old data.
112 for (int index = 0; index < arraysize(g_process_template); index++)
113 process_data_[index].processes.clear();
114
115 for (int index = 0; index < num_processes; index++) {
[email protected]a27a9382009-02-11 23:55:10116 int pid = process_list.get()[index];
117 ScopedHandle handle(OpenProcess(
118 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
119 if (!handle.Get())
120 continue;
121 TCHAR name[MAX_PATH];
122 if (!GetModuleBaseName(handle, NULL, name, MAX_PATH - 1))
123 continue;
124 for (int index2 = 0; index2 < arraysize(g_process_template); index2++) {
125 if (_wcsicmp(process_data_[index2].process_name, name) != 0)
126 continue;
127 // Get Memory Information.
128 ProcessMemoryInformation info;
129 info.pid = pid;
130 if (info.pid == GetCurrentProcessId())
131 info.type = ChildProcessInfo::BROWSER_PROCESS;
132 else
133 info.type = ChildProcessInfo::UNKNOWN_PROCESS;
initial.commit09911bf2008-07-26 23:55:29134
[email protected]a27a9382009-02-11 23:55:10135 scoped_ptr<base::ProcessMetrics> metrics;
136 metrics.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
137 metrics->GetCommittedKBytes(&info.committed);
138 metrics->GetWorkingSetKBytes(&info.working_set);
initial.commit09911bf2008-07-26 23:55:29139
[email protected]a27a9382009-02-11 23:55:10140 // Get Version Information.
141 if (index2 == 0) { // Chrome
142 scoped_ptr<FileVersionInfo> version_info(
143 FileVersionInfo::CreateFileVersionInfoForCurrentModule());
144 if (version_info != NULL)
145 info.version = version_info->file_version();
146 // Check if this is one of the child processes whose data we collected
147 // on the IO thread, and if so copy over that data.
148 for (size_t child = 0; child < child_info.size(); child++) {
149 if (child_info[child].pid != info.pid)
150 continue;
151 info.titles = child_info[child].titles;
152 info.type = child_info[child].type;
153 break;
154 }
155 } else if (GetModuleFileNameEx(handle, NULL, name, MAX_PATH - 1)) {
156 std::wstring str_name(name);
157 scoped_ptr<FileVersionInfo> version_info(
158 FileVersionInfo::CreateFileVersionInfo(str_name));
159 if (version_info != NULL) {
160 info.version = version_info->product_version();
161 info.product_name = version_info->product_name();
initial.commit09911bf2008-07-26 23:55:29162 }
163 }
[email protected]a27a9382009-02-11 23:55:10164
165 // Add the process info to our list.
166 process_data_[index2].processes.push_back(info);
167 break;
initial.commit09911bf2008-07-26 23:55:29168 }
169 }
initial.commit09911bf2008-07-26 23:55:29170
171 // Finally return to the browser thread.
172 ui_loop_->PostTask(FROM_HERE,
[email protected]a27a9382009-02-11 23:55:10173 NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnUIThread));
initial.commit09911bf2008-07-26 23:55:29174}
175
[email protected]a27a9382009-02-11 23:55:10176void MemoryDetails::CollectChildInfoOnUIThread() {
initial.commit09911bf2008-07-26 23:55:29177 DCHECK(MessageLoop::current() == ui_loop_);
178
[email protected]a27a9382009-02-11 23:55:10179 // Get more information about the process.
initial.commit09911bf2008-07-26 23:55:29180 for (size_t index = 0; index < process_data_[CHROME_BROWSER].processes.size();
181 index++) {
[email protected]a27a9382009-02-11 23:55:10182 // Check if it's a renderer, if so get the list of page titles in it and
183 // check if it's a diagnostics-related process. We skip all diagnostics
184 // pages (e.g. "about:xxx" URLs). Iterate the RenderProcessHosts to find
[email protected]cd3d7892009-03-04 23:55:06185 // the tab contents.
initial.commit09911bf2008-07-26 23:55:29186 RenderProcessHost::iterator renderer_iter;
187 for (renderer_iter = RenderProcessHost::begin(); renderer_iter !=
188 RenderProcessHost::end(); ++renderer_iter) {
189 DCHECK(renderer_iter->second);
[email protected]a27a9382009-02-11 23:55:10190 ProcessMemoryInformation& process =
191 process_data_[CHROME_BROWSER].processes[index];
192 if (process.pid != renderer_iter->second->process().pid())
193 continue;
194 process.type = ChildProcessInfo::RENDER_PROCESS;
195 // The RenderProcessHost may host multiple TabContents. Any
196 // of them which contain diagnostics information make the whole
197 // process be considered a diagnostics process.
198 //
199 // NOTE: This is a bit dangerous. We know that for now, listeners
200 // are always RenderWidgetHosts. But in theory, they don't
201 // have to be.
202 RenderProcessHost::listeners_iterator iter;
203 for (iter = renderer_iter->second->listeners_begin();
204 iter != renderer_iter->second->listeners_end(); ++iter) {
205 RenderWidgetHost* widget =
206 static_cast<RenderWidgetHost*>(iter->second);
207 DCHECK(widget);
208 if (!widget || !widget->IsRenderView())
209 continue;
initial.commit09911bf2008-07-26 23:55:29210
[email protected]a27a9382009-02-11 23:55:10211 RenderViewHost* host = static_cast<RenderViewHost*>(widget);
[email protected]6d53eb22009-02-13 20:48:39212 TabContents* contents = NULL;
213 if (host->delegate())
214 contents = host->delegate()->GetAsWebContents();
[email protected]a27a9382009-02-11 23:55:10215 if (!contents)
216 continue;
[email protected]4c4d8d22009-03-04 05:29:27217 std::wstring title = UTF16ToWideHack(contents->GetTitle());
[email protected]a27a9382009-02-11 23:55:10218 if (!title.length())
219 title = L"Untitled";
220 process.titles.push_back(title);
[email protected]cd3d7892009-03-04 23:55:06221
222 // We need to check the pending entry as well as the pending entry to
223 // see if it's an about:memory URL (we don't want to count these in the
224 // total memory usage of the browser).
225 //
226 // When we reach here, about:memory will be the pending entry since we
227 // haven't responded with any data such that it would be committed. If
228 // you have another about:memory tab open (which would be committed),
229 // we don't want to count it either, so we also check the last committed
230 // entry.
231 //
232 // Either the pending or last committed entries can be NULL.
233 const NavigationEntry* pending_entry = NULL;
234 //contents->controller()->GetPendingEntry();
235 const NavigationEntry* last_committed_entry =
236 contents->controller()->GetLastCommittedEntry();
237 if ((last_committed_entry &&
238 LowerCaseEqualsASCII(last_committed_entry->display_url().spec(),
239 chrome::kAboutMemoryURL)) ||
240 (pending_entry &&
241 LowerCaseEqualsASCII(pending_entry->display_url().spec(),
242 chrome::kAboutMemoryURL)))
[email protected]a27a9382009-02-11 23:55:10243 process.is_diagnostics = true;
initial.commit09911bf2008-07-26 23:55:29244 }
245 }
246 }
247
[email protected]a27a9382009-02-11 23:55:10248 // Get rid of other Chrome processes that are from a different profile.
249 for (size_t index = 0; index < process_data_[CHROME_BROWSER].processes.size();
250 index++) {
251 if (process_data_[CHROME_BROWSER].processes[index].type ==
252 ChildProcessInfo::UNKNOWN_PROCESS) {
253 process_data_[CHROME_BROWSER].processes.erase(
254 process_data_[CHROME_BROWSER].processes.begin() + index);
[email protected]a436d922009-02-13 23:16:42255 index--;
[email protected]a27a9382009-02-11 23:55:10256 }
257 }
258
initial.commit09911bf2008-07-26 23:55:29259 UpdateHistograms();
260
261 OnDetailsAvailable();
262}
263
264void MemoryDetails::UpdateHistograms() {
265 // Reports a set of memory metrics to UMA.
266 // Memory is measured in units of 10KB.
267
initial.commit09911bf2008-07-26 23:55:29268 ProcessData browser = process_data_[CHROME_BROWSER];
269 size_t aggregate_memory = 0;
[email protected]a27a9382009-02-11 23:55:10270 int plugin_count = 0;
271 int worker_count = 0;
initial.commit09911bf2008-07-26 23:55:29272 for (size_t index = 0; index < browser.processes.size(); index++) {
[email protected]921cd0cc2008-10-21 22:30:55273 int sample = static_cast<int>(browser.processes[index].working_set.priv);
274 aggregate_memory += sample;
[email protected]a27a9382009-02-11 23:55:10275 switch (browser.processes[index].type) {
276 case ChildProcessInfo::BROWSER_PROCESS:
[email protected]553dba62009-02-24 19:08:23277 UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample);
[email protected]a27a9382009-02-11 23:55:10278 break;
279 case ChildProcessInfo::RENDER_PROCESS:
[email protected]553dba62009-02-24 19:08:23280 UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample);
[email protected]a27a9382009-02-11 23:55:10281 break;
282 case ChildProcessInfo::PLUGIN_PROCESS:
[email protected]553dba62009-02-24 19:08:23283 UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample);
[email protected]a27a9382009-02-11 23:55:10284 plugin_count++;
285 break;
286 case ChildProcessInfo::WORKER_PROCESS:
[email protected]553dba62009-02-24 19:08:23287 UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample);
[email protected]a27a9382009-02-11 23:55:10288 worker_count++;
289 break;
initial.commit09911bf2008-07-26 23:55:29290 }
291 }
[email protected]a27a9382009-02-11 23:55:10292
[email protected]553dba62009-02-24 19:08:23293 UMA_HISTOGRAM_COUNTS_100("Memory.ProcessCount",
initial.commit09911bf2008-07-26 23:55:29294 static_cast<int>(browser.processes.size()));
[email protected]553dba62009-02-24 19:08:23295 UMA_HISTOGRAM_COUNTS_100("Memory.PluginProcessCount", plugin_count);
296 UMA_HISTOGRAM_COUNTS_100("Memory.WorkerProcessCount", worker_count);
[email protected]921cd0cc2008-10-21 22:30:55297
298 int total_sample = static_cast<int>(aggregate_memory / 1000);
[email protected]553dba62009-02-24 19:08:23299 UMA_HISTOGRAM_MEMORY_MB("Memory.Total", total_sample);
initial.commit09911bf2008-07-26 23:55:29300}