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