• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
ProcessInfoSnapshot()20 ProcessInfoSnapshot::ProcessInfoSnapshot() { }
21 
22 // Destructor: just call |Reset()| to release everything.
~ProcessInfoSnapshot()23 ProcessInfoSnapshot::~ProcessInfoSnapshot() {
24   Reset();
25 }
26 
27 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;
28 
GetKInfoForProcessID(pid_t pid,kinfo_proc * kinfo)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 
GetExecutableNameForProcessID(pid_t pid,std::string * executable_name)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
ConvertByteUnitToScale(char unit,uint64_t * out_scale)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).
GetProcessMemoryInfoUsingPS(const std::vector<base::ProcessId> & pid_list,std::map<int,ProcessInfoSnapshot::ProcInfoEntry> & proc_info_entries)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 
GetProcessMemoryInfoUsingTop(std::map<int,ProcessInfoSnapshot::ProcInfoEntry> & proc_info_entries)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 
Sample(std::vector<base::ProcessId> pid_list)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.
Reset()325 void ProcessInfoSnapshot::Reset() {
326   proc_info_entries_.clear();
327 }
328 
ProcInfoEntry()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 
GetProcInfo(int pid,ProcInfoEntry * proc_info) const340 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 
GetCommittedKBytesOfPID(int pid,base::CommittedKBytes * usage) const350 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 
GetWorkingSetKBytesOfPID(int pid,base::WorkingSetKBytes * ws_usage) const374 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