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