1 // Copyright (c) 2012 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.
5 #include "chrome/browser/memory_details.h"
8 #include "base/file_version_info.h"
9 #include "base/metrics/histogram.h"
10 #include "base/process_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/extension_process_manager.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/chrome_process_type.h"
18 #include "chrome/common/extensions/extension.h"
19 #include "chrome/common/url_constants.h"
20 #include "content/public/browser/browser_child_process_host_iterator.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/child_process_data.h"
23 #include "content/public/browser/navigation_controller.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "content/public/browser/render_view_host.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/common/bindings_policy.h"
29 #include "extensions/browser/view_type_utils.h"
30 #include "grit/chromium_strings.h"
31 #include "grit/generated_resources.h"
32 #include "ui/base/l10n/l10n_util.h"
34 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
35 #include "content/public/browser/zygote_host_linux.h"
38 using base::StringPrintf
;
39 using content::BrowserChildProcessHostIterator
;
40 using content::BrowserThread
;
41 using content::NavigationEntry
;
42 using content::RenderViewHost
;
43 using content::RenderWidgetHost
;
44 using content::WebContents
;
45 using extensions::Extension
;
48 std::string
ProcessMemoryInformation::GetRendererTypeNameInEnglish(
49 RendererProcessType type
) {
54 return "Tab (Chrome)";
55 case RENDERER_EXTENSION
:
57 case RENDERER_DEVTOOLS
:
59 case RENDERER_INTERSTITIAL
:
60 return "Interstitial";
61 case RENDERER_NOTIFICATION
:
62 return "Notification";
63 case RENDERER_BACKGROUND_APP
:
64 return "Background App";
65 case RENDERER_UNKNOWN
:
67 NOTREACHED() << "Unknown renderer process type!";
73 std::string
ProcessMemoryInformation::GetFullTypeNameInEnglish(
75 RendererProcessType rtype
) {
76 if (process_type
== content::PROCESS_TYPE_RENDERER
)
77 return GetRendererTypeNameInEnglish(rtype
);
78 return content::GetProcessTypeNameInEnglish(process_type
);
81 ProcessMemoryInformation::ProcessMemoryInformation()
84 is_diagnostics(false),
85 process_type(content::PROCESS_TYPE_UNKNOWN
),
86 renderer_type(RENDERER_UNKNOWN
) {
89 ProcessMemoryInformation::~ProcessMemoryInformation() {}
91 bool ProcessMemoryInformation::operator<(
92 const ProcessMemoryInformation
& rhs
) const {
93 return working_set
.priv
< rhs
.working_set
.priv
;
96 ProcessData::ProcessData() {}
98 ProcessData::ProcessData(const ProcessData
& rhs
)
100 process_name(rhs
.process_name
),
101 processes(rhs
.processes
) {
104 ProcessData::~ProcessData() {}
106 ProcessData
& ProcessData::operator=(const ProcessData
& rhs
) {
108 process_name
= rhs
.process_name
;
109 processes
= rhs
.processes
;
115 // This operation will hit no fewer than 3 threads.
117 // The BrowserChildProcessHostIterator can only be accessed from the IO thread.
119 // The RenderProcessHostIterator can only be accessed from the UI thread.
121 // This operation can take 30-100ms to complete. We never want to have
122 // one task run for that long on the UI or IO threads. So, we run the
123 // expensive parts of this operation over on the file thread.
125 void MemoryDetails::StartFetch(UserMetricsMode user_metrics_mode
) {
126 // This might get called from the UI or FILE threads, but should not be
127 // getting called from the IO thread.
128 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO
));
129 user_metrics_mode_
= user_metrics_mode
;
131 // In order to process this request, we need to use the plugin information.
132 // However, plugin process information is only available from the IO thread.
133 BrowserThread::PostTask(
134 BrowserThread::IO
, FROM_HERE
,
135 base::Bind(&MemoryDetails::CollectChildInfoOnIOThread
, this));
138 MemoryDetails::~MemoryDetails() {}
140 std::string
MemoryDetails::ToLogString() {
143 ProcessMemoryInformationList processes
= ChromeBrowser()->processes
;
144 // Sort by memory consumption, low to high.
145 std::sort(processes
.begin(), processes
.end());
146 // Print from high to low.
147 for (ProcessMemoryInformationList::reverse_iterator iter1
=
149 iter1
!= processes
.rend();
151 log
+= ProcessMemoryInformation::GetFullTypeNameInEnglish(
152 iter1
->process_type
, iter1
->renderer_type
);
153 if (!iter1
->titles
.empty()) {
155 for (std::vector
<string16
>::const_iterator iter2
=
156 iter1
->titles
.begin();
157 iter2
!= iter1
->titles
.end(); ++iter2
) {
158 if (iter2
!= iter1
->titles
.begin())
160 log
+= UTF16ToUTF8(*iter2
);
164 log
+= StringPrintf(" %d MB private, %d MB shared\n",
165 static_cast<int>(iter1
->working_set
.priv
) / 1024,
166 static_cast<int>(iter1
->working_set
.shared
) / 1024);
171 void MemoryDetails::CollectChildInfoOnIOThread() {
172 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
174 std::vector
<ProcessMemoryInformation
> child_info
;
176 // Collect the list of child processes. A 0 |handle| means that
177 // the process is being launched, so we skip it.
178 for (BrowserChildProcessHostIterator iter
; !iter
.Done(); ++iter
) {
179 ProcessMemoryInformation info
;
180 if (!iter
.GetData().handle
)
182 info
.pid
= base::GetProcId(iter
.GetData().handle
);
186 info
.process_type
= iter
.GetData().process_type
;
187 info
.renderer_type
= ProcessMemoryInformation::RENDERER_UNKNOWN
;
188 info
.titles
.push_back(iter
.GetData().name
);
189 child_info
.push_back(info
);
192 // Now go do expensive memory lookups from the file thread.
193 BrowserThread::PostTask(
194 BrowserThread::FILE, FROM_HERE
,
195 base::Bind(&MemoryDetails::CollectProcessData
, this, child_info
));
198 void MemoryDetails::CollectChildInfoOnUIThread() {
199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
201 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
202 const pid_t zygote_pid
= content::ZygoteHost::GetInstance()->GetPid();
203 const pid_t sandbox_helper_pid
=
204 content::ZygoteHost::GetInstance()->GetSandboxHelperPid();
207 ProcessData
* const chrome_browser
= ChromeBrowser();
208 // Get more information about the process.
209 for (size_t index
= 0; index
< chrome_browser
->processes
.size();
211 // Check if it's a renderer, if so get the list of page titles in it and
212 // check if it's a diagnostics-related process. We skip about:memory pages.
213 // Iterate the RenderProcessHosts to find the tab contents.
214 ProcessMemoryInformation
& process
=
215 chrome_browser
->processes
[index
];
217 for (content::RenderProcessHost::iterator
renderer_iter(
218 content::RenderProcessHost::AllHostsIterator());
219 !renderer_iter
.IsAtEnd(); renderer_iter
.Advance()) {
220 content::RenderProcessHost
* render_process_host
=
221 renderer_iter
.GetCurrentValue();
222 DCHECK(render_process_host
);
223 // Ignore processes that don't have a connection, such as crashed tabs.
224 if (!render_process_host
->HasConnection() ||
225 process
.pid
!= base::GetProcId(render_process_host
->GetHandle())) {
228 process
.process_type
= content::PROCESS_TYPE_RENDERER
;
230 Profile::FromBrowserContext(
231 render_process_host
->GetBrowserContext());
232 ExtensionService
* extension_service
= profile
->GetExtensionService();
233 extensions::ProcessMap
* extension_process_map
= NULL
;
234 // No extensions on Android. So extension_service can be NULL.
235 if (extension_service
)
236 extension_process_map
= extension_service
->process_map();
238 // The RenderProcessHost may host multiple WebContentses. Any
239 // of them which contain diagnostics information make the whole
240 // process be considered a diagnostics process.
241 content::RenderProcessHost::RenderWidgetHostsIterator
iter(
242 render_process_host
->GetRenderWidgetHostsIterator());
243 for (; !iter
.IsAtEnd(); iter
.Advance()) {
244 const RenderWidgetHost
* widget
= iter
.GetCurrentValue();
246 if (!widget
|| !widget
->IsRenderView())
249 RenderViewHost
* host
=
250 RenderViewHost::From(const_cast<RenderWidgetHost
*>(widget
));
251 WebContents
* contents
= WebContents::FromRenderViewHost(host
);
254 url
= contents
->GetURL();
255 SiteData
* site_data
=
256 &chrome_browser
->site_data
[contents
->GetBrowserContext()];
257 SiteDetails::CollectSiteInfo(contents
, site_data
);
259 extensions::ViewType type
= extensions::GetViewType(contents
);
260 if (host
->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI
) {
261 process
.renderer_type
= ProcessMemoryInformation::RENDERER_CHROME
;
262 } else if (extension_process_map
&&
263 extension_process_map
->Contains(host
->GetProcess()->GetID())) {
264 // For our purposes, don't count processes containing only hosted apps
265 // as extension processes. See also: crbug.com/102533.
266 std::set
<std::string
> extension_ids
=
267 extension_process_map
->GetExtensionsInProcess(
268 host
->GetProcess()->GetID());
269 for (std::set
<std::string
>::iterator iter
= extension_ids
.begin();
270 iter
!= extension_ids
.end(); ++iter
) {
271 const Extension
* extension
=
272 extension_service
->GetExtensionById(*iter
, false);
273 if (extension
&& !extension
->is_hosted_app()) {
274 process
.renderer_type
=
275 ProcessMemoryInformation::RENDERER_EXTENSION
;
280 if (extension_process_map
&&
281 extension_process_map
->Contains(host
->GetProcess()->GetID())) {
282 const Extension
* extension
=
283 extension_service
->extensions()->GetByID(url
.host());
285 string16 title
= UTF8ToUTF16(extension
->name());
286 process
.titles
.push_back(title
);
287 process
.renderer_type
=
288 ProcessMemoryInformation::RENDERER_EXTENSION
;
294 process
.renderer_type
=
295 ProcessMemoryInformation::RENDERER_INTERSTITIAL
;
299 if (type
== extensions::VIEW_TYPE_BACKGROUND_CONTENTS
) {
300 process
.titles
.push_back(UTF8ToUTF16(url
.spec()));
301 process
.renderer_type
=
302 ProcessMemoryInformation::RENDERER_BACKGROUND_APP
;
306 if (type
== extensions::VIEW_TYPE_NOTIFICATION
) {
307 process
.titles
.push_back(UTF8ToUTF16(url
.spec()));
308 process
.renderer_type
=
309 ProcessMemoryInformation::RENDERER_NOTIFICATION
;
313 // Since we have a WebContents and and the renderer type hasn't been
314 // set yet, it must be a normal tabbed renderer.
315 if (process
.renderer_type
== ProcessMemoryInformation::RENDERER_UNKNOWN
)
316 process
.renderer_type
= ProcessMemoryInformation::RENDERER_NORMAL
;
318 string16 title
= contents
->GetTitle();
320 title
= l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE
);
321 process
.titles
.push_back(title
);
323 // We need to check the pending entry as well as the virtual_url to
324 // see if it's a chrome://memory URL (we don't want to count these in
325 // the total memory usage of the browser).
327 // When we reach here, chrome://memory will be the pending entry since
328 // we haven't responded with any data such that it would be committed.
329 // If you have another chrome://memory tab open (which would be
330 // committed), we don't want to count it either, so we also check the
331 // last committed entry.
333 // Either the pending or last committed entries can be NULL.
334 const NavigationEntry
* pending_entry
=
335 contents
->GetController().GetPendingEntry();
336 const NavigationEntry
* last_committed_entry
=
337 contents
->GetController().GetLastCommittedEntry();
338 if ((last_committed_entry
&&
339 LowerCaseEqualsASCII(last_committed_entry
->GetVirtualURL().spec(),
340 chrome::kChromeUIMemoryURL
)) ||
342 LowerCaseEqualsASCII(pending_entry
->GetVirtualURL().spec(),
343 chrome::kChromeUIMemoryURL
)))
344 process
.is_diagnostics
= true;
348 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
349 if (process
.pid
== zygote_pid
) {
350 process
.process_type
= content::PROCESS_TYPE_ZYGOTE
;
351 } else if (process
.pid
== sandbox_helper_pid
) {
352 process
.process_type
= content::PROCESS_TYPE_SANDBOX_HELPER
;
357 // Get rid of other Chrome processes that are from a different profile.
358 for (size_t index
= 0; index
< chrome_browser
->processes
.size();
360 if (chrome_browser
->processes
[index
].process_type
==
361 content::PROCESS_TYPE_UNKNOWN
) {
362 chrome_browser
->processes
.erase(
363 chrome_browser
->processes
.begin() + index
);
368 if (user_metrics_mode_
== UPDATE_USER_METRICS
)
371 OnDetailsAvailable();
374 void MemoryDetails::UpdateHistograms() {
375 // Reports a set of memory metrics to UMA.
376 // Memory is measured in KB.
378 const ProcessData
& browser
= *ChromeBrowser();
379 size_t aggregate_memory
= 0;
380 int chrome_count
= 0;
381 int extension_count
= 0;
382 int plugin_count
= 0;
383 int pepper_plugin_count
= 0;
384 int pepper_plugin_broker_count
= 0;
385 int renderer_count
= 0;
387 int worker_count
= 0;
388 int process_limit
= content::RenderProcessHost::GetMaxRendererProcessCount();
389 for (size_t index
= 0; index
< browser
.processes
.size(); index
++) {
390 int sample
= static_cast<int>(browser
.processes
[index
].working_set
.priv
);
391 aggregate_memory
+= sample
;
392 switch (browser
.processes
[index
].process_type
) {
393 case content::PROCESS_TYPE_BROWSER
:
394 UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample
);
396 case content::PROCESS_TYPE_RENDERER
: {
397 ProcessMemoryInformation::RendererProcessType renderer_type
=
398 browser
.processes
[index
].renderer_type
;
399 switch (renderer_type
) {
400 case ProcessMemoryInformation::RENDERER_EXTENSION
:
401 UMA_HISTOGRAM_MEMORY_KB("Memory.Extension", sample
);
404 case ProcessMemoryInformation::RENDERER_CHROME
:
405 UMA_HISTOGRAM_MEMORY_KB("Memory.Chrome", sample
);
408 case ProcessMemoryInformation::RENDERER_UNKNOWN
:
409 NOTREACHED() << "Unknown renderer process type.";
411 case ProcessMemoryInformation::RENDERER_NORMAL
:
413 // TODO(erikkay): Should we bother splitting out the other subtypes?
414 UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample
);
420 case content::PROCESS_TYPE_PLUGIN
:
421 UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample
);
424 case content::PROCESS_TYPE_WORKER
:
425 UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample
);
428 case content::PROCESS_TYPE_UTILITY
:
429 UMA_HISTOGRAM_MEMORY_KB("Memory.Utility", sample
);
432 case content::PROCESS_TYPE_ZYGOTE
:
433 UMA_HISTOGRAM_MEMORY_KB("Memory.Zygote", sample
);
436 case content::PROCESS_TYPE_SANDBOX_HELPER
:
437 UMA_HISTOGRAM_MEMORY_KB("Memory.SandboxHelper", sample
);
440 case content::PROCESS_TYPE_GPU
:
441 UMA_HISTOGRAM_MEMORY_KB("Memory.Gpu", sample
);
444 case content::PROCESS_TYPE_PPAPI_PLUGIN
:
445 UMA_HISTOGRAM_MEMORY_KB("Memory.PepperPlugin", sample
);
446 pepper_plugin_count
++;
448 case content::PROCESS_TYPE_PPAPI_BROKER
:
449 UMA_HISTOGRAM_MEMORY_KB("Memory.PepperPluginBroker", sample
);
450 pepper_plugin_broker_count
++;
452 case PROCESS_TYPE_NACL_LOADER
:
453 UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClient", sample
);
456 case PROCESS_TYPE_NACL_BROKER
:
457 UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClientBroker", sample
);
465 UMA_HISTOGRAM_MEMORY_KB("Memory.BackingStore",
466 RenderWidgetHost::BackingStoreMemorySize() / 1024);
467 #if defined(OS_CHROMEOS)
468 // Chrome OS exposes system-wide graphics driver memory which has historically
469 // been a source of leak/bloat.
470 base::SystemMemoryInfoKB meminfo
;
471 if (base::GetSystemMemoryInfo(&meminfo
) && meminfo
.gem_size
!= -1)
472 UMA_HISTOGRAM_MEMORY_MB("Memory.Graphics", meminfo
.gem_size
/ 1024 / 1024);
475 UMA_HISTOGRAM_COUNTS_100("Memory.ProcessLimit", process_limit
);
476 UMA_HISTOGRAM_COUNTS_100("Memory.ProcessCount",
477 static_cast<int>(browser
.processes
.size()));
478 UMA_HISTOGRAM_COUNTS_100("Memory.ChromeProcessCount", chrome_count
);
479 UMA_HISTOGRAM_COUNTS_100("Memory.ExtensionProcessCount", extension_count
);
480 UMA_HISTOGRAM_COUNTS_100("Memory.OtherProcessCount", other_count
);
481 UMA_HISTOGRAM_COUNTS_100("Memory.PluginProcessCount", plugin_count
);
482 UMA_HISTOGRAM_COUNTS_100("Memory.PepperPluginProcessCount",
483 pepper_plugin_count
);
484 UMA_HISTOGRAM_COUNTS_100("Memory.PepperPluginBrokerProcessCount",
485 pepper_plugin_broker_count
);
486 UMA_HISTOGRAM_COUNTS_100("Memory.RendererProcessCount", renderer_count
);
487 UMA_HISTOGRAM_COUNTS_100("Memory.WorkerProcessCount", worker_count
);
488 // TODO(viettrungluu): Do we want separate counts for the other
489 // (platform-specific) process types?
491 int total_sample
= static_cast<int>(aggregate_memory
/ 1000);
492 UMA_HISTOGRAM_MEMORY_MB("Memory.Total", total_sample
);
494 // Predict the number of processes needed when isolating all sites and when
495 // isolating only HTTPS sites.
496 int all_renderer_count
= renderer_count
+ chrome_count
+ extension_count
;
497 int non_renderer_count
= browser
.processes
.size() - all_renderer_count
;
498 SiteDetails::UpdateHistograms(browser
.site_data
, all_renderer_count
,