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/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/url_constants.h"
16 #include "components/nacl/common/nacl_process_type.h"
17 #include "content/public/browser/browser_child_process_host_iterator.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/child_process_data.h"
20 #include "content/public/browser/navigation_controller.h"
21 #include "content/public/browser/navigation_entry.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/render_widget_host_iterator.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/bindings_policy.h"
27 #include "extensions/browser/process_manager.h"
28 #include "extensions/browser/process_map.h"
29 #include "extensions/browser/view_type_utils.h"
30 #include "extensions/common/extension.h"
31 #include "grit/chromium_strings.h"
32 #include "grit/generated_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
35 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
36 #include "content/public/browser/zygote_host_linux.h"
39 using base::StringPrintf
;
40 using content::BrowserChildProcessHostIterator
;
41 using content::BrowserThread
;
42 using content::NavigationEntry
;
43 using content::RenderViewHost
;
44 using content::RenderWidgetHost
;
45 using content::WebContents
;
46 using extensions::Extension
;
49 std::string
ProcessMemoryInformation::GetRendererTypeNameInEnglish(
50 RendererProcessType type
) {
55 return "Tab (Chrome)";
56 case RENDERER_EXTENSION
:
58 case RENDERER_DEVTOOLS
:
60 case RENDERER_INTERSTITIAL
:
61 return "Interstitial";
62 case RENDERER_BACKGROUND_APP
:
63 return "Background App";
64 case RENDERER_UNKNOWN
:
66 NOTREACHED() << "Unknown renderer process type!";
72 std::string
ProcessMemoryInformation::GetFullTypeNameInEnglish(
74 RendererProcessType rtype
) {
75 if (process_type
== content::PROCESS_TYPE_RENDERER
)
76 return GetRendererTypeNameInEnglish(rtype
);
77 return content::GetProcessTypeNameInEnglish(process_type
);
80 ProcessMemoryInformation::ProcessMemoryInformation()
83 is_diagnostics(false),
84 process_type(content::PROCESS_TYPE_UNKNOWN
),
85 renderer_type(RENDERER_UNKNOWN
) {
88 ProcessMemoryInformation::~ProcessMemoryInformation() {}
90 bool ProcessMemoryInformation::operator<(
91 const ProcessMemoryInformation
& rhs
) const {
92 return working_set
.priv
< rhs
.working_set
.priv
;
95 ProcessData::ProcessData() {}
97 ProcessData::ProcessData(const ProcessData
& rhs
)
99 process_name(rhs
.process_name
),
100 processes(rhs
.processes
) {
103 ProcessData::~ProcessData() {}
105 ProcessData
& ProcessData::operator=(const ProcessData
& rhs
) {
107 process_name
= rhs
.process_name
;
108 processes
= rhs
.processes
;
114 // This operation will hit no fewer than 3 threads.
116 // The BrowserChildProcessHostIterator can only be accessed from the IO thread.
118 // The RenderProcessHostIterator can only be accessed from the UI thread.
120 // This operation can take 30-100ms to complete. We never want to have
121 // one task run for that long on the UI or IO threads. So, we run the
122 // expensive parts of this operation over on the file thread.
124 void MemoryDetails::StartFetch(UserMetricsMode user_metrics_mode
) {
125 // This might get called from the UI or FILE threads, but should not be
126 // getting called from the IO thread.
127 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO
));
128 user_metrics_mode_
= user_metrics_mode
;
130 // In order to process this request, we need to use the plugin information.
131 // However, plugin process information is only available from the IO thread.
132 BrowserThread::PostTask(
133 BrowserThread::IO
, FROM_HERE
,
134 base::Bind(&MemoryDetails::CollectChildInfoOnIOThread
, this));
137 MemoryDetails::~MemoryDetails() {}
139 std::string
MemoryDetails::ToLogString() {
142 ProcessMemoryInformationList processes
= ChromeBrowser()->processes
;
143 // Sort by memory consumption, low to high.
144 std::sort(processes
.begin(), processes
.end());
145 // Print from high to low.
146 for (ProcessMemoryInformationList::reverse_iterator iter1
=
148 iter1
!= processes
.rend();
150 log
+= ProcessMemoryInformation::GetFullTypeNameInEnglish(
151 iter1
->process_type
, iter1
->renderer_type
);
152 if (!iter1
->titles
.empty()) {
154 for (std::vector
<base::string16
>::const_iterator iter2
=
155 iter1
->titles
.begin();
156 iter2
!= iter1
->titles
.end(); ++iter2
) {
157 if (iter2
!= iter1
->titles
.begin())
159 log
+= base::UTF16ToUTF8(*iter2
);
163 log
+= StringPrintf(" %d MB private, %d MB shared",
164 static_cast<int>(iter1
->working_set
.priv
) / 1024,
165 static_cast<int>(iter1
->working_set
.shared
) / 1024);
166 #if defined(OS_CHROMEOS)
167 log
+= StringPrintf(", %d MB swapped",
168 static_cast<int>(iter1
->working_set
.swapped
) / 1024);
175 void MemoryDetails::CollectChildInfoOnIOThread() {
176 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
178 std::vector
<ProcessMemoryInformation
> child_info
;
180 // Collect the list of child processes. A 0 |handle| means that
181 // the process is being launched, so we skip it.
182 for (BrowserChildProcessHostIterator iter
; !iter
.Done(); ++iter
) {
183 ProcessMemoryInformation info
;
184 if (!iter
.GetData().handle
)
186 info
.pid
= base::GetProcId(iter
.GetData().handle
);
190 info
.process_type
= iter
.GetData().process_type
;
191 info
.renderer_type
= ProcessMemoryInformation::RENDERER_UNKNOWN
;
192 info
.titles
.push_back(iter
.GetData().name
);
193 child_info
.push_back(info
);
196 // Now go do expensive memory lookups from the file thread.
197 BrowserThread::PostTask(
198 BrowserThread::FILE, FROM_HERE
,
199 base::Bind(&MemoryDetails::CollectProcessData
, this, child_info
));
202 void MemoryDetails::CollectChildInfoOnUIThread() {
203 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
205 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
206 const pid_t zygote_pid
= content::ZygoteHost::GetInstance()->GetPid();
207 const pid_t sandbox_helper_pid
=
208 content::ZygoteHost::GetInstance()->GetSandboxHelperPid();
211 ProcessData
* const chrome_browser
= ChromeBrowser();
212 // Get more information about the process.
213 for (size_t index
= 0; index
< chrome_browser
->processes
.size();
215 // Check if it's a renderer, if so get the list of page titles in it and
216 // check if it's a diagnostics-related process. We skip about:memory pages.
217 // Iterate the RenderProcessHosts to find the tab contents.
218 ProcessMemoryInformation
& process
=
219 chrome_browser
->processes
[index
];
221 scoped_ptr
<content::RenderWidgetHostIterator
> widgets(
222 RenderWidgetHost::GetRenderWidgetHosts());
223 while (content::RenderWidgetHost
* widget
= widgets
->GetNextHost()) {
224 content::RenderProcessHost
* render_process_host
=
225 widget
->GetProcess();
226 DCHECK(render_process_host
);
227 // Ignore processes that don't have a connection, such as crashed tabs.
228 if (!render_process_host
->HasConnection() ||
229 process
.pid
!= base::GetProcId(render_process_host
->GetHandle())) {
232 process
.process_type
= content::PROCESS_TYPE_RENDERER
;
234 Profile::FromBrowserContext(
235 render_process_host
->GetBrowserContext());
236 ExtensionService
* extension_service
= profile
->GetExtensionService();
237 extensions::ProcessMap
* extension_process_map
= NULL
;
238 // No extensions on Android. So extension_service can be NULL.
239 if (extension_service
)
240 extension_process_map
= extensions::ProcessMap::Get(profile
);
242 // The RenderProcessHost may host multiple WebContentses. Any
243 // of them which contain diagnostics information make the whole
244 // process be considered a diagnostics process.
245 if (!widget
->IsRenderView())
248 RenderViewHost
* host
= RenderViewHost::From(widget
);
249 WebContents
* contents
= WebContents::FromRenderViewHost(host
);
252 url
= contents
->GetURL();
253 SiteData
* site_data
=
254 &chrome_browser
->site_data
[contents
->GetBrowserContext()];
255 SiteDetails::CollectSiteInfo(contents
, site_data
);
257 extensions::ViewType type
= extensions::GetViewType(contents
);
258 if (host
->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI
) {
259 process
.renderer_type
= ProcessMemoryInformation::RENDERER_CHROME
;
260 } else if (extension_process_map
&&
261 extension_process_map
->Contains(host
->GetProcess()->GetID())) {
262 // For our purposes, don't count processes containing only hosted apps
263 // as extension processes. See also: crbug.com/102533.
264 std::set
<std::string
> extension_ids
=
265 extension_process_map
->GetExtensionsInProcess(
266 host
->GetProcess()->GetID());
267 for (std::set
<std::string
>::iterator iter
= extension_ids
.begin();
268 iter
!= extension_ids
.end(); ++iter
) {
269 const Extension
* extension
=
270 extension_service
->GetExtensionById(*iter
, false);
271 if (extension
&& !extension
->is_hosted_app()) {
272 process
.renderer_type
=
273 ProcessMemoryInformation::RENDERER_EXTENSION
;
278 if (extension_process_map
&&
279 extension_process_map
->Contains(host
->GetProcess()->GetID())) {
280 const Extension
* extension
=
281 extension_service
->extensions()->GetByID(url
.host());
283 base::string16 title
= base::UTF8ToUTF16(extension
->name());
284 process
.titles
.push_back(title
);
285 process
.renderer_type
=
286 ProcessMemoryInformation::RENDERER_EXTENSION
;
292 process
.renderer_type
=
293 ProcessMemoryInformation::RENDERER_INTERSTITIAL
;
297 if (type
== extensions::VIEW_TYPE_BACKGROUND_CONTENTS
) {
298 process
.titles
.push_back(base::UTF8ToUTF16(url
.spec()));
299 process
.renderer_type
=
300 ProcessMemoryInformation::RENDERER_BACKGROUND_APP
;
304 // Since we have a WebContents and and the renderer type hasn't been
305 // set yet, it must be a normal tabbed renderer.
306 if (process
.renderer_type
== ProcessMemoryInformation::RENDERER_UNKNOWN
)
307 process
.renderer_type
= ProcessMemoryInformation::RENDERER_NORMAL
;
309 base::string16 title
= contents
->GetTitle();
311 title
= l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE
);
312 process
.titles
.push_back(title
);
314 // We need to check the pending entry as well as the virtual_url to
315 // see if it's a chrome://memory URL (we don't want to count these in
316 // the total memory usage of the browser).
318 // When we reach here, chrome://memory will be the pending entry since
319 // we haven't responded with any data such that it would be committed.
320 // If you have another chrome://memory tab open (which would be
321 // committed), we don't want to count it either, so we also check the
322 // last committed entry.
324 // Either the pending or last committed entries can be NULL.
325 const NavigationEntry
* pending_entry
=
326 contents
->GetController().GetPendingEntry();
327 const NavigationEntry
* last_committed_entry
=
328 contents
->GetController().GetLastCommittedEntry();
329 if ((last_committed_entry
&&
330 LowerCaseEqualsASCII(last_committed_entry
->GetVirtualURL().spec(),
331 chrome::kChromeUIMemoryURL
)) ||
333 LowerCaseEqualsASCII(pending_entry
->GetVirtualURL().spec(),
334 chrome::kChromeUIMemoryURL
))) {
335 process
.is_diagnostics
= true;
339 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
340 if (process
.pid
== zygote_pid
) {
341 process
.process_type
= content::PROCESS_TYPE_ZYGOTE
;
342 } else if (process
.pid
== sandbox_helper_pid
) {
343 process
.process_type
= content::PROCESS_TYPE_SANDBOX_HELPER
;
348 // Get rid of other Chrome processes that are from a different profile.
349 for (size_t index
= 0; index
< chrome_browser
->processes
.size();
351 if (chrome_browser
->processes
[index
].process_type
==
352 content::PROCESS_TYPE_UNKNOWN
) {
353 chrome_browser
->processes
.erase(
354 chrome_browser
->processes
.begin() + index
);
359 if (user_metrics_mode_
== UPDATE_USER_METRICS
)
362 OnDetailsAvailable();
365 void MemoryDetails::UpdateHistograms() {
366 // Reports a set of memory metrics to UMA.
367 // Memory is measured in KB.
369 const ProcessData
& browser
= *ChromeBrowser();
370 size_t aggregate_memory
= 0;
371 int chrome_count
= 0;
372 int extension_count
= 0;
373 int plugin_count
= 0;
374 int pepper_plugin_count
= 0;
375 int pepper_plugin_broker_count
= 0;
376 int renderer_count
= 0;
378 int worker_count
= 0;
379 int process_limit
= content::RenderProcessHost::GetMaxRendererProcessCount();
380 for (size_t index
= 0; index
< browser
.processes
.size(); index
++) {
381 int sample
= static_cast<int>(browser
.processes
[index
].working_set
.priv
);
382 aggregate_memory
+= sample
;
383 switch (browser
.processes
[index
].process_type
) {
384 case content::PROCESS_TYPE_BROWSER
:
385 UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample
);
387 case content::PROCESS_TYPE_RENDERER
: {
388 ProcessMemoryInformation::RendererProcessType renderer_type
=
389 browser
.processes
[index
].renderer_type
;
390 switch (renderer_type
) {
391 case ProcessMemoryInformation::RENDERER_EXTENSION
:
392 UMA_HISTOGRAM_MEMORY_KB("Memory.Extension", sample
);
395 case ProcessMemoryInformation::RENDERER_CHROME
:
396 UMA_HISTOGRAM_MEMORY_KB("Memory.Chrome", sample
);
399 case ProcessMemoryInformation::RENDERER_UNKNOWN
:
400 NOTREACHED() << "Unknown renderer process type.";
402 case ProcessMemoryInformation::RENDERER_NORMAL
:
404 // TODO(erikkay): Should we bother splitting out the other subtypes?
405 UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample
);
410 case content::PROCESS_TYPE_PLUGIN
:
411 UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample
);
414 case content::PROCESS_TYPE_WORKER
:
415 UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample
);
418 case content::PROCESS_TYPE_UTILITY
:
419 UMA_HISTOGRAM_MEMORY_KB("Memory.Utility", sample
);
422 case content::PROCESS_TYPE_ZYGOTE
:
423 UMA_HISTOGRAM_MEMORY_KB("Memory.Zygote", sample
);
426 case content::PROCESS_TYPE_SANDBOX_HELPER
:
427 UMA_HISTOGRAM_MEMORY_KB("Memory.SandboxHelper", sample
);
430 case content::PROCESS_TYPE_GPU
:
431 UMA_HISTOGRAM_MEMORY_KB("Memory.Gpu", sample
);
434 case content::PROCESS_TYPE_PPAPI_PLUGIN
:
435 UMA_HISTOGRAM_MEMORY_KB("Memory.PepperPlugin", sample
);
436 pepper_plugin_count
++;
438 case content::PROCESS_TYPE_PPAPI_BROKER
:
439 UMA_HISTOGRAM_MEMORY_KB("Memory.PepperPluginBroker", sample
);
440 pepper_plugin_broker_count
++;
442 case PROCESS_TYPE_NACL_LOADER
:
443 UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClient", sample
);
446 case PROCESS_TYPE_NACL_BROKER
:
447 UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClientBroker", sample
);
455 #if defined(OS_CHROMEOS)
456 // Chrome OS exposes system-wide graphics driver memory which has historically
457 // been a source of leak/bloat.
458 base::SystemMemoryInfoKB meminfo
;
459 if (base::GetSystemMemoryInfo(&meminfo
) && meminfo
.gem_size
!= -1)
460 UMA_HISTOGRAM_MEMORY_MB("Memory.Graphics", meminfo
.gem_size
/ 1024 / 1024);
463 UMA_HISTOGRAM_COUNTS_100("Memory.ProcessLimit", process_limit
);
464 UMA_HISTOGRAM_COUNTS_100("Memory.ProcessCount",
465 static_cast<int>(browser
.processes
.size()));
466 UMA_HISTOGRAM_COUNTS_100("Memory.ChromeProcessCount", chrome_count
);
467 UMA_HISTOGRAM_COUNTS_100("Memory.ExtensionProcessCount", extension_count
);
468 UMA_HISTOGRAM_COUNTS_100("Memory.OtherProcessCount", other_count
);
469 UMA_HISTOGRAM_COUNTS_100("Memory.PluginProcessCount", plugin_count
);
470 UMA_HISTOGRAM_COUNTS_100("Memory.PepperPluginProcessCount",
471 pepper_plugin_count
);
472 UMA_HISTOGRAM_COUNTS_100("Memory.PepperPluginBrokerProcessCount",
473 pepper_plugin_broker_count
);
474 UMA_HISTOGRAM_COUNTS_100("Memory.RendererProcessCount", renderer_count
);
475 UMA_HISTOGRAM_COUNTS_100("Memory.WorkerProcessCount", worker_count
);
476 // TODO(viettrungluu): Do we want separate counts for the other
477 // (platform-specific) process types?
479 int total_sample
= static_cast<int>(aggregate_memory
/ 1000);
480 UMA_HISTOGRAM_MEMORY_MB("Memory.Total", total_sample
);
482 #if defined(OS_CHROMEOS)
483 UpdateSwapHistograms();
488 #if defined(OS_CHROMEOS)
489 void MemoryDetails::UpdateSwapHistograms() {
490 UMA_HISTOGRAM_BOOLEAN("Memory.Swap.HaveSwapped", swap_info_
.num_writes
> 0);
491 if (swap_info_
.num_writes
== 0)
494 // Only record swap info when any swaps have happened, to give us more
495 // detail in the histograms.
496 const ProcessData
& browser
= *ChromeBrowser();
497 size_t aggregate_memory
= 0;
498 for (size_t index
= 0; index
< browser
.processes
.size(); index
++) {
499 int sample
= static_cast<int>(browser
.processes
[index
].working_set
.swapped
);
500 aggregate_memory
+= sample
;
501 switch (browser
.processes
[index
].process_type
) {
502 case content::PROCESS_TYPE_BROWSER
:
503 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Browser", sample
);
505 case content::PROCESS_TYPE_RENDERER
: {
506 ProcessMemoryInformation::RendererProcessType renderer_type
=
507 browser
.processes
[index
].renderer_type
;
508 switch (renderer_type
) {
509 case ProcessMemoryInformation::RENDERER_EXTENSION
:
510 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Extension", sample
);
512 case ProcessMemoryInformation::RENDERER_CHROME
:
513 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Chrome", sample
);
515 case ProcessMemoryInformation::RENDERER_UNKNOWN
:
516 NOTREACHED() << "Unknown renderer process type.";
518 case ProcessMemoryInformation::RENDERER_NORMAL
:
520 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Renderer", sample
);
524 case content::PROCESS_TYPE_PLUGIN
:
525 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Plugin", sample
);
527 case content::PROCESS_TYPE_WORKER
:
528 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Worker", sample
);
530 case content::PROCESS_TYPE_UTILITY
:
531 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Utility", sample
);
533 case content::PROCESS_TYPE_ZYGOTE
:
534 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Zygote", sample
);
536 case content::PROCESS_TYPE_SANDBOX_HELPER
:
537 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.SandboxHelper", sample
);
539 case content::PROCESS_TYPE_GPU
:
540 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.Gpu", sample
);
542 case content::PROCESS_TYPE_PPAPI_PLUGIN
:
543 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.PepperPlugin", sample
);
545 case content::PROCESS_TYPE_PPAPI_BROKER
:
546 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.PepperPluginBroker", sample
);
548 case PROCESS_TYPE_NACL_LOADER
:
549 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.NativeClient", sample
);
551 case PROCESS_TYPE_NACL_BROKER
:
552 UMA_HISTOGRAM_MEMORY_KB("Memory.Swap.NativeClientBroker", sample
);
560 int total_sample
= static_cast<int>(aggregate_memory
/ 1000);
561 UMA_HISTOGRAM_MEMORY_MB("Memory.Swap.Total", total_sample
);
563 UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Swap.CompressedDataSize",
564 swap_info_
.compr_data_size
/ (1024 * 1024),
566 UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Swap.OriginalDataSize",
567 swap_info_
.orig_data_size
/ (1024 * 1024),
569 UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Swap.MemUsedTotal",
570 swap_info_
.mem_used_total
/ (1024 * 1024),
572 UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Swap.NumReads",
573 swap_info_
.num_reads
,
575 UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Swap.NumWrites",
576 swap_info_
.num_writes
,
579 if (swap_info_
.orig_data_size
> 0 && swap_info_
.compr_data_size
> 0) {
580 UMA_HISTOGRAM_CUSTOM_COUNTS(
581 "Memory.Swap.CompressionRatio",
582 swap_info_
.orig_data_size
/ swap_info_
.compr_data_size
,