1// Copyright (c) 2009 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/logging.h"
13#include "base/string_number_conversions.h"
14#include "base/string_util.h"
15#include "base/sys_info.h"
16#include "base/threading/thread.h"
17
18// Default constructor.
19ProcessInfoSnapshot::ProcessInfoSnapshot() { }
20
21// Destructor: just call |Reset()| to release everything.
22ProcessInfoSnapshot::~ProcessInfoSnapshot() {
23  Reset();
24}
25
26const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;
27
28static bool GetKInfoForProcessID(pid_t pid, kinfo_proc* kinfo) {
29  int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
30  size_t len = sizeof(*kinfo);
31  if (sysctl(mib, arraysize(mib), kinfo, &len, NULL, 0) != 0) {
32    PLOG(ERROR) << "sysctl() for KERN_PROC";
33    return false;
34  }
35
36  if (len == 0) {
37    // If the process isn't found then sysctl returns a length of 0.
38    return false;
39  }
40
41  return true;
42}
43
44static bool GetExecutableNameForProcessID(
45    pid_t pid,
46    std::string* executable_name) {
47  if (!executable_name) {
48    NOTREACHED();
49    return false;
50  }
51
52  static int s_arg_max = 0;
53  if (s_arg_max == 0) {
54    int mib[] = {CTL_KERN, KERN_ARGMAX};
55    size_t size = sizeof(s_arg_max);
56    if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0)
57      PLOG(ERROR) << "sysctl() for KERN_ARGMAX";
58  }
59
60  if (s_arg_max == 0)
61    return false;
62
63  int mib[] = {CTL_KERN, KERN_PROCARGS, pid};
64  size_t size = s_arg_max;
65  executable_name->resize(s_arg_max + 1);
66  if (sysctl(mib, arraysize(mib), &(*executable_name)[0],
67             &size, NULL, 0) != 0) {
68    // Don't log the error since it's normal for this to fail.
69    return false;
70  }
71
72  // KERN_PROCARGS returns multiple NULL terminated strings. Truncate
73  // executable_name to just the first string.
74  size_t end_pos = executable_name->find('\0');
75  if (end_pos == std::string::npos) {
76    return false;
77  }
78
79  executable_name->resize(end_pos);
80  return true;
81}
82
83// Converts a byte unit such as 'K' or 'M' into the scale for the unit.
84// The scale can then be used to calculate the number of bytes in a value.
85// The units are based on humanize_number(). See:
86// http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c
87static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) {
88  int shift = 0;
89  switch (unit) {
90    case 'B':
91      shift = 0;
92      break;
93    case 'K':
94    case 'k':
95      shift = 1;
96      break;
97    case 'M':
98      shift = 2;
99      break;
100    case 'G':
101      shift = 3;
102      break;
103    case 'T':
104      shift = 4;
105      break;
106    case 'P':
107      shift = 5;
108      break;
109    case 'E':
110      shift = 6;
111      break;
112    default:
113      return false;
114  }
115
116  uint64_t scale = 1;
117  for (int i = 0; i < shift; i++)
118    scale *= 1024;
119  *out_scale = scale;
120
121  return true;
122}
123
124// Capture the information by calling '/bin/ps'.
125// Note: we ignore the "tsiz" (text size) display option of ps because it's
126// always zero (tested on 10.5 and 10.6).
127static bool GetProcessMemoryInfoUsingPS(
128    const std::vector<base::ProcessId>& pid_list,
129    std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
130  const char kPsPathName[] = "/bin/ps";
131  std::vector<std::string> argv;
132  argv.push_back(kPsPathName);
133
134  // Get resident set size, virtual memory size.
135  argv.push_back("-o");
136  argv.push_back("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    argv.push_back("-p");
141    argv.push_back(base::Int64ToString(static_cast<int64>(*it)));
142  }
143
144  std::string output;
145  CommandLine command_line(argv);
146  // Limit output read to a megabyte for safety.
147  if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
148    LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data.";
149    return false;
150  }
151
152  std::istringstream in(output, std::istringstream::in);
153  std::string line;
154
155  // Process lines until done.
156  while (true) {
157    // The format is as specified above to ps (see ps(1)):
158    //   "-o pid=,rss=,vsz=".
159    // Try to read the PID; if we get it, we should be able to get the rest of
160    // the line.
161    pid_t pid;
162    in >> pid;
163    if (in.eof())
164      break;
165
166    ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
167    proc_info.pid = pid;
168    in >> proc_info.rss;
169    in >> proc_info.vsize;
170    proc_info.rss *= 1024;                // Convert from kilobytes to bytes.
171    proc_info.vsize *= 1024;
172    in.ignore(1, ' ');                    // Eat the space.
173    std::getline(in, proc_info.command);  // Get the rest of the line.
174    if (!in.good()) {
175      LOG(ERROR) << "Error parsing output from " << kPsPathName << ".";
176      return false;
177    }
178
179    if (!proc_info.pid || ! proc_info.vsize) {
180      LOG(WARNING) << "Invalid data from " << kPsPathName << ".";
181      return false;
182    }
183
184    // Record the process information.
185    proc_info_entries[proc_info.pid] = proc_info;
186  }
187
188  return true;
189}
190
191static bool GetProcessMemoryInfoUsingTop(
192    std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
193  const char kTopPathName[] = "/usr/bin/top";
194  std::vector<std::string> argv;
195  argv.push_back(kTopPathName);
196
197  // -stats tells top to print just the given fields as ordered.
198  argv.push_back("-stats");
199  argv.push_back("pid,"    // Process ID
200                 "rsize,"  // Resident memory
201                 "rshrd,"  // Resident shared memory
202                 "rprvt,"  // Resident private memory
203                 "vsize"); // Total virtual memory
204  // Run top in logging (non-interactive) mode.
205  argv.push_back("-l");
206  argv.push_back("1");
207  // Set the delay between updates to 0.
208  argv.push_back("-s");
209  argv.push_back("0");
210
211  std::string output;
212  CommandLine command_line(argv);
213  // Limit output read to a megabyte for safety.
214  if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
215    LOG(ERROR) << "Failure running " << kTopPathName << " 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
275static bool GetProcessMemoryInfoUsingTop_10_5(
276    std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
277  const char kTopPathName[] = "/usr/bin/top";
278  std::vector<std::string> argv;
279  argv.push_back(kTopPathName);
280
281  // -p tells top to print just the given fields as ordered.
282  argv.push_back("-p");
283  argv.push_back("^aaaaaaaaaaaaaaaaaaaa "  // Process ID (PID)
284                 "^jjjjjjjjjjjjjjjjjjjj "  // Resident memory (RSIZE)
285                 "^iiiiiiiiiiiiiiiiiiii "  // Resident shared memory (RSHRD)
286                 "^hhhhhhhhhhhhhhhhhhhh "  // Resident private memory (RPRVT)
287                 "^llllllllllllllllllll"); // Total virtual memory (VSIZE)
288  // Run top in logging (non-interactive) mode.
289  argv.push_back("-l");
290  argv.push_back("1");
291  // Set the delay between updates to 0.
292  argv.push_back("-s");
293  argv.push_back("0");
294
295  std::string output;
296  CommandLine command_line(argv);
297  // Limit output read to a megabyte for safety.
298  if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
299    LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data.";
300    return false;
301  }
302
303  // Process lines until done. Lines should look something like this:
304  // PID      RSIZE     RSHRD     RPRVT     VSIZE
305  // 16943    815104    262144    290816    18489344
306  // 16922    954368    720896    278528    18976768
307  std::istringstream top_in(output, std::istringstream::in);
308  std::string line;
309  while (std::getline(top_in, line)) {
310    std::istringstream in(line, std::istringstream::in);
311
312    // Try to read the PID.
313    pid_t pid;
314    in >> pid;
315    if (in.fail())
316      continue;
317
318    // Make sure that caller is interested in this process.
319    if (proc_info_entries.find(pid) == proc_info_entries.end())
320      continue;
321
322    uint64_t values[4];
323    size_t i;
324    for (i = 0; i < arraysize(values); i++) {
325      in >> values[i];
326      if (in.fail())
327        break;
328    }
329    if (i != arraysize(values))
330      continue;
331
332    ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
333    proc_info.rss = values[0];
334    proc_info.rshrd = values[1];
335    proc_info.rprvt = values[2];
336    proc_info.vsize = values[3];
337    // Record the process information.
338    proc_info_entries[proc_info.pid] = proc_info;
339  }
340
341  return true;
342}
343
344
345bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) {
346  Reset();
347
348  // Nothing to do if no PIDs given.
349  if (pid_list.empty())
350    return true;
351  if (pid_list.size() > kMaxPidListSize) {
352    // The spec says |pid_list| *must* not have more than this many entries.
353    NOTREACHED();
354    return false;
355  }
356
357  // Get basic process info from KERN_PROC.
358  for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
359       it != pid_list.end(); ++it) {
360    ProcInfoEntry proc_info;
361    proc_info.pid = *it;
362
363    kinfo_proc kinfo;
364    if (!GetKInfoForProcessID(*it, &kinfo))
365      return false;
366
367    proc_info.ppid = kinfo.kp_eproc.e_ppid;
368    proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid;
369    proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid;
370    // Note, p_comm is truncated to 16 characters.
371    proc_info.command = kinfo.kp_proc.p_comm;
372    proc_info_entries_[*it] = proc_info;
373  }
374
375  // Use KERN_PROCARGS to get the full executable name. This may fail if this
376  // process doesn't have privileges to inspect the target process.
377  for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
378       it != pid_list.end(); ++it) {
379    std::string exectuable_name;
380    if (GetExecutableNameForProcessID(*it, &exectuable_name)) {
381      ProcInfoEntry proc_info = proc_info_entries_[*it];
382      proc_info.command = exectuable_name;
383    }
384  }
385
386  // Get memory information using top.
387  bool memory_info_success = false;
388  int32 major, minor, bugfix;
389  base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
390  if (major == 10 && minor == 5)
391    memory_info_success = GetProcessMemoryInfoUsingTop_10_5(proc_info_entries_);
392  else if ((major == 10 && minor >= 6) || major > 10)
393    memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_);
394
395  // If top didn't work then fall back to ps.
396  if (!memory_info_success) {
397    memory_info_success = GetProcessMemoryInfoUsingPS(pid_list,
398                                                      proc_info_entries_);
399  }
400
401  return memory_info_success;
402}
403
404// Clear all the stored information.
405void ProcessInfoSnapshot::Reset() {
406  proc_info_entries_.clear();
407}
408
409bool ProcessInfoSnapshot::GetProcInfo(int pid,
410                                      ProcInfoEntry* proc_info) const {
411  std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid);
412  if (it == proc_info_entries_.end())
413    return false;
414
415  *proc_info = it->second;
416  return true;
417}
418
419bool ProcessInfoSnapshot::GetCommittedKBytesOfPID(
420    int pid,
421    base::CommittedKBytes* usage) const {
422  // Try to avoid crashing on a bug; stats aren't usually so crucial.
423  if (!usage) {
424    NOTREACHED();
425    return false;
426  }
427
428  // Failure of |GetProcInfo()| is "normal", due to racing.
429  ProcInfoEntry proc_info;
430  if (!GetProcInfo(pid, &proc_info)) {
431    usage->priv = 0;
432    usage->mapped = 0;
433    usage->image = 0;
434    return false;
435  }
436
437  usage->priv = proc_info.vsize / 1024;
438  usage->mapped = 0;
439  usage->image = 0;
440  return true;
441}
442
443bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID(
444    int pid,
445    base::WorkingSetKBytes* ws_usage) const {
446  // Try to avoid crashing on a bug; stats aren't usually so crucial.
447  if (!ws_usage) {
448    NOTREACHED();
449    return false;
450  }
451
452  // Failure of |GetProcInfo()| is "normal", due to racing.
453  ProcInfoEntry proc_info;
454  if (!GetProcInfo(pid, &proc_info)) {
455    ws_usage->priv = 0;
456    ws_usage->shareable = 0;
457    ws_usage->shared = 0;
458    return false;
459  }
460
461  ws_usage->priv = proc_info.rprvt / 1024;
462  ws_usage->shareable = proc_info.rss / 1024;
463  ws_usage->shared = proc_info.rshrd / 1024;
464  return true;
465}
466