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