Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2010 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.
      4 
      5 #include "chrome/browser/memory_details.h"
      6 
      7 #include "base/file_version_info.h"
      8 #include "base/metrics/histogram.h"
      9 #include "base/process_util.h"
     10 #include "base/string_util.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/browser/extensions/extension_service.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/common/extensions/extension.h"
     15 #include "chrome/common/url_constants.h"
     16 #include "content/browser/browser_child_process_host.h"
     17 #include "content/browser/browser_thread.h"
     18 #include "content/browser/renderer_host/backing_store_manager.h"
     19 #include "content/browser/renderer_host/render_process_host.h"
     20 #include "content/browser/renderer_host/render_view_host.h"
     21 #include "content/browser/tab_contents/navigation_entry.h"
     22 #include "content/browser/tab_contents/tab_contents.h"
     23 #include "content/common/bindings_policy.h"
     24 #include "grit/chromium_strings.h"
     25 #include "grit/generated_resources.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 
     28 #if defined(OS_LINUX)
     29 #include "content/browser/zygote_host_linux.h"
     30 #include "content/browser/renderer_host/render_sandbox_host_linux.h"
     31 #endif
     32 
     33 ProcessMemoryInformation::ProcessMemoryInformation()
     34     : pid(0),
     35       num_processes(0),
     36       is_diagnostics(false),
     37       type(ChildProcessInfo::UNKNOWN_PROCESS),
     38       renderer_type(ChildProcessInfo::RENDERER_UNKNOWN) {
     39 }
     40 
     41 ProcessMemoryInformation::~ProcessMemoryInformation() {}
     42 
     43 ProcessData::ProcessData() {}
     44 
     45 ProcessData::ProcessData(const ProcessData& rhs)
     46     : name(rhs.name),
     47       process_name(rhs.process_name),
     48       processes(rhs.processes) {
     49 }
     50 
     51 ProcessData::~ProcessData() {}
     52 
     53 ProcessData& ProcessData::operator=(const ProcessData& rhs) {
     54   name = rhs.name;
     55   process_name = rhs.process_name;
     56   processes = rhs.processes;
     57   return *this;
     58 }
     59 
     60 // About threading:
     61 //
     62 // This operation will hit no fewer than 3 threads.
     63 //
     64 // The ChildProcessInfo::Iterator can only be accessed from the IO thread.
     65 //
     66 // The RenderProcessHostIterator can only be accessed from the UI thread.
     67 //
     68 // This operation can take 30-100ms to complete.  We never want to have
     69 // one task run for that long on the UI or IO threads.  So, we run the
     70 // expensive parts of this operation over on the file thread.
     71 //
     72 void MemoryDetails::StartFetch() {
     73   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
     74   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
     75 
     76   // In order to process this request, we need to use the plugin information.
     77   // However, plugin process information is only available from the IO thread.
     78   BrowserThread::PostTask(
     79       BrowserThread::IO, FROM_HERE,
     80       NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnIOThread));
     81 }
     82 
     83 MemoryDetails::~MemoryDetails() {}
     84 
     85 void MemoryDetails::CollectChildInfoOnIOThread() {
     86   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     87 
     88   std::vector<ProcessMemoryInformation> child_info;
     89 
     90   // Collect the list of child processes.
     91   for (BrowserChildProcessHost::Iterator iter; !iter.Done(); ++iter) {
     92     ProcessMemoryInformation info;
     93     info.pid = base::GetProcId(iter->handle());
     94     if (!info.pid)
     95       continue;
     96 
     97     info.type = iter->type();
     98     info.renderer_type = iter->renderer_type();
     99     info.titles.push_back(WideToUTF16Hack(iter->name()));
    100     child_info.push_back(info);
    101   }
    102 
    103   // Now go do expensive memory lookups from the file thread.
    104   BrowserThread::PostTask(
    105       BrowserThread::FILE, FROM_HERE,
    106       NewRunnableMethod(this, &MemoryDetails::CollectProcessData, child_info));
    107 }
    108 
    109 void MemoryDetails::CollectChildInfoOnUIThread() {
    110   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    111 
    112 #if defined(OS_LINUX)
    113   const pid_t zygote_pid = ZygoteHost::GetInstance()->pid();
    114   const pid_t sandbox_helper_pid = RenderSandboxHostLinux::GetInstance()->pid();
    115 #endif
    116 
    117   ProcessData* const chrome_browser = ChromeBrowser();
    118   // Get more information about the process.
    119   for (size_t index = 0; index < chrome_browser->processes.size();
    120       index++) {
    121     // Check if it's a renderer, if so get the list of page titles in it and
    122     // check if it's a diagnostics-related process.  We skip about:memory pages.
    123     // Iterate the RenderProcessHosts to find the tab contents.
    124     ProcessMemoryInformation& process =
    125         chrome_browser->processes[index];
    126 
    127     for (RenderProcessHost::iterator renderer_iter(
    128          RenderProcessHost::AllHostsIterator()); !renderer_iter.IsAtEnd();
    129          renderer_iter.Advance()) {
    130       RenderProcessHost* render_process_host = renderer_iter.GetCurrentValue();
    131       DCHECK(render_process_host);
    132       // Ignore processes that don't have a connection, such as crashed tabs.
    133       if (!render_process_host->HasConnection() ||
    134           process.pid != base::GetProcId(render_process_host->GetHandle())) {
    135         continue;
    136       }
    137       process.type = ChildProcessInfo::RENDER_PROCESS;
    138       Profile* profile = render_process_host->profile();
    139       ExtensionService* extension_service = profile->GetExtensionService();
    140 
    141       // The RenderProcessHost may host multiple TabContents.  Any
    142       // of them which contain diagnostics information make the whole
    143       // process be considered a diagnostics process.
    144       //
    145       // NOTE: This is a bit dangerous.  We know that for now, listeners
    146       //       are always RenderWidgetHosts.  But in theory, they don't
    147       //       have to be.
    148       RenderProcessHost::listeners_iterator iter(
    149           render_process_host->ListenersIterator());
    150       for (; !iter.IsAtEnd(); iter.Advance()) {
    151         const RenderWidgetHost* widget =
    152             static_cast<const RenderWidgetHost*>(iter.GetCurrentValue());
    153         DCHECK(widget);
    154         if (!widget || !widget->IsRenderView())
    155           continue;
    156 
    157         const RenderViewHost* host = static_cast<const RenderViewHost*>(widget);
    158         RenderViewHostDelegate* host_delegate = host->delegate();
    159         DCHECK(host_delegate);
    160         GURL url = host_delegate->GetURL();
    161         ViewType::Type type = host_delegate->GetRenderViewType();
    162         if (host->enabled_bindings() & BindingsPolicy::WEB_UI) {
    163           // TODO(erikkay) the type for devtools doesn't actually appear to
    164           // be set.
    165           if (type == ViewType::DEV_TOOLS_UI)
    166             process.renderer_type = ChildProcessInfo::RENDERER_DEVTOOLS;
    167           else
    168             process.renderer_type = ChildProcessInfo::RENDERER_CHROME;
    169         } else if (host->enabled_bindings() & BindingsPolicy::EXTENSION) {
    170           process.renderer_type = ChildProcessInfo::RENDERER_EXTENSION;
    171         }
    172         TabContents* contents = host_delegate->GetAsTabContents();
    173         if (!contents) {
    174           if (host->is_extension_process()) {
    175             const Extension* extension =
    176                 extension_service->GetExtensionByURL(url);
    177             if (extension) {
    178               string16 title = UTF8ToUTF16(extension->name());
    179               process.titles.push_back(title);
    180             }
    181           } else if (process.renderer_type ==
    182                      ChildProcessInfo::RENDERER_UNKNOWN) {
    183             process.titles.push_back(UTF8ToUTF16(url.spec()));
    184             switch (type) {
    185               case ViewType::BACKGROUND_CONTENTS:
    186                 process.renderer_type =
    187                     ChildProcessInfo::RENDERER_BACKGROUND_APP;
    188                 break;
    189               case ViewType::INTERSTITIAL_PAGE:
    190                 process.renderer_type = ChildProcessInfo::RENDERER_INTERSTITIAL;
    191                 break;
    192               case ViewType::NOTIFICATION:
    193                 process.renderer_type = ChildProcessInfo::RENDERER_NOTIFICATION;
    194                 break;
    195               default:
    196                 process.renderer_type = ChildProcessInfo::RENDERER_UNKNOWN;
    197                 break;
    198             }
    199           }
    200           continue;
    201         }
    202 
    203         // Since We have a TabContents and and the renderer type hasn't been
    204         // set yet, it must be a normal tabbed renderer.
    205         if (process.renderer_type == ChildProcessInfo::RENDERER_UNKNOWN)
    206           process.renderer_type = ChildProcessInfo::RENDERER_NORMAL;
    207 
    208         string16 title = contents->GetTitle();
    209         if (!title.length())
    210           title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE);
    211         process.titles.push_back(title);
    212 
    213         // We need to check the pending entry as well as the virtual_url to
    214         // see if it's an about:memory URL (we don't want to count these in the
    215         // total memory usage of the browser).
    216         //
    217         // When we reach here, about:memory will be the pending entry since we
    218         // haven't responded with any data such that it would be committed. If
    219         // you have another about:memory tab open (which would be committed),
    220         // we don't want to count it either, so we also check the last committed
    221         // entry.
    222         //
    223         // Either the pending or last committed entries can be NULL.
    224         const NavigationEntry* pending_entry =
    225             contents->controller().pending_entry();
    226         const NavigationEntry* last_committed_entry =
    227             contents->controller().GetLastCommittedEntry();
    228         if ((last_committed_entry &&
    229              LowerCaseEqualsASCII(last_committed_entry->virtual_url().spec(),
    230                                   chrome::kAboutMemoryURL)) ||
    231             (pending_entry &&
    232              LowerCaseEqualsASCII(pending_entry->virtual_url().spec(),
    233                                   chrome::kAboutMemoryURL)))
    234           process.is_diagnostics = true;
    235       }
    236     }
    237 
    238 #if defined(OS_LINUX)
    239     if (process.pid == zygote_pid) {
    240       process.type = ChildProcessInfo::ZYGOTE_PROCESS;
    241     } else if (process.pid == sandbox_helper_pid) {
    242       process.type = ChildProcessInfo::SANDBOX_HELPER_PROCESS;
    243     }
    244 #endif
    245   }
    246 
    247   // Get rid of other Chrome processes that are from a different profile.
    248   for (size_t index = 0; index < chrome_browser->processes.size();
    249       index++) {
    250     if (chrome_browser->processes[index].type ==
    251         ChildProcessInfo::UNKNOWN_PROCESS) {
    252       chrome_browser->processes.erase(
    253           chrome_browser->processes.begin() + index);
    254       index--;
    255     }
    256   }
    257 
    258   UpdateHistograms();
    259 
    260   OnDetailsAvailable();
    261 }
    262 
    263 void MemoryDetails::UpdateHistograms() {
    264   // Reports a set of memory metrics to UMA.
    265   // Memory is measured in KB.
    266 
    267   const ProcessData& browser = *ChromeBrowser();
    268   size_t aggregate_memory = 0;
    269   int chrome_count = 0;
    270   int extension_count = 0;
    271   int plugin_count = 0;
    272   int renderer_count = 0;
    273   int other_count = 0;
    274   int worker_count = 0;
    275   for (size_t index = 0; index < browser.processes.size(); index++) {
    276     int sample = static_cast<int>(browser.processes[index].working_set.priv);
    277     aggregate_memory += sample;
    278     switch (browser.processes[index].type) {
    279       case ChildProcessInfo::BROWSER_PROCESS:
    280         UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample);
    281         break;
    282       case ChildProcessInfo::RENDER_PROCESS: {
    283         ChildProcessInfo::RendererProcessType renderer_type =
    284             browser.processes[index].renderer_type;
    285         switch (renderer_type) {
    286           case ChildProcessInfo::RENDERER_EXTENSION:
    287             UMA_HISTOGRAM_MEMORY_KB("Memory.Extension", sample);
    288             extension_count++;
    289             break;
    290           case ChildProcessInfo::RENDERER_CHROME:
    291             UMA_HISTOGRAM_MEMORY_KB("Memory.Chrome", sample);
    292             chrome_count++;
    293             break;
    294           case ChildProcessInfo::RENDERER_UNKNOWN:
    295             NOTREACHED() << "Unknown renderer process type.";
    296             break;
    297           case ChildProcessInfo::RENDERER_NORMAL:
    298           default:
    299             // TODO(erikkay): Should we bother splitting out the other subtypes?
    300             UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample);
    301             renderer_count++;
    302             break;
    303         }
    304         break;
    305       }
    306       case ChildProcessInfo::PLUGIN_PROCESS:
    307         UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample);
    308         plugin_count++;
    309         break;
    310       case ChildProcessInfo::WORKER_PROCESS:
    311         UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample);
    312         worker_count++;
    313         break;
    314       case ChildProcessInfo::UTILITY_PROCESS:
    315         UMA_HISTOGRAM_MEMORY_KB("Memory.Utility", sample);
    316         other_count++;
    317         break;
    318       case ChildProcessInfo::ZYGOTE_PROCESS:
    319         UMA_HISTOGRAM_MEMORY_KB("Memory.Zygote", sample);
    320         other_count++;
    321         break;
    322       case ChildProcessInfo::SANDBOX_HELPER_PROCESS:
    323         UMA_HISTOGRAM_MEMORY_KB("Memory.SandboxHelper", sample);
    324         other_count++;
    325         break;
    326       case ChildProcessInfo::NACL_LOADER_PROCESS:
    327         UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClient", sample);
    328         other_count++;
    329         break;
    330       case ChildProcessInfo::NACL_BROKER_PROCESS:
    331         UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClientBroker", sample);
    332         other_count++;
    333         break;
    334       case ChildProcessInfo::GPU_PROCESS:
    335         UMA_HISTOGRAM_MEMORY_KB("Memory.Gpu", sample);
    336         other_count++;
    337         break;
    338       default:
    339         NOTREACHED();
    340     }
    341   }
    342   UMA_HISTOGRAM_MEMORY_KB("Memory.BackingStore",
    343                           BackingStoreManager::MemorySize() / 1024);
    344 
    345   UMA_HISTOGRAM_COUNTS_100("Memory.ProcessCount",
    346       static_cast<int>(browser.processes.size()));
    347   UMA_HISTOGRAM_COUNTS_100("Memory.ChromeProcessCount", chrome_count);
    348   UMA_HISTOGRAM_COUNTS_100("Memory.ExtensionProcessCount", extension_count);
    349   UMA_HISTOGRAM_COUNTS_100("Memory.OtherProcessCount", other_count);
    350   UMA_HISTOGRAM_COUNTS_100("Memory.PluginProcessCount", plugin_count);
    351   UMA_HISTOGRAM_COUNTS_100("Memory.RendererProcessCount", renderer_count);
    352   UMA_HISTOGRAM_COUNTS_100("Memory.WorkerProcessCount", worker_count);
    353   // TODO(viettrungluu): Do we want separate counts for the other
    354   // (platform-specific) process types?
    355 
    356   int total_sample = static_cast<int>(aggregate_memory / 1000);
    357   UMA_HISTOGRAM_MEMORY_MB("Memory.Total", total_sample);
    358 }
    359