| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/memory_details.h" |
| |
| #include <algorithm> |
| #include <set> |
| |
| #include "base/bind.h" |
| #include "base/file_version_info.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task_scheduler/post_task.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/nacl/common/nacl_process_type.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "content/public/browser/browser_child_process_host_iterator.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/child_process_data.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/render_widget_host_iterator.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/bindings_policy.h" |
| #include "content/public/common/content_constants.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h" |
| #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) |
| #include "services/service_manager/zygote/zygote_host_linux.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/browser/process_map.h" |
| #include "extensions/browser/view_type_utils.h" |
| #include "extensions/common/extension.h" |
| #endif |
| |
| using base::StringPrintf; |
| using content::BrowserChildProcessHostIterator; |
| using content::BrowserThread; |
| using content::NavigationEntry; |
| using content::RenderViewHost; |
| using content::RenderWidgetHost; |
| using content::WebContents; |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| using extensions::Extension; |
| #endif |
| |
| // static |
| std::string ProcessMemoryInformation::GetRendererTypeNameInEnglish( |
| RendererProcessType type) { |
| switch (type) { |
| case RENDERER_NORMAL: |
| return "Tab"; |
| case RENDERER_CHROME: |
| return "Tab (Chrome)"; |
| case RENDERER_EXTENSION: |
| return "Extension"; |
| case RENDERER_DEVTOOLS: |
| return "Devtools"; |
| case RENDERER_INTERSTITIAL: |
| return "Interstitial"; |
| case RENDERER_BACKGROUND_APP: |
| return "Background App"; |
| case RENDERER_UNKNOWN: |
| default: |
| NOTREACHED() << "Unknown renderer process type!"; |
| return "Unknown"; |
| } |
| } |
| |
| // static |
| std::string ProcessMemoryInformation::GetFullTypeNameInEnglish( |
| int process_type, |
| RendererProcessType rtype) { |
| if (process_type == content::PROCESS_TYPE_RENDERER) |
| return GetRendererTypeNameInEnglish(rtype); |
| return content::GetProcessTypeNameInEnglish(process_type); |
| } |
| |
| ProcessMemoryInformation::ProcessMemoryInformation() |
| : pid(0), |
| num_processes(0), |
| process_type(content::PROCESS_TYPE_UNKNOWN), |
| num_open_fds(-1), |
| open_fds_soft_limit(-1), |
| renderer_type(RENDERER_UNKNOWN), |
| private_memory_footprint_kb(0) {} |
| |
| ProcessMemoryInformation::ProcessMemoryInformation( |
| const ProcessMemoryInformation& other) = default; |
| |
| ProcessMemoryInformation::~ProcessMemoryInformation() {} |
| |
| bool ProcessMemoryInformation::operator<( |
| const ProcessMemoryInformation& rhs) const { |
| return private_memory_footprint_kb < rhs.private_memory_footprint_kb; |
| } |
| |
| ProcessData::ProcessData() {} |
| |
| ProcessData::ProcessData(const ProcessData& rhs) |
| : name(rhs.name), |
| process_name(rhs.process_name), |
| processes(rhs.processes) { |
| } |
| |
| ProcessData::~ProcessData() {} |
| |
| ProcessData& ProcessData::operator=(const ProcessData& rhs) { |
| name = rhs.name; |
| process_name = rhs.process_name; |
| processes = rhs.processes; |
| return *this; |
| } |
| |
| // About threading: |
| // |
| // This operation will hit no fewer than 3 threads. |
| // |
| // The BrowserChildProcessHostIterator can only be accessed from the IO thread. |
| // |
| // The RenderProcessHostIterator can only be accessed from the UI thread. |
| // |
| // This operation can take 30-100ms to complete. We never want to have |
| // one task run for that long on the UI or IO threads. So, we run the |
| // expensive parts of this operation over on the blocking pool. |
| // |
| void MemoryDetails::StartFetch() { |
| // This might get called from the UI or FILE threads, but should not be |
| // getting called from the IO thread. |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| // In order to process this request, we need to use the plugin information. |
| // However, plugin process information is only available from the IO thread. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&MemoryDetails::CollectChildInfoOnIOThread, this)); |
| } |
| |
| MemoryDetails::~MemoryDetails() {} |
| |
| std::string MemoryDetails::ToLogString() { |
| std::string log; |
| log.reserve(4096); |
| ProcessMemoryInformationList processes = ChromeBrowser()->processes; |
| // Sort by memory consumption, low to high. |
| std::sort(processes.begin(), processes.end()); |
| // Print from high to low. |
| for (ProcessMemoryInformationList::reverse_iterator iter1 = |
| processes.rbegin(); |
| iter1 != processes.rend(); |
| ++iter1) { |
| log += ProcessMemoryInformation::GetFullTypeNameInEnglish( |
| iter1->process_type, iter1->renderer_type); |
| if (!iter1->titles.empty()) { |
| log += " ["; |
| for (std::vector<base::string16>::const_iterator iter2 = |
| iter1->titles.begin(); |
| iter2 != iter1->titles.end(); ++iter2) { |
| if (iter2 != iter1->titles.begin()) |
| log += "|"; |
| log += base::UTF16ToUTF8(*iter2); |
| } |
| log += "]"; |
| } |
| log += StringPrintf( |
| " %d MB", static_cast<int>(iter1->private_memory_footprint_kb) / 1024); |
| if (iter1->num_open_fds != -1 || iter1->open_fds_soft_limit != -1) { |
| log += StringPrintf(", %d FDs open of %d", iter1->num_open_fds, |
| iter1->open_fds_soft_limit); |
| } |
| log += "\n"; |
| } |
| return log; |
| } |
| |
| void MemoryDetails::CollectChildInfoOnIOThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| std::vector<ProcessMemoryInformation> child_info; |
| |
| // Collect the list of child processes. A 0 |handle| means that |
| // the process is being launched, so we skip it. |
| for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) { |
| ProcessMemoryInformation info; |
| if (!iter.GetData().handle) |
| continue; |
| info.pid = base::GetProcId(iter.GetData().handle); |
| if (!info.pid) |
| continue; |
| |
| info.process_type = iter.GetData().process_type; |
| info.renderer_type = ProcessMemoryInformation::RENDERER_UNKNOWN; |
| info.titles.push_back(iter.GetData().name); |
| child_info.push_back(info); |
| } |
| |
| // Now go do expensive memory lookups in a thread pool. |
| base::PostTaskWithTraits( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::BACKGROUND, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| base::BindOnce(&MemoryDetails::CollectProcessData, this, child_info)); |
| } |
| |
| void MemoryDetails::CollectChildInfoOnUIThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| ProcessData* const chrome_browser = ChromeBrowser(); |
| |
| // First pass, collate the widgets by process ID. |
| std::map<base::ProcessId, std::vector<RenderWidgetHost*>> widgets_by_pid; |
| std::unique_ptr<content::RenderWidgetHostIterator> widget_it( |
| RenderWidgetHost::GetRenderWidgetHosts()); |
| while (content::RenderWidgetHost* widget = widget_it->GetNextHost()) { |
| // Ignore processes that don't have a connection, such as crashed tabs, |
| // or processes that are still launching. |
| if (!widget->GetProcess()->IsReady()) |
| continue; |
| base::ProcessId pid = widget->GetProcess()->GetProcess().Pid(); |
| widgets_by_pid[pid].push_back(widget); |
| } |
| |
| // Get more information about the process. |
| for (ProcessMemoryInformation& process : chrome_browser->processes) { |
| // If there's at least one widget in the process, it is some kind of |
| // renderer process belonging to this browser. All these widgets will share |
| // a RenderProcessHost. |
| content::RenderProcessHost* render_process_host = nullptr; |
| if (!widgets_by_pid[process.pid].empty()) { |
| // Mark it as a normal renderer process, if we don't refine it to some |
| // other |renderer_type| later. |
| process.process_type = content::PROCESS_TYPE_RENDERER; |
| process.renderer_type = ProcessMemoryInformation::RENDERER_NORMAL; |
| render_process_host = widgets_by_pid[process.pid].front()->GetProcess(); |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| // Determine if this is an extension process. |
| bool process_is_for_extensions = false; |
| if (render_process_host) { |
| content::BrowserContext* context = |
| render_process_host->GetBrowserContext(); |
| extensions::ExtensionRegistry* extension_registry = |
| extensions::ExtensionRegistry::Get(context); |
| extensions::ProcessMap* process_map = |
| extensions::ProcessMap::Get(context); |
| int rph_id = render_process_host->GetID(); |
| process_is_for_extensions = process_map->Contains(rph_id); |
| |
| // For our purposes, don't count processes containing only hosted apps |
| // as extension processes. See also: crbug.com/102533. |
| for (auto& extension_id : process_map->GetExtensionsInProcess(rph_id)) { |
| const Extension* extension = |
| extension_registry->enabled_extensions().GetByID(extension_id); |
| if (extension && !extension->is_hosted_app()) { |
| process.renderer_type = ProcessMemoryInformation::RENDERER_EXTENSION; |
| break; |
| } |
| } |
| } |
| #endif |
| |
| // Use the list of widgets to iterate over the WebContents instances whose |
| // main RenderFrameHosts are in |process|. Refine our determination of the |
| // |process.renderer_type|, and record the page titles. |
| for (content::RenderWidgetHost* widget : widgets_by_pid[process.pid]) { |
| DCHECK_EQ(render_process_host, widget->GetProcess()); |
| |
| RenderViewHost* rvh = RenderViewHost::From(widget); |
| if (!rvh) |
| continue; |
| |
| WebContents* contents = WebContents::FromRenderViewHost(rvh); |
| |
| // Assume that an RVH without a web contents is an interstitial. |
| if (!contents) { |
| process.renderer_type = ProcessMemoryInformation::RENDERER_INTERSTITIAL; |
| continue; |
| } |
| |
| // If this is a RVH for a subframe; skip it to avoid double-counting the |
| // WebContents. |
| if (rvh != contents->GetRenderViewHost()) |
| continue; |
| |
| // The rest of this block will happen only once per WebContents. |
| GURL page_url = contents->GetLastCommittedURL(); |
| SiteData& site_data = |
| chrome_browser->site_data[contents->GetBrowserContext()]; |
| SiteDetails::CollectSiteInfo(contents, &site_data); |
| |
| bool is_webui = rvh->GetMainFrame()->GetEnabledBindings() & |
| content::BINDINGS_POLICY_WEB_UI; |
| |
| if (is_webui) { |
| process.renderer_type = ProcessMemoryInformation::RENDERER_CHROME; |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| if (!is_webui && process_is_for_extensions) { |
| const Extension* extension = |
| extensions::ExtensionRegistry::Get( |
| render_process_host->GetBrowserContext()) |
| ->enabled_extensions() |
| .GetByID(page_url.host()); |
| if (extension) { |
| base::string16 title = base::UTF8ToUTF16(extension->name()); |
| process.titles.push_back(title); |
| process.renderer_type = |
| ProcessMemoryInformation::RENDERER_EXTENSION; |
| continue; |
| } |
| } |
| |
| extensions::ViewType type = extensions::GetViewType(contents); |
| if (type == extensions::VIEW_TYPE_BACKGROUND_CONTENTS) { |
| process.titles.push_back(base::UTF8ToUTF16(page_url.spec())); |
| process.renderer_type = |
| ProcessMemoryInformation::RENDERER_BACKGROUND_APP; |
| continue; |
| } |
| #endif |
| |
| base::string16 title = contents->GetTitle(); |
| if (!title.length()) |
| title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE); |
| process.titles.push_back(title); |
| } |
| |
| #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) |
| if (service_manager::ZygoteHost::GetInstance()->IsZygotePid(process.pid)) { |
| process.process_type = content::PROCESS_TYPE_ZYGOTE; |
| } |
| #endif |
| } |
| |
| // Get rid of other Chrome processes that are from a different profile. |
| auto is_unknown = [](ProcessMemoryInformation& process) { |
| return process.process_type == content::PROCESS_TYPE_UNKNOWN; |
| }; |
| auto& vector = chrome_browser->processes; |
| vector.erase(std::remove_if(vector.begin(), vector.end(), is_unknown), |
| vector.end()); |
| |
| // Grab a memory dump for all processes. |
| // Using AdaptCallbackForRepeating allows for an easier transition to |
| // OnceCallbacks for https://crbug.com/714018. |
| memory_instrumentation::MemoryInstrumentation::GetInstance() |
| ->RequestPrivateMemoryFootprint( |
| base::kNullProcessId, |
| base::AdaptCallbackForRepeating( |
| base::BindOnce(&MemoryDetails::DidReceiveMemoryDump, this))); |
| } |
| |
| void MemoryDetails::DidReceiveMemoryDump( |
| bool success, |
| std::unique_ptr<memory_instrumentation::GlobalMemoryDump> global_dump) { |
| ProcessData* const chrome_browser = ChromeBrowser(); |
| if (success) { |
| for (const memory_instrumentation::GlobalMemoryDump::ProcessDump& dump : |
| global_dump->process_dumps()) { |
| base::ProcessId dump_pid = dump.pid(); |
| for (ProcessMemoryInformation& pmi : chrome_browser->processes) { |
| if (pmi.pid == dump_pid) { |
| pmi.private_memory_footprint_kb = dump.os_dump().private_footprint_kb; |
| break; |
| } |
| } |
| } |
| } |
| |
| OnDetailsAvailable(); |
| } |