process_metrics_linux.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1// Copyright (c) 2013 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 "base/process/process_metrics.h" 6 7#include <dirent.h> 8#include <fcntl.h> 9#include <sys/stat.h> 10#include <sys/time.h> 11#include <sys/types.h> 12#include <unistd.h> 13 14#include "base/file_util.h" 15#include "base/logging.h" 16#include "base/process/internal_linux.h" 17#include "base/strings/string_number_conversions.h" 18#include "base/strings/string_split.h" 19#include "base/strings/string_tokenizer.h" 20#include "base/strings/string_util.h" 21#include "base/sys_info.h" 22#include "base/threading/thread_restrictions.h" 23 24namespace base { 25 26namespace { 27 28enum ParsingState { 29 KEY_NAME, 30 KEY_VALUE 31}; 32 33// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure. 34// Only works for fields in the form of "Field: value kB". 35size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) { 36 FilePath stat_file = internal::GetProcPidDir(pid).Append("status"); 37 std::string status; 38 { 39 // Synchronously reading files in /proc is safe. 40 ThreadRestrictions::ScopedAllowIO allow_io; 41 if (!file_util::ReadFileToString(stat_file, &status)) 42 return 0; 43 } 44 45 StringTokenizer tokenizer(status, ":\n"); 46 ParsingState state = KEY_NAME; 47 StringPiece last_key_name; 48 while (tokenizer.GetNext()) { 49 switch (state) { 50 case KEY_NAME: 51 last_key_name = tokenizer.token_piece(); 52 state = KEY_VALUE; 53 break; 54 case KEY_VALUE: 55 DCHECK(!last_key_name.empty()); 56 if (last_key_name == field) { 57 std::string value_str; 58 tokenizer.token_piece().CopyToString(&value_str); 59 std::string value_str_trimmed; 60 TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed); 61 std::vector<std::string> split_value_str; 62 SplitString(value_str_trimmed, ' ', &split_value_str); 63 if (split_value_str.size() != 2 || split_value_str[1] != "kB") { 64 NOTREACHED(); 65 return 0; 66 } 67 size_t value; 68 if (!StringToSizeT(split_value_str[0], &value)) { 69 NOTREACHED(); 70 return 0; 71 } 72 return value; 73 } 74 state = KEY_NAME; 75 break; 76 } 77 } 78 NOTREACHED(); 79 return 0; 80} 81 82// Get the total CPU of a single process. Return value is number of jiffies 83// on success or -1 on error. 84int GetProcessCPU(pid_t pid) { 85 // Use /proc/<pid>/task to find all threads and parse their /stat file. 86 FilePath task_path = internal::GetProcPidDir(pid).Append("task"); 87 88 DIR* dir = opendir(task_path.value().c_str()); 89 if (!dir) { 90 DPLOG(ERROR) << "opendir(" << task_path.value() << ")"; 91 return -1; 92 } 93 94 int total_cpu = 0; 95 while (struct dirent* ent = readdir(dir)) { 96 pid_t tid = internal::ProcDirSlotToPid(ent->d_name); 97 if (!tid) 98 continue; 99 100 // Synchronously reading files in /proc is safe. 101 ThreadRestrictions::ScopedAllowIO allow_io; 102 103 std::string stat; 104 FilePath stat_path = 105 task_path.Append(ent->d_name).Append(internal::kStatFile); 106 if (file_util::ReadFileToString(stat_path, &stat)) { 107 int cpu = ParseProcStatCPU(stat); 108 if (cpu > 0) 109 total_cpu += cpu; 110 } 111 } 112 closedir(dir); 113 114 return total_cpu; 115} 116 117} // namespace 118 119// static 120ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { 121 return new ProcessMetrics(process); 122} 123 124// On linux, we return vsize. 125size_t ProcessMetrics::GetPagefileUsage() const { 126 return internal::ReadProcStatsAndGetFieldAsSizeT(process_, 127 internal::VM_VSIZE); 128} 129 130// On linux, we return the high water mark of vsize. 131size_t ProcessMetrics::GetPeakPagefileUsage() const { 132 return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024; 133} 134 135// On linux, we return RSS. 136size_t ProcessMetrics::GetWorkingSetSize() const { 137 return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) * 138 getpagesize(); 139} 140 141// On linux, we return the high water mark of RSS. 142size_t ProcessMetrics::GetPeakWorkingSetSize() const { 143 return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024; 144} 145 146bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, 147 size_t* shared_bytes) { 148 WorkingSetKBytes ws_usage; 149 if (!GetWorkingSetKBytes(&ws_usage)) 150 return false; 151 152 if (private_bytes) 153 *private_bytes = ws_usage.priv * 1024; 154 155 if (shared_bytes) 156 *shared_bytes = ws_usage.shared * 1024; 157 158 return true; 159} 160 161bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { 162#if defined(OS_CHROMEOS) 163 if (GetWorkingSetKBytesTotmaps(ws_usage)) 164 return true; 165#endif 166 return GetWorkingSetKBytesStatm(ws_usage); 167} 168 169double ProcessMetrics::GetCPUUsage() { 170 // This queries the /proc-specific scaling factor which is 171 // conceptually the system hertz. To dump this value on another 172 // system, try 173 // od -t dL /proc/self/auxv 174 // and look for the number after 17 in the output; mine is 175 // 0000040 17 100 3 134512692 176 // which means the answer is 100. 177 // It may be the case that this value is always 100. 178 static const int kHertz = sysconf(_SC_CLK_TCK); 179 180 struct timeval now; 181 int retval = gettimeofday(&now, NULL); 182 if (retval) 183 return 0; 184 int64 time = TimeValToMicroseconds(now); 185 186 if (last_time_ == 0) { 187 // First call, just set the last values. 188 last_time_ = time; 189 last_cpu_ = GetProcessCPU(process_); 190 return 0; 191 } 192 193 int64 time_delta = time - last_time_; 194 DCHECK_NE(time_delta, 0); 195 if (time_delta == 0) 196 return 0; 197 198 int cpu = GetProcessCPU(process_); 199 200 // We have the number of jiffies in the time period. Convert to percentage. 201 // Note this means we will go *over* 100 in the case where multiple threads 202 // are together adding to more than one CPU's worth. 203 int percentage = 100 * (cpu - last_cpu_) / 204 (kHertz * TimeDelta::FromMicroseconds(time_delta).InSecondsF()); 205 206 last_time_ = time; 207 last_cpu_ = cpu; 208 209 return percentage; 210} 211 212// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING 213// in your kernel configuration. 214bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { 215 // Synchronously reading files in /proc is safe. 216 ThreadRestrictions::ScopedAllowIO allow_io; 217 218 std::string proc_io_contents; 219 FilePath io_file = internal::GetProcPidDir(process_).Append("io"); 220 if (!file_util::ReadFileToString(io_file, &proc_io_contents)) 221 return false; 222 223 (*io_counters).OtherOperationCount = 0; 224 (*io_counters).OtherTransferCount = 0; 225 226 StringTokenizer tokenizer(proc_io_contents, ": \n"); 227 ParsingState state = KEY_NAME; 228 StringPiece last_key_name; 229 while (tokenizer.GetNext()) { 230 switch (state) { 231 case KEY_NAME: 232 last_key_name = tokenizer.token_piece(); 233 state = KEY_VALUE; 234 break; 235 case KEY_VALUE: 236 DCHECK(!last_key_name.empty()); 237 if (last_key_name == "syscr") { 238 StringToInt64(tokenizer.token_piece(), 239 reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount)); 240 } else if (last_key_name == "syscw") { 241 StringToInt64(tokenizer.token_piece(), 242 reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount)); 243 } else if (last_key_name == "rchar") { 244 StringToInt64(tokenizer.token_piece(), 245 reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount)); 246 } else if (last_key_name == "wchar") { 247 StringToInt64(tokenizer.token_piece(), 248 reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount)); 249 } 250 state = KEY_NAME; 251 break; 252 } 253 } 254 return true; 255} 256 257ProcessMetrics::ProcessMetrics(ProcessHandle process) 258 : process_(process), 259 last_time_(0), 260 last_system_time_(0), 261 last_cpu_(0) { 262 processor_count_ = base::SysInfo::NumberOfProcessors(); 263} 264 265#if defined(OS_CHROMEOS) 266// Private, Shared and Proportional working set sizes are obtained from 267// /proc/<pid>/totmaps 268bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage) 269 const { 270 // The format of /proc/<pid>/totmaps is: 271 // 272 // Rss: 6120 kB 273 // Pss: 3335 kB 274 // Shared_Clean: 1008 kB 275 // Shared_Dirty: 4012 kB 276 // Private_Clean: 4 kB 277 // Private_Dirty: 1096 kB 278 // Referenced: XXX kB 279 // Anonymous: XXX kB 280 // AnonHugePages: XXX kB 281 // Swap: XXX kB 282 // Locked: XXX kB 283 const size_t kPssIndex = (1 * 3) + 1; 284 const size_t kPrivate_CleanIndex = (4 * 3) + 1; 285 const size_t kPrivate_DirtyIndex = (5 * 3) + 1; 286 const size_t kSwapIndex = (9 * 3) + 1; 287 288 std::string totmaps_data; 289 { 290 FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps"); 291 ThreadRestrictions::ScopedAllowIO allow_io; 292 bool ret = file_util::ReadFileToString(totmaps_file, &totmaps_data); 293 if (!ret || totmaps_data.length() == 0) 294 return false; 295 } 296 297 std::vector<std::string> totmaps_fields; 298 SplitStringAlongWhitespace(totmaps_data, &totmaps_fields); 299 300 DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]); 301 DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]); 302 DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]); 303 DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]); 304 305 int pss = 0; 306 int private_clean = 0; 307 int private_dirty = 0; 308 int swap = 0; 309 bool ret = true; 310 ret &= StringToInt(totmaps_fields[kPssIndex], &pss); 311 ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean); 312 ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty); 313 ret &= StringToInt(totmaps_fields[kSwapIndex], &swap); 314 315 // On ChromeOS swap is to zram. We count this as private / shared, as 316 // increased swap decreases available RAM to user processes, which would 317 // otherwise create surprising results. 318 ws_usage->priv = private_clean + private_dirty + swap; 319 ws_usage->shared = pss + swap; 320 ws_usage->shareable = 0; 321 ws_usage->swapped = swap; 322 return ret; 323} 324#endif 325 326// Private and Shared working set sizes are obtained from /proc/<pid>/statm. 327bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage) 328 const { 329 // Use statm instead of smaps because smaps is: 330 // a) Large and slow to parse. 331 // b) Unavailable in the SUID sandbox. 332 333 // First we need to get the page size, since everything is measured in pages. 334 // For details, see: man 5 proc. 335 const int page_size_kb = getpagesize() / 1024; 336 if (page_size_kb <= 0) 337 return false; 338 339 std::string statm; 340 { 341 FilePath statm_file = internal::GetProcPidDir(process_).Append("statm"); 342 // Synchronously reading files in /proc is safe. 343 ThreadRestrictions::ScopedAllowIO allow_io; 344 bool ret = file_util::ReadFileToString(statm_file, &statm); 345 if (!ret || statm.length() == 0) 346 return false; 347 } 348 349 std::vector<std::string> statm_vec; 350 SplitString(statm, ' ', &statm_vec); 351 if (statm_vec.size() != 7) 352 return false; // Not the format we expect. 353 354 int statm_rss, statm_shared; 355 bool ret = true; 356 ret &= StringToInt(statm_vec[1], &statm_rss); 357 ret &= StringToInt(statm_vec[2], &statm_shared); 358 359 ws_usage->priv = (statm_rss - statm_shared) * page_size_kb; 360 ws_usage->shared = statm_shared * page_size_kb; 361 362 // Sharable is not calculated, as it does not provide interesting data. 363 ws_usage->shareable = 0; 364 365#if defined(OS_CHROMEOS) 366 // Can't get swapped memory from statm. 367 ws_usage->swapped = 0; 368#endif 369 370 return ret; 371} 372 373size_t GetSystemCommitCharge() { 374 SystemMemoryInfoKB meminfo; 375 if (!GetSystemMemoryInfo(&meminfo)) 376 return 0; 377 return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached; 378} 379 380// Exposed for testing. 381int ParseProcStatCPU(const std::string& input) { 382 std::vector<std::string> proc_stats; 383 if (!internal::ParseProcStats(input, &proc_stats)) 384 return -1; 385 386 if (proc_stats.size() <= internal::VM_STIME) 387 return -1; 388 int utime = GetProcStatsFieldAsInt(proc_stats, internal::VM_UTIME); 389 int stime = GetProcStatsFieldAsInt(proc_stats, internal::VM_STIME); 390 return utime + stime; 391} 392 393namespace { 394 395// The format of /proc/meminfo is: 396// 397// MemTotal: 8235324 kB 398// MemFree: 1628304 kB 399// Buffers: 429596 kB 400// Cached: 4728232 kB 401// ... 402const size_t kMemTotalIndex = 1; 403const size_t kMemFreeIndex = 4; 404const size_t kMemBuffersIndex = 7; 405const size_t kMemCachedIndex = 10; 406const size_t kMemActiveAnonIndex = 22; 407const size_t kMemInactiveAnonIndex = 25; 408const size_t kMemActiveFileIndex = 28; 409const size_t kMemInactiveFileIndex = 31; 410 411} // namespace 412 413SystemMemoryInfoKB::SystemMemoryInfoKB() 414 : total(0), 415 free(0), 416 buffers(0), 417 cached(0), 418 active_anon(0), 419 inactive_anon(0), 420 active_file(0), 421 inactive_file(0), 422 shmem(0), 423 gem_objects(-1), 424 gem_size(-1) { 425} 426 427bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { 428 // Synchronously reading files in /proc is safe. 429 ThreadRestrictions::ScopedAllowIO allow_io; 430 431 // Used memory is: total - free - buffers - caches 432 FilePath meminfo_file("/proc/meminfo"); 433 std::string meminfo_data; 434 if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) { 435 DLOG(WARNING) << "Failed to open " << meminfo_file.value(); 436 return false; 437 } 438 std::vector<std::string> meminfo_fields; 439 SplitStringAlongWhitespace(meminfo_data, &meminfo_fields); 440 441 if (meminfo_fields.size() < kMemCachedIndex) { 442 DLOG(WARNING) << "Failed to parse " << meminfo_file.value() 443 << ". Only found " << meminfo_fields.size() << " fields."; 444 return false; 445 } 446 447 DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:"); 448 DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:"); 449 DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:"); 450 DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:"); 451 DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):"); 452 DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):"); 453 DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):"); 454 DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):"); 455 456 StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total); 457 StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free); 458 StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers); 459 StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached); 460 StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon); 461 StringToInt(meminfo_fields[kMemInactiveAnonIndex], 462 &meminfo->inactive_anon); 463 StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file); 464 StringToInt(meminfo_fields[kMemInactiveFileIndex], 465 &meminfo->inactive_file); 466#if defined(OS_CHROMEOS) 467 // Chrome OS has a tweaked kernel that allows us to query Shmem, which is 468 // usually video memory otherwise invisible to the OS. Unfortunately, the 469 // meminfo format varies on different hardware so we have to search for the 470 // string. It always appears after "Cached:". 471 for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) { 472 if (meminfo_fields[i] == "Shmem:") { 473 StringToInt(meminfo_fields[i+1], &meminfo->shmem); 474 break; 475 } 476 } 477 478 // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a 479 // bind mount into /sys/kernel/debug and synchronously reading the in-memory 480 // files in /sys is fast. 481#if defined(ARCH_CPU_ARM_FAMILY) 482 FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects"); 483#else 484 FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects"); 485#endif 486 std::string geminfo_data; 487 meminfo->gem_objects = -1; 488 meminfo->gem_size = -1; 489 if (file_util::ReadFileToString(geminfo_file, &geminfo_data)) { 490 int gem_objects = -1; 491 long long gem_size = -1; 492 int num_res = sscanf(geminfo_data.c_str(), 493 "%d objects, %lld bytes", 494 &gem_objects, &gem_size); 495 if (num_res == 2) { 496 meminfo->gem_objects = gem_objects; 497 meminfo->gem_size = gem_size; 498 } 499 } 500 501#if defined(ARCH_CPU_ARM_FAMILY) 502 // Incorporate Mali graphics memory if present. 503 FilePath mali_memory_file("/sys/devices/platform/mali.0/memory"); 504 std::string mali_memory_data; 505 if (file_util::ReadFileToString(mali_memory_file, &mali_memory_data)) { 506 long long mali_size = -1; 507 int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size); 508 if (num_res == 1) 509 meminfo->gem_size += mali_size; 510 } 511#endif // defined(ARCH_CPU_ARM_FAMILY) 512#endif // defined(OS_CHROMEOS) 513 514 return true; 515} 516 517const char kProcSelfExe[] = "/proc/self/exe"; 518 519int GetNumberOfThreads(ProcessHandle process) { 520 return internal::ReadProcStatsAndGetFieldAsInt(process, 521 internal::VM_NUMTHREADS); 522} 523 524} // namespace base 525