1 // Copyright 2012 The Chromium Authors
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/containers/adapters.h"
11 #include "base/containers/cxx20_erase.h"
12 #include "base/file_version_info.h"
13 #include "base/functional/bind.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/task/thread_pool.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "components/nacl/common/nacl_process_type.h"
22 #include "components/strings/grit/components_strings.h"
23 #include "content/public/browser/browser_child_process_host_iterator.h"
24 #include "content/public/browser/browser_task_traits.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/child_process_data.h"
27 #include "content/public/browser/navigation_controller.h"
28 #include "content/public/browser/navigation_entry.h"
29 #include "content/public/browser/render_frame_host.h"
30 #include "content/public/browser/render_process_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 BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
42 #include "content/public/browser/zygote_host/zygote_host_linux.h"
45 #if BUILDFLAG(ENABLE_EXTENSIONS)
46 #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
47 #include "extensions/browser/extension_registry.h"
48 #include "extensions/browser/process_manager.h"
49 #include "extensions/browser/process_map.h"
50 #include "extensions/browser/view_type_utils.h"
51 #include "extensions/common/extension.h"
52 #include "extensions/common/mojom/view_type.mojom.h"
55 using base::StringPrintf;
56 using content::BrowserChildProcessHostIterator;
57 using content::BrowserThread;
58 using content::NavigationEntry;
59 using content::RenderWidgetHost;
60 using content::WebContents;
61 #if BUILDFLAG(ENABLE_EXTENSIONS)
62 using extensions::Extension;
67 void UpdateProcessTypeAndTitles(
68 #if BUILDFLAG(ENABLE_EXTENSIONS)
69 const extensions::ExtensionSet* extension_set,
70 #endif // BUILDFLAG(ENABLE_EXTENSIONS)
71 ProcessMemoryInformation& process,
72 content::RenderFrameHost* rfh) {
73 // We check the title and the renderer type only of the primary main
74 // RenderFrameHost, not subframes or non-primary main RenderFrameHosts. It is
75 // OK because this logic is used to get the title and the renderer type only
76 // for chrome://system and for printing the details to the error log when the
78 if (!rfh->IsInPrimaryMainFrame())
81 WebContents* contents = WebContents::FromRenderFrameHost(rfh);
84 // The rest of this block will happen only once per WebContents.
85 GURL page_url = contents->GetLastCommittedURL();
86 bool is_webui = rfh->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI;
89 process.renderer_type = ProcessMemoryInformation::RENDERER_CHROME;
92 #if BUILDFLAG(ENABLE_EXTENSIONS)
93 if (!is_webui && extension_set) {
94 const Extension* extension = extension_set->GetByID(page_url.host());
96 process.titles.push_back(base::UTF8ToUTF16(extension->name()));
97 process.renderer_type = ProcessMemoryInformation::RENDERER_EXTENSION;
102 extensions::mojom::ViewType type = extensions::GetViewType(contents);
103 if (type == extensions::mojom::ViewType::kBackgroundContents) {
104 process.titles.push_back(base::UTF8ToUTF16(page_url.spec()));
105 process.renderer_type = ProcessMemoryInformation::RENDERER_BACKGROUND_APP;
110 std::u16string title = contents->GetTitle();
112 title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE);
113 process.titles.push_back(title);
119 std::string ProcessMemoryInformation::GetRendererTypeNameInEnglish(
120 RendererProcessType type) {
122 case RENDERER_NORMAL:
124 case RENDERER_CHROME:
125 return "Tab (Chrome)";
126 case RENDERER_EXTENSION:
128 case RENDERER_DEVTOOLS:
130 case RENDERER_INTERSTITIAL:
131 return "Interstitial";
132 case RENDERER_BACKGROUND_APP:
133 return "Background App";
134 case RENDERER_UNKNOWN:
136 NOTREACHED() << "Unknown renderer process type!";
142 std::string ProcessMemoryInformation::GetFullTypeNameInEnglish(
144 RendererProcessType rtype) {
145 if (process_type == content::PROCESS_TYPE_RENDERER)
146 return GetRendererTypeNameInEnglish(rtype);
147 return content::GetProcessTypeNameInEnglish(process_type);
150 ProcessMemoryInformation::ProcessMemoryInformation()
153 process_type(content::PROCESS_TYPE_UNKNOWN),
155 open_fds_soft_limit(-1),
156 renderer_type(RENDERER_UNKNOWN),
157 private_memory_footprint_kb(0) {}
159 ProcessMemoryInformation::ProcessMemoryInformation(
160 const ProcessMemoryInformation& other) = default;
162 ProcessMemoryInformation::~ProcessMemoryInformation() {}
164 bool ProcessMemoryInformation::operator<(
165 const ProcessMemoryInformation& rhs) const {
166 return private_memory_footprint_kb < rhs.private_memory_footprint_kb;
169 ProcessData::ProcessData() {}
171 ProcessData::ProcessData(const ProcessData& rhs)
173 process_name(rhs.process_name),
174 processes(rhs.processes) {
177 ProcessData::~ProcessData() {}
179 ProcessData& ProcessData::operator=(const ProcessData& rhs) {
181 process_name = rhs.process_name;
182 processes = rhs.processes;
186 // This operation can take 30-100ms to complete. We never want to have
187 // one task run for that long on the UI or IO threads. So, we run the
188 // expensive parts of this operation over on the blocking pool.
189 void MemoryDetails::StartFetch() {
190 // This might get called from the UI or FILE threads, but should not be
191 // getting called from the IO thread.
192 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
194 std::vector<ProcessMemoryInformation> child_info;
196 // Collect the list of child processes. A 0 |handle| means that
197 // the process is being launched, so we skip it.
198 for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
199 ProcessMemoryInformation info;
200 if (!iter.GetData().GetProcess().IsValid())
202 info.pid = iter.GetData().GetProcess().Pid();
206 info.process_type = iter.GetData().process_type;
207 info.renderer_type = ProcessMemoryInformation::RENDERER_UNKNOWN;
208 info.titles.push_back(iter.GetData().name);
209 child_info.push_back(info);
212 // Now go do expensive memory lookups in a thread pool.
213 base::ThreadPool::PostTask(
215 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
216 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
217 base::BindOnce(&MemoryDetails::CollectProcessData, this, child_info));
220 MemoryDetails::~MemoryDetails() {}
222 std::string MemoryDetails::ToLogString(bool include_tab_title) {
225 ProcessMemoryInformationList processes = ChromeBrowser()->processes;
226 // Sort by memory consumption, low to high.
227 std::sort(processes.begin(), processes.end());
228 // Print from high to low.
229 for (const ProcessMemoryInformation& process_info :
230 base::Reversed(processes)) {
231 log += ProcessMemoryInformation::GetFullTypeNameInEnglish(
232 process_info.process_type, process_info.renderer_type);
233 // The title of a renderer may contain PII.
234 if ((process_info.process_type != content::PROCESS_TYPE_RENDERER ||
235 include_tab_title) &&
236 !process_info.titles.empty()) {
238 bool first_title = true;
239 for (const std::u16string& title : process_info.titles) {
243 log += base::UTF16ToUTF8(title);
249 static_cast<int>(process_info.private_memory_footprint_kb) / 1024);
250 if (process_info.num_open_fds != -1 ||
251 process_info.open_fds_soft_limit != -1) {
252 log += StringPrintf(", %d FDs open of %d", process_info.num_open_fds,
253 process_info.open_fds_soft_limit);
260 void MemoryDetails::CollectChildInfoOnUIThread() {
261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
262 ProcessData* const chrome_browser = ChromeBrowser();
264 // First pass, collate the widgets by process ID.
265 std::map<base::ProcessId, std::vector<RenderWidgetHost*>> widgets_by_pid;
266 std::unique_ptr<content::RenderWidgetHostIterator> widget_it(
267 RenderWidgetHost::GetRenderWidgetHosts());
268 while (content::RenderWidgetHost* widget = widget_it->GetNextHost()) {
269 // Ignore processes that don't have a connection, such as crashed tabs,
270 // or processes that are still launching.
271 if (!widget->GetProcess()->IsReady())
273 base::ProcessId pid = widget->GetProcess()->GetProcess().Pid();
274 widgets_by_pid[pid].push_back(widget);
277 // Get more information about the process.
278 for (ProcessMemoryInformation& process : chrome_browser->processes) {
279 // If there's at least one widget in the process, it is some kind of
280 // renderer process belonging to this browser. All these widgets will share
281 // a RenderProcessHost.
282 content::RenderProcessHost* render_process_host = nullptr;
283 if (!widgets_by_pid[process.pid].empty()) {
284 // Mark it as a normal renderer process, if we don't refine it to some
285 // other |renderer_type| later.
286 process.process_type = content::PROCESS_TYPE_RENDERER;
287 process.renderer_type = ProcessMemoryInformation::RENDERER_NORMAL;
288 render_process_host = widgets_by_pid[process.pid].front()->GetProcess();
291 #if BUILDFLAG(ENABLE_EXTENSIONS)
292 // Determine if this is an extension process.
293 bool process_is_for_extensions = false;
294 const extensions::ExtensionSet* extension_set = nullptr;
295 if (render_process_host &&
296 !extensions::ChromeContentBrowserClientExtensionsPart::
297 AreExtensionsDisabledForProfile(
298 render_process_host->GetBrowserContext())) {
299 content::BrowserContext* context =
300 render_process_host->GetBrowserContext();
301 extensions::ExtensionRegistry* extension_registry =
302 extensions::ExtensionRegistry::Get(context);
303 DCHECK(extension_registry);
304 extension_set = &extension_registry->enabled_extensions();
305 extensions::ProcessMap* process_map =
306 extensions::ProcessMap::Get(context);
308 int rph_id = render_process_host->GetID();
309 process_is_for_extensions = process_map->Contains(rph_id);
311 // For our purposes, don't count processes containing only hosted
312 // apps as extension processes. See also: crbug.com/102533.
313 for (auto& extension_id : process_map->GetExtensionsInProcess(rph_id)) {
314 const Extension* extension = extension_set->GetByID(extension_id);
315 if (extension && !extension->is_hosted_app()) {
316 process.renderer_type = ProcessMemoryInformation::RENDERER_EXTENSION;
323 if (render_process_host) {
324 // Use the list of RenderFrameHosts to iterate over the WebContents
325 // instances whose primary main RenderFrameHosts are in `process`. Refine
326 // our determination of the `process.renderer_type`, and record the page
328 render_process_host->ForEachRenderFrameHost(base::BindRepeating(
329 &UpdateProcessTypeAndTitles,
330 #if BUILDFLAG(ENABLE_EXTENSIONS)
331 process_is_for_extensions ? extension_set : nullptr,
333 // It is safe to use `std::ref` here, since `process` outlives this
338 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
339 if (content::ZygoteHost::GetInstance()->IsZygotePid(process.pid)) {
340 process.process_type = content::PROCESS_TYPE_ZYGOTE;
345 // Get rid of other Chrome processes that are from a different profile.
346 auto is_unknown = [](ProcessMemoryInformation& process) {
347 return process.process_type == content::PROCESS_TYPE_UNKNOWN;
349 auto& vector = chrome_browser->processes;
350 base::EraseIf(vector, is_unknown);
352 // Grab a memory dump for all processes.
353 memory_instrumentation::MemoryInstrumentation::GetInstance()
354 ->RequestPrivateMemoryFootprint(
355 base::kNullProcessId,
356 base::BindOnce(&MemoryDetails::DidReceiveMemoryDump, this));
359 void MemoryDetails::DidReceiveMemoryDump(
361 std::unique_ptr<memory_instrumentation::GlobalMemoryDump> global_dump) {
362 ProcessData* const chrome_browser = ChromeBrowser();
364 for (const memory_instrumentation::GlobalMemoryDump::ProcessDump& dump :
365 global_dump->process_dumps()) {
366 base::ProcessId dump_pid = dump.pid();
367 for (ProcessMemoryInformation& pmi : chrome_browser->processes) {
368 if (pmi.pid == dump_pid) {
369 pmi.private_memory_footprint_kb = dump.os_dump().private_footprint_kb;
376 OnDetailsAvailable();