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