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/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" 19 20// Default constructor. 21ProcessInfoSnapshot::ProcessInfoSnapshot() { } 22 23// Destructor: just call |Reset()| to release everything. 24ProcessInfoSnapshot::~ProcessInfoSnapshot() { 25 Reset(); 26} 27 28const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; 29 30static 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; 36 } 37 38 if (len == 0) { 39 // If the process isn't found then sysctl returns a length of 0. 40 return false; 41 } 42 43 return true; 44} 45 46static bool GetExecutableNameForProcessID( 47 pid_t pid, 48 std::string* executable_name) { 49 if (!executable_name) { 50 NOTREACHED(); 51 return false; 52 } 53 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"; 60 } 61 62 if (s_arg_max == 0) 63 return false; 64 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; 72 } 73 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; 79 } 80 81 executable_name->resize(end_pos); 82 return true; 83} 84 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 89static 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; 116 } 117 118 uint64_t scale = 1; 119 for (int i = 0; i < shift; i++) 120 scale *= 1024; 121 *out_scale = scale; 122 123 return true; 124} 125 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). 129static 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 CommandLine command_line(kProgram); 134 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))); 143 } 144 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; 150 } 151 152 std::istringstream in(output, std::istringstream::in); 153 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; 164 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; 171 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; 176 } 177 178 if (!proc_info.pid || ! proc_info.vsize) { 179 LOG(WARNING) << "Invalid data from " << kProgram.value() << "."; 180 return false; 181 } 182 183 // Record the process information. 184 proc_info_entries[proc_info.pid] = proc_info; 185 186 // Ignore the rest of the line. 187 in.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 188 } 189 190 return true; 191} 192 193static bool GetProcessMemoryInfoUsingTop( 194 std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) { 195 const base::FilePath kProgram("/usr/bin/top"); 196 CommandLine command_line(kProgram); 197 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"); 211 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; 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 275bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) { 276 Reset(); 277 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; 285 } 286 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; 292 293 kinfo_proc kinfo; 294 if (!GetKInfoForProcessID(*it, &kinfo)) 295 return false; 296 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; 303 } 304 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; 313 } 314 } 315 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_); 322 323 // Get memory information using top. 324 bool memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_); 325 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_); 330 } 331 332 return memory_info_success; 333} 334 335// Clear all the stored information. 336void ProcessInfoSnapshot::Reset() { 337 proc_info_entries_.clear(); 338} 339 340ProcessInfoSnapshot::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) { 349} 350 351bool 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; 356 357 *proc_info = it->second; 358 return true; 359} 360 361bool 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; 368 } 369 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; 377 } 378 379 usage->priv = proc_info.vsize / 1024; 380 usage->mapped = 0; 381 usage->image = 0; 382 return true; 383} 384 385bool 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; 392 } 393 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; 401 } 402 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; 407} 408