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. 19 ProcessInfoSnapshot::ProcessInfoSnapshot() { } 20 21 // Destructor: just call |Reset()| to release everything. 22 ProcessInfoSnapshot::~ProcessInfoSnapshot() { 23 Reset(); 24 } 25 26 const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000; 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 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 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 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). 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 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 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 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. 405 void ProcessInfoSnapshot::Reset() { 406 proc_info_entries_.clear(); 407 } 408 409 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 419 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 443 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