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 /base | |
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 'base')
-rw-r--r-- | base/mac_util.h | 8 | ||||
-rw-r--r-- | base/mac_util.mm | 49 | ||||
-rw-r--r-- | base/mac_util_unittest.mm | 44 | ||||
-rw-r--r-- | base/process_util.h | 8 | ||||
-rw-r--r-- | base/process_util_posix.cc | 58 | ||||
-rw-r--r-- | base/process_util_unittest.cc | 45 |
6 files changed, 203 insertions, 9 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()); |