process_metrics_linux.cc revision ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16
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  struct timeval now;
171  int retval = gettimeofday(&now, NULL);
172  if (retval)
173    return 0;
174  int64 time = TimeValToMicroseconds(now);
175
176  if (last_time_ == 0) {
177    // First call, just set the last values.
178    last_time_ = time;
179    last_cpu_ = GetProcessCPU(process_);
180    return 0;
181  }
182
183  int64 time_delta = time - last_time_;
184  DCHECK_NE(time_delta, 0);
185  if (time_delta == 0)
186    return 0;
187
188  int cpu = GetProcessCPU(process_);
189
190  // We have the number of jiffies in the time period.  Convert to percentage.
191  // Note this means we will go *over* 100 in the case where multiple threads
192  // are together adding to more than one CPU's worth.
193  TimeDelta cpu_time = internal::ClockTicksToTimeDelta(cpu);
194  TimeDelta last_cpu_time = internal::ClockTicksToTimeDelta(last_cpu_);
195  int percentage = 100 * (cpu_time - last_cpu_time).InSecondsF() /
196      TimeDelta::FromMicroseconds(time_delta).InSecondsF();
197
198  last_time_ = time;
199  last_cpu_ = cpu;
200
201  return percentage;
202}
203
204// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING
205// in your kernel configuration.
206bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
207  // Synchronously reading files in /proc is safe.
208  ThreadRestrictions::ScopedAllowIO allow_io;
209
210  std::string proc_io_contents;
211  FilePath io_file = internal::GetProcPidDir(process_).Append("io");
212  if (!file_util::ReadFileToString(io_file, &proc_io_contents))
213    return false;
214
215  (*io_counters).OtherOperationCount = 0;
216  (*io_counters).OtherTransferCount = 0;
217
218  StringTokenizer tokenizer(proc_io_contents, ": \n");
219  ParsingState state = KEY_NAME;
220  StringPiece last_key_name;
221  while (tokenizer.GetNext()) {
222    switch (state) {
223      case KEY_NAME:
224        last_key_name = tokenizer.token_piece();
225        state = KEY_VALUE;
226        break;
227      case KEY_VALUE:
228        DCHECK(!last_key_name.empty());
229        if (last_key_name == "syscr") {
230          StringToInt64(tokenizer.token_piece(),
231              reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount));
232        } else if (last_key_name == "syscw") {
233          StringToInt64(tokenizer.token_piece(),
234              reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount));
235        } else if (last_key_name == "rchar") {
236          StringToInt64(tokenizer.token_piece(),
237              reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount));
238        } else if (last_key_name == "wchar") {
239          StringToInt64(tokenizer.token_piece(),
240              reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount));
241        }
242        state = KEY_NAME;
243        break;
244    }
245  }
246  return true;
247}
248
249ProcessMetrics::ProcessMetrics(ProcessHandle process)
250    : process_(process),
251      last_time_(0),
252      last_system_time_(0),
253      last_cpu_(0) {
254  processor_count_ = base::SysInfo::NumberOfProcessors();
255}
256
257#if defined(OS_CHROMEOS)
258// Private, Shared and Proportional working set sizes are obtained from
259// /proc/<pid>/totmaps
260bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
261  const {
262  // The format of /proc/<pid>/totmaps is:
263  //
264  // Rss:                6120 kB
265  // Pss:                3335 kB
266  // Shared_Clean:       1008 kB
267  // Shared_Dirty:       4012 kB
268  // Private_Clean:         4 kB
269  // Private_Dirty:      1096 kB
270  // Referenced:          XXX kB
271  // Anonymous:           XXX kB
272  // AnonHugePages:       XXX kB
273  // Swap:                XXX kB
274  // Locked:              XXX kB
275  const size_t kPssIndex = (1 * 3) + 1;
276  const size_t kPrivate_CleanIndex = (4 * 3) + 1;
277  const size_t kPrivate_DirtyIndex = (5 * 3) + 1;
278  const size_t kSwapIndex = (9 * 3) + 1;
279
280  std::string totmaps_data;
281  {
282    FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps");
283    ThreadRestrictions::ScopedAllowIO allow_io;
284    bool ret = file_util::ReadFileToString(totmaps_file, &totmaps_data);
285    if (!ret || totmaps_data.length() == 0)
286      return false;
287  }
288
289  std::vector<std::string> totmaps_fields;
290  SplitStringAlongWhitespace(totmaps_data, &totmaps_fields);
291
292  DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]);
293  DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]);
294  DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]);
295  DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]);
296
297  int pss = 0;
298  int private_clean = 0;
299  int private_dirty = 0;
300  int swap = 0;
301  bool ret = true;
302  ret &= StringToInt(totmaps_fields[kPssIndex], &pss);
303  ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean);
304  ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty);
305  ret &= StringToInt(totmaps_fields[kSwapIndex], &swap);
306
307  // On ChromeOS swap is to zram. We count this as private / shared, as
308  // increased swap decreases available RAM to user processes, which would
309  // otherwise create surprising results.
310  ws_usage->priv = private_clean + private_dirty + swap;
311  ws_usage->shared = pss + swap;
312  ws_usage->shareable = 0;
313  ws_usage->swapped = swap;
314  return ret;
315}
316#endif
317
318// Private and Shared working set sizes are obtained from /proc/<pid>/statm.
319bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage)
320    const {
321  // Use statm instead of smaps because smaps is:
322  // a) Large and slow to parse.
323  // b) Unavailable in the SUID sandbox.
324
325  // First we need to get the page size, since everything is measured in pages.
326  // For details, see: man 5 proc.
327  const int page_size_kb = getpagesize() / 1024;
328  if (page_size_kb <= 0)
329    return false;
330
331  std::string statm;
332  {
333    FilePath statm_file = internal::GetProcPidDir(process_).Append("statm");
334    // Synchronously reading files in /proc is safe.
335    ThreadRestrictions::ScopedAllowIO allow_io;
336    bool ret = file_util::ReadFileToString(statm_file, &statm);
337    if (!ret || statm.length() == 0)
338      return false;
339  }
340
341  std::vector<std::string> statm_vec;
342  SplitString(statm, ' ', &statm_vec);
343  if (statm_vec.size() != 7)
344    return false;  // Not the format we expect.
345
346  int statm_rss, statm_shared;
347  bool ret = true;
348  ret &= StringToInt(statm_vec[1], &statm_rss);
349  ret &= StringToInt(statm_vec[2], &statm_shared);
350
351  ws_usage->priv = (statm_rss - statm_shared) * page_size_kb;
352  ws_usage->shared = statm_shared * page_size_kb;
353
354  // Sharable is not calculated, as it does not provide interesting data.
355  ws_usage->shareable = 0;
356
357#if defined(OS_CHROMEOS)
358  // Can't get swapped memory from statm.
359  ws_usage->swapped = 0;
360#endif
361
362  return ret;
363}
364
365size_t GetSystemCommitCharge() {
366  SystemMemoryInfoKB meminfo;
367  if (!GetSystemMemoryInfo(&meminfo))
368    return 0;
369  return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached;
370}
371
372// Exposed for testing.
373int ParseProcStatCPU(const std::string& input) {
374  std::vector<std::string> proc_stats;
375  if (!internal::ParseProcStats(input, &proc_stats))
376    return -1;
377
378  if (proc_stats.size() <= internal::VM_STIME)
379    return -1;
380  int utime = GetProcStatsFieldAsInt(proc_stats, internal::VM_UTIME);
381  int stime = GetProcStatsFieldAsInt(proc_stats, internal::VM_STIME);
382  return utime + stime;
383}
384
385namespace {
386
387// The format of /proc/meminfo is:
388//
389// MemTotal:      8235324 kB
390// MemFree:       1628304 kB
391// Buffers:        429596 kB
392// Cached:        4728232 kB
393// ...
394const size_t kMemTotalIndex = 1;
395const size_t kMemFreeIndex = 4;
396const size_t kMemBuffersIndex = 7;
397const size_t kMemCachedIndex = 10;
398const size_t kMemActiveAnonIndex = 22;
399const size_t kMemInactiveAnonIndex = 25;
400const size_t kMemActiveFileIndex = 28;
401const size_t kMemInactiveFileIndex = 31;
402
403}  // namespace
404
405SystemMemoryInfoKB::SystemMemoryInfoKB()
406    : total(0),
407      free(0),
408      buffers(0),
409      cached(0),
410      active_anon(0),
411      inactive_anon(0),
412      active_file(0),
413      inactive_file(0),
414      shmem(0),
415      gem_objects(-1),
416      gem_size(-1) {
417}
418
419bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
420  // Synchronously reading files in /proc is safe.
421  ThreadRestrictions::ScopedAllowIO allow_io;
422
423  // Used memory is: total - free - buffers - caches
424  FilePath meminfo_file("/proc/meminfo");
425  std::string meminfo_data;
426  if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) {
427    DLOG(WARNING) << "Failed to open " << meminfo_file.value();
428    return false;
429  }
430  std::vector<std::string> meminfo_fields;
431  SplitStringAlongWhitespace(meminfo_data, &meminfo_fields);
432
433  if (meminfo_fields.size() < kMemCachedIndex) {
434    DLOG(WARNING) << "Failed to parse " << meminfo_file.value()
435                  << ".  Only found " << meminfo_fields.size() << " fields.";
436    return false;
437  }
438
439  DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:");
440  DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:");
441  DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:");
442  DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:");
443  DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):");
444  DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):");
445  DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):");
446  DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):");
447
448  StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total);
449  StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free);
450  StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers);
451  StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached);
452  StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon);
453  StringToInt(meminfo_fields[kMemInactiveAnonIndex],
454                    &meminfo->inactive_anon);
455  StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file);
456  StringToInt(meminfo_fields[kMemInactiveFileIndex],
457                    &meminfo->inactive_file);
458#if defined(OS_CHROMEOS)
459  // Chrome OS has a tweaked kernel that allows us to query Shmem, which is
460  // usually video memory otherwise invisible to the OS.  Unfortunately, the
461  // meminfo format varies on different hardware so we have to search for the
462  // string.  It always appears after "Cached:".
463  for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) {
464    if (meminfo_fields[i] == "Shmem:") {
465      StringToInt(meminfo_fields[i+1], &meminfo->shmem);
466      break;
467    }
468  }
469
470  // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a
471  // bind mount into /sys/kernel/debug and synchronously reading the in-memory
472  // files in /sys is fast.
473#if defined(ARCH_CPU_ARM_FAMILY)
474  FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects");
475#else
476  FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects");
477#endif
478  std::string geminfo_data;
479  meminfo->gem_objects = -1;
480  meminfo->gem_size = -1;
481  if (file_util::ReadFileToString(geminfo_file, &geminfo_data)) {
482    int gem_objects = -1;
483    long long gem_size = -1;
484    int num_res = sscanf(geminfo_data.c_str(),
485                         "%d objects, %lld bytes",
486                         &gem_objects, &gem_size);
487    if (num_res == 2) {
488      meminfo->gem_objects = gem_objects;
489      meminfo->gem_size = gem_size;
490    }
491  }
492
493#if defined(ARCH_CPU_ARM_FAMILY)
494  // Incorporate Mali graphics memory if present.
495  FilePath mali_memory_file("/sys/devices/platform/mali.0/memory");
496  std::string mali_memory_data;
497  if (file_util::ReadFileToString(mali_memory_file, &mali_memory_data)) {
498    long long mali_size = -1;
499    int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
500    if (num_res == 1)
501      meminfo->gem_size += mali_size;
502  }
503#endif  // defined(ARCH_CPU_ARM_FAMILY)
504#endif  // defined(OS_CHROMEOS)
505
506  return true;
507}
508
509const char kProcSelfExe[] = "/proc/self/exe";
510
511int GetNumberOfThreads(ProcessHandle process) {
512  return internal::ReadProcStatsAndGetFieldAsInt(process,
513                                                 internal::VM_NUMTHREADS);
514}
515
516}  // namespace base
517