// 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/sysctl.h> #include <sstream> #include "base/command_line.h" #include "base/logging.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/sys_info.h" #include "base/threading/thread.h" // Default constructor. ProcessInfoSnapshot::ProcessInfoSnapshot() { } // Destructor: just call |Reset()| to release everything. ProcessInfoSnapshot::~ProcessInfoSnapshot() { Reset(); } const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; static bool GetKInfoForProcessID(pid_t pid, kinfo_proc* kinfo) { int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; size_t len = sizeof(*kinfo); if (sysctl(mib, arraysize(mib), kinfo, &len, NULL, 0) != 0) { PLOG(ERROR) << "sysctl() for KERN_PROC"; return false; } if (len == 0) { // If the process isn't found then sysctl returns a length of 0. return false; } return true; } static bool GetExecutableNameForProcessID( pid_t pid, std::string* executable_name) { if (!executable_name) { NOTREACHED(); return false; } static int s_arg_max = 0; if (s_arg_max == 0) { int mib[] = {CTL_KERN, KERN_ARGMAX}; size_t size = sizeof(s_arg_max); if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0) PLOG(ERROR) << "sysctl() for KERN_ARGMAX"; } if (s_arg_max == 0) return false; int mib[] = {CTL_KERN, KERN_PROCARGS, pid}; size_t size = s_arg_max; executable_name->resize(s_arg_max + 1); if (sysctl(mib, arraysize(mib), &(*executable_name)[0], &size, NULL, 0) != 0) { // Don't log the error since it's normal for this to fail. return false; } // KERN_PROCARGS returns multiple NULL terminated strings. Truncate // executable_name to just the first string. size_t end_pos = executable_name->find('\0'); if (end_pos == std::string::npos) { return false; } executable_name->resize(end_pos); return true; } // Converts a byte unit such as 'K' or 'M' into the scale for the unit. // The scale can then be used to calculate the number of bytes in a value. // The units are based on humanize_number(). See: // http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) { int shift = 0; switch (unit) { case 'B': shift = 0; break; case 'K': case 'k': shift = 1; break; case 'M': shift = 2; break; case 'G': shift = 3; break; case 'T': shift = 4; break; case 'P': shift = 5; break; case 'E': shift = 6; break; default: return false; } uint64_t scale = 1; for (int i = 0; i < shift; i++) scale *= 1024; *out_scale = scale; return true; } // 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). static bool GetProcessMemoryInfoUsingPS( const std::vector<base::ProcessId>& pid_list, std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { const char kPsPathName[] = "/bin/ps"; std::vector<std::string> argv; argv.push_back(kPsPathName); // Get resident set size, virtual memory size. argv.push_back("-o"); argv.push_back("pid=,rss=,vsz="); // Only display the specified PIDs. for (std::vector<base::ProcessId>::const_iterator it = pid_list.begin(); it != pid_list.end(); ++it) { argv.push_back("-p"); argv.push_back(base::Int64ToString(static_cast<int64>(*it))); } std::string output; CommandLine command_line(argv); // Limit output read to a megabyte for safety. if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data."; return false; } std::istringstream in(output, std::istringstream::in); std::string line; // Process lines until done. while (true) { // The format is as specified above to ps (see ps(1)): // "-o pid=,rss=,vsz=". // Try to read the PID; if we get it, we should be able to get the rest of // the line. pid_t pid; in >> pid; if (in.eof()) break; ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; proc_info.pid = pid; in >> proc_info.rss; in >> proc_info.vsize; proc_info.rss *= 1024; // Convert from kilobytes to bytes. proc_info.vsize *= 1024; 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 " << kPsPathName << "."; return false; } if (!proc_info.pid || ! proc_info.vsize) { LOG(WARNING) << "Invalid data from " << kPsPathName << "."; return false; } // Record the process information. proc_info_entries[proc_info.pid] = proc_info; } return true; } static bool GetProcessMemoryInfoUsingTop( std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { const char kTopPathName[] = "/usr/bin/top"; std::vector<std::string> argv; argv.push_back(kTopPathName); // -stats tells top to print just the given fields as ordered. argv.push_back("-stats"); argv.push_back("pid," // Process ID "rsize," // Resident memory "rshrd," // Resident shared memory "rprvt," // Resident private memory "vsize"); // Total virtual memory // Run top in logging (non-interactive) mode. argv.push_back("-l"); argv.push_back("1"); // Set the delay between updates to 0. argv.push_back("-s"); argv.push_back("0"); std::string output; CommandLine command_line(argv); // Limit output read to a megabyte for safety. if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data."; return false; } // Process lines until done. Lines should look something like this: // PID RSIZE RSHRD RPRVT VSIZE // 58539 1276K+ 336K+ 740K+ 2378M+ // 58485 1888K+ 592K+ 1332K+ 2383M+ std::istringstream top_in(output, std::istringstream::in); std::string line; while (std::getline(top_in, line)) { std::istringstream in(line, std::istringstream::in); // Try to read the PID. pid_t pid; in >> pid; if (in.fail()) continue; // Make sure that caller is interested in this process. if (proc_info_entries.find(pid) == proc_info_entries.end()) continue; // Skip the - or + sign that top puts after the pid. in.get(); uint64_t values[4]; size_t i; for (i = 0; i < arraysize(values); i++) { in >> values[i]; if (in.fail()) break; std::string unit; in >> unit; if (in.fail()) break; if (unit.empty()) break; uint64_t scale; if (!ConvertByteUnitToScale(unit[0], &scale)) break; values[i] *= scale; } if (i != arraysize(values)) continue; ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; proc_info.rss = values[0]; proc_info.rshrd = values[1]; proc_info.rprvt = values[2]; proc_info.vsize = values[3]; // Record the process information. proc_info_entries[proc_info.pid] = proc_info; } return true; } static bool GetProcessMemoryInfoUsingTop_10_5( std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { const char kTopPathName[] = "/usr/bin/top"; std::vector<std::string> argv; argv.push_back(kTopPathName); // -p tells top to print just the given fields as ordered. argv.push_back("-p"); argv.push_back("^aaaaaaaaaaaaaaaaaaaa " // Process ID (PID) "^jjjjjjjjjjjjjjjjjjjj " // Resident memory (RSIZE) "^iiiiiiiiiiiiiiiiiiii " // Resident shared memory (RSHRD) "^hhhhhhhhhhhhhhhhhhhh " // Resident private memory (RPRVT) "^llllllllllllllllllll"); // Total virtual memory (VSIZE) // Run top in logging (non-interactive) mode. argv.push_back("-l"); argv.push_back("1"); // Set the delay between updates to 0. argv.push_back("-s"); argv.push_back("0"); std::string output; CommandLine command_line(argv); // Limit output read to a megabyte for safety. if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data."; return false; } // Process lines until done. Lines should look something like this: // PID RSIZE RSHRD RPRVT VSIZE // 16943 815104 262144 290816 18489344 // 16922 954368 720896 278528 18976768 std::istringstream top_in(output, std::istringstream::in); std::string line; while (std::getline(top_in, line)) { std::istringstream in(line, std::istringstream::in); // Try to read the PID. pid_t pid; in >> pid; if (in.fail()) continue; // Make sure that caller is interested in this process. if (proc_info_entries.find(pid) == proc_info_entries.end()) continue; uint64_t values[4]; size_t i; for (i = 0; i < arraysize(values); i++) { in >> values[i]; if (in.fail()) break; } if (i != arraysize(values)) continue; ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; proc_info.rss = values[0]; proc_info.rshrd = values[1]; proc_info.rprvt = values[2]; proc_info.vsize = values[3]; // Record the process information. proc_info_entries[proc_info.pid] = proc_info; } return true; } bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { Reset(); // Nothing to do if no PIDs given. if (pid_list.empty()) return true; if (pid_list.size() > kMaxPidListSize) { // The spec says |pid_list| *must* not have more than this many entries. NOTREACHED(); return false; } // Get basic process info from KERN_PROC. for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); it != pid_list.end(); ++it) { ProcInfoEntry proc_info; proc_info.pid = *it; kinfo_proc kinfo; if (!GetKInfoForProcessID(*it, &kinfo)) return false; proc_info.ppid = kinfo.kp_eproc.e_ppid; proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid; proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid; // Note, p_comm is truncated to 16 characters. proc_info.command = kinfo.kp_proc.p_comm; proc_info_entries_[*it] = proc_info; } // Use KERN_PROCARGS to get the full executable name. This may fail if this // process doesn't have privileges to inspect the target process. for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); it != pid_list.end(); ++it) { std::string exectuable_name; if (GetExecutableNameForProcessID(*it, &exectuable_name)) { ProcInfoEntry proc_info = proc_info_entries_[*it]; proc_info.command = exectuable_name; } } // Get memory information using top. bool memory_info_success = false; int32 major, minor, bugfix; base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); if (major == 10 && minor == 5) memory_info_success = GetProcessMemoryInfoUsingTop_10_5(proc_info_entries_); else if ((major == 10 && minor >= 6) || major > 10) memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_); // If top didn't work then fall back to ps. if (!memory_info_success) { memory_info_success = GetProcessMemoryInfoUsingPS(pid_list, proc_info_entries_); } return memory_info_success; } // 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 / 1024; 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 = proc_info.rprvt / 1024; ws_usage->shareable = proc_info.rss / 1024; ws_usage->shared = proc_info.rshrd / 1024; return true; }