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