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.
21ProcessInfoSnapshot::ProcessInfoSnapshot() { }
22
23// Destructor: just call |Reset()| to release everything.
24ProcessInfoSnapshot::~ProcessInfoSnapshot() {
25  Reset();
26}
27
28const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;
29
30static 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
46static 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
89static 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).
129static 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
193static 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
275bool 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.
336void ProcessInfoSnapshot::Reset() {
337  proc_info_entries_.clear();
338}
339
340ProcessInfoSnapshot::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
351bool 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
361bool 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
385bool 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