Chromium Code Reviews| Index: base/process_util_linux.cc |
| =================================================================== |
| --- base/process_util_linux.cc (revision 125518) |
| +++ base/process_util_linux.cc (working copy) |
| @@ -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 @@ |
| }; |
| 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)); |
| + return FilePath(kProcDir).Append(base::IntToString(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; |
| - base::SplitString(mem_stats, ' ', proc_stats); |
| + } |
| 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; |
| + } |
| + 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. |
| +int 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)) { |
| + int value; |
| + if (base::StringToInt(proc_stats[field_num], &value)) |
| + return value; |
| + } |
| + NOTREACHED(); |
|
Jeffrey Yasskin
2012/09/27 23:33:07
This NOTREACHED gets hit when a field in proc is t
|
| + return 0; |
| +} |
| + |
| +// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and |
| +// ReadProcStats(). See GetProcStatsFieldAsInt() for details. |
| +int 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 @@ |
| 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 @@ |
| 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". |
| +int 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 @@ |
| 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; |
| + } |
| + int value; |
| + if (!base::StringToInt(split_value_str[0], &value)) { |
| + NOTREACHED(); |
| + return 0; |
| + } |
| + return value; |
| } |
| state = KEY_NAME; |
| break; |
| } |
| } |
| NOTREACHED(); |
| + return 0; |
| +} |
| + |
| +} // namespace |
| + |
| +namespace base { |
| + |
| +ProcessId GetParentProcessId(ProcessHandle process) { |
| + ProcessId pid = ReadProcStatsAndGetFieldAsInt(process, VM_PPID); |
| + if (pid) |
| + return pid; |
| return -1; |
| } |
| @@ -166,66 +308,45 @@ |
| 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 @@ |
| 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 @@ |
| // 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 @@ |
| 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 @@ |
| // 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 @@ |
| // 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; |
| + int utime = GetProcStatsFieldAsInt(proc_stats, VM_UTIME); |
| + int stime = GetProcStatsFieldAsInt(proc_stats, VM_STIME); |
| + return utime + stime; |
| } |
| namespace { |
| @@ -519,15 +592,15 @@ |
| 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; |
| } |