blob: e1279fe06ea02978068cf68259dd0d207f354481 [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"
9#include "base/histogram.h"
10#include "base/image_util.h"
11#include "base/message_loop.h"
12#include "base/process_util.h"
[email protected]a27a9382009-02-11 23:55:1013#include "base/scoped_ptr.h"
initial.commit09911bf2008-07-26 23:55:2914#include "base/thread.h"
15#include "chrome/browser/browser_process.h"
[email protected]dcccb942009-02-01 18:23:0016#include "chrome/browser/chrome_thread.h"
initial.commit09911bf2008-07-26 23:55:2917#include "chrome/browser/plugin_process_host.h"
18#include "chrome/browser/plugin_service.h"
[email protected]8c8657d62009-01-16 18:31:2619#include "chrome/browser/renderer_host/render_process_host.h"
[email protected]6524b5f92009-01-22 17:48:2520#include "chrome/browser/renderer_host/render_view_host.h"
[email protected]f3ec7742009-01-15 00:59:1621#include "chrome/browser/tab_contents/tab_contents.h"
22#include "chrome/browser/tab_contents/web_contents.h"
initial.commit09911bf2008-07-26 23:55:2923
24class RenderViewHostDelegate;
25
26// Template of static data we use for finding browser process information.
27// These entries must match the ordering for MemoryDetails::BrowserProcess.
28static ProcessData g_process_template[] = {
[email protected]1e1402c12008-08-28 00:58:4129 { L"Chromium", L"chrome.exe", },
initial.commit09911bf2008-07-26 23:55:2930 { L"IE", L"iexplore.exe", },
31 { L"Firefox", L"firefox.exe", },
32 { L"Opera", L"opera.exe", },
33 { L"Safari", L"safari.exe", },
34 };
35
36
37// About threading:
38//
39// This operation will hit no fewer than 3 threads.
40//
41// The PluginHostIterator can only be accessed from the IO thread.
42//
43// The RenderProcessHostIterator can only be accessed from the UI thread.
44//
45// This operation can take 30-100ms to complete. We never want to have
46// one task run for that long on the UI or IO threads. So, we run the
47// expensive parts of this operation over on the file thread.
48//
49
50MemoryDetails::MemoryDetails()
51 : ui_loop_(NULL) {
52 for (int index = 0; index < arraysize(g_process_template); ++index) {
53 process_data_[index].name = g_process_template[index].name;
54 process_data_[index].process_name = g_process_template[index].process_name;
55 }
56}
57
58void MemoryDetails::StartFetch() {
59 ui_loop_ = MessageLoop::current();
60
61 DCHECK(ui_loop_ != g_browser_process->io_thread()->message_loop());
62 DCHECK(ui_loop_ != g_browser_process->file_thread()->message_loop());
63
64 // In order to process this request, we need to use the plugin information.
65 // However, plugin process information is only available from the IO thread.
66 g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE,
[email protected]a27a9382009-02-11 23:55:1067 NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnIOThread));
initial.commit09911bf2008-07-26 23:55:2968}
69
[email protected]a27a9382009-02-11 23:55:1070void MemoryDetails::CollectChildInfoOnIOThread() {
initial.commit09911bf2008-07-26 23:55:2971 DCHECK(MessageLoop::current() ==
72 ChromeThread::GetMessageLoop(ChromeThread::IO));
73
[email protected]a27a9382009-02-11 23:55:1074 std::vector<ProcessMemoryInformation> child_info;
75
initial.commit09911bf2008-07-26 23:55:2976 // Collect the list of plugins.
77 for (PluginProcessHostIterator plugin_iter;
78 !plugin_iter.Done(); ++plugin_iter) {
[email protected]a27a9382009-02-11 23:55:1079 ChildProcessInfo* child = const_cast<PluginProcessHost*>(*plugin_iter);
80 DCHECK(child);
81 if (!child || !child->process().handle())
initial.commit09911bf2008-07-26 23:55:2982 continue;
83
[email protected]a27a9382009-02-11 23:55:1084 ProcessMemoryInformation info;
85 info.pid = child->process().pid();
86 if (!info.pid)
87 continue;
88
89 info.type = child->type();
90 info.titles.push_back(child->name());
91 child_info.push_back(info);
initial.commit09911bf2008-07-26 23:55:2992 }
93
94 // Now go do expensive memory lookups from the file thread.
95 ChromeThread::GetMessageLoop(ChromeThread::FILE)->PostTask(FROM_HERE,
[email protected]a27a9382009-02-11 23:55:1096 NewRunnableMethod(this, &MemoryDetails::CollectProcessData, child_info));
initial.commit09911bf2008-07-26 23:55:2997}
98
[email protected]a27a9382009-02-11 23:55:1099void MemoryDetails::CollectProcessData(
100 std::vector<ProcessMemoryInformation> child_info) {
initial.commit09911bf2008-07-26 23:55:29101 DCHECK(MessageLoop::current() ==
102 ChromeThread::GetMessageLoop(ChromeThread::FILE));
103
104 int array_size = 32;
[email protected]a27a9382009-02-11 23:55:10105 scoped_ptr_malloc<DWORD> process_list;
initial.commit09911bf2008-07-26 23:55:29106 DWORD bytes_used = 0;
107 do {
108 array_size *= 2;
[email protected]a27a9382009-02-11 23:55:10109 process_list.reset(static_cast<DWORD*>(
110 realloc(process_list.release(), sizeof(DWORD) * array_size)));
initial.commit09911bf2008-07-26 23:55:29111 // EnumProcesses doesn't return an error if the array is too small.
112 // We have to check if the return buffer is full, and if so, call it
113 // again. See msdn docs for more info.
[email protected]a27a9382009-02-11 23:55:10114 if (!EnumProcesses(process_list.get(), array_size * sizeof(DWORD),
initial.commit09911bf2008-07-26 23:55:29115 &bytes_used)) {
116 LOG(ERROR) << "EnumProcesses failed: " << GetLastError();
117 return;
118 }
[email protected]a27a9382009-02-11 23:55:10119 } while (bytes_used == (array_size * sizeof(DWORD)));
initial.commit09911bf2008-07-26 23:55:29120
[email protected]a27a9382009-02-11 23:55:10121 int num_processes = bytes_used / sizeof(DWORD);
initial.commit09911bf2008-07-26 23:55:29122
123 // Clear old data.
124 for (int index = 0; index < arraysize(g_process_template); index++)
125 process_data_[index].processes.clear();
126
127 for (int index = 0; index < num_processes; index++) {
[email protected]a27a9382009-02-11 23:55:10128 int pid = process_list.get()[index];
129 ScopedHandle handle(OpenProcess(
130 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
131 if (!handle.Get())
132 continue;
133 TCHAR name[MAX_PATH];
134 if (!GetModuleBaseName(handle, NULL, name, MAX_PATH - 1))
135 continue;
136 for (int index2 = 0; index2 < arraysize(g_process_template); index2++) {
137 if (_wcsicmp(process_data_[index2].process_name, name) != 0)
138 continue;
139 // Get Memory Information.
140 ProcessMemoryInformation info;
141 info.pid = pid;
142 if (info.pid == GetCurrentProcessId())
143 info.type = ChildProcessInfo::BROWSER_PROCESS;
144 else
145 info.type = ChildProcessInfo::UNKNOWN_PROCESS;
initial.commit09911bf2008-07-26 23:55:29146
[email protected]a27a9382009-02-11 23:55:10147 scoped_ptr<base::ProcessMetrics> metrics;
148 metrics.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
149 metrics->GetCommittedKBytes(&info.committed);
150 metrics->GetWorkingSetKBytes(&info.working_set);
initial.commit09911bf2008-07-26 23:55:29151
[email protected]a27a9382009-02-11 23:55:10152 // Get Version Information.
153 if (index2 == 0) { // Chrome
154 scoped_ptr<FileVersionInfo> version_info(
155 FileVersionInfo::CreateFileVersionInfoForCurrentModule());
156 if (version_info != NULL)
157 info.version = version_info->file_version();
158 // Check if this is one of the child processes whose data we collected
159 // on the IO thread, and if so copy over that data.
160 for (size_t child = 0; child < child_info.size(); child++) {
161 if (child_info[child].pid != info.pid)
162 continue;
163 info.titles = child_info[child].titles;
164 info.type = child_info[child].type;
165 break;
166 }
167 } else if (GetModuleFileNameEx(handle, NULL, name, MAX_PATH - 1)) {
168 std::wstring str_name(name);
169 scoped_ptr<FileVersionInfo> version_info(
170 FileVersionInfo::CreateFileVersionInfo(str_name));
171 if (version_info != NULL) {
172 info.version = version_info->product_version();
173 info.product_name = version_info->product_name();
initial.commit09911bf2008-07-26 23:55:29174 }
175 }
[email protected]a27a9382009-02-11 23:55:10176
177 // Add the process info to our list.
178 process_data_[index2].processes.push_back(info);
179 break;
initial.commit09911bf2008-07-26 23:55:29180 }
181 }
initial.commit09911bf2008-07-26 23:55:29182
183 // Finally return to the browser thread.
184 ui_loop_->PostTask(FROM_HERE,
[email protected]a27a9382009-02-11 23:55:10185 NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnUIThread));
initial.commit09911bf2008-07-26 23:55:29186}
187
[email protected]a27a9382009-02-11 23:55:10188void MemoryDetails::CollectChildInfoOnUIThread() {
initial.commit09911bf2008-07-26 23:55:29189 DCHECK(MessageLoop::current() == ui_loop_);
190
[email protected]a27a9382009-02-11 23:55:10191 // Get more information about the process.
initial.commit09911bf2008-07-26 23:55:29192 for (size_t index = 0; index < process_data_[CHROME_BROWSER].processes.size();
193 index++) {
[email protected]a27a9382009-02-11 23:55:10194 // Check if it's a renderer, if so get the list of page titles in it and
195 // check if it's a diagnostics-related process. We skip all diagnostics
196 // pages (e.g. "about:xxx" URLs). Iterate the RenderProcessHosts to find
197 // the tab contents. If it is of type TAB_CONTENTS_ABOUT_UI, mark the
198 // process as diagnostics related.
initial.commit09911bf2008-07-26 23:55:29199 RenderProcessHost::iterator renderer_iter;
200 for (renderer_iter = RenderProcessHost::begin(); renderer_iter !=
201 RenderProcessHost::end(); ++renderer_iter) {
202 DCHECK(renderer_iter->second);
[email protected]a27a9382009-02-11 23:55:10203 ProcessMemoryInformation& process =
204 process_data_[CHROME_BROWSER].processes[index];
205 if (process.pid != renderer_iter->second->process().pid())
206 continue;
207 process.type = ChildProcessInfo::RENDER_PROCESS;
208 // The RenderProcessHost may host multiple TabContents. Any
209 // of them which contain diagnostics information make the whole
210 // process be considered a diagnostics process.
211 //
212 // NOTE: This is a bit dangerous. We know that for now, listeners
213 // are always RenderWidgetHosts. But in theory, they don't
214 // have to be.
215 RenderProcessHost::listeners_iterator iter;
216 for (iter = renderer_iter->second->listeners_begin();
217 iter != renderer_iter->second->listeners_end(); ++iter) {
218 RenderWidgetHost* widget =
219 static_cast<RenderWidgetHost*>(iter->second);
220 DCHECK(widget);
221 if (!widget || !widget->IsRenderView())
222 continue;
initial.commit09911bf2008-07-26 23:55:29223
[email protected]a27a9382009-02-11 23:55:10224 RenderViewHost* host = static_cast<RenderViewHost*>(widget);
225 TabContents* contents =
226 static_cast<WebContents*>(host->delegate());
227 DCHECK(contents);
228 if (!contents)
229 continue;
230 std::wstring title = contents->GetTitle();
231 if (!title.length())
232 title = L"Untitled";
233 process.titles.push_back(title);
234 if (contents->type() == TAB_CONTENTS_ABOUT_UI)
235 process.is_diagnostics = true;
initial.commit09911bf2008-07-26 23:55:29236 }
237 }
238 }
239
[email protected]a27a9382009-02-11 23:55:10240 // Get rid of other Chrome processes that are from a different profile.
241 for (size_t index = 0; index < process_data_[CHROME_BROWSER].processes.size();
242 index++) {
243 if (process_data_[CHROME_BROWSER].processes[index].type ==
244 ChildProcessInfo::UNKNOWN_PROCESS) {
245 process_data_[CHROME_BROWSER].processes.erase(
246 process_data_[CHROME_BROWSER].processes.begin() + index);
247 index --;
248 }
249 }
250
initial.commit09911bf2008-07-26 23:55:29251 UpdateHistograms();
252
253 OnDetailsAvailable();
254}
255
256void MemoryDetails::UpdateHistograms() {
257 // Reports a set of memory metrics to UMA.
258 // Memory is measured in units of 10KB.
259
initial.commit09911bf2008-07-26 23:55:29260 ProcessData browser = process_data_[CHROME_BROWSER];
261 size_t aggregate_memory = 0;
[email protected]a27a9382009-02-11 23:55:10262 int plugin_count = 0;
263 int worker_count = 0;
initial.commit09911bf2008-07-26 23:55:29264 for (size_t index = 0; index < browser.processes.size(); index++) {
[email protected]921cd0cc2008-10-21 22:30:55265 int sample = static_cast<int>(browser.processes[index].working_set.priv);
266 aggregate_memory += sample;
[email protected]a27a9382009-02-11 23:55:10267 switch (browser.processes[index].type) {
268 case ChildProcessInfo::BROWSER_PROCESS:
269 UMA_HISTOGRAM_MEMORY_KB(L"Memory.Browser", sample);
270 break;
271 case ChildProcessInfo::RENDER_PROCESS:
272 UMA_HISTOGRAM_MEMORY_KB(L"Memory.Renderer", sample);
273 break;
274 case ChildProcessInfo::PLUGIN_PROCESS:
275 UMA_HISTOGRAM_MEMORY_KB(L"Memory.Plugin", sample);
276 plugin_count++;
277 break;
278 case ChildProcessInfo::WORKER_PROCESS:
279 UMA_HISTOGRAM_MEMORY_KB(L"Memory.Worker", sample);
280 worker_count++;
281 break;
initial.commit09911bf2008-07-26 23:55:29282 }
283 }
[email protected]a27a9382009-02-11 23:55:10284
initial.commit09911bf2008-07-26 23:55:29285 UMA_HISTOGRAM_COUNTS_100(L"Memory.ProcessCount",
286 static_cast<int>(browser.processes.size()));
[email protected]a27a9382009-02-11 23:55:10287 UMA_HISTOGRAM_COUNTS_100(L"Memory.PluginProcessCount", plugin_count);
288 UMA_HISTOGRAM_COUNTS_100(L"Memory.WorkerProcessCount", worker_count);
[email protected]921cd0cc2008-10-21 22:30:55289
290 int total_sample = static_cast<int>(aggregate_memory / 1000);
[email protected]a27a9382009-02-11 23:55:10291 UMA_HISTOGRAM_MEMORY_MB(L"Memory.Total", total_sample);
initial.commit09911bf2008-07-26 23:55:29292}