summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorviettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-05 23:37:40 +0000
committerviettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-05 23:37:40 +0000
commitf164cea41472f9e9ec21579233e2c42a7b9d5184 (patch)
tree523c6aa180be86ace3ac7797e4c9c75c369dff57
parent422c0f17466cddf30fd6815f9d3519a3815264c6 (diff)
downloadchromium_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
-rw-r--r--base/mac_util.h8
-rw-r--r--base/mac_util.mm49
-rw-r--r--base/mac_util_unittest.mm44
-rw-r--r--base/process_util.h8
-rw-r--r--base/process_util_posix.cc58
-rw-r--r--base/process_util_unittest.cc45
-rw-r--r--chrome/browser/browser_resources.grd6
-rw-r--r--chrome/browser/memory_details.cc55
-rw-r--r--chrome/browser/memory_details.h14
-rw-r--r--chrome/browser/memory_details_mac.cc226
-rw-r--r--chrome/browser/process_info_snapshot.h106
-rw-r--r--chrome/browser/process_info_snapshot_mac.cc156
-rw-r--r--chrome/browser/process_info_snapshot_mac_unittest.cc85
-rw-r--r--chrome/browser/resources/about_memory_mac.html577
-rwxr-xr-xchrome/chrome.gyp4
-rwxr-xr-xchrome/common/temp_scaffolding_stubs.cc15
16 files changed, 1405 insertions, 51 deletions
diff --git a/base/mac_util.h b/base/mac_util.h
index 093ae63..a65576d 100644
--- a/base/mac_util.h
+++ b/base/mac_util.h
@@ -81,6 +81,14 @@ void ActivateProcess(pid_t);
// Pulls a snapshot of the entire browser into png_representation.
void GrabWindowSnapshot(NSWindow* window,
std::vector<unsigned char>* png_representation);
+
+// Takes a path to an (executable) binary and tries to provide the path to an
+// application bundle containing it. It takes the outermost bundle that it can
+// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app").
+// |exec_name| - path to the binary
+// returns - path to the application bundle, or empty on error
+FilePath GetAppBundlePath(const FilePath& exec_name);
+
} // namespace mac_util
#endif // BASE_MAC_UTIL_H_
diff --git a/base/mac_util.mm b/base/mac_util.mm
index de07c88..0eee0e2 100644
--- a/base/mac_util.mm
+++ b/base/mac_util.mm
@@ -188,4 +188,53 @@ void ActivateProcess(pid_t pid) {
}
}
+// Takes a path to an (executable) binary and tries to provide the path to an
+// application bundle containing it. It takes the outermost bundle that it can
+// find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app").
+// |exec_name| - path to the binary
+// returns - path to the application bundle, or empty on error
+FilePath GetAppBundlePath(const FilePath& exec_name) {
+ const char kExt[] = ".app";
+ const size_t kExtLength = arraysize(kExt) - 1;
+
+ // Split the path into components.
+ std::vector<std::string> components;
+ exec_name.GetComponents(&components);
+
+ // It's an error if we don't get any components.
+ if (!components.size())
+ return FilePath();
+
+ // Don't prepend '/' to the first component.
+ std::vector<std::string>::const_iterator it = components.begin();
+ std::string bundle_name = *it;
+ DCHECK(it->length() > 0);
+ // If the first component ends in ".app", we're already done.
+ if (it->length() > kExtLength &&
+ !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
+ return FilePath(bundle_name);
+
+ // The first component may be "/" or "//", etc. Only append '/' if it doesn't
+ // already end in '/'.
+ if (bundle_name[bundle_name.length() - 1] != '/')
+ bundle_name += '/';
+
+ // Go through the remaining components.
+ for (++it; it != components.end(); ++it) {
+ DCHECK(it->length() > 0);
+
+ bundle_name += *it;
+
+ // If the current component ends in ".app", we're done.
+ if (it->length() > kExtLength &&
+ !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
+ return FilePath(bundle_name);
+
+ // Separate this component from the next one.
+ bundle_name += '/';
+ }
+
+ return FilePath();
+}
+
} // namespace mac_util
diff --git a/base/mac_util_unittest.mm b/base/mac_util_unittest.mm
index 162a746..0abdee7 100644
--- a/base/mac_util_unittest.mm
+++ b/base/mac_util_unittest.mm
@@ -57,3 +57,47 @@ TEST_F(MacUtilTest, TestGrabWindowSnapshot) {
[color getRed:&red green:&green blue:&blue alpha:&alpha];
EXPECT_GE(red + green + blue, 3.0);
}
+
+TEST_F(MacUtilTest, TestGetAppBundlePath) {
+ FilePath out;
+
+ // Make sure it doesn't crash.
+ out = mac_util::GetAppBundlePath(FilePath());
+ EXPECT_TRUE(out.empty());
+
+ // Some more invalid inputs.
+ const char* invalid_inputs[] = {
+ "/", "/foo", "foo", "/foo/bar.", "foo/bar.", "/foo/bar./bazquux",
+ "foo/bar./bazquux", "foo/.app", "//foo",
+ };
+ for (size_t i = 0; i < arraysize(invalid_inputs); i++) {
+ out = mac_util::GetAppBundlePath(FilePath(invalid_inputs[i]));
+ EXPECT_TRUE(out.empty()) << "loop: " << i;
+ }
+
+ // Some valid inputs; this and |expected_outputs| should be in sync.
+ struct {
+ const char *in;
+ const char *expected_out;
+ } valid_inputs[] = {
+ { "FooBar.app/", "FooBar.app" },
+ { "/FooBar.app", "/FooBar.app" },
+ { "/FooBar.app/", "/FooBar.app" },
+ { "//FooBar.app", "//FooBar.app" },
+ { "/Foo/Bar.app", "/Foo/Bar.app" },
+ { "/Foo/Bar.app/", "/Foo/Bar.app" },
+ { "/F/B.app", "/F/B.app" },
+ { "/F/B.app/", "/F/B.app" },
+ { "/Foo/Bar.app/baz", "/Foo/Bar.app" },
+ { "/Foo/Bar.app/baz/", "/Foo/Bar.app" },
+ { "/Foo/Bar.app/baz/quux.app/quuux", "/Foo/Bar.app" },
+ { "/Applications/Google Foo.app/bar/Foo Helper.app/quux/Foo Helper",
+ "/Applications/Google Foo.app" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(valid_inputs); i++) {
+ out = mac_util::GetAppBundlePath(FilePath(valid_inputs[i].in));
+ EXPECT_FALSE(out.empty()) << "loop: " << i;
+ EXPECT_STREQ(valid_inputs[i].expected_out,
+ out.value().c_str()) << "loop: " << i;
+ }
+}
diff --git a/base/process_util.h b/base/process_util.h
index cc91c67..135b92e 100644
--- a/base/process_util.h
+++ b/base/process_util.h
@@ -169,6 +169,14 @@ bool LaunchApp(const CommandLine& cl,
// successfully.
bool GetAppOutput(const CommandLine& cl, std::string* output);
+#if defined(OS_POSIX)
+// A restricted version of |GetAppOutput()| which (a) clears the environment,
+// and (b) stores at most |max_output| bytes; also, it doesn't search the path
+// for the command.
+bool GetAppOutputRestricted(const CommandLine& cl,
+ std::string* output, size_t max_output);
+#endif
+
// Used to filter processes by process ID.
class ProcessFilter {
public:
diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc
index d5eb4ee..bfcd739 100644
--- a/base/process_util_posix.cc
+++ b/base/process_util_posix.cc
@@ -541,10 +541,24 @@ int ProcessMetrics::GetCPUUsage() {
}
#endif
-bool GetAppOutput(const CommandLine& cl, std::string* output) {
+// Executes the application specified by |cl| and wait for it to exit. Stores
+// the output (stdout) in |output|. If |do_search_path| is set, it searches the
+// path for the application; in that case, |envp| must be null, and it will use
+// the current environment. If |do_search_path| is false, |cl| should fully
+// specify the path of the application, and |envp| will be used as the
+// environment. Redirects stderr to /dev/null. Returns true on success
+// (application launched and exited cleanly, with exit code indicating success).
+// |output| is modified only when the function finished successfully.
+static bool GetAppOutputInternal(const CommandLine& cl, char* const envp[],
+ std::string* output, size_t max_output,
+ bool do_search_path) {
int pipe_fd[2];
pid_t pid;
+ // Either |do_search_path| should be false or |envp| should be null, but not
+ // both.
+ DCHECK(!do_search_path ^ !envp);
+
if (pipe(pipe_fd) < 0)
return false;
@@ -583,7 +597,10 @@ bool GetAppOutput(const CommandLine& cl, std::string* output) {
for (size_t i = 0; i < argv.size(); i++)
argv_cstr[i] = const_cast<char*>(argv[i].c_str());
argv_cstr[argv.size()] = NULL;
- execvp(argv_cstr[0], argv_cstr.get());
+ if (do_search_path)
+ execvp(argv_cstr[0], argv_cstr.get());
+ else
+ execve(argv_cstr[0], argv_cstr.get(), envp);
_exit(127);
}
default: // parent
@@ -595,20 +612,28 @@ bool GetAppOutput(const CommandLine& cl, std::string* output) {
char buffer[256];
std::string buf_output;
+ size_t output_left = max_output;
+ ssize_t bytes_read = 1; // A lie to properly handle |max_output == 0|
+ // case in the logic below.
- while (true) {
- ssize_t bytes_read =
- HANDLE_EINTR(read(pipe_fd[0], buffer, sizeof(buffer)));
+ while (output_left > 0) {
+ bytes_read = HANDLE_EINTR(read(pipe_fd[0], buffer,
+ std::min(output_left, sizeof(buffer))));
if (bytes_read <= 0)
break;
buf_output.append(buffer, bytes_read);
+ output_left -= static_cast<size_t>(bytes_read);
}
close(pipe_fd[0]);
- int exit_code = EXIT_FAILURE;
- bool success = WaitForExitCode(pid, &exit_code);
- if (!success || exit_code != EXIT_SUCCESS)
- return false;
+ // If we stopped because we read as much as we wanted, we always declare
+ // success (because the child may exit due to |SIGPIPE|).
+ if (output_left || bytes_read <= 0) {
+ int exit_code = EXIT_FAILURE;
+ bool success = WaitForExitCode(pid, &exit_code);
+ if (!success || exit_code != EXIT_SUCCESS)
+ return false;
+ }
output->swap(buf_output);
return true;
@@ -616,6 +641,21 @@ bool GetAppOutput(const CommandLine& cl, std::string* output) {
}
}
+bool GetAppOutput(const CommandLine& cl, std::string* output) {
+ // Run |execve()| with the current environment and store "unlimited" data.
+ return GetAppOutputInternal(cl, NULL, output,
+ std::numeric_limits<std::size_t>::max(), true);
+}
+
+// TODO(viettrungluu): Conceivably, we should have a timeout as well, so we
+// don't hang if what we're calling hangs.
+bool GetAppOutputRestricted(const CommandLine& cl,
+ std::string* output, size_t max_output) {
+ // Run |execve()| with the empty environment.
+ char* const empty_environ = NULL;
+ return GetAppOutputInternal(cl, &empty_environ, output, max_output, false);
+}
+
int GetProcessCount(const std::wstring& executable_name,
const ProcessFilter* filter) {
int count = 0;
diff --git a/base/process_util_unittest.cc b/base/process_util_unittest.cc
index e2fb5bf..961ac7e 100644
--- a/base/process_util_unittest.cc
+++ b/base/process_util_unittest.cc
@@ -287,6 +287,51 @@ TEST_F(ProcessUtilTest, GetAppOutput) {
EXPECT_STREQ("foobar42", output.c_str());
}
+TEST_F(ProcessUtilTest, GetAppOutputRestricted) {
+ // Unfortunately, since we can't rely on the path, we need to know where
+ // everything is. So let's use /bin/sh, which is on every POSIX system, and
+ // its built-ins.
+ std::vector<std::string> argv;
+ argv.push_back("/bin/sh"); // argv[0]
+ argv.push_back("-c"); // argv[1]
+
+ // On success, should set |output|. We use |/bin/sh -c 'exit 0'| instead of
+ // |true| since the location of the latter may be |/bin| or |/usr/bin| (and we
+ // need absolute paths).
+ argv.push_back("exit 0"); // argv[2]; equivalent to "true"
+ std::string output = "abc";
+ EXPECT_TRUE(GetAppOutputRestricted(CommandLine(argv), &output, 100));
+ EXPECT_STREQ("", output.c_str());
+
+ // On failure, should not touch |output|. As above, but for |false|.
+ argv[2] = "exit 1"; // equivalent to "false"
+ output = "abc";
+ EXPECT_FALSE(GetAppOutputRestricted(CommandLine(argv),
+ &output, 100));
+ EXPECT_STREQ("abc", output.c_str());
+
+ // Amount of output exactly equal to space allowed.
+ argv[2] = "echo 123456789"; // (the sh built-in doesn't take "-n")
+ output.clear();
+ EXPECT_TRUE(GetAppOutputRestricted(CommandLine(argv), &output, 10));
+ EXPECT_STREQ("123456789\n", output.c_str());
+
+ // Amount of output greater than space allowed.
+ output.clear();
+ EXPECT_TRUE(GetAppOutputRestricted(CommandLine(argv), &output, 5));
+ EXPECT_STREQ("12345", output.c_str());
+
+ // Amount of output less than space allowed.
+ output.clear();
+ EXPECT_TRUE(GetAppOutputRestricted(CommandLine(argv), &output, 15));
+ EXPECT_STREQ("123456789\n", output.c_str());
+
+ // Zero space allowed.
+ output = "abc";
+ EXPECT_TRUE(GetAppOutputRestricted(CommandLine(argv), &output, 0));
+ EXPECT_STREQ("", output.c_str());
+}
+
#if defined(OS_LINUX)
TEST_F(ProcessUtilTest, GetParentProcessId) {
base::ProcessId ppid = GetParentProcessId(GetCurrentProcId());
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>
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index efdc20b..cf23373 100755
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1764,6 +1764,7 @@
'browser/login_prompt_win.cc',
'browser/memory_details.cc',
'browser/memory_details_linux.cc',
+ 'browser/memory_details_mac.cc',
'browser/memory_details_win.cc',
'browser/memory_details.h',
'browser/memory_purger.cc',
@@ -1935,6 +1936,8 @@
'browser/privacy_blacklist/blacklist_store.cc',
'browser/privacy_blacklist/blocked_response.h',
'browser/privacy_blacklist/blocked_response.cc',
+ 'browser/process_info_snapshot_mac.cc',
+ 'browser/process_info_snapshot.h',
'browser/process_singleton.h',
'browser/process_singleton_linux.cc',
'browser/process_singleton_mac.cc',
@@ -4651,6 +4654,7 @@
'browser/privacy_blacklist/blacklist_manager_unittest.cc',
'browser/privacy_blacklist/blacklist_unittest.cc',
'browser/privacy_blacklist/blacklist_io_unittest.cc',
+ 'browser/process_info_snapshot_mac_unittest.cc',
'browser/profile_manager_unittest.cc',
'browser/renderer_host/audio_renderer_host_unittest.cc',
'browser/renderer_host/render_widget_host_unittest.cc',
diff --git a/chrome/common/temp_scaffolding_stubs.cc b/chrome/common/temp_scaffolding_stubs.cc
index a1dc297..936e35b 100755
--- a/chrome/common/temp_scaffolding_stubs.cc
+++ b/chrome/common/temp_scaffolding_stubs.cc
@@ -66,21 +66,6 @@ void InstallJankometer(const CommandLine&) {
void UninstallJankometer() {
// http://code.google.com/p/chromium/issues/detail?id=8077
}
-MemoryDetails::MemoryDetails() {
-
- NOTIMPLEMENTED();
- process_data_.push_back(ProcessData());
-}
-
-void MemoryDetails::StartFetch() {
- NOTIMPLEMENTED();
-
- // Other implementations implicitly own the object by passing it to
- // IO and UI tasks. This code is called from AboutMemoryHandler's
- // constructor, so there is no reference to Release(), yet.
- MessageLoop::current()->PostTask(
- FROM_HERE, NewRunnableMethod(this, &MemoryDetails::OnDetailsAvailable));
-}
void BrowserList::AllBrowsersClosed() {
// TODO(port): Close any dependent windows if necessary when the last browser