• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
ProcessInfoSnapshot()19 ProcessInfoSnapshot::ProcessInfoSnapshot() { }
20 
21 // Destructor: just call |Reset()| to release everything.
~ProcessInfoSnapshot()22 ProcessInfoSnapshot::~ProcessInfoSnapshot() {
23   Reset();
24 }
25 
26 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;
27 
GetKInfoForProcessID(pid_t pid,kinfo_proc * kinfo)28 static 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 
GetExecutableNameForProcessID(pid_t pid,std::string * executable_name)44 static 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
ConvertByteUnitToScale(char unit,uint64_t * out_scale)87 static 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).
GetProcessMemoryInfoUsingPS(const std::vector<base::ProcessId> & pid_list,std::map<int,ProcessInfoSnapshot::ProcInfoEntry> & proc_info_entries)127 static 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 
GetProcessMemoryInfoUsingTop(std::map<int,ProcessInfoSnapshot::ProcInfoEntry> & proc_info_entries)191 static 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 
GetProcessMemoryInfoUsingTop_10_5(std::map<int,ProcessInfoSnapshot::ProcInfoEntry> & proc_info_entries)275 static 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 
Sample(std::vector<base::ProcessId> pid_list)345 bool 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.
Reset()405 void ProcessInfoSnapshot::Reset() {
406   proc_info_entries_.clear();
407 }
408 
GetProcInfo(int pid,ProcInfoEntry * proc_info) const409 bool 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 
GetCommittedKBytesOfPID(int pid,base::CommittedKBytes * usage) const419 bool 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 
GetWorkingSetKBytesOfPID(int pid,base::WorkingSetKBytes * ws_usage) const443 bool 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