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