summaryrefslogtreecommitdiffstats
path: root/base
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 /base
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
Diffstat (limited to 'base')
-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
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());