OLD | NEW |
---|---|
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/process_info_snapshot.h" | 5 #include "chrome/browser/process_info_snapshot.h" |
6 | 6 |
7 #include <sys/sysctl.h> | |
8 | |
7 #include <sstream> | 9 #include <sstream> |
8 | 10 |
9 #include "base/command_line.h" | 11 #include "base/command_line.h" |
10 #include "base/logging.h" | 12 #include "base/logging.h" |
11 #include "base/string_number_conversions.h" | 13 #include "base/string_number_conversions.h" |
12 #include "base/string_util.h" | 14 #include "base/string_util.h" |
15 #include "base/sys_info.h" | |
13 #include "base/threading/thread.h" | 16 #include "base/threading/thread.h" |
14 | 17 |
15 // Implementation for the Mac; calls '/bin/ps' for information when | |
16 // |Sample()| is called. | |
17 | |
18 // Default constructor. | 18 // Default constructor. |
19 ProcessInfoSnapshot::ProcessInfoSnapshot() { } | 19 ProcessInfoSnapshot::ProcessInfoSnapshot() { } |
20 | 20 |
21 // Destructor: just call |Reset()| to release everything. | 21 // Destructor: just call |Reset()| to release everything. |
22 ProcessInfoSnapshot::~ProcessInfoSnapshot() { | 22 ProcessInfoSnapshot::~ProcessInfoSnapshot() { |
23 Reset(); | 23 Reset(); |
24 } | 24 } |
25 | 25 |
26 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; | 26 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; |
27 | 27 |
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 return true; | |
36 } | |
37 | |
38 static bool GetExecutableNameForProcessID( | |
39 pid_t pid, | |
40 std::string* executable_name) { | |
41 if (!executable_name) { | |
42 NOTREACHED(); | |
43 return false; | |
44 } | |
45 | |
46 static int s_arg_max = 0; | |
47 if (s_arg_max == 0) { | |
48 int mib[] = {CTL_KERN, KERN_ARGMAX}; | |
49 size_t size = sizeof(s_arg_max); | |
50 if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0) | |
51 PLOG(ERROR) << "sysctl() for KERN_ARGMAX"; | |
52 } | |
53 | |
54 if (s_arg_max == 0) | |
55 return false; | |
56 | |
57 int mib[] = {CTL_KERN, KERN_PROCARGS, pid}; | |
58 size_t size = s_arg_max; | |
59 executable_name->resize(s_arg_max + 1); | |
60 if (sysctl(mib, arraysize(mib), &(*executable_name)[0], | |
61 &size, NULL, 0) != 0) { | |
62 // Don't log the error since it's normal for this to fail. | |
63 return false; | |
64 } | |
65 | |
66 // KERN_PROCARGS returns multiple NULL terminated strings. Truncate | |
67 // executable_name to just the first string. | |
68 size_t end_pos = executable_name->find('\0'); | |
69 if (end_pos == std::string::npos) { | |
70 return false; | |
71 } | |
72 | |
73 executable_name->resize(end_pos); | |
74 return true; | |
75 } | |
76 | |
77 // Converts a byte unit such as 'K' or 'M' into the scale for the unit. | |
78 // The scale can then be used to calculate the number of bytes in a value. | |
79 // The units are based on humanize_number(). See: | |
80 // http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c | |
81 static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) { | |
82 int shift = 0; | |
83 switch (unit) { | |
84 case 'B': | |
85 shift = 0; | |
86 break; | |
87 case 'K': | |
88 case 'k': | |
89 shift = 1; | |
90 break; | |
91 case 'M': | |
92 shift = 2; | |
93 break; | |
94 case 'G': | |
95 shift = 3; | |
96 break; | |
97 case 'T': | |
98 shift = 4; | |
99 break; | |
100 case 'P': | |
101 shift = 5; | |
102 break; | |
103 case 'E': | |
104 shift = 6; | |
105 break; | |
106 default: | |
107 return false; | |
108 } | |
109 | |
110 uint64_t scale = 1; | |
111 for (int i = 0; i < shift; i++) | |
112 scale *= 1024; | |
113 *out_scale = scale; | |
114 | |
115 return true; | |
116 } | |
117 | |
28 // Capture the information by calling '/bin/ps'. | 118 // Capture the information by calling '/bin/ps'. |
29 // Note: we ignore the "tsiz" (text size) display option of ps because it's | 119 // Note: we ignore the "tsiz" (text size) display option of ps because it's |
30 // always zero (tested on 10.5 and 10.6). | 120 // always zero (tested on 10.5 and 10.6). |
31 bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { | 121 static bool GetProcessMemoryInfoUsingPS( |
32 const char* kPsPathName = "/bin/ps"; | 122 const std::vector<base::ProcessId>& pid_list, |
33 Reset(); | 123 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { |
34 | 124 const char kPsPathName[] = "/bin/ps"; |
35 // Nothing to do if no PIDs given. | |
36 if (pid_list.size() == 0) | |
37 return true; | |
38 if (pid_list.size() > kMaxPidListSize) { | |
39 // The spec says |pid_list| *must* not have more than this many entries. | |
40 NOTREACHED(); | |
41 return false; | |
42 } | |
43 | |
44 std::vector<std::string> argv; | 125 std::vector<std::string> argv; |
45 argv.push_back(kPsPathName); | 126 argv.push_back(kPsPathName); |
46 // Get PID, PPID, (real) UID, effective UID, resident set size, virtual memory | 127 |
47 // size, and command. | 128 // Get resident set size, virtual memory size. |
48 argv.push_back("-o"); | 129 argv.push_back("-o"); |
49 argv.push_back("pid=,ppid=,ruid=,uid=,rss=,vsz=,comm="); | 130 argv.push_back("pid=,rss=,vsz="); |
Mark Mentovai
2011/01/06 21:50:04
Why did you take the ruid and uid out for ps?
sail
2011/01/06 23:29:21
I'm trying to use kinfo.kp_eproc.e_pcred.p_ruid an
| |
50 // Only display the specified PIDs. | 131 // Only display the specified PIDs. |
51 for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); | 132 for (std::vector<base::ProcessId>::const_iterator it = pid_list.begin(); |
52 it != pid_list.end(); ++it) { | 133 it != pid_list.end(); ++it) { |
53 argv.push_back("-p"); | 134 argv.push_back("-p"); |
54 argv.push_back(base::Int64ToString(static_cast<int64>(*it))); | 135 argv.push_back(base::Int64ToString(static_cast<int64>(*it))); |
55 } | 136 } |
56 | 137 |
57 std::string output; | 138 std::string output; |
58 CommandLine command_line(argv); | 139 CommandLine command_line(argv); |
59 // Limit output read to a megabyte for safety. | 140 // Limit output read to a megabyte for safety. |
60 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { | 141 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { |
61 LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data."; | 142 LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data."; |
62 return false; | 143 return false; |
63 } | 144 } |
64 | 145 |
65 std::istringstream in(output, std::istringstream::in); | 146 std::istringstream in(output, std::istringstream::in); |
66 std::string line; | 147 std::string line; |
67 | 148 |
68 // Process lines until done. | 149 // Process lines until done. |
69 while (true) { | 150 while (true) { |
70 ProcInfoEntry proc_info; | |
71 | |
72 // The format is as specified above to ps (see ps(1)): | 151 // The format is as specified above to ps (see ps(1)): |
73 // "-o pid=,ppid=,ruid=,uid=,rss=,vsz=,comm=". | 152 // "-o pid=,rss=,vsz=". |
74 // Try to read the PID; if we get it, we should be able to get the rest of | 153 // Try to read the PID; if we get it, we should be able to get the rest of |
75 // the line. | 154 // the line. |
76 in >> proc_info.pid; | 155 pid_t pid; |
156 in >> pid; | |
77 if (in.eof()) | 157 if (in.eof()) |
78 break; | 158 break; |
79 in >> proc_info.ppid; | 159 |
80 in >> proc_info.uid; | 160 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; |
81 in >> proc_info.euid; | 161 proc_info.pid = pid; |
82 in >> proc_info.rss; | 162 in >> proc_info.rss; |
83 in >> proc_info.vsize; | 163 in >> proc_info.vsize; |
164 proc_info.rss *= 1024; // Convert from kilobytes to bytes. | |
165 proc_info.vsize *= 1024; | |
84 in.ignore(1, ' '); // Eat the space. | 166 in.ignore(1, ' '); // Eat the space. |
85 std::getline(in, proc_info.command); // Get the rest of the line. | 167 std::getline(in, proc_info.command); // Get the rest of the line. |
86 if (!in.good()) { | 168 if (!in.good()) { |
87 LOG(ERROR) << "Error parsing output from " << kPsPathName << "."; | 169 LOG(ERROR) << "Error parsing output from " << kPsPathName << "."; |
88 return false; | 170 return false; |
89 } | 171 } |
90 | 172 |
91 // Make sure the new PID isn't already in our list. | |
92 if (proc_info_entries_.find(proc_info.pid) != proc_info_entries_.end()) { | |
93 LOG(ERROR) << "Duplicate PID in output from " << kPsPathName << "."; | |
94 return false; | |
95 } | |
96 | |
97 if (!proc_info.pid || ! proc_info.vsize) { | 173 if (!proc_info.pid || ! proc_info.vsize) { |
98 LOG(WARNING) << "Invalid data from " << kPsPathName << "."; | 174 LOG(WARNING) << "Invalid data from " << kPsPathName << "."; |
99 return false; | 175 return false; |
100 } | 176 } |
101 | 177 |
102 // Record the process information. | 178 // Record the process information. |
103 proc_info_entries_[proc_info.pid] = proc_info; | 179 proc_info_entries[proc_info.pid] = proc_info; |
104 } | 180 } |
105 | 181 |
106 return true; | 182 return true; |
107 } | 183 } |
108 | 184 |
185 static bool GetProcessMemoryInfoUsingTop( | |
186 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { | |
187 const char kTopPathName[] = "/usr/bin/top"; | |
188 std::vector<std::string> argv; | |
189 argv.push_back(kTopPathName); | |
190 | |
191 // -stats tells top to print just the given fields as ordered. | |
192 argv.push_back("-stats"); | |
193 argv.push_back("pid," // Process ID | |
194 "rsize," // Resident memory | |
195 "rshrd," // Resident shared memory | |
196 "rprvt," // Resident private memory | |
197 "vsize"); // Total virtual memory | |
Mark Mentovai
2011/01/06 21:50:04
You can do uid (which gives you effective user ID)
| |
198 // Run top in logging (non-interactive) mode. | |
199 argv.push_back("-l"); | |
200 argv.push_back("1"); | |
201 | |
Mark Mentovai
2011/01/06 21:50:04
Add |-s 0| too, to prevent top from sleeping for o
sail
2011/01/06 23:29:21
Wow, I don't know how I missed that. Thanks so muc
| |
202 std::string output; | |
203 CommandLine command_line(argv); | |
204 // Limit output read to a megabyte for safety. | |
205 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { | |
206 LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data."; | |
207 return false; | |
208 } | |
209 | |
210 // Process lines until done. Lines should look something like this: | |
211 // PID RSIZE RSHRD RPRVT VSIZE | |
212 // 58539 1276K+ 336K+ 740K+ 2378M+ | |
213 // 58485 1888K+ 592K+ 1332K+ 2383M+ | |
214 std::istringstream top_in(output, std::istringstream::in); | |
215 std::string line; | |
216 while (std::getline(top_in, line)) { | |
217 std::istringstream in(line, std::istringstream::in); | |
218 | |
219 // Try to read the PID. | |
220 pid_t pid; | |
221 in >> pid; | |
222 if (in.fail()) | |
223 continue; | |
224 | |
225 // Make sure that caller is interested in this process. | |
226 if (proc_info_entries.find(pid) == proc_info_entries.end()) | |
227 continue; | |
228 | |
229 // Skip the - or + sign that top puts after the pid. | |
230 in.get(); | |
231 | |
232 uint64_t values[4]; | |
233 size_t i; | |
234 for (i = 0; i < arraysize(values); i++) { | |
235 in >> values[i]; | |
236 if (in.fail()) | |
237 break; | |
238 std::string unit; | |
239 in >> unit; | |
240 if (in.fail()) | |
241 break; | |
242 | |
243 if (unit.size() == 0) | |
244 break; | |
245 | |
246 uint64_t scale; | |
247 if (!ConvertByteUnitToScale(unit[0], &scale)) | |
248 break; | |
249 values[i] *= scale; | |
250 } | |
251 if (i != arraysize(values)) | |
252 continue; | |
253 | |
254 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; | |
255 proc_info.rss = values[0]; | |
256 proc_info.rshrd = values[1]; | |
257 proc_info.rprvt = values[2]; | |
258 proc_info.vsize = values[3]; | |
259 // Record the process information. | |
260 proc_info_entries[proc_info.pid] = proc_info; | |
261 } | |
262 | |
263 return true; | |
264 } | |
265 | |
266 static bool GetProcessMemoryInfoUsingTop_10_5( | |
267 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { | |
268 const char kTopPathName[] = "/usr/bin/top"; | |
269 std::vector<std::string> argv; | |
270 argv.push_back(kTopPathName); | |
271 | |
272 // -p tells top to print just the given fields as ordered. | |
273 argv.push_back("-p"); | |
274 argv.push_back("^aaaaaaaaaaaaaaaaaaaa " // Process ID (PID) | |
275 "^jjjjjjjjjjjjjjjjjjjj " // Resident memory (RSIZE) | |
276 "^iiiiiiiiiiiiiiiiiiii " // Resident shared memory (RSHRD) | |
277 "^hhhhhhhhhhhhhhhhhhhh " // Resident private memory (RPRVT) | |
278 "^llllllllllllllllllll"); // Total virtual memory (VSIZE) | |
279 // Run top in logging (non-interactive) mode. | |
280 argv.push_back("-l"); | |
281 argv.push_back("1"); | |
282 | |
Mark Mentovai
2011/01/06 21:50:04
10.5 top seems to exit immediately even without -s
sail
2011/01/06 23:29:21
Done.
| |
283 std::string output; | |
284 CommandLine command_line(argv); | |
285 // Limit output read to a megabyte for safety. | |
286 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) { | |
287 LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data."; | |
288 return false; | |
289 } | |
290 | |
291 // Process lines until done. Lines should look something like this: | |
292 // PID RSIZE RSHRD RPRVT VSIZE | |
293 // 16943 815104 262144 290816 18489344 | |
294 // 16922 954368 720896 278528 18976768 | |
295 std::istringstream top_in(output, std::istringstream::in); | |
296 std::string line; | |
297 while (std::getline(top_in, line)) { | |
298 std::istringstream in(line, std::istringstream::in); | |
299 | |
300 // Try to read the PID. | |
301 pid_t pid; | |
302 in >> pid; | |
303 if (in.fail()) | |
304 continue; | |
305 | |
306 // Make sure that caller is interested in this process. | |
307 if (proc_info_entries.find(pid) == proc_info_entries.end()) | |
308 continue; | |
309 | |
310 uint64_t values[4]; | |
311 size_t i; | |
312 for (i = 0; i < arraysize(values); i++) { | |
313 in >> values[i]; | |
314 if (in.fail()) | |
315 break; | |
316 } | |
317 if (i != arraysize(values)) | |
318 continue; | |
319 | |
320 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid]; | |
321 proc_info.rss = values[0]; | |
322 proc_info.rshrd = values[1]; | |
323 proc_info.rprvt = values[2]; | |
324 proc_info.vsize = values[3]; | |
325 // Record the process information. | |
326 proc_info_entries[proc_info.pid] = proc_info; | |
327 } | |
328 | |
329 return true; | |
330 } | |
331 | |
332 | |
333 bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { | |
334 Reset(); | |
335 | |
336 // Nothing to do if no PIDs given. | |
337 if (pid_list.size() == 0) | |
338 return true; | |
339 if (pid_list.size() > kMaxPidListSize) { | |
340 // The spec says |pid_list| *must* not have more than this many entries. | |
341 NOTREACHED(); | |
342 return false; | |
343 } | |
344 | |
345 // Get basic process info from KERN_PROC. | |
346 for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); | |
347 it != pid_list.end(); ++it) { | |
348 ProcInfoEntry proc_info; | |
349 proc_info.pid = *it; | |
350 | |
351 kinfo_proc kinfo; | |
352 if (!GetKInfoForProcessID(*it, &kinfo)) | |
353 return false; | |
354 | |
355 proc_info.ppid = kinfo.kp_eproc.e_ppid; | |
356 proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid; | |
357 proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid; | |
358 // Note, p_comm is truncated to 16 characters. | |
359 proc_info.command = kinfo.kp_proc.p_comm; | |
360 proc_info_entries_[*it] = proc_info; | |
361 } | |
362 | |
363 // Use KERN_PROCARGS to get the full executable name. This may fail if this | |
364 // process doesn't have privileges to inspect the target process. | |
365 for (std::vector<base::ProcessId>::iterator it = pid_list.begin(); | |
366 it != pid_list.end(); ++it) { | |
367 std::string exectuable_name; | |
368 if (GetExecutableNameForProcessID(*it, &exectuable_name)) { | |
369 ProcInfoEntry proc_info = proc_info_entries_[*it]; | |
370 proc_info.command = exectuable_name; | |
371 } | |
372 } | |
373 | |
374 // Get memory information using top. | |
375 bool memory_info_success = false; | |
376 int32 major, minor, bugfix; | |
377 base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); | |
378 if (major == 10 && minor == 5) | |
379 memory_info_success = GetProcessMemoryInfoUsingTop_10_5(proc_info_entries_); | |
380 else if ((major == 10 && minor >= 6) || major > 10) | |
381 memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_); | |
382 | |
383 // If top didn't work then fall back to ps. | |
384 if (!memory_info_success) { | |
385 memory_info_success = GetProcessMemoryInfoUsingPS(pid_list, | |
386 proc_info_entries_); | |
387 } | |
388 | |
389 return memory_info_success; | |
390 } | |
391 | |
109 // Clear all the stored information. | 392 // Clear all the stored information. |
110 void ProcessInfoSnapshot::Reset() { | 393 void ProcessInfoSnapshot::Reset() { |
111 proc_info_entries_.clear(); | 394 proc_info_entries_.clear(); |
112 } | 395 } |
113 | 396 |
114 bool ProcessInfoSnapshot::GetProcInfo(int pid, | 397 bool ProcessInfoSnapshot::GetProcInfo(int pid, |
115 ProcInfoEntry* proc_info) const { | 398 ProcInfoEntry* proc_info) const { |
116 std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid); | 399 std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid); |
117 if (it == proc_info_entries_.end()) | 400 if (it == proc_info_entries_.end()) |
118 return false; | 401 return false; |
(...skipping 13 matching lines...) Expand all Loading... | |
132 | 415 |
133 // Failure of |GetProcInfo()| is "normal", due to racing. | 416 // Failure of |GetProcInfo()| is "normal", due to racing. |
134 ProcInfoEntry proc_info; | 417 ProcInfoEntry proc_info; |
135 if (!GetProcInfo(pid, &proc_info)) { | 418 if (!GetProcInfo(pid, &proc_info)) { |
136 usage->priv = 0; | 419 usage->priv = 0; |
137 usage->mapped = 0; | 420 usage->mapped = 0; |
138 usage->image = 0; | 421 usage->image = 0; |
139 return false; | 422 return false; |
140 } | 423 } |
141 | 424 |
142 usage->priv = proc_info.vsize; | 425 usage->priv = proc_info.vsize / 1024; |
143 usage->mapped = 0; | 426 usage->mapped = 0; |
144 usage->image = 0; | 427 usage->image = 0; |
145 return true; | 428 return true; |
146 } | 429 } |
147 | 430 |
148 bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID( | 431 bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID( |
149 int pid, | 432 int pid, |
150 base::WorkingSetKBytes* ws_usage) const { | 433 base::WorkingSetKBytes* ws_usage) const { |
151 // Try to avoid crashing on a bug; stats aren't usually so crucial. | 434 // Try to avoid crashing on a bug; stats aren't usually so crucial. |
152 if (!ws_usage) { | 435 if (!ws_usage) { |
153 NOTREACHED(); | 436 NOTREACHED(); |
154 return false; | 437 return false; |
155 } | 438 } |
156 | 439 |
157 // Failure of |GetProcInfo()| is "normal", due to racing. | 440 // Failure of |GetProcInfo()| is "normal", due to racing. |
158 ProcInfoEntry proc_info; | 441 ProcInfoEntry proc_info; |
159 if (!GetProcInfo(pid, &proc_info)) { | 442 if (!GetProcInfo(pid, &proc_info)) { |
160 ws_usage->priv = 0; | 443 ws_usage->priv = 0; |
161 ws_usage->shareable = 0; | 444 ws_usage->shareable = 0; |
162 ws_usage->shared = 0; | 445 ws_usage->shared = 0; |
163 return false; | 446 return false; |
164 } | 447 } |
165 | 448 |
166 ws_usage->priv = 0; | 449 ws_usage->priv = proc_info.rprvt / 1024; |
167 ws_usage->shareable = proc_info.rss; | 450 ws_usage->shareable = proc_info.rss / 1024; |
168 ws_usage->shared = 0; | 451 ws_usage->shared = proc_info.rshrd / 1024; |
169 return true; | 452 return true; |
170 } | 453 } |
OLD | NEW |