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"
10 #include "base/bind.h"
11 #include "base/file_version_info.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/url_constants.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "components/nacl/common/nacl_process_type.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/render_widget_host_iterator.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/common/bindings_policy.h"
30 #include "content/public/common/content_constants.h"
31 #include "ui/base/l10n/l10n_util.h"
33 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
34 #include "content/public/browser/zygote_host_linux.h"
37 #if defined(ENABLE_EXTENSIONS)
38 #include "extensions/browser/extension_registry.h"
39 #include "extensions/browser/process_manager.h"
40 #include "extensions/browser/process_map.h"
41 #include "extensions/browser/view_type_utils.h"
42 #include "extensions/common/extension.h"
45 using base::StringPrintf
;
46 using content::BrowserChildProcessHostIterator
;
47 using content::BrowserThread
;
48 using content::NavigationEntry
;
49 using content::RenderViewHost
;
50 using content::RenderWidgetHost
;
51 using content::WebContents
;
52 #if defined(ENABLE_EXTENSIONS)
53 using extensions::Extension
;
57 std::string
ProcessMemoryInformation::GetRendererTypeNameInEnglish(
58 RendererProcessType type
) {
63 return "Tab (Chrome)";
64 case RENDERER_EXTENSION
:
66 case RENDERER_DEVTOOLS
:
68 case RENDERER_INTERSTITIAL
:
69 return "Interstitial";
70 case RENDERER_BACKGROUND_APP
:
71 return "Background App";
72 case RENDERER_UNKNOWN
:
74 NOTREACHED() << "Unknown renderer process type!";
80 std::string
ProcessMemoryInformation::GetFullTypeNameInEnglish(
82 RendererProcessType rtype
) {
83 if (process_type
== content::PROCESS_TYPE_RENDERER
)
84 return GetRendererTypeNameInEnglish(rtype
);
85 return content::GetProcessTypeNameInEnglish(process_type
);
88 ProcessMemoryInformation::ProcessMemoryInformation()
91 is_diagnostics(false),
92 process_type(content::PROCESS_TYPE_UNKNOWN
),
93 renderer_type(RENDERER_UNKNOWN
) {
96 ProcessMemoryInformation::~ProcessMemoryInformation() {}
98 bool ProcessMemoryInformation::operator<(
99 const ProcessMemoryInformation
& rhs
) const {
100 return working_set
.priv
< rhs
.working_set
.priv
;
103 ProcessData::ProcessData() {}
105 ProcessData::ProcessData(const ProcessData
& rhs
)
107 process_name(rhs
.process_name
),
108 processes(rhs
.processes
) {
111 ProcessData::~ProcessData() {}
113 ProcessData
& ProcessData::operator=(const ProcessData
& rhs
) {
115 process_name
= rhs
.process_name
;
116 processes
= rhs
.processes
;
122 // This operation will hit no fewer than 3 threads.
124 // The BrowserChildProcessHostIterator can only be accessed from the IO thread.
126 // The RenderProcessHostIterator can only be accessed from the UI thread.
128 // This operation can take 30-100ms to complete. We never want to have
129 // one task run for that long on the UI or IO threads. So, we run the
130 // expensive parts of this operation over on the file thread.
132 void MemoryDetails::StartFetch(CollectionMode mode
) {
133 // This might get called from the UI or FILE threads, but should not be
134 // getting called from the IO thread.
135 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO
));
137 // In order to process this request, we need to use the plugin information.
138 // However, plugin process information is only available from the IO thread.
139 BrowserThread::PostTask(
140 BrowserThread::IO
, FROM_HERE
,
141 base::Bind(&MemoryDetails::CollectChildInfoOnIOThread
, this, mode
));
144 MemoryDetails::~MemoryDetails() {}
146 std::string
MemoryDetails::ToLogString() {
149 ProcessMemoryInformationList processes
= ChromeBrowser()->processes
;
150 // Sort by memory consumption, low to high.
151 std::sort(processes
.begin(), processes
.end());
152 // Print from high to low.
153 for (ProcessMemoryInformationList::reverse_iterator iter1
=
155 iter1
!= processes
.rend();
157 log
+= ProcessMemoryInformation::GetFullTypeNameInEnglish(
158 iter1
->process_type
, iter1
->renderer_type
);
159 if (!iter1
->titles
.empty()) {
161 for (std::vector
<base::string16
>::const_iterator iter2
=
162 iter1
->titles
.begin();
163 iter2
!= iter1
->titles
.end(); ++iter2
) {
164 if (iter2
!= iter1
->titles
.begin())
166 log
+= base::UTF16ToUTF8(*iter2
);
170 log
+= StringPrintf(" %d MB private, %d MB shared",
171 static_cast<int>(iter1
->working_set
.priv
) / 1024,
172 static_cast<int>(iter1
->working_set
.shared
) / 1024);
173 #if defined(OS_CHROMEOS)
174 log
+= StringPrintf(", %d MB swapped",
175 static_cast<int>(iter1
->working_set
.swapped
) / 1024);
182 void MemoryDetails::CollectChildInfoOnIOThread(CollectionMode mode
) {
183 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
185 std::vector
<ProcessMemoryInformation
> child_info
;
187 // Collect the list of child processes. A 0 |handle| means that
188 // the process is being launched, so we skip it.
189 for (BrowserChildProcessHostIterator iter
; !iter
.Done(); ++iter
) {
190 ProcessMemoryInformation info
;
191 if (!iter
.GetData().handle
)
193 info
.pid
= base::GetProcId(iter
.GetData().handle
);
197 info
.process_type
= iter
.GetData().process_type
;
198 info
.renderer_type
= ProcessMemoryInformation::RENDERER_UNKNOWN
;
199 info
.titles
.push_back(iter
.GetData().name
);
200 child_info
.push_back(info
);
203 // Now go do expensive memory lookups from the file thread.
204 BrowserThread::PostTask(
205 BrowserThread::FILE, FROM_HERE
,
206 base::Bind(&MemoryDetails::CollectProcessData
, this, mode
, child_info
));
209 void MemoryDetails::CollectChildInfoOnUIThread() {
210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
212 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
213 const pid_t zygote_pid
= content::ZygoteHost::GetInstance()->GetPid();
216 ProcessData
* const chrome_browser
= ChromeBrowser();
217 // Get more information about the process.
218 for (size_t index
= 0; index
< chrome_browser
->processes
.size();
220 // Check if it's a renderer, if so get the list of page titles in it and
221 // check if it's a diagnostics-related process. We skip about:memory pages.
222 // Iterate the RenderProcessHosts to find the tab contents.
223 ProcessMemoryInformation
& process
=
224 chrome_browser
->processes
[index
];
226 scoped_ptr
<content::RenderWidgetHostIterator
> widgets(
227 RenderWidgetHost::GetRenderWidgetHosts());
228 while (content::RenderWidgetHost
* widget
= widgets
->GetNextHost()) {
229 content::RenderProcessHost
* render_process_host
=
230 widget
->GetProcess();
231 DCHECK(render_process_host
);
232 // Ignore processes that don't have a connection, such as crashed tabs.
233 if (!render_process_host
->HasConnection() ||
234 process
.pid
!= base::GetProcId(render_process_host
->GetHandle())) {
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 if (!widget
->IsRenderView())
244 process
.process_type
= content::PROCESS_TYPE_RENDERER
;
245 bool is_extension
= false;
246 RenderViewHost
* host
= RenderViewHost::From(widget
);
247 #if defined(ENABLE_EXTENSIONS)
248 content::BrowserContext
* context
=
249 render_process_host
->GetBrowserContext();
250 extensions::ExtensionRegistry
* extension_registry
=
251 extensions::ExtensionRegistry::Get(context
);
252 extensions::ProcessMap
* extension_process_map
=
253 extensions::ProcessMap::Get(context
);
254 is_extension
= extension_process_map
->Contains(
255 host
->GetProcess()->GetID());
258 WebContents
* contents
= WebContents::FromRenderViewHost(host
);
261 url
= contents
->GetURL();
262 SiteData
* site_data
=
263 &chrome_browser
->site_data
[contents
->GetBrowserContext()];
264 SiteDetails::CollectSiteInfo(contents
, site_data
);
266 #if defined(ENABLE_EXTENSIONS)
267 extensions::ViewType type
= extensions::GetViewType(contents
);
269 if (host
->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI
) {
270 process
.renderer_type
= ProcessMemoryInformation::RENDERER_CHROME
;
271 } else if (is_extension
) {
272 #if defined(ENABLE_EXTENSIONS)
273 // For our purposes, don't count processes containing only hosted apps
274 // as extension processes. See also: crbug.com/102533.
275 std::set
<std::string
> extension_ids
=
276 extension_process_map
->GetExtensionsInProcess(
277 host
->GetProcess()->GetID());
278 for (std::set
<std::string
>::iterator iter
= extension_ids
.begin();
279 iter
!= extension_ids
.end(); ++iter
) {
280 const Extension
* extension
=
281 extension_registry
->enabled_extensions().GetByID(*iter
);
282 if (extension
&& !extension
->is_hosted_app()) {
283 process
.renderer_type
=
284 ProcessMemoryInformation::RENDERER_EXTENSION
;
290 #if defined(ENABLE_EXTENSIONS)
292 const Extension
* extension
=
293 extension_registry
->enabled_extensions().GetByID(url
.host());
295 base::string16 title
= base::UTF8ToUTF16(extension
->name());
296 process
.titles
.push_back(title
);
297 process
.renderer_type
=
298 ProcessMemoryInformation::RENDERER_EXTENSION
;
305 process
.renderer_type
=
306 ProcessMemoryInformation::RENDERER_INTERSTITIAL
;
310 #if defined(ENABLE_EXTENSIONS)
311 if (type
== extensions::VIEW_TYPE_BACKGROUND_CONTENTS
) {
312 process
.titles
.push_back(base::UTF8ToUTF16(url
.spec()));
313 process
.renderer_type
=
314 ProcessMemoryInformation::RENDERER_BACKGROUND_APP
;
319 // Since we have a WebContents and and the renderer type hasn't been
320 // set yet, it must be a normal tabbed renderer.
321 if (process
.renderer_type
== ProcessMemoryInformation::RENDERER_UNKNOWN
)
322 process
.renderer_type
= ProcessMemoryInformation::RENDERER_NORMAL
;
324 base::string16 title
= contents
->GetTitle();
326 title
= l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE
);
327 process
.titles
.push_back(title
);
329 // We need to check the pending entry as well as the virtual_url to
330 // see if it's a chrome://memory URL (we don't want to count these in
331 // the total memory usage of the browser).
333 // When we reach here, chrome://memory will be the pending entry since
334 // we haven't responded with any data such that it would be committed.
335 // If you have another chrome://memory tab open (which would be
336 // committed), we don't want to count it either, so we also check the
337 // last committed entry.
339 // Either the pending or last committed entries can be NULL.
340 const NavigationEntry
* pending_entry
=
341 contents
->GetController().GetPendingEntry();
342 const NavigationEntry
* last_committed_entry
=
343 contents
->GetController().GetLastCommittedEntry();
344 if ((last_committed_entry
&&
345 LowerCaseEqualsASCII(last_committed_entry
->GetVirtualURL().spec(),
346 chrome::kChromeUIMemoryURL
)) ||
348 LowerCaseEqualsASCII(pending_entry
->GetVirtualURL().spec(),
349 chrome::kChromeUIMemoryURL
))) {
350 process
.is_diagnostics
= true;
354 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
355 if (process
.pid
== zygote_pid
) {
356 process
.process_type
= content::PROCESS_TYPE_ZYGOTE
;
361 // Get rid of other Chrome processes that are from a different profile.
362 for (size_t index
= 0; index
< chrome_browser
->processes
.size();
364 if (chrome_browser
->processes
[index
].process_type
==
365 content::PROCESS_TYPE_UNKNOWN
) {
366 chrome_browser
->processes
.erase(
367 chrome_browser
->processes
.begin() + index
);
372 OnDetailsAvailable();