diff options
author | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-03 08:34:23 +0000 |
---|---|---|
committer | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-03 08:34:23 +0000 |
commit | 879acba578e6b5573bc9b1250ac55836007a5c47 (patch) | |
tree | 49496175a7b071f6a5c343515006d97a46b2ed9f /base/process_util_linux.cc | |
parent | 8f73f9426f0d07f2635f9120863dd6e496f22fee (diff) | |
download | chromium_src-879acba578e6b5573bc9b1250ac55836007a5c47.zip chromium_src-879acba578e6b5573bc9b1250ac55836007a5c47.tar.gz chromium_src-879acba578e6b5573bc9b1250ac55836007a5c47.tar.bz2 |
Overhaul base/process_util_linux.cc.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/9584024
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124839 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/process_util_linux.cc')
-rw-r--r-- | base/process_util_linux.cc | 355 |
1 files changed, 214 insertions, 141 deletions
diff --git a/base/process_util_linux.cc b/base/process_util_linux.cc index 5c52d56..c44163e 100644 --- a/base/process_util_linux.cc +++ b/base/process_util_linux.cc @@ -4,15 +4,10 @@ #include "base/process_util.h" -#include <ctype.h> #include <dirent.h> -#include <dlfcn.h> -#include <errno.h> -#include <fcntl.h> +#include <malloc.h> #include <sys/time.h> #include <sys/types.h> -#include <sys/wait.h> -#include <time.h> #include <unistd.h> #include "base/file_util.h" @@ -32,26 +27,125 @@ enum ParsingState { }; const char kProcDir[] = "/proc"; +const char kStatFile[] = "stat"; // Returns a FilePath to "/proc/pid". FilePath GetProcPidDir(pid_t pid) { return FilePath(kProcDir).Append(base::Int64ToString(pid)); } -// Reads /proc/<pid>/stat and populates |proc_stats| with the values split by -// spaces. Returns true if successful. -bool GetProcStats(pid_t pid, std::vector<std::string>* proc_stats) { +// Fields from /proc/<pid>/stat, 0-based. See man 5 proc. +// If the ordering ever changes, carefully review functions that use these +// values. +enum ProcStatsFields { + VM_COMM = 1, // Filename of executable, without parentheses. + VM_STATE = 2, // Letter indicating the state of the process. + VM_PPID = 3, // PID of the parent. + VM_PGRP = 4, // Process group id. + VM_UTIME = 13, // Time scheduled in user mode in clock ticks. + VM_STIME = 14, // Time scheduled in kernel mode in clock ticks. + VM_VSIZE = 22, // Virtual memory size in bytes. + VM_RSS = 23, // Resident Set Size in pages. +}; + +// Reads /proc/<pid>/stat into |buffer|. Returns true if successful. +bool ReadProcStats(pid_t pid, std::string* buffer) { // Synchronously reading files in /proc is safe. base::ThreadRestrictions::ScopedAllowIO allow_io; - FilePath stat_file = GetProcPidDir(pid).Append("stat"); - std::string mem_stats; - if (!file_util::ReadFileToString(stat_file, &mem_stats)) + FilePath stat_file = GetProcPidDir(pid).Append(kStatFile); + if (!file_util::ReadFileToString(stat_file, buffer)) { + DLOG(WARNING) << "Failed to get process stats."; + return false; + } + return true; +} + +// Takes |stats_data| and populates |proc_stats| with the values split by +// spaces. Taking into account the 2nd field may, in itself, contain spaces. +// Returns true if successful. +bool ParseProcStats(const std::string& stats_data, + std::vector<std::string>* proc_stats) { + // The stat file is formatted as: + // pid (process name) data1 data2 .... dataN + // Look for the closing paren by scanning backwards, to avoid being fooled by + // processes with ')' in the name. + size_t open_parens_idx = stats_data.find(" ("); + size_t close_parens_idx = stats_data.rfind(") "); + if (open_parens_idx == std::string::npos || + close_parens_idx == std::string::npos || + open_parens_idx > close_parens_idx) { + NOTREACHED(); return false; - base::SplitString(mem_stats, ' ', proc_stats); + } + open_parens_idx++; + + proc_stats->clear(); + // PID. + proc_stats->push_back(stats_data.substr(0, open_parens_idx)); + // Process name without parentheses. + proc_stats->push_back( + stats_data.substr(open_parens_idx + 1, + close_parens_idx - (open_parens_idx + 1))); + + // Split the rest. + std::vector<std::string> other_stats; + base::SplitString(stats_data.substr(close_parens_idx + 2), ' ', &other_stats); + for (size_t i = 0; i < other_stats.size(); ++i) + proc_stats->push_back(other_stats[i]); return true; } +// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure. +// This version does not handle the first 3 values, since the first value is +// simply |pid|, and the next two values are strings. +size_t GetProcStatsFieldAsInt(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + if (field_num < VM_PPID) { + NOTREACHED(); + return 0; + } + + if (proc_stats.size() > static_cast<size_t>(field_num)) { + size_t value; + if (base::StringToUint64(proc_stats[field_num], &value)) + return value; + } + NOTREACHED(); + return 0; +} + +// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and +// ReadProcStats(). See GetProcStatsFieldAsInt() for details. +size_t ReadProcStatsAndGetFieldAsInt(pid_t pid, ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector<std::string> proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsInt(proc_stats, field_num); +} + +// Reads the |field_num|th field from |proc_stats|. +// Returns an empty string on failure. +// This version only handles VM_COMM and VM_STATE, which are the only fields +// that are strings. +std::string GetProcStatsFieldAsString( + const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + if (field_num < VM_COMM || field_num > VM_STATE) { + NOTREACHED(); + return ""; + } + + if (proc_stats.size() > static_cast<size_t>(field_num)) + return proc_stats[field_num]; + + NOTREACHED(); + return 0; +} + // Reads /proc/<pid>/cmdline and populates |proc_cmd_line_args| with the command // line arguments. Returns true if successful. // Note: /proc/<pid>/cmdline contains command line arguments separated by single @@ -71,28 +165,53 @@ bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) { return true; } +// Take a /proc directory entry named |d_name|, and if it is the directory for +// a process, convert it to a pid_t. +// Returns 0 on failure. +// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234. +pid_t ProcDirSlotToPid(const char* d_name) { + int i; + for (i = 0; i < NAME_MAX && d_name[i]; ++i) { + if (!IsAsciiDigit(d_name[i])) { + return 0; + } + } + if (i == NAME_MAX) + return 0; + + // Read the process's command line. + pid_t pid; + std::string pid_string(d_name); + if (!base::StringToInt(pid_string, &pid)) { + NOTREACHED(); + return 0; + } + return pid; +} + // Get the total CPU of a single process. Return value is number of jiffies // on success or -1 on error. int GetProcessCPU(pid_t pid) { - // Synchronously reading files in /proc is safe. - base::ThreadRestrictions::ScopedAllowIO allow_io; - // Use /proc/<pid>/task to find all threads and parse their /stat file. - FilePath path = GetProcPidDir(pid).Append("task"); + FilePath task_path = GetProcPidDir(pid).Append("task"); - DIR* dir = opendir(path.value().c_str()); + DIR* dir = opendir(task_path.value().c_str()); if (!dir) { - DPLOG(ERROR) << "opendir(" << path.value() << ")"; + DPLOG(ERROR) << "opendir(" << task_path.value() << ")"; return -1; } int total_cpu = 0; while (struct dirent* ent = readdir(dir)) { - if (ent->d_name[0] == '.') + pid_t tid = ProcDirSlotToPid(ent->d_name); + if (!tid) continue; - FilePath stat_path = path.AppendASCII(ent->d_name).AppendASCII("stat"); + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + std::string stat; + FilePath stat_path = task_path.Append(ent->d_name).Append(kStatFile); if (file_util::ReadFileToString(stat_path, &stat)) { int cpu = base::ParseProcStatCPU(stat); if (cpu > 0) @@ -104,22 +223,21 @@ int GetProcessCPU(pid_t pid) { return total_cpu; } -} // namespace - -namespace base { - -ProcessId GetParentProcessId(ProcessHandle process) { - // Synchronously reading files in /proc is safe. - base::ThreadRestrictions::ScopedAllowIO allow_io; - - FilePath stat_file = GetProcPidDir(process).Append("status"); +// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure. +// Only works for fields in the form of "Field: value kB". +size_t ReadProcStatusAndGetFieldAsInt(pid_t pid, const std::string& field) { + FilePath stat_file = GetProcPidDir(pid).Append("status"); std::string status; - if (!file_util::ReadFileToString(stat_file, &status)) - return -1; + { + // Synchronously reading files in /proc is safe. + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!file_util::ReadFileToString(stat_file, &status)) + return 0; + } StringTokenizer tokenizer(status, ":\n"); ParsingState state = KEY_NAME; - StringPiece last_key_name; + base::StringPiece last_key_name; while (tokenizer.GetNext()) { switch (state) { case KEY_NAME: @@ -128,16 +246,40 @@ ProcessId GetParentProcessId(ProcessHandle process) { break; case KEY_VALUE: DCHECK(!last_key_name.empty()); - if (last_key_name == "PPid") { - int ppid; - base::StringToInt(tokenizer.token_piece(), &ppid); - return ppid; + if (last_key_name == field) { + std::string value_str; + tokenizer.token_piece().CopyToString(&value_str); + std::string value_str_trimmed; + TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed); + std::vector<std::string> split_value_str; + base::SplitString(value_str_trimmed, ' ', &split_value_str); + if (split_value_str.size() != 2 || split_value_str[1] != "kB") { + NOTREACHED(); + return 0; + } + size_t value; + if (!base::StringToUint64(split_value_str[0], &value)) { + NOTREACHED(); + return 0; + } + return value; } state = KEY_NAME; break; } } NOTREACHED(); + return 0; +} + +} // namespace + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + size_t pid = ReadProcStatsAndGetFieldAsInt(process, VM_PPID); + if (pid) + return pid; return -1; } @@ -166,66 +308,45 @@ ProcessIterator::~ProcessIterator() { bool ProcessIterator::CheckForNextProcess() { // TODO(port): skip processes owned by different UID - dirent* slot = 0; - const char* openparen; - const char* closeparen; + pid_t pid = kNullProcessId; std::vector<std::string> cmd_line_args; + std::string stats_data; + std::vector<std::string> proc_stats; // Arbitrarily guess that there will never be more than 200 non-process - // files in /proc. Hardy has 53. + // files in /proc. Hardy has 53 and Lucid has 61. int skipped = 0; const int kSkipLimit = 200; while (skipped < kSkipLimit) { - slot = readdir(procfs_dir_); + dirent* slot = readdir(procfs_dir_); // all done looking through /proc? if (!slot) return false; // If not a process, keep looking for one. - bool notprocess = false; - int i; - for (i = 0; i < NAME_MAX && slot->d_name[i]; ++i) { - if (!isdigit(slot->d_name[i])) { - notprocess = true; - break; - } - } - if (i == NAME_MAX || notprocess) { + pid = ProcDirSlotToPid(slot->d_name); + if (!pid) { skipped++; continue; } - // Read the process's command line. - std::string pid_string(slot->d_name); - int pid; - if (StringToInt(pid_string, &pid) && !GetProcCmdline(pid, &cmd_line_args)) + if (!GetProcCmdline(pid, &cmd_line_args)) continue; - // Read the process's status. - char buf[NAME_MAX + 12]; - sprintf(buf, "/proc/%s/stat", slot->d_name); - FILE* fp = fopen(buf, "r"); - if (!fp) + if (!ReadProcStats(pid, &stats_data)) continue; - const char* result = fgets(buf, sizeof(buf), fp); - fclose(fp); - if (!result) + if (!ParseProcStats(stats_data, &proc_stats)) continue; - // Parse the status. It is formatted like this: - // %d (%s) %c %d %d ... - // pid (name) runstate ppid gid - // To avoid being fooled by names containing a closing paren, scan - // backwards. - openparen = strchr(buf, '('); - closeparen = strrchr(buf, ')'); - if (!openparen || !closeparen) + std::string runstate = GetProcStatsFieldAsString(proc_stats, VM_STATE); + if (runstate.size() != 1) { + NOTREACHED(); continue; - char runstate = closeparen[2]; + } // Is the process in 'Zombie' state, i.e. dead but waiting to be reaped? // Allowed values: D R S T Z - if (runstate != 'Z') + if (runstate[0] != 'Z') break; // Nope, it's a zombie; somebody isn't cleaning up after their children. @@ -237,17 +358,14 @@ bool ProcessIterator::CheckForNextProcess() { return false; } - // This seems fragile. - entry_.pid_ = atoi(slot->d_name); - entry_.ppid_ = atoi(closeparen + 3); - entry_.gid_ = atoi(strchr(closeparen + 4, ' ')); - + entry_.pid_ = pid; + entry_.ppid_ = GetProcStatsFieldAsInt(proc_stats, VM_PPID); + entry_.gid_ = GetProcStatsFieldAsInt(proc_stats, VM_PGRP); entry_.cmd_line_args_.assign(cmd_line_args.begin(), cmd_line_args.end()); // TODO(port): read pid's commandline's $0, like killall does. Using the // short name between openparen and closeparen won't work for long names! - int len = closeparen - openparen - 1; - entry_.exe_file_.assign(openparen + 1, len); + entry_.exe_file_ = GetProcStatsFieldAsString(proc_stats, VM_COMM); return true; } @@ -265,58 +383,22 @@ ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { // On linux, we return vsize. size_t ProcessMetrics::GetPagefileUsage() const { - std::vector<std::string> proc_stats; - if (!GetProcStats(process_, &proc_stats)) - DLOG(WARNING) << "Failed to get process stats."; - const size_t kVmSize = 22; - if (proc_stats.size() > kVmSize) { - int vm_size; - base::StringToInt(proc_stats[kVmSize], &vm_size); - return static_cast<size_t>(vm_size); - } - return 0; + return ReadProcStatsAndGetFieldAsInt(process_, VM_VSIZE); } // On linux, we return the high water mark of vsize. size_t ProcessMetrics::GetPeakPagefileUsage() const { - std::vector<std::string> proc_stats; - if (!GetProcStats(process_, &proc_stats)) - DLOG(WARNING) << "Failed to get process stats."; - const size_t kVmPeak = 21; - if (proc_stats.size() > kVmPeak) { - int vm_peak; - if (base::StringToInt(proc_stats[kVmPeak], &vm_peak)) - return vm_peak; - } - return 0; + return ReadProcStatusAndGetFieldAsInt(process_, "VmPeak") * 1024; } // On linux, we return RSS. size_t ProcessMetrics::GetWorkingSetSize() const { - std::vector<std::string> proc_stats; - if (!GetProcStats(process_, &proc_stats)) - DLOG(WARNING) << "Failed to get process stats."; - const size_t kVmRss = 23; - if (proc_stats.size() > kVmRss) { - int num_pages; - if (base::StringToInt(proc_stats[kVmRss], &num_pages)) - return static_cast<size_t>(num_pages) * getpagesize(); - } - return 0; + return ReadProcStatsAndGetFieldAsInt(process_, VM_RSS) * getpagesize(); } // On linux, we return the high water mark of RSS. size_t ProcessMetrics::GetPeakWorkingSetSize() const { - std::vector<std::string> proc_stats; - if (!GetProcStats(process_, &proc_stats)) - DLOG(WARNING) << "Failed to get process stats."; - const size_t kVmHwm = 23; - if (proc_stats.size() > kVmHwm) { - int num_pages; - base::StringToInt(proc_stats[kVmHwm], &num_pages); - return static_cast<size_t>(num_pages) * getpagesize(); - } - return 0; + return ReadProcStatusAndGetFieldAsInt(process_, "VmHWM") * 1024; } bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, @@ -326,7 +408,7 @@ bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, return false; if (private_bytes) - *private_bytes = ws_usage.priv << 10; + *private_bytes = ws_usage.priv * 1024; if (shared_bytes) *shared_bytes = ws_usage.shared * 1024; @@ -342,7 +424,7 @@ bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { // First we need to get the page size, since everything is measured in pages. // For details, see: man 5 proc. - const int page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; + const int page_size_kb = getpagesize() / 1024; if (page_size_kb <= 0) return false; @@ -473,24 +555,15 @@ ProcessMetrics::ProcessMetrics(ProcessHandle process) // Exposed for testing. int ParseProcStatCPU(const std::string& input) { - // /proc/<pid>/stat contains the process name in parens. In case the - // process name itself contains parens, skip past them. - std::string::size_type rparen = input.rfind(')'); - if (rparen == std::string::npos) + std::vector<std::string> proc_stats; + if (!ParseProcStats(input, &proc_stats)) return -1; - // From here, we expect a bunch of space-separated fields, where the - // 0-indexed 11th and 12th are utime and stime. On two different machines - // I found 42 and 39 fields, so let's just expect the ones we need. - std::vector<std::string> fields; - base::SplitString(input.substr(rparen + 2), ' ', &fields); - if (fields.size() < 13) - return -1; // Output not in the format we expect. - - int fields11, fields12; - base::StringToInt(fields[11], &fields11); - base::StringToInt(fields[12], &fields12); - return fields11 + fields12; + if (proc_stats.size() <= VM_STIME) + return -1; + size_t utime = GetProcStatsFieldAsInt(proc_stats, VM_UTIME); + size_t stime = GetProcStatsFieldAsInt(proc_stats, VM_STIME); + return utime + stime; } namespace { @@ -519,15 +592,15 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { FilePath meminfo_file("/proc/meminfo"); std::string meminfo_data; if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) { - DLOG(WARNING) << "Failed to open /proc/meminfo."; + DLOG(WARNING) << "Failed to open " << meminfo_file.value(); return false; } std::vector<std::string> meminfo_fields; SplitStringAlongWhitespace(meminfo_data, &meminfo_fields); if (meminfo_fields.size() < kMemCachedIndex) { - DLOG(WARNING) << "Failed to parse /proc/meminfo. Only found " << - meminfo_fields.size() << " fields."; + DLOG(WARNING) << "Failed to parse " << meminfo_file.value() + << ". Only found " << meminfo_fields.size() << " fields."; return false; } |