Support SizeClassIdiom on iOS7.
[chromium-blink-merge.git] / chrome / browser / process_info_snapshot_mac.cc
blobf2a001989a9db516fc855ac7b33f1d3ad27c9468
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.
5 #include "chrome/browser/process_info_snapshot.h"
7 #include <sys/sysctl.h>
9 #include <sstream>
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/logging.h"
14 #include "base/mac/mac_util.h"
15 #include "base/process/launch.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/threading/thread.h"
20 // Default constructor.
21 ProcessInfoSnapshot::ProcessInfoSnapshot() { }
23 // Destructor: just call |Reset()| to release everything.
24 ProcessInfoSnapshot::~ProcessInfoSnapshot() {
25 Reset();
28 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;
30 static bool GetKInfoForProcessID(pid_t pid, kinfo_proc* kinfo) {
31 int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
32 size_t len = sizeof(*kinfo);
33 if (sysctl(mib, arraysize(mib), kinfo, &len, NULL, 0) != 0) {
34 PLOG(ERROR) << "sysctl() for KERN_PROC";
35 return false;
38 if (len == 0) {
39 // If the process isn't found then sysctl returns a length of 0.
40 return false;
43 return true;
46 static bool GetExecutableNameForProcessID(
47 pid_t pid,
48 std::string* executable_name) {
49 if (!executable_name) {
50 NOTREACHED();
51 return false;
54 static int s_arg_max = 0;
55 if (s_arg_max == 0) {
56 int mib[] = {CTL_KERN, KERN_ARGMAX};
57 size_t size = sizeof(s_arg_max);
58 if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0)
59 PLOG(ERROR) << "sysctl() for KERN_ARGMAX";
62 if (s_arg_max == 0)
63 return false;
65 int mib[] = {CTL_KERN, KERN_PROCARGS, pid};
66 size_t size = s_arg_max;
67 executable_name->resize(s_arg_max + 1);
68 if (sysctl(mib, arraysize(mib), &(*executable_name)[0],
69 &size, NULL, 0) != 0) {
70 // Don't log the error since it's normal for this to fail.
71 return false;
74 // KERN_PROCARGS returns multiple NULL terminated strings. Truncate
75 // executable_name to just the first string.
76 size_t end_pos = executable_name->find('\0');
77 if (end_pos == std::string::npos) {
78 return false;
81 executable_name->resize(end_pos);
82 return true;
85 // Converts a byte unit such as 'K' or 'M' into the scale for the unit.
86 // The scale can then be used to calculate the number of bytes in a value.
87 // The units are based on humanize_number(). See:
88 // http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c
89 static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) {
90 int shift = 0;
91 switch (unit) {
92 case 'B':
93 shift = 0;
94 break;
95 case 'K':
96 case 'k':
97 shift = 1;
98 break;
99 case 'M':
100 shift = 2;
101 break;
102 case 'G':
103 shift = 3;
104 break;
105 case 'T':
106 shift = 4;
107 break;
108 case 'P':
109 shift = 5;
110 break;
111 case 'E':
112 shift = 6;
113 break;
114 default:
115 return false;
118 uint64_t scale = 1;
119 for (int i = 0; i < shift; i++)
120 scale *= 1024;
121 *out_scale = scale;
123 return true;
126 // Capture the information by calling '/bin/ps'.
127 // Note: we ignore the "tsiz" (text size) display option of ps because it's
128 // always zero (tested on 10.5 and 10.6).
129 static bool GetProcessMemoryInfoUsingPS(
130 const std::vector<base::ProcessId>& pid_list,
131 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
132 const base::FilePath kProgram("/bin/ps");
133 base::CommandLine command_line(kProgram);
135 // Get resident set size, virtual memory size.
136 command_line.AppendArg("-o");
137 command_line.AppendArg("pid=,rss=,vsz=");
138 // Only display the specified PIDs.
139 for (std::vector<base::ProcessId>::const_iterator it = pid_list.begin();
140 it != pid_list.end(); ++it) {
141 command_line.AppendArg("-p");
142 command_line.AppendArg(base::Int64ToString(static_cast<int64>(*it)));
145 std::string output;
146 // Limit output read to a megabyte for safety.
147 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
148 LOG(ERROR) << "Failure running " << kProgram.value() << " to acquire data.";
149 return false;
152 std::istringstream in(output, std::istringstream::in);
154 // Process lines until done.
155 while (true) {
156 // The format is as specified above to ps (see ps(1)):
157 // "-o pid=,rss=,vsz=".
158 // Try to read the PID; if we get it, we should be able to get the rest of
159 // the line.
160 pid_t pid;
161 in >> pid;
162 if (in.eof())
163 break;
165 ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
166 proc_info.pid = pid;
167 in >> proc_info.rss;
168 in >> proc_info.vsize;
169 proc_info.rss *= 1024; // Convert from kilobytes to bytes.
170 proc_info.vsize *= 1024;
172 // If the fail or bad bits were set, then there was an error reading input.
173 if (in.fail()) {
174 LOG(ERROR) << "Error parsing output from " << kProgram.value() << ".";
175 return false;
178 if (!proc_info.pid || ! proc_info.vsize) {
179 LOG(WARNING) << "Invalid data from " << kProgram.value() << ".";
180 return false;
183 // Record the process information.
184 proc_info_entries[proc_info.pid] = proc_info;
186 // Ignore the rest of the line.
187 in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
190 return true;
193 static bool GetProcessMemoryInfoUsingTop(
194 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
195 const base::FilePath kProgram("/usr/bin/top");
196 base::CommandLine command_line(kProgram);
198 // -stats tells top to print just the given fields as ordered.
199 command_line.AppendArg("-stats");
200 command_line.AppendArg("pid," // Process ID
201 "rsize," // Resident memory
202 "rshrd," // Resident shared memory
203 "rprvt," // Resident private memory
204 "vsize"); // Total virtual memory
205 // Run top in logging (non-interactive) mode.
206 command_line.AppendArg("-l");
207 command_line.AppendArg("1");
208 // Set the delay between updates to 0.
209 command_line.AppendArg("-s");
210 command_line.AppendArg("0");
212 std::string output;
213 // Limit output read to a megabyte for safety.
214 if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
215 LOG(ERROR) << "Failure running " << kProgram.value() << " to acquire data.";
216 return false;
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);
228 // Try to read the PID.
229 pid_t pid;
230 in >> pid;
231 if (in.fail())
232 continue;
234 // Make sure that caller is interested in this process.
235 if (proc_info_entries.find(pid) == proc_info_entries.end())
236 continue;
238 // Skip the - or + sign that top puts after the pid.
239 in.get();
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;
252 if (unit.empty())
253 break;
255 uint64_t scale;
256 if (!ConvertByteUnitToScale(unit[0], &scale))
257 break;
258 values[i] *= scale;
260 if (i != arraysize(values))
261 continue;
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;
272 return true;
275 bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) {
276 Reset();
278 // Nothing to do if no PIDs given.
279 if (pid_list.empty())
280 return true;
281 if (pid_list.size() > kMaxPidListSize) {
282 // The spec says |pid_list| *must* not have more than this many entries.
283 NOTREACHED();
284 return false;
287 // Get basic process info from KERN_PROC.
288 for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
289 it != pid_list.end(); ++it) {
290 ProcInfoEntry proc_info;
291 proc_info.pid = *it;
293 kinfo_proc kinfo;
294 if (!GetKInfoForProcessID(*it, &kinfo))
295 return false;
297 proc_info.ppid = kinfo.kp_eproc.e_ppid;
298 proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid;
299 proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid;
300 // Note, p_comm is truncated to 16 characters.
301 proc_info.command = kinfo.kp_proc.p_comm;
302 proc_info_entries_[*it] = proc_info;
305 // Use KERN_PROCARGS to get the full executable name. This may fail if this
306 // process doesn't have privileges to inspect the target process.
307 for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
308 it != pid_list.end(); ++it) {
309 std::string exectuable_name;
310 if (GetExecutableNameForProcessID(*it, &exectuable_name)) {
311 ProcInfoEntry proc_info = proc_info_entries_[*it];
312 proc_info.command = exectuable_name;
316 // In OSX 10.9+, top no longer returns any useful information. 'rshrd' is no
317 // longer supported, and 'rprvt' and 'vsize' return N/A. 'rsize' still works,
318 // but the information is also available from ps.
319 // http://crbug.com/383553
320 if (base::mac::IsOSMavericksOrLater())
321 return GetProcessMemoryInfoUsingPS(pid_list, proc_info_entries_);
323 // Get memory information using top.
324 bool memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_);
326 // If top didn't work then fall back to ps.
327 if (!memory_info_success) {
328 memory_info_success = GetProcessMemoryInfoUsingPS(pid_list,
329 proc_info_entries_);
332 return memory_info_success;
335 // Clear all the stored information.
336 void ProcessInfoSnapshot::Reset() {
337 proc_info_entries_.clear();
340 ProcessInfoSnapshot::ProcInfoEntry::ProcInfoEntry()
341 : pid(0),
342 ppid(0),
343 uid(0),
344 euid(0),
345 rss(0),
346 rshrd(0),
347 rprvt(0),
348 vsize(0) {
351 bool ProcessInfoSnapshot::GetProcInfo(int pid,
352 ProcInfoEntry* proc_info) const {
353 std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid);
354 if (it == proc_info_entries_.end())
355 return false;
357 *proc_info = it->second;
358 return true;
361 bool ProcessInfoSnapshot::GetCommittedKBytesOfPID(
362 int pid,
363 base::CommittedKBytes* usage) const {
364 // Try to avoid crashing on a bug; stats aren't usually so crucial.
365 if (!usage) {
366 NOTREACHED();
367 return false;
370 // Failure of |GetProcInfo()| is "normal", due to racing.
371 ProcInfoEntry proc_info;
372 if (!GetProcInfo(pid, &proc_info)) {
373 usage->priv = 0;
374 usage->mapped = 0;
375 usage->image = 0;
376 return false;
379 usage->priv = proc_info.vsize / 1024;
380 usage->mapped = 0;
381 usage->image = 0;
382 return true;
385 bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID(
386 int pid,
387 base::WorkingSetKBytes* ws_usage) const {
388 // Try to avoid crashing on a bug; stats aren't usually so crucial.
389 if (!ws_usage) {
390 NOTREACHED();
391 return false;
394 // Failure of |GetProcInfo()| is "normal", due to racing.
395 ProcInfoEntry proc_info;
396 if (!GetProcInfo(pid, &proc_info)) {
397 ws_usage->priv = 0;
398 ws_usage->shareable = 0;
399 ws_usage->shared = 0;
400 return false;
403 ws_usage->priv = proc_info.rprvt / 1024;
404 ws_usage->shareable = proc_info.rss / 1024;
405 ws_usage->shared = proc_info.rshrd / 1024;
406 return true;