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_macros.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/task/thread_pool.h"
18 #include "build/build_config.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "components/nacl/common/nacl_process_type.h"
21 #include "components/strings/grit/components_strings.h"
22 #include "content/public/browser/browser_child_process_host_iterator.h"
23 #include "content/public/browser/browser_task_traits.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/child_process_data.h"
26 #include "content/public/browser/navigation_controller.h"
27 #include "content/public/browser/navigation_entry.h"
28 #include "content/public/browser/render_frame_host.h"
29 #include "content/public/browser/render_process_host.h"
30 #include "content/public/browser/render_view_host.h"
31 #include "content/public/browser/render_widget_host.h"
32 #include "content/public/browser/render_widget_host_iterator.h"
33 #include "content/public/browser/web_contents.h"
34 #include "content/public/common/bindings_policy.h"
35 #include "content/public/common/content_constants.h"
36 #include "extensions/buildflags/buildflags.h"
37 #include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h"
38 #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
39 #include "ui/base/l10n/l10n_util.h"
41 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
42 #include "content/public/browser/zygote_host/zygote_host_linux.h"
45 #if BUILDFLAG(ENABLE_EXTENSIONS)
46 #include "extensions/browser/extension_registry.h"
47 #include "extensions/browser/process_manager.h"
48 #include "extensions/browser/process_map.h"
49 #include "extensions/browser/view_type_utils.h"
50 #include "extensions/common/extension.h"
53 using base::StringPrintf;
54 using content::BrowserChildProcessHostIterator;
55 using content::BrowserThread;
56 using content::NavigationEntry;
57 using content::RenderViewHost;
58 using content::RenderWidgetHost;
59 using content::WebContents;
60 #if BUILDFLAG(ENABLE_EXTENSIONS)
61 using extensions::Extension;
65 std::string ProcessMemoryInformation::GetRendererTypeNameInEnglish(
66 RendererProcessType type) {
71 return "Tab (Chrome)";
72 case RENDERER_EXTENSION:
74 case RENDERER_DEVTOOLS:
76 case RENDERER_INTERSTITIAL:
77 return "Interstitial";
78 case RENDERER_BACKGROUND_APP:
79 return "Background App";
80 case RENDERER_UNKNOWN:
82 NOTREACHED() << "Unknown renderer process type!";
88 std::string ProcessMemoryInformation::GetFullTypeNameInEnglish(
90 RendererProcessType rtype) {
91 if (process_type == content::PROCESS_TYPE_RENDERER)
92 return GetRendererTypeNameInEnglish(rtype);
93 return content::GetProcessTypeNameInEnglish(process_type);
96 ProcessMemoryInformation::ProcessMemoryInformation()
99 process_type(content::PROCESS_TYPE_UNKNOWN),
101 open_fds_soft_limit(-1),
102 renderer_type(RENDERER_UNKNOWN),
103 private_memory_footprint_kb(0) {}
105 ProcessMemoryInformation::ProcessMemoryInformation(
106 const ProcessMemoryInformation& other) = default;
108 ProcessMemoryInformation::~ProcessMemoryInformation() {}
110 bool ProcessMemoryInformation::operator<(
111 const ProcessMemoryInformation& rhs) const {
112 return private_memory_footprint_kb < rhs.private_memory_footprint_kb;
115 ProcessData::ProcessData() {}
117 ProcessData::ProcessData(const ProcessData& rhs)
119 process_name(rhs.process_name),
120 processes(rhs.processes) {
123 ProcessData::~ProcessData() {}
125 ProcessData& ProcessData::operator=(const ProcessData& rhs) {
127 process_name = rhs.process_name;
128 processes = rhs.processes;
134 // This operation will hit no fewer than 3 threads.
136 // The BrowserChildProcessHostIterator can only be accessed from the IO thread.
138 // The RenderProcessHostIterator can only be accessed from the UI thread.
140 // This operation can take 30-100ms to complete. We never want to have
141 // one task run for that long on the UI or IO threads. So, we run the
142 // expensive parts of this operation over on the blocking pool.
144 void MemoryDetails::StartFetch() {
145 // This might get called from the UI or FILE threads, but should not be
146 // getting called from the IO thread.
147 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
149 // In order to process this request, we need to use the plugin information.
150 // However, plugin process information is only available from the IO thread.
151 content::GetIOThreadTaskRunner({})->PostTask(
153 base::BindOnce(&MemoryDetails::CollectChildInfoOnIOThread, this));
156 MemoryDetails::~MemoryDetails() {}
158 std::string MemoryDetails::ToLogString(bool include_tab_title) {
161 ProcessMemoryInformationList processes = ChromeBrowser()->processes;
162 // Sort by memory consumption, low to high.
163 std::sort(processes.begin(), processes.end());
164 // Print from high to low.
165 for (auto iter1 = processes.rbegin(); iter1 != processes.rend(); ++iter1) {
166 log += ProcessMemoryInformation::GetFullTypeNameInEnglish(
167 iter1->process_type, iter1->renderer_type);
168 // The title of a renderer may contain PII.
169 if ((iter1->process_type != content::PROCESS_TYPE_RENDERER ||
170 include_tab_title) &&
171 !iter1->titles.empty()) {
173 for (std::vector<base::string16>::const_iterator iter2 =
174 iter1->titles.begin();
175 iter2 != iter1->titles.end(); ++iter2) {
176 if (iter2 != iter1->titles.begin())
178 log += base::UTF16ToUTF8(*iter2);
183 " %d MB", static_cast<int>(iter1->private_memory_footprint_kb) / 1024);
184 if (iter1->num_open_fds != -1 || iter1->open_fds_soft_limit != -1) {
185 log += StringPrintf(", %d FDs open of %d", iter1->num_open_fds,
186 iter1->open_fds_soft_limit);
193 void MemoryDetails::CollectChildInfoOnIOThread() {
194 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
196 std::vector<ProcessMemoryInformation> child_info;
198 // Collect the list of child processes. A 0 |handle| means that
199 // the process is being launched, so we skip it.
200 for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
201 ProcessMemoryInformation info;
202 if (!iter.GetData().GetProcess().IsValid())
204 info.pid = iter.GetData().GetProcess().Pid();
208 info.process_type = iter.GetData().process_type;
209 info.renderer_type = ProcessMemoryInformation::RENDERER_UNKNOWN;
210 info.titles.push_back(iter.GetData().name);
211 child_info.push_back(info);
214 // Now go do expensive memory lookups in a thread pool.
215 base::ThreadPool::PostTask(
217 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
218 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
219 base::BindOnce(&MemoryDetails::CollectProcessData, this, child_info));
222 void MemoryDetails::CollectChildInfoOnUIThread() {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
224 ProcessData* const chrome_browser = ChromeBrowser();
226 // First pass, collate the widgets by process ID.
227 std::map<base::ProcessId, std::vector<RenderWidgetHost*>> widgets_by_pid;
228 std::unique_ptr<content::RenderWidgetHostIterator> widget_it(
229 RenderWidgetHost::GetRenderWidgetHosts());
230 while (content::RenderWidgetHost* widget = widget_it->GetNextHost()) {
231 // Ignore processes that don't have a connection, such as crashed tabs,
232 // or processes that are still launching.
233 if (!widget->GetProcess()->IsReady())
235 base::ProcessId pid = widget->GetProcess()->GetProcess().Pid();
236 widgets_by_pid[pid].push_back(widget);
239 // Get more information about the process.
240 for (ProcessMemoryInformation& process : chrome_browser->processes) {
241 // If there's at least one widget in the process, it is some kind of
242 // renderer process belonging to this browser. All these widgets will share
243 // a RenderProcessHost.
244 content::RenderProcessHost* render_process_host = nullptr;
245 if (!widgets_by_pid[process.pid].empty()) {
246 // Mark it as a normal renderer process, if we don't refine it to some
247 // other |renderer_type| later.
248 process.process_type = content::PROCESS_TYPE_RENDERER;
249 process.renderer_type = ProcessMemoryInformation::RENDERER_NORMAL;
250 render_process_host = widgets_by_pid[process.pid].front()->GetProcess();
253 #if BUILDFLAG(ENABLE_EXTENSIONS)
254 // Determine if this is an extension process.
255 bool process_is_for_extensions = false;
256 if (render_process_host) {
257 content::BrowserContext* context =
258 render_process_host->GetBrowserContext();
259 extensions::ExtensionRegistry* extension_registry =
260 extensions::ExtensionRegistry::Get(context);
261 extensions::ProcessMap* process_map =
262 extensions::ProcessMap::Get(context);
263 int rph_id = render_process_host->GetID();
264 process_is_for_extensions = process_map->Contains(rph_id);
266 // For our purposes, don't count processes containing only hosted apps
267 // as extension processes. See also: crbug.com/102533.
268 for (auto& extension_id : process_map->GetExtensionsInProcess(rph_id)) {
269 const Extension* extension =
270 extension_registry->enabled_extensions().GetByID(extension_id);
271 if (extension && !extension->is_hosted_app()) {
272 process.renderer_type = ProcessMemoryInformation::RENDERER_EXTENSION;
279 // Use the list of widgets to iterate over the WebContents instances whose
280 // main RenderFrameHosts are in |process|. Refine our determination of the
281 // |process.renderer_type|, and record the page titles.
282 for (content::RenderWidgetHost* widget : widgets_by_pid[process.pid]) {
283 DCHECK_EQ(render_process_host, widget->GetProcess());
285 RenderViewHost* rvh = RenderViewHost::From(widget);
289 WebContents* contents = WebContents::FromRenderViewHost(rvh);
291 // Assume that an RVH without a web contents is an interstitial.
293 process.renderer_type = ProcessMemoryInformation::RENDERER_INTERSTITIAL;
297 // If this is a RVH for a subframe; skip it to avoid double-counting the
299 if (rvh != contents->GetRenderViewHost())
302 // The rest of this block will happen only once per WebContents.
303 GURL page_url = contents->GetLastCommittedURL();
304 bool is_webui = rvh->GetMainFrame()->GetEnabledBindings() &
305 content::BINDINGS_POLICY_WEB_UI;
308 process.renderer_type = ProcessMemoryInformation::RENDERER_CHROME;
311 #if BUILDFLAG(ENABLE_EXTENSIONS)
312 if (!is_webui && process_is_for_extensions) {
313 const Extension* extension =
314 extensions::ExtensionRegistry::Get(
315 render_process_host->GetBrowserContext())
316 ->enabled_extensions()
317 .GetByID(page_url.host());
319 base::string16 title = base::UTF8ToUTF16(extension->name());
320 process.titles.push_back(title);
321 process.renderer_type =
322 ProcessMemoryInformation::RENDERER_EXTENSION;
327 extensions::ViewType type = extensions::GetViewType(contents);
328 if (type == extensions::VIEW_TYPE_BACKGROUND_CONTENTS) {
329 process.titles.push_back(base::UTF8ToUTF16(page_url.spec()));
330 process.renderer_type =
331 ProcessMemoryInformation::RENDERER_BACKGROUND_APP;
336 base::string16 title = contents->GetTitle();
338 title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE);
339 process.titles.push_back(title);
342 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
343 if (content::ZygoteHost::GetInstance()->IsZygotePid(process.pid)) {
344 process.process_type = content::PROCESS_TYPE_ZYGOTE;
349 // Get rid of other Chrome processes that are from a different profile.
350 auto is_unknown = [](ProcessMemoryInformation& process) {
351 return process.process_type == content::PROCESS_TYPE_UNKNOWN;
353 auto& vector = chrome_browser->processes;
354 base::EraseIf(vector, is_unknown);
356 // Grab a memory dump for all processes.
357 // Using AdaptCallbackForRepeating allows for an easier transition to
358 // OnceCallbacks for https://crbug.com/714018.
359 memory_instrumentation::MemoryInstrumentation::GetInstance()
360 ->RequestPrivateMemoryFootprint(
361 base::kNullProcessId,
362 base::BindOnce(&MemoryDetails::DidReceiveMemoryDump, this));
365 void MemoryDetails::DidReceiveMemoryDump(
367 std::unique_ptr<memory_instrumentation::GlobalMemoryDump> global_dump) {
368 ProcessData* const chrome_browser = ChromeBrowser();
370 for (const memory_instrumentation::GlobalMemoryDump::ProcessDump& dump :
371 global_dump->process_dumps()) {
372 base::ProcessId dump_pid = dump.pid();
373 for (ProcessMemoryInformation& pmi : chrome_browser->processes) {
374 if (pmi.pid == dump_pid) {
375 pmi.private_memory_footprint_kb = dump.os_dump().private_footprint_kb;
382 OnDetailsAvailable();