Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/process_info_snapshot.h"
      6 
      7 #include <sys/sysctl.h>
      8 
      9 #include <sstream>
     10 
     11 #include "base/command_line.h"
     12 #include "base/files/file_path.h"
     13 #include "base/logging.h"
     14 #include "base/mac/mac_util.h"
     15 #include "base/process/launch.h"
     16 #include "base/strings/string_number_conversions.h"
     17 #include "base/strings/string_util.h"
     18 #include "base/threading/thread.h"
     19 
     20 // Default constructor.
     21 ProcessInfoSnapshot::ProcessInfoSnapshot() { }
     22 
     23 // Destructor: just call |Reset()| to release everything.
     24 ProcessInfoSnapshot::~ProcessInfoSnapshot() {
     25   Reset();
     26 }
     27 
     28 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;
     29 
     30 static bool GetKInfoForProcessID(pid_t pid, kinfo_proc* kinfo) {
     31   int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
     32   size_t len = sizeof(*kinfo);
     33   if (sysctl(mib, arraysize(mib), kinfo, &len, NULL, 0) != 0) {
     34     PLOG(ERROR) << "sysctl() for KERN_PROC";
     35     return false;
     36   }
     37 
     38   if (len == 0) {
     39     // If the process isn't found then sysctl returns a length of 0.
     40     return false;
     41   }
     42 
     43   return true;
     44 }
     45 
     46 static bool GetExecutableNameForProcessID(
     47     pid_t pid,
     48     std::string* executable_name) {
     49   if (!executable_name) {
     50     NOTREACHED();
     51     return false;
     52   }
     53 
     54   static int s_arg_max = 0;
     55   if (s_arg_max == 0) {
     56     int mib[] = {CTL_KERN, KERN_ARGMAX};
     57     size_t size = sizeof(s_arg_max);
     58     if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0)
     59       PLOG(ERROR) << "sysctl() for KERN_ARGMAX";
     60   }
     61 
     62   if (s_arg_max == 0)
     63     return false;
     64 
     65   int mib[] = {CTL_KERN, KERN_PROCARGS, pid};
     66   size_t size = s_arg_max;
     67   executable_name->resize(s_arg_max + 1);
     68   if (sysctl(mib, arraysize(mib), &(*executable_name)[0],
     69              &size, NULL, 0) != 0) {
     70     // Don't log the error since it's normal for this to fail.
     71     return false;
     72   }
     73 
     74   // KERN_PROCARGS returns multiple NULL terminated strings. Truncate
     75   // executable_name to just the first string.
     76   size_t end_pos = executable_name->find('\0');
     77   if (end_pos == std::string::npos) {
     78     return false;
     79   }
     80 
     81   executable_name->resize(end_pos);
     82   return true;
     83 }
     84 
     85 // Converts a byte unit such as 'K' or 'M' into the scale for the unit.
     86 // The scale can then be used to calculate the number of bytes in a value.
     87 // The units are based on humanize_number(). See:
     88 // http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c
     89 static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) {
     90   int shift = 0;
     91   switch (unit) {
     92     case 'B':
     93       shift = 0;
     94       break;
     95     case 'K':
     96     case 'k':
     97       shift = 1;
     98       break;
     99     case 'M':
    100       shift = 2;
    101       break;
    102     case 'G':
    103       shift = 3;
    104       break;
    105     case 'T':
    106       shift = 4;
    107       break;
    108     case 'P':
    109       shift = 5;
    110       break;
    111     case 'E':
    112       shift = 6;
    113       break;
    114     default:
    115       return false;
    116   }
    117 
    118   uint64_t scale = 1;
    119   for (int i = 0; i < shift; i++)
    120     scale *= 1024;
    121   *out_scale = scale;
    122 
    123   return true;
    124 }
    125 
    126 // Capture the information by calling '/bin/ps'.
    127 // Note: we ignore the "tsiz" (text size) display option of ps because it's
    128 // always zero (tested on 10.5 and 10.6).
    129 static bool GetProcessMemoryInfoUsingPS(
    130     const std::vector<base::ProcessId>& pid_list,
    131     std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
    132   const base::FilePath kProgram("/bin/ps");
    133   CommandLine command_line(kProgram);
    134 
    135   // Get resident set size, virtual memory size.
    136   command_line.AppendArg("-o");
    137   command_line.AppendArg("pid=,rss=,vsz=");
    138   // Only display the specified PIDs.
    139   for (std::vector<base::ProcessId>::const_iterator it = pid_list.begin();
    140        it != pid_list.end(); ++it) {
    141     command_line.AppendArg("-p");
    142     command_line.AppendArg(base::Int64ToString(static_cast<int64>(*it)));
    143   }
    144 
    145   std::string output;
    146   // Limit output read to a megabyte for safety.
    147   if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
    148     LOG(ERROR) << "Failure running " << kProgram.value() << " to acquire data.";
    149     return false;
    150   }
    151 
    152   std::istringstream in(output, std::istringstream::in);
    153 
    154   // Process lines until done.
    155   while (true) {
    156     // The format is as specified above to ps (see ps(1)):
    157     //   "-o pid=,rss=,vsz=".
    158     // Try to read the PID; if we get it, we should be able to get the rest of
    159     // the line.
    160     pid_t pid;
    161     in >> pid;
    162     if (in.eof())
    163       break;
    164 
    165     ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
    166     proc_info.pid = pid;
    167     in >> proc_info.rss;
    168     in >> proc_info.vsize;
    169     proc_info.rss *= 1024;                // Convert from kilobytes to bytes.
    170     proc_info.vsize *= 1024;
    171 
    172     // If the fail or bad bits were set, then there was an error reading input.
    173     if (in.fail()) {
    174       LOG(ERROR) << "Error parsing output from " << kProgram.value() << ".";
    175       return false;
    176     }
    177 
    178     if (!proc_info.pid || ! proc_info.vsize) {
    179       LOG(WARNING) << "Invalid data from " << kProgram.value() << ".";
    180       return false;
    181     }
    182 
    183     // Record the process information.
    184     proc_info_entries[proc_info.pid] = proc_info;
    185 
    186     // Ignore the rest of the line.
    187     in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    188   }
    189 
    190   return true;
    191 }
    192 
    193 static bool GetProcessMemoryInfoUsingTop(
    194     std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
    195   const base::FilePath kProgram("/usr/bin/top");
    196   CommandLine command_line(kProgram);
    197 
    198   // -stats tells top to print just the given fields as ordered.
    199   command_line.AppendArg("-stats");
    200   command_line.AppendArg("pid,"    // Process ID
    201                          "rsize,"  // Resident memory
    202                          "rshrd,"  // Resident shared memory
    203                          "rprvt,"  // Resident private memory
    204                          "vsize"); // Total virtual memory
    205   // Run top in logging (non-interactive) mode.
    206   command_line.AppendArg("-l");
    207   command_line.AppendArg("1");
    208   // Set the delay between updates to 0.
    209   command_line.AppendArg("-s");
    210   command_line.AppendArg("0");
    211 
    212   std::string output;
    213   // Limit output read to a megabyte for safety.
    214   if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
    215     LOG(ERROR) << "Failure running " << kProgram.value() << " to acquire data.";
    216     return false;
    217   }
    218 
    219   // Process lines until done. Lines should look something like this:
    220   // PID    RSIZE  RSHRD  RPRVT  VSIZE
    221   // 58539  1276K+ 336K+  740K+  2378M+
    222   // 58485  1888K+ 592K+  1332K+ 2383M+
    223   std::istringstream top_in(output, std::istringstream::in);
    224   std::string line;
    225   while (std::getline(top_in, line)) {
    226     std::istringstream in(line, std::istringstream::in);
    227 
    228     // Try to read the PID.
    229     pid_t pid;
    230     in >> pid;
    231     if (in.fail())
    232       continue;
    233 
    234     // Make sure that caller is interested in this process.
    235     if (proc_info_entries.find(pid) == proc_info_entries.end())
    236       continue;
    237 
    238     // Skip the - or + sign that top puts after the pid.
    239     in.get();
    240 
    241     uint64_t values[4];
    242     size_t i;
    243     for (i = 0; i < arraysize(values); i++) {
    244       in >> values[i];
    245       if (in.fail())
    246         break;
    247       std::string unit;
    248       in >> unit;
    249       if (in.fail())
    250         break;
    251 
    252       if (unit.empty())
    253         break;
    254 
    255       uint64_t scale;
    256       if (!ConvertByteUnitToScale(unit[0], &scale))
    257         break;
    258       values[i] *= scale;
    259     }
    260     if (i != arraysize(values))
    261       continue;
    262 
    263     ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
    264     proc_info.rss = values[0];
    265     proc_info.rshrd = values[1];
    266     proc_info.rprvt = values[2];
    267     proc_info.vsize = values[3];
    268     // Record the process information.
    269     proc_info_entries[proc_info.pid] = proc_info;
    270   }
    271 
    272   return true;
    273 }
    274 
    275 bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) {
    276   Reset();
    277 
    278   // Nothing to do if no PIDs given.
    279   if (pid_list.empty())
    280     return true;
    281   if (pid_list.size() > kMaxPidListSize) {
    282     // The spec says |pid_list| *must* not have more than this many entries.
    283     NOTREACHED();
    284     return false;
    285   }
    286 
    287   // Get basic process info from KERN_PROC.
    288   for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
    289        it != pid_list.end(); ++it) {
    290     ProcInfoEntry proc_info;
    291     proc_info.pid = *it;
    292 
    293     kinfo_proc kinfo;
    294     if (!GetKInfoForProcessID(*it, &kinfo))
    295       return false;
    296 
    297     proc_info.ppid = kinfo.kp_eproc.e_ppid;
    298     proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid;
    299     proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid;
    300     // Note, p_comm is truncated to 16 characters.
    301     proc_info.command = kinfo.kp_proc.p_comm;
    302     proc_info_entries_[*it] = proc_info;
    303   }
    304 
    305   // Use KERN_PROCARGS to get the full executable name. This may fail if this
    306   // process doesn't have privileges to inspect the target process.
    307   for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
    308        it != pid_list.end(); ++it) {
    309     std::string exectuable_name;
    310     if (GetExecutableNameForProcessID(*it, &exectuable_name)) {
    311       ProcInfoEntry proc_info = proc_info_entries_[*it];
    312       proc_info.command = exectuable_name;
    313     }
    314   }
    315 
    316   // In OSX 10.9+, top no longer returns any useful information. 'rshrd' is no
    317   // longer supported, and 'rprvt' and 'vsize' return N/A. 'rsize' still works,
    318   // but the information is also available from ps.
    319   // http://crbug.com/383553
    320   if (base::mac::IsOSMavericksOrLater())
    321     return GetProcessMemoryInfoUsingPS(pid_list, proc_info_entries_);
    322 
    323   // Get memory information using top.
    324   bool memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_);
    325 
    326   // If top didn't work then fall back to ps.
    327   if (!memory_info_success) {
    328     memory_info_success = GetProcessMemoryInfoUsingPS(pid_list,
    329                                                       proc_info_entries_);
    330   }
    331 
    332   return memory_info_success;
    333 }
    334 
    335 // Clear all the stored information.
    336 void ProcessInfoSnapshot::Reset() {
    337   proc_info_entries_.clear();
    338 }
    339 
    340 ProcessInfoSnapshot::ProcInfoEntry::ProcInfoEntry()
    341     : pid(0),
    342       ppid(0),
    343       uid(0),
    344       euid(0),
    345       rss(0),
    346       rshrd(0),
    347       rprvt(0),
    348       vsize(0) {
    349 }
    350 
    351 bool ProcessInfoSnapshot::GetProcInfo(int pid,
    352                                       ProcInfoEntry* proc_info) const {
    353   std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid);
    354   if (it == proc_info_entries_.end())
    355     return false;
    356 
    357   *proc_info = it->second;
    358   return true;
    359 }
    360 
    361 bool ProcessInfoSnapshot::GetCommittedKBytesOfPID(
    362     int pid,
    363     base::CommittedKBytes* usage) const {
    364   // Try to avoid crashing on a bug; stats aren't usually so crucial.
    365   if (!usage) {
    366     NOTREACHED();
    367     return false;
    368   }
    369 
    370   // Failure of |GetProcInfo()| is "normal", due to racing.
    371   ProcInfoEntry proc_info;
    372   if (!GetProcInfo(pid, &proc_info)) {
    373     usage->priv = 0;
    374     usage->mapped = 0;
    375     usage->image = 0;
    376     return false;
    377   }
    378 
    379   usage->priv = proc_info.vsize / 1024;
    380   usage->mapped = 0;
    381   usage->image = 0;
    382   return true;
    383 }
    384 
    385 bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID(
    386     int pid,
    387     base::WorkingSetKBytes* ws_usage) const {
    388   // Try to avoid crashing on a bug; stats aren't usually so crucial.
    389   if (!ws_usage) {
    390     NOTREACHED();
    391     return false;
    392   }
    393 
    394   // Failure of |GetProcInfo()| is "normal", due to racing.
    395   ProcInfoEntry proc_info;
    396   if (!GetProcInfo(pid, &proc_info)) {
    397     ws_usage->priv = 0;
    398     ws_usage->shareable = 0;
    399     ws_usage->shared = 0;
    400     return false;
    401   }
    402 
    403   ws_usage->priv = proc_info.rprvt / 1024;
    404   ws_usage->shareable = proc_info.rss / 1024;
    405   ws_usage->shared = proc_info.rshrd / 1024;
    406   return true;
    407 }
    408