123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- // 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/containers/adapters.h"
- #include "base/containers/cxx20_erase.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/thread_pool.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_task_traits.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_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 BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
- #include "content/public/browser/zygote_host/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"
- #include "extensions/common/mojom/view_type.mojom.h"
- #endif
- using base::StringPrintf;
- using content::BrowserChildProcessHostIterator;
- using content::BrowserThread;
- using content::NavigationEntry;
- using content::RenderWidgetHost;
- using content::WebContents;
- #if BUILDFLAG(ENABLE_EXTENSIONS)
- using extensions::Extension;
- #endif
- namespace {
- void UpdateProcessTypeAndTitles(
- #if BUILDFLAG(ENABLE_EXTENSIONS)
- const extensions::ExtensionSet* extension_set,
- #endif // BUILDFLAG(ENABLE_EXTENSIONS)
- ProcessMemoryInformation& process,
- content::RenderFrameHost* rfh) {
- // We check the title and the renderer type only of the primary main
- // RenderFrameHost, not subframes or non-primary main RenderFrameHosts. It is
- // OK because this logic is used to get the title and the renderer type only
- // for chrome://system and for printing the details to the error log when the
- // tab is oom-killed.
- if (!rfh->IsInPrimaryMainFrame())
- return;
- WebContents* contents = WebContents::FromRenderFrameHost(rfh);
- DCHECK(contents);
- // The rest of this block will happen only once per WebContents.
- GURL page_url = contents->GetLastCommittedURL();
- bool is_webui = rfh->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI;
- if (is_webui) {
- process.renderer_type = ProcessMemoryInformation::RENDERER_CHROME;
- }
- #if BUILDFLAG(ENABLE_EXTENSIONS)
- if (!is_webui && extension_set) {
- const Extension* extension = extension_set->GetByID(page_url.host());
- if (extension) {
- process.titles.push_back(base::UTF8ToUTF16(extension->name()));
- process.renderer_type = ProcessMemoryInformation::RENDERER_EXTENSION;
- return;
- }
- }
- extensions::mojom::ViewType type = extensions::GetViewType(contents);
- if (type == extensions::mojom::ViewType::kBackgroundContents) {
- process.titles.push_back(base::UTF8ToUTF16(page_url.spec()));
- process.renderer_type = ProcessMemoryInformation::RENDERER_BACKGROUND_APP;
- return;
- }
- #endif
- std::u16string title = contents->GetTitle();
- if (!title.length())
- title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE);
- process.titles.push_back(title);
- }
- } // namespace
- // 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;
- }
- // 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));
- 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().GetProcess().IsValid())
- continue;
- info.pid = iter.GetData().GetProcess().Pid();
- 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::ThreadPool::PostTask(
- FROM_HERE,
- {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
- base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
- base::BindOnce(&MemoryDetails::CollectProcessData, this, child_info));
- }
- MemoryDetails::~MemoryDetails() {}
- std::string MemoryDetails::ToLogString(bool include_tab_title) {
- 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 (const ProcessMemoryInformation& process_info :
- base::Reversed(processes)) {
- log += ProcessMemoryInformation::GetFullTypeNameInEnglish(
- process_info.process_type, process_info.renderer_type);
- // The title of a renderer may contain PII.
- if ((process_info.process_type != content::PROCESS_TYPE_RENDERER ||
- include_tab_title) &&
- !process_info.titles.empty()) {
- log += " [";
- bool first_title = true;
- for (const std::u16string& title : process_info.titles) {
- if (!first_title)
- log += "|";
- first_title = false;
- log += base::UTF16ToUTF8(title);
- }
- log += "]";
- }
- log += StringPrintf(
- " %d MB",
- static_cast<int>(process_info.private_memory_footprint_kb) / 1024);
- if (process_info.num_open_fds != -1 ||
- process_info.open_fds_soft_limit != -1) {
- log += StringPrintf(", %d FDs open of %d", process_info.num_open_fds,
- process_info.open_fds_soft_limit);
- }
- log += "\n";
- }
- return log;
- }
- 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;
- const extensions::ExtensionSet* extension_set = nullptr;
- if (render_process_host) {
- content::BrowserContext* context =
- render_process_host->GetBrowserContext();
- extensions::ExtensionRegistry* extension_registry =
- extensions::ExtensionRegistry::Get(context);
- extension_set = &extension_registry->enabled_extensions();
- 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_set->GetByID(extension_id);
- if (extension && !extension->is_hosted_app()) {
- process.renderer_type = ProcessMemoryInformation::RENDERER_EXTENSION;
- break;
- }
- }
- }
- #endif
- if (render_process_host) {
- // Use the list of RenderFrameHosts to iterate over the WebContents
- // instances whose primary main RenderFrameHosts are in `process`. Refine
- // our determination of the `process.renderer_type`, and record the page
- // titles.
- render_process_host->ForEachRenderFrameHost(base::BindRepeating(
- &UpdateProcessTypeAndTitles,
- #if BUILDFLAG(ENABLE_EXTENSIONS)
- process_is_for_extensions ? extension_set : nullptr,
- #endif
- // It is safe to use `std::ref` here, since `process` outlives this
- // callback.
- std::ref(process)));
- }
- #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
- if (content::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;
- base::EraseIf(vector, is_unknown);
- // Grab a memory dump for all processes.
- memory_instrumentation::MemoryInstrumentation::GetInstance()
- ->RequestPrivateMemoryFootprint(
- base::kNullProcessId,
- 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();
- }
|