diff options
author | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-05 23:37:40 +0000 |
---|---|---|
committer | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-05 23:37:40 +0000 |
commit | f164cea41472f9e9ec21579233e2c42a7b9d5184 (patch) | |
tree | 523c6aa180be86ace3ac7797e4c9c75c369dff57 /chrome/browser | |
parent | 422c0f17466cddf30fd6815f9d3519a3815264c6 (diff) | |
download | chromium_src-f164cea41472f9e9ec21579233e2c42a7b9d5184.zip chromium_src-f164cea41472f9e9ec21579233e2c42a7b9d5184.tar.gz chromium_src-f164cea41472f9e9ec21579233e2c42a7b9d5184.tar.bz2 |
Mac: Implement about:memory.
This implements about:memory on Mac. It calls /bin/ps to obtain information
about processes (this is Apple's officially supported "API"). Unfortunately, ps
provides fairly minimal information (rss and vsize); top is better, but not a
stable API -- it has changed greatly between Mac OS 10.5 and 10.6, and moreover
the 10.6 version is more limited in its output formatting.
BUG=9653
TEST=Go to about:memory under a variety of conditions (with a variety of browsers loaded).
Review URL: http://codereview.chromium.org/333008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31168 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/browser_resources.grd | 6 | ||||
-rw-r--r-- | chrome/browser/memory_details.cc | 55 | ||||
-rw-r--r-- | chrome/browser/memory_details.h | 14 | ||||
-rw-r--r-- | chrome/browser/memory_details_mac.cc | 226 | ||||
-rw-r--r-- | chrome/browser/process_info_snapshot.h | 106 | ||||
-rw-r--r-- | chrome/browser/process_info_snapshot_mac.cc | 156 | ||||
-rw-r--r-- | chrome/browser/process_info_snapshot_mac_unittest.cc | 85 | ||||
-rw-r--r-- | chrome/browser/resources/about_memory_mac.html | 577 |
8 files changed, 1198 insertions, 27 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index 134adae..3a5ce14 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -17,8 +17,10 @@ without changes to the corresponding grd file. ek --> <if expr="os == 'linux2'"> <include name="IDR_ABOUT_MEMORY_HTML" file="resources\about_memory_linux.html" flattenhtml="true" type="BINDATA" /> </if> - - <if expr="os != 'linux2'"> + <if expr="os == 'darwin'"> + <include name="IDR_ABOUT_MEMORY_HTML" file="resources\about_memory_mac.html" flattenhtml="true" type="BINDATA" /> + </if> + <if expr="os != 'linux2' and os != 'darwin'"> <include name="IDR_ABOUT_MEMORY_HTML" file="resources\about_memory.html" flattenhtml="true" type="BINDATA" /> </if> diff --git a/chrome/browser/memory_details.cc b/chrome/browser/memory_details.cc index 4ea4444..9953442a 100644 --- a/chrome/browser/memory_details.cc +++ b/chrome/browser/memory_details.cc @@ -184,31 +184,34 @@ void MemoryDetails::UpdateHistograms() { int sample = static_cast<int>(browser.processes[index].working_set.priv); aggregate_memory += sample; switch (browser.processes[index].type) { - case ChildProcessInfo::BROWSER_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample); - break; - case ChildProcessInfo::RENDER_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample); - break; - case ChildProcessInfo::PLUGIN_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample); - plugin_count++; - break; - case ChildProcessInfo::WORKER_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample); - worker_count++; - break; - case ChildProcessInfo::ZYGOTE_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.Zygote", sample); - break; - case ChildProcessInfo::SANDBOX_HELPER_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.SandboxHelper", sample); - break; - case ChildProcessInfo::NACL_PROCESS: - UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClient", sample); - break; - default: - NOTREACHED(); + case ChildProcessInfo::BROWSER_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.Browser", sample); + break; + case ChildProcessInfo::RENDER_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer", sample); + break; + case ChildProcessInfo::PLUGIN_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.Plugin", sample); + plugin_count++; + break; + case ChildProcessInfo::WORKER_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.Worker", sample); + worker_count++; + break; + case ChildProcessInfo::UTILITY_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.Utility", sample); + break; + case ChildProcessInfo::ZYGOTE_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.Zygote", sample); + break; + case ChildProcessInfo::SANDBOX_HELPER_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.SandboxHelper", sample); + break; + case ChildProcessInfo::NACL_PROCESS: + UMA_HISTOGRAM_MEMORY_KB("Memory.NativeClient", sample); + break; + default: + NOTREACHED(); } } UMA_HISTOGRAM_MEMORY_KB("Memory.BackingStore", @@ -218,6 +221,8 @@ void MemoryDetails::UpdateHistograms() { static_cast<int>(browser.processes.size())); UMA_HISTOGRAM_COUNTS_100("Memory.PluginProcessCount", plugin_count); UMA_HISTOGRAM_COUNTS_100("Memory.WorkerProcessCount", worker_count); + // TODO(viettrungluu): Do we want separate counts for the other + // (platform-specific) process types? int total_sample = static_cast<int>(aggregate_memory / 1000); UMA_HISTOGRAM_MEMORY_MB("Memory.Total", total_sample); diff --git a/chrome/browser/memory_details.h b/chrome/browser/memory_details.h index b92fbae..0b35b4e 100644 --- a/chrome/browser/memory_details.h +++ b/chrome/browser/memory_details.h @@ -52,6 +52,10 @@ struct ProcessData { ProcessMemoryInformationList processes; }; +#if defined(OS_MACOSX) +class ProcessInfoSnapshot; +#endif + // MemoryDetails fetches memory details about current running browsers. // Because this data can only be fetched asynchronously, callers use // this class via a callback. @@ -103,6 +107,16 @@ class MemoryDetails : public base::RefCountedThreadSafe<MemoryDetails> { // The parameter holds information about processes from the IO thread. void CollectProcessData(std::vector<ProcessMemoryInformation>); +#if defined(OS_MACOSX) + // A helper for |CollectProcessData()|, collecting data on the Chrome/Chromium + // process with PID |pid|. The collected data is added to the state of the + // object (in |process_data_|). + void CollectProcessDataChrome( + const std::vector<ProcessMemoryInformation>& child_info, + base::ProcessId pid, + const ProcessInfoSnapshot& process_info); +#endif + // Collect child process information on the UI thread. Information about // renderer processes is only available there. void CollectChildInfoOnUIThread(); diff --git a/chrome/browser/memory_details_mac.cc b/chrome/browser/memory_details_mac.cc new file mode 100644 index 0000000..577a809 --- /dev/null +++ b/chrome/browser/memory_details_mac.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2009 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 <set> +#include <string> + +#include "app/l10n_util.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_version_info.h" +#include "base/mac_util.h" +#include "base/string_util.h" +#include "base/process_util.h" +#include "base/thread.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/process_info_snapshot.h" +#include "chrome/browser/renderer_host/backing_store_manager.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/child_process_host.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/url_constants.h" +#include "grit/chromium_strings.h" + +// TODO(viettrungluu): Many of the TODOs below are subsumed by a general need to +// refactor the about:memory code (not just on Mac, but probably on other +// platforms as well). I've filed crbug.com/25456. + +class RenderViewHostDelegate; + +// Known browsers which we collect details for. |CHROME_BROWSER| *must* be the +// first browser listed. The order here must match those in |process_template| +// (in |MemoryDetails::MemoryDetails()| below). +// TODO(viettrungluu): In the big refactoring (see above), get rid of this order +// dependence. +enum BrowserType { + // TODO(viettrungluu): possibly add more? + CHROME_BROWSER = 0, + SAFARI_BROWSER, + FIREFOX_BROWSER, + CAMINO_BROWSER, + OPERA_BROWSER, + OMNIWEB_BROWSER, + MAX_BROWSERS +} BrowserProcess; + + +MemoryDetails::MemoryDetails() { + static const std::wstring google_browser_name = + l10n_util::GetString(IDS_PRODUCT_NAME); + // (Human and process) names of browsers; should match the ordering for + // |BrowserProcess| (i.e., |BrowserType|). + // TODO(viettrungluu): The current setup means that we can't detect both + // Chrome and Chromium at the same time! + // TODO(viettrungluu): Get localized browser names for other browsers + // (crbug.com/25779). + ProcessData process_template[MAX_BROWSERS] = { + { google_browser_name.c_str(), chrome::kBrowserProcessExecutableName, }, + { L"Safari", L"Safari", }, + { L"Firefox", L"firefox-bin", }, + { L"Camino", L"Camino", }, + { L"Opera", L"Opera", }, + { L"OmniWeb", L"OmniWeb", }, + }; + + for (size_t index = 0; index < arraysize(process_template); ++index) { + ProcessData process; + process.name = process_template[index].name; + process.process_name = process_template[index].process_name; + process_data_.push_back(process); + } +} + +ProcessData* MemoryDetails::ChromeBrowser() { + return &process_data_[CHROME_BROWSER]; +} + +void MemoryDetails::CollectProcessData( + std::vector<ProcessMemoryInformation> child_info) { + // This must be run on the file thread to avoid jank (|ProcessInfoSnapshot| + // runs /bin/ps, which isn't instantaneous). + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + + // Clear old data. + for (size_t index = 0; index < MAX_BROWSERS; index++) + process_data_[index].processes.clear(); + + // First, we use |NamedProcessIterator| to get the PIDs of the processes we're + // interested in; we save our results to avoid extra calls to + // |NamedProcessIterator| (for performance reasons) and to avoid additional + // inconsistencies caused by racing. Then we run |/bin/ps| *once* to get + // information on those PIDs. Then we used our saved information to iterate + // over browsers, then over PIDs. + + // Get PIDs of main browser processes. + std::vector<base::ProcessId> pids_by_browser[MAX_BROWSERS]; + std::vector<base::ProcessId> all_pids; + for (size_t index = CHROME_BROWSER; index < MAX_BROWSERS; index++) { + base::NamedProcessIterator process_it(process_data_[index].process_name, + NULL); + + while (const ProcessEntry* process_entry = process_it.NextProcessEntry()) { + pids_by_browser[index].push_back(process_entry->pid); + all_pids.push_back(process_entry->pid); + } + } + + // Get PIDs of helpers. + std::vector<base::ProcessId> helper_pids; + { + base::NamedProcessIterator helper_it(chrome::kHelperProcessExecutableName, + NULL); + while (const ProcessEntry* process_entry = helper_it.NextProcessEntry()) { + helper_pids.push_back(process_entry->pid); + all_pids.push_back(process_entry->pid); + } + } + + // Capture information about the processes we're interested in. + ProcessInfoSnapshot process_info; + process_info.Sample(all_pids); + + // Handle the other processes first. + for (size_t index = CHROME_BROWSER + 1; index < MAX_BROWSERS; index++) { + for (std::vector<base::ProcessId>::const_iterator it = + pids_by_browser[index].begin(); + it != pids_by_browser[index].end(); ++it) { + ProcessMemoryInformation info; + info.pid = *it; + info.type = ChildProcessInfo::UNKNOWN_PROCESS; + + // Try to get version information. To do this, we need first to get the + // executable's name (we can only believe |proc_info.command| if it looks + // like an absolute path). Then we need strip the executable's name back + // to the bundle's name. And only then can we try to get the version. + scoped_ptr<FileVersionInfo> version_info; + ProcessInfoSnapshot::ProcInfoEntry proc_info; + if (process_info.GetProcInfo(info.pid, &proc_info)) { + if (proc_info.command.length() > 1 && proc_info.command[0] == '/') { + FilePath bundle_name = + mac_util::GetAppBundlePath(FilePath(proc_info.command)); + if (!bundle_name.empty()) { + version_info.reset(FileVersionInfo::CreateFileVersionInfo( + bundle_name)); + } + } + } + if (version_info.get()) { + info.product_name = version_info->product_name(); + info.version = version_info->product_version(); + } else { + info.product_name = process_data_[index].name; + info.version = L""; + } + + // Memory info. + process_info.GetCommittedKBytesOfPID(info.pid, &info.committed); + process_info.GetWorkingSetKBytesOfPID(info.pid, &info.working_set); + + // Add the process info to our list. + process_data_[index].processes.push_back(info); + } + } + + // Collect data about Chrome/Chromium. + for (std::vector<base::ProcessId>::const_iterator it = + pids_by_browser[CHROME_BROWSER].begin(); + it != pids_by_browser[CHROME_BROWSER].end(); ++it) { + CollectProcessDataChrome(child_info, *it, process_info); + } + + // And collect data about the helpers. + for (std::vector<base::ProcessId>::const_iterator it = helper_pids.begin(); + it != helper_pids.end(); ++it) { + CollectProcessDataChrome(child_info, *it, process_info); + } + + // Finally return to the browser thread. + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnUIThread)); +} + +void MemoryDetails::CollectProcessDataChrome( + const std::vector<ProcessMemoryInformation>& child_info, + base::ProcessId pid, + const ProcessInfoSnapshot& process_info) { + ProcessMemoryInformation info; + info.pid = pid; + if (info.pid == base::GetCurrentProcId()) + info.type = ChildProcessInfo::BROWSER_PROCESS; + else + info.type = ChildProcessInfo::UNKNOWN_PROCESS; + + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + if (version_info.get()) { + info.product_name = version_info->product_name(); + info.version = version_info->product_version(); + } else { + info.product_name = process_data_[CHROME_BROWSER].name; + info.version = L""; + } + + // Check if this is one of the child processes whose data we collected + // on the IO thread, and if so copy over that data. + for (size_t child = 0; child < child_info.size(); child++) { + if (child_info[child].pid == info.pid) { + info.titles = child_info[child].titles; + info.type = child_info[child].type; + break; + } + } + + // Memory info. + process_info.GetCommittedKBytesOfPID(info.pid, &info.committed); + process_info.GetWorkingSetKBytesOfPID(info.pid, &info.working_set); + + // Add the process info to our list. + process_data_[CHROME_BROWSER].processes.push_back(info); +} diff --git a/chrome/browser/process_info_snapshot.h b/chrome/browser/process_info_snapshot.h new file mode 100644 index 0000000..88064c6 --- /dev/null +++ b/chrome/browser/process_info_snapshot.h @@ -0,0 +1,106 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_PROCESS_INFO_SNAPSHOT_H_ +#define CHROME_BROWSER_PROCESS_INFO_SNAPSHOT_H_ + +#include <sys/types.h> + +#include <map> +#include <string> +#include <vector> + +#include "base/process_util.h" + +// A class which captures process information at a given point in time when its +// |Sample()| method is called. This information can then be probed by PID. +// |Sample()| may take a while to complete, so if calling from the browser +// process, only do so from the file thread. The current implementation, only on +// Mac, pulls information from /bin/ps. /usr/bin/top provides much more +// information about memory, but it has changed greatly from Mac OS 10.5.x to +// 10.6.x, thereby raising future compatibility concerns. Moreover, the 10.6.x +// version is less capable in terms of configuring output and its output is +// harder to parse. +// TODO(viettrungluu): This is currently only implemented and used on Mac, so +// things are very Mac-specific. If this is ever implemented for other +// platforms, we should subclass and add opaqueness (probably |ProcInfoEntry| +// should be considered opaque). +class ProcessInfoSnapshot { + public: + ProcessInfoSnapshot(); + ~ProcessInfoSnapshot(); + + // Capture a snapshot of process memory information (by running ps) for the + // given list of PIDs. Call only from the file thread. + // |pid_list| - list of |ProcessId|s on which to capture information. + // returns - |true| if okay, |false| on error. + bool Sample(std::vector<base::ProcessId> pid_list); + + // Reset all statistics (deallocating any memory allocated). + void Reset(); + + // Our basic structure for storing information about a process (the names are + // mostly self-explanatory). Note that |command| may not actually reflect the + // actual executable name; never trust it absolutely, and only take it + // half-seriously when it begins with '/'. + struct ProcInfoEntry { + base::ProcessId pid; + base::ProcessId ppid; + uid_t uid; + uid_t euid; + size_t rss; + size_t vsize; + std::string command; + }; + + // Get process information for a given PID. + // |pid| - self-explanatory. + // |proc_info| - place to put the process information. + // returns - |true| if okay, |false| on error (including PID not found). + bool GetProcInfo(int pid, + ProcInfoEntry* proc_info) const; + + // Fills a |CommittedKBytes| with both resident and paged memory usage, as per + // its definition (or as close as we can manage). In the current (Mac) + // implementation, we map: + // vsize --> comm_priv, + // 0 --> comm_mapped, + // 0 --> comm_image; + // in about:memory: virtual:private = comm_priv, + // virtual:mapped = comm_mapped. + // TODO(viettrungluu): Doing such a mapping is kind of ugly. + // |pid| - self-explanatory. + // |usage| - pointer to |CommittedBytes| to fill; zero-ed on error. + // returns - |true| on success, |false| on error (including PID not found). + bool GetCommittedKBytesOfPID(int pid, + base::CommittedKBytes* usage) const; + + // Fills a |WorkingSetKBytes| containing resident private and shared memory, + // as per its definition (or as close as we can manage). In the current (Mac) + // implementation, we map: + // 0 --> ws_priv, + // rss --> ws_shareable, + // 0 --> ws_shared; + // in about:memory: res:private = ws_priv + ws_shareable - ws_shared, + // res:shared = ws_shared / num_procs, + // res:total = res:private + res:shared. + // TODO(viettrungluu): Doing such a mapping is kind of ugly. + // |pid| - self-explanatory. + // |ws_usage| - pointer to |WorkingSetKBytes| to fill; zero-ed on error. + // returns - |true| on success, |false| on error (including PID not found). + bool GetWorkingSetKBytesOfPID(int pid, + base::WorkingSetKBytes* ws_usage) const; + + // TODO(viettrungluu): Maybe we should also have the following (again, for + // "compatibility"): + // size_t GetWorkingSetSizeOfPID(int pid) const; + // size_t GetPeakWorkingSetSizeOfPID(int pid) const; + // size_t GetPrivateBytesOfPID(int pid) const; + + private: + // map from |int| (PID) to |ProcInfoEntry| + std::map<int,ProcInfoEntry> proc_info_entries_; +}; + +#endif // CHROME_BROWSER_PROCESS_INFO_SNAPSHOT_H_ diff --git a/chrome/browser/process_info_snapshot_mac.cc b/chrome/browser/process_info_snapshot_mac.cc new file mode 100644 index 0000000..c594e75 --- /dev/null +++ b/chrome/browser/process_info_snapshot_mac.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2009 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/process_info_snapshot.h" + +#include <iostream> +#include <sstream> + +#include "base/string_util.h" +#include "base/thread.h" + +// Implementation for the Mac; calls '/bin/ps' for information when +// |Sample()| is called. + +// Default constructor. +ProcessInfoSnapshot::ProcessInfoSnapshot() { } + +// Destructor: just call |Reset()| to release everything. +ProcessInfoSnapshot::~ProcessInfoSnapshot() { + Reset(); +} + +// Capture the information by calling '/bin/ps'. +// Note: we ignore the "tsiz" (text size) display option of ps because it's +// always zero (tested on 10.5 and 10.6). +bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { + Reset(); + + std::vector<std::string> argv; + argv.push_back("/bin/ps"); + // Get PID, PPID, (real) UID, effective UID, resident set size, virtual memory + // size, and command. + argv.push_back("-o"); + argv.push_back("pid=,ppid=,ruid=,uid=,rss=,vsz=,comm="); + // Only display the specified PIDs. + for(std::vector<base::ProcessId>::iterator it = pid_list.begin(); + it != pid_list.end(); ++it) { + argv.push_back("-p"); + argv.push_back(Int64ToString(static_cast<int64>(*it))); + } + + std::string output; + CommandLine command_line(argv); + if (!base::GetAppOutputRestricted(command_line, + &output, (pid_list.size() + 10) * 100)) { + LOG(ERROR) << "Failure running /bin/ps to acquire data."; + return false; + } + + std::istringstream in(output, std::istringstream::in); + std::string line; + + // Process lines until done. + while (true) { + ProcInfoEntry proc_info; + + // The format is as specified above to ps (see ps(1)): + // "-o pid=,ppid=,ruid=,uid=,rss=,vsz=,comm=". + // Try to read the PID; if we get it, we should be able to get the rest of + // the line. + in >> proc_info.pid; + if (in.eof()) + break; + in >> proc_info.ppid; + in >> proc_info.uid; + in >> proc_info.euid; + in >> proc_info.rss; + in >> proc_info.vsize; + in.ignore(1, ' '); // Eat the space. + std::getline(in, proc_info.command); // Get the rest of the line. + if (!in.good()) { + LOG(ERROR) << "Error parsing output from /usr/bin/top."; + return false; + } + + // Make sure the new PID isn't already in our list. + if (proc_info_entries_.find(proc_info.pid) != proc_info_entries_.end()) { + LOG(ERROR) << "Duplicate PID in output from /bin/ps."; + return false; + } + + if (!proc_info.pid || ! proc_info.vsize) { + LOG(WARNING) << "Invalid data from /bin/ps."; + return false; + } + + // Record the process information. + proc_info_entries_[proc_info.pid] = proc_info; + } + + return true; +} + +// Clear all the stored information. +void ProcessInfoSnapshot::Reset() { + proc_info_entries_.clear(); +} + +bool ProcessInfoSnapshot::GetProcInfo(int pid, + ProcInfoEntry* proc_info) const { + std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid); + if (it == proc_info_entries_.end()) + return false; + + *proc_info = it->second; + return true; +} + +bool ProcessInfoSnapshot::GetCommittedKBytesOfPID( + int pid, + base::CommittedKBytes* usage) const { + // Try to avoid crashing on a bug; stats aren't usually so crucial. + if (!usage) { + NOTREACHED(); + return false; + } + + // Failure of |GetProcInfo()| is "normal", due to racing. + ProcInfoEntry proc_info; + if (!GetProcInfo(pid, &proc_info)) { + usage->priv = 0; + usage->mapped = 0; + usage->image = 0; + return false; + } + + usage->priv = proc_info.vsize; + usage->mapped = 0; + usage->image = 0; + return true; +} + +bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID( + int pid, + base::WorkingSetKBytes* ws_usage) const { + // Try to avoid crashing on a bug; stats aren't usually so crucial. + if (!ws_usage) { + NOTREACHED(); + return false; + } + + // Failure of |GetProcInfo()| is "normal", due to racing. + ProcInfoEntry proc_info; + if (!GetProcInfo(pid, &proc_info)) { + ws_usage->priv = 0; + ws_usage->shareable = 0; + ws_usage->shared = 0; + return false; + } + + ws_usage->priv = 0; + ws_usage->shareable = proc_info.rss; + ws_usage->shared = 0; + return true; +} diff --git a/chrome/browser/process_info_snapshot_mac_unittest.cc b/chrome/browser/process_info_snapshot_mac_unittest.cc new file mode 100644 index 0000000..babdf27 --- /dev/null +++ b/chrome/browser/process_info_snapshot_mac_unittest.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2009 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/process_info_snapshot.h" + +#include <sys/types.h> // For |uid_t| (and |pid_t|). +#include <unistd.h> // For |getpid()|, |getuid()|, etc. + +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +typedef testing::Test ProcessInfoSnapshotMacTest; + +TEST_F(ProcessInfoSnapshotMacTest, FindPidOneTest) { + // Sample process with PID 1, which should exist and presumably belong to + // root. + std::vector<base::ProcessId> pid_list; + pid_list.push_back(1); + ProcessInfoSnapshot snapshot; + ASSERT_TRUE(snapshot.Sample(pid_list)); + + ProcessInfoSnapshot::ProcInfoEntry proc_info; + ASSERT_TRUE(snapshot.GetProcInfo(1, &proc_info)); + EXPECT_EQ(1, static_cast<int64>(proc_info.pid)); + EXPECT_EQ(0, static_cast<int64>(proc_info.ppid)); + EXPECT_EQ(0, static_cast<int64>(proc_info.uid)); + EXPECT_EQ(0, static_cast<int64>(proc_info.euid)); + EXPECT_GE(proc_info.rss, 0u); + EXPECT_GT(proc_info.vsize, 0u); + + // Try out the |Get...OfPID()|, but don't examine the results, since they + // depend on how we map |ProcInfoEntry| to |...KBytes|. + base::CommittedKBytes usage; + EXPECT_TRUE(snapshot.GetCommittedKBytesOfPID(1, &usage)); + base::WorkingSetKBytes ws_usage; + EXPECT_TRUE(snapshot.GetWorkingSetKBytesOfPID(1, &ws_usage)); + + // Make sure it hasn't picked up some other PID (say, 2). + EXPECT_FALSE(snapshot.GetProcInfo(2, &proc_info)); + + // Make sure PID 2 still isn't there (in case I mess up my use of std::map). + EXPECT_FALSE(snapshot.GetProcInfo(2, &proc_info)); + + // Test |Reset()|. + snapshot.Reset(); + EXPECT_FALSE(snapshot.GetProcInfo(1, &proc_info)); +} + +TEST_F(ProcessInfoSnapshotMacTest, FindPidSelfTest) { + // Sample this process and its parent. + base::ProcessId pid = static_cast<base::ProcessId>(getpid()); + base::ProcessId ppid = static_cast<base::ProcessId>(getppid()); + uid_t uid = getuid(); + uid_t euid = geteuid(); + EXPECT_NE(static_cast<int64>(ppid), 0); + + std::vector<base::ProcessId> pid_list; + pid_list.push_back(pid); + pid_list.push_back(ppid); + ProcessInfoSnapshot snapshot; + ASSERT_TRUE(snapshot.Sample(pid_list)); + + // Find our process. + ProcessInfoSnapshot::ProcInfoEntry proc_info; + ASSERT_TRUE(snapshot.GetProcInfo(pid, &proc_info)); + EXPECT_EQ(pid, proc_info.pid); + EXPECT_EQ(ppid, proc_info.ppid); + EXPECT_EQ(uid, proc_info.uid); + EXPECT_EQ(euid, proc_info.euid); + EXPECT_GE(proc_info.rss, 100u); // Sanity check: we're running, so we + // should occupy at least 100 kilobytes. + EXPECT_GE(proc_info.vsize, 1024u); // Sanity check: our |vsize| is presumably + // at least a megabyte. + + // Find our parent. + ASSERT_TRUE(snapshot.GetProcInfo(ppid, &proc_info)); + EXPECT_EQ(ppid, proc_info.pid); + EXPECT_NE(static_cast<int64>(proc_info.ppid), 0); + EXPECT_EQ(uid, proc_info.uid); // This (and the following) should be true + EXPECT_EQ(euid, proc_info.euid); // under reasonable circumstances. + // Can't say anything definite about its |rss|. + EXPECT_GT(proc_info.vsize, 0u); // Its |vsize| should be nonzero though. +} diff --git a/chrome/browser/resources/about_memory_mac.html b/chrome/browser/resources/about_memory_mac.html new file mode 100644 index 0000000..9b6d223 --- /dev/null +++ b/chrome/browser/resources/about_memory_mac.html @@ -0,0 +1,577 @@ +<!DOCTYPE HTML> + +<!-- +about:memory template page +--> +<html id="t"> + <head> + <title>About Memory</title> + +<style> +body { + font-size: 84%; + font-family: Helvetica, sans-serif; + padding: 0.75em; + margin: 0; + min-width: 45em; +} + +h1 { + font-size: 110%; + font-weight: bold; + color: #4a8ee6; + letter-spacing: -1px; + padding: 0; + margin: 0; +} +h2 { + font-size: 110%; + letter-spacing: -1px; + font-weight: normal; + color: #4a8ee6; + padding: 0; + margin: 0; + padding: 0.5em 1em; + color: #3a75bd; + margin-left: -38px; + padding-left: 38px; + + border-top: 1px solid #3a75bd; + padding-top: 0.5em; + +} +h2:first-child { + border-top: 0; + padding-top: 0; +} +span.th { + padding-left: 0.35em; +} +a { + color: black; +} + +div#header { + padding: 0.75em 1em; + padding-top: 0.6em; + padding-left: 0; + margin-bottom: 0.75em; + position: relative; + overflow: hidden; + background: #5296de; + -webkit-background-size: 100%; + border: 1px solid #3a75bd; + -webkit-border-radius: 6px; + color: white; + text-shadow: 0 0 2px black; +} +div#header h1 { + padding-left: 37px; + margin: 0; + display: inline; + background: url('gear.png') 12px 60% no-repeat; + color: white; +} +div#header p { + font-size: 84%; + font-style: italic; + padding: 0; + margin: 0; + color: white; + padding-left: 0.4em; + display: inline; +} +div#header div.navigation { + position: absolute; + top: 0; + right: 1em; + line-height: 3.5em; + font-size: 92%; +} +div#header select { + font-size: 100%; + font-family: Helvetica, sans-serif; + border: 1px solid #3a75bd; + line-height: 140%; + color: #315d94; +} +div#header select option { + padding: 0 0.2em; +} + +div#sidebar { + display: none; + float: left; + margin-left: 26px; + width: 45em; + min-height: 20em; + padding: 0.75em; + padding-top: 0; + + border-right: 1px solid #cfcfcf; +} +div#content { + margin-left: 0px; +} + +div.viewOptions { + float: right; + font-size: 92%; + color: #5f5f5f; + margin-top: 1em; +} +hr { + visibility: hidden; + display: inline; + padding: 0 0.5em; +} +div.viewOptions input { + font-family: Helvetica, sans-serif; + font-size: 100%; + border: 1px solid #b5b5b5; + -webkit-border-radius: 6px; + padding: 0.3em 0.4em; +} +div.viewOptions input:focus { + border-color: white; +} + +.k { + opacity: 0.4; + font-weight: normal; + padding-left: 0.1em; +} + +.legend { + font-size: 84%; + padding: 0; + padding-top: 0.4em; + margin-top: 2em; + text-align: right; + line-height: 140%; + color: #7f7f7f; +} +.legend h3 { + padding: 0; + padding-right: 0.5em; + margin: 0; + font-weight: normal; + color: black; + display: inline; + font-size: 100%; +} +.legend .swatch { + opacity: 0.66; + padding: 0 0.5em; + display: inline-block; + margin-right: 0.2em; + height: 0.9em; +} +.legend .swatch.heavyUse { + background: #cc0000; +} + +table.list { + width: 100%; + line-height: 200%; + border-collapse: collapse; + font-size: 84%; + table-layout: fixed; +} +table.list:not([class*='filtered']) tr:nth-child(odd) td { + background: #eff3ff; +} +.hidden { + display: none; +} +table.list th { + padding: 0 0.5em; + vertical-align: top; + font-weight: bold; + color: #315d94; + color: black; + white-space: nowrap; +} +table.list .firstRow th { + text-align: left; + line-height: 100%; +} +table.list .secondRow * { + text-align: left; + border-bottom: 1px solid #b5c6de; +} +table.list td { + padding: 0 0.5em; + vertical-align: top; + line-height: 1.4em; + padding-top: 0.35em; +} +table.list tr td:nth-last-child(1), +table.list tr th:nth-last-child(1) { + padding-right: 1em; +} +table.list:not([class*='filtered']) .tab .name { + padding-left: 1.5em; +} + +table.list .name { + width: 100%; +} + +table.list .name div { + height: 1.6em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +table.list .pid { + width: 4em; + text-align: right; +} +table.list .pid .th { + padding: 0; +} +table.list .type { + width: 5em; +} +table.list .number { + width: 7em; + text-align: right; +} +table.list .total { + font-weight: bold; +} +table.list .total .name { + color: #315d94; + text-align: right; +} +table.list .total td { + border-top: 1px solid #b5c6de; + background: white !important; +} +table.list .noResults { + display: none; +} +table.list.noResults .noResults { + display: table-row; +} +table.list .noResults td { + text-align: center; + padding: 3em 0; + color: #3f3f3f; +} + +.heavyUse { + color: #cc0000; + font-weight: bold; +} + +table.list#memoryDetails tr:not([class*='firstRow']) > *:nth-child(2), +table.list#memoryDetails tr:not([class*='firstRow']) > *:nth-child(5), +table.list#memoryDetails tr.firstRow th:nth-child(2) { + border-right: 1px solid #b5c6de; +} + +table.list#browserComparison tr:not([class*='firstRow']) > *:nth-child(1), +table.list#browserComparison tr:not([class*='firstRow']) > *:nth-child(4), +table.list#browserComparison tr.firstRow th:nth-child(1) { + border-right: 1px solid #b5c6de; +} +table.list#browserComparison .name { + padding-left: 25px; + background-position: 5px center; + background-repeat: no-repeat; +} + +div.help { + display: inline-block; + width: 14px; + margin: -1px 0; + height: 14px; + background: url('help.gif') center bottom no-repeat; + opacity: 0.33; +} +div.help:hover { + opacity: 1; +} +div.help div { + display: none; +} +#helpTooltip { + z-index: 1000; + position: absolute; + background: #d6e8ff; + padding: 0.3em 0.8em; + max-width: 30em; + -webkit-box-shadow: 3px 3px 4px rgba(0, 0, 0, 0.33); + border: 1px solid #a8cfff; + -webkit-border-radius: 0; + line-height: 140%; + font-size: 92%; +} +#helpTooltip p { + margin: 0.6em 0; +} +div.otherbrowsers { + font-family: Helvetica, sans-serif; + font-size: 84%; + width: 100%; + text-align: center; +} +</style> +<script> +function reload() { + if (document.getElementById('helpTooltip')) + return; + history.go(0); +} + +function formatNumber(str) { + str += ''; + if (str == '0') { + return 'N/A '; + } + var x = str.split('.'); + var x1 = x[0]; + var x2 = x.length > 1 ? '.' + x[1] : ''; + var regex = /(\d+)(\d{3})/; + while (regex.test(x1)) { + x1 = x1.replace(regex, '$1' + ',' + '$2'); + } + return x1; +} + +function addToSum(id, value) { + var target = document.getElementById(id); + var sum = parseInt(target.innerHTML); + sum += parseInt(value); + target.innerHTML = sum; +} + +function handleHelpTooltipMouseOver(event) { + var el = document.createElement('div'); + el.id = 'helpTooltip'; + el.innerHTML = event.toElement.getElementsByTagName('div')[0].innerHTML; + el.style.top = 0; + el.style.left = 0; + el.style.visibility = 'hidden'; + document.body.appendChild(el); + + var width = el.offsetWidth; + var height = el.offsetHeight; + + if (event.pageX - width - 50 + document.body.scrollLeft >= 0 ) { + el.style.left = (event.pageX - width - 20) + 'px'; + } else { + el.style.left = (event.pageX + 20) + 'px'; + } + + + if (event.pageY - height - 50 + document.body.scrollTop >= 0) { + el.style.top = (event.pageY - height - 20) + 'px'; + } else { + el.style.top = (event.pageY + 20) + 'px'; + } + + el.style.visibility = 'visible'; +} + +function handleHelpTooltipMouseOut(event) { + var el = document.getElementById('helpTooltip'); + el.parentNode.removeChild(el); +} + +function enableHelpTooltips() { + var helpEls = document.getElementsByClassName('help'); + + for (var i = 0, helpEl; helpEl = helpEls[i]; i++) { + helpEl.onmouseover = handleHelpTooltipMouseOver; + helpEl.onmouseout = handleHelpTooltipMouseOut; + } +} + +//setInterval("reload()", 10000); +</script> +</head> +<body> + <div id='header'> + <h1> + About memory + </h1> + <p> + Measuring memory usage in a multi-process browser + </p> + </div> + + <div id='content'> + <h2> + Summary + <div class='help'> + <div> + <p> + Summary of memory used by currently active browsers.<p> + For Chromium, processes used to to display diagnostics + information (such as this "about:memory") are excluded. + </p> + </div> + </div> + </h2> + + <table class='list' id='browserComparison'> + <colgroup> + <col class='name' /> + <col class='number' /> + <col class='number' /> + </colgroup> + <tr class='firstRow doNotFilter'> + <th> + </th> + <th colspan='2'> + Memory + <div class='help'> + <div> + <p> + <strong>Memory</strong> + </p> + <p> + <strong>Resident:</strong> + Amount of memory that is present in physical RAM. + This is the best indicator of browser memory resource usage. + </p> + <p> + <strong>Virtual:</strong> + Amount of address space allocated in virtual memory. + </p> + + <p> + <i>(Note that the memory for this tab is not included in the browser totals.)</i> + </p> + </div> + </div> + </th> + </tr> + <tr class='secondRow doNotFilter'> + <th class='name'> + Browser + </th> + <th class='name'> + Resident + </th> + <th class='number'> + Virtual + </th> + </tr> + <tr jsselect="browsers"> + <td class='name'> + <div> + <strong jscontent="name"></strong> <span jscontent="version"></span> + </div> + </td> + <td class='number'> + <span class='th' jscontent="formatNumber(ws_shareable)"></span><span class='k'>k</span> + </td> + <td class='number'> + <span class='th' jscontent="formatNumber(comm_priv)"></span><span class='k'>k</span> + </td> + </tr> + </table> + <div class=otherbrowsers jsdisplay="browsers.length == 1"> + Note: If other browsers (e.g., Safari, Firefox, Camino) are running, I'll show their memory details here. + </div> + <div class="otherbrowsers"> + (Bug: We seriously overcount our own memory usage: <a href="http://crbug.com/25454">Issue 25454</a>.) + </div> + + <br /><br /><br /> + + <h2> + Processes + <div class='help'> + <div> + <p> + Details of memory usage for each of Chromium's processes. + </p> + </div> + </div> + </h2> + + <table class='list' id='memoryDetails'> + <colgroup> + <col class='pid' /> + <col class='name' /> + <col class='number' /> + <col class='number' /> + </colgroup> + <tr class='firstRow doNotFilter'> + <th> + </th> + <th> + </th> + <th colspan='2'> + Memory + </th> + </tr> + <tr class='secondRow doNotFilter'> + <th class='pid'> + PID + </th> + <th class='name'> + Name + </th> + <th class='number'> + Resident + </th> + <th class='number'> + Virtual + </th> + </tr> + + <tr jsselect="browzr_data"> + <td class='pid'> + <span class='th' jscontent="pid"></span> + </td> + <td class='name'> + <div> + Browser + </div> + </td> + <td class='number'> + <span class='th' jscontent="formatNumber(ws_shareable)"></span><span class='k'>k</span> + </td> + <td class='number'> + <span class='th' jscontent="formatNumber(comm_priv)"></span><span class='k'>k</span> + </td> + </tr> + <tr jsselect="child_data"> + <td class='pid'> + <span class='th' jscontent="pid"></span> + </td> + <td class='name'> + <div jscontent="child_name"></div> + <div jsselect="titles"> + <span jscontent="$this"></span><br> + </div> + </td> + <td class='number'> + <span class='th' jscontent="formatNumber(ws_shareable)"></span><span class='k'>k</span> + </td> + <td class='number'> + <span class='th' jscontent="formatNumber(comm_priv)"></span><span class='k'>k</span> + </td> + </tr> + + <tr class='noResults'> + <td colspan='99'> + No results found. + </td> + </tr> + </table> + <div class="otherbrowsers"> + (Note: Due to memory sharing between processes, summing memory usage does not give total memory usage.) + </div> + </div> +</body> +<script> + enableHelpTooltips(); +</script> +</html> |