Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(239)

Side by Side Diff: chrome/browser/process_info_snapshot_mac.cc

Issue 6052005: Proposal: Use /usr/bin/top in about:memory... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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 }
OLDNEW
« no previous file with comments | « chrome/browser/process_info_snapshot.h ('k') | chrome/browser/process_info_snapshot_mac_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698