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