process_metrics_linux.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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#ifdef OS_CHROMEOS
34// Read a file with a single number string and return the number as a uint64.
35static uint64 ReadFileToUint64(const base::FilePath file) {
36  std::string file_as_string;
37  if (!ReadFileToString(file, &file_as_string))
38    return 0;
39  base::TrimWhitespaceASCII(file_as_string, base::TRIM_ALL, &file_as_string);
40  uint64 file_as_uint64 = 0;
41  if (!base::StringToUint64(file_as_string, &file_as_uint64))
42    return 0;
43  return file_as_uint64;
44}
45#endif
46
47// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure.
48// Only works for fields in the form of "Field: value kB".
49size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) {
50  FilePath stat_file = internal::GetProcPidDir(pid).Append("status");
51  std::string status;
52  {
53    // Synchronously reading files in /proc is safe.
54    ThreadRestrictions::ScopedAllowIO allow_io;
55    if (!ReadFileToString(stat_file, &status))
56      return 0;
57  }
58
59  StringTokenizer tokenizer(status, ":\n");
60  ParsingState state = KEY_NAME;
61  StringPiece last_key_name;
62  while (tokenizer.GetNext()) {
63    switch (state) {
64      case KEY_NAME:
65        last_key_name = tokenizer.token_piece();
66        state = KEY_VALUE;
67        break;
68      case KEY_VALUE:
69        DCHECK(!last_key_name.empty());
70        if (last_key_name == field) {
71          std::string value_str;
72          tokenizer.token_piece().CopyToString(&value_str);
73          std::string value_str_trimmed;
74          base::TrimWhitespaceASCII(value_str, base::TRIM_ALL,
75                                    &value_str_trimmed);
76          std::vector<std::string> split_value_str;
77          SplitString(value_str_trimmed, ' ', &split_value_str);
78          if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
79            NOTREACHED();
80            return 0;
81          }
82          size_t value;
83          if (!StringToSizeT(split_value_str[0], &value)) {
84            NOTREACHED();
85            return 0;
86          }
87          return value;
88        }
89        state = KEY_NAME;
90        break;
91    }
92  }
93  NOTREACHED();
94  return 0;
95}
96
97// Get the total CPU of a single process.  Return value is number of jiffies
98// on success or -1 on error.
99int GetProcessCPU(pid_t pid) {
100  // Use /proc/<pid>/task to find all threads and parse their /stat file.
101  FilePath task_path = internal::GetProcPidDir(pid).Append("task");
102
103  DIR* dir = opendir(task_path.value().c_str());
104  if (!dir) {
105    DPLOG(ERROR) << "opendir(" << task_path.value() << ")";
106    return -1;
107  }
108
109  int total_cpu = 0;
110  while (struct dirent* ent = readdir(dir)) {
111    pid_t tid = internal::ProcDirSlotToPid(ent->d_name);
112    if (!tid)
113      continue;
114
115    // Synchronously reading files in /proc is safe.
116    ThreadRestrictions::ScopedAllowIO allow_io;
117
118    std::string stat;
119    FilePath stat_path =
120        task_path.Append(ent->d_name).Append(internal::kStatFile);
121    if (ReadFileToString(stat_path, &stat)) {
122      int cpu = ParseProcStatCPU(stat);
123      if (cpu > 0)
124        total_cpu += cpu;
125    }
126  }
127  closedir(dir);
128
129  return total_cpu;
130}
131
132}  // namespace
133
134// static
135ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) {
136  return new ProcessMetrics(process);
137}
138
139// On linux, we return vsize.
140size_t ProcessMetrics::GetPagefileUsage() const {
141  return internal::ReadProcStatsAndGetFieldAsSizeT(process_,
142                                                   internal::VM_VSIZE);
143}
144
145// On linux, we return the high water mark of vsize.
146size_t ProcessMetrics::GetPeakPagefileUsage() const {
147  return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024;
148}
149
150// On linux, we return RSS.
151size_t ProcessMetrics::GetWorkingSetSize() const {
152  return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
153      getpagesize();
154}
155
156// On linux, we return the high water mark of RSS.
157size_t ProcessMetrics::GetPeakWorkingSetSize() const {
158  return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024;
159}
160
161bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
162                                    size_t* shared_bytes) {
163  WorkingSetKBytes ws_usage;
164  if (!GetWorkingSetKBytes(&ws_usage))
165    return false;
166
167  if (private_bytes)
168    *private_bytes = ws_usage.priv * 1024;
169
170  if (shared_bytes)
171    *shared_bytes = ws_usage.shared * 1024;
172
173  return true;
174}
175
176bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
177#if defined(OS_CHROMEOS)
178  if (GetWorkingSetKBytesTotmaps(ws_usage))
179    return true;
180#endif
181  return GetWorkingSetKBytesStatm(ws_usage);
182}
183
184double ProcessMetrics::GetCPUUsage() {
185  struct timeval now;
186  int retval = gettimeofday(&now, NULL);
187  if (retval)
188    return 0;
189  int64 time = TimeValToMicroseconds(now);
190
191  if (last_cpu_time_ == 0) {
192    // First call, just set the last values.
193    last_cpu_time_ = time;
194    last_cpu_ = GetProcessCPU(process_);
195    return 0;
196  }
197
198  int64 time_delta = time - last_cpu_time_;
199  DCHECK_NE(time_delta, 0);
200  if (time_delta == 0)
201    return 0;
202
203  int cpu = GetProcessCPU(process_);
204
205  // We have the number of jiffies in the time period.  Convert to percentage.
206  // Note this means we will go *over* 100 in the case where multiple threads
207  // are together adding to more than one CPU's worth.
208  TimeDelta cpu_time = internal::ClockTicksToTimeDelta(cpu);
209  TimeDelta last_cpu_time = internal::ClockTicksToTimeDelta(last_cpu_);
210  int percentage = 100 * (cpu_time - last_cpu_time).InSecondsF() /
211      TimeDelta::FromMicroseconds(time_delta).InSecondsF();
212
213  last_cpu_time_ = time;
214  last_cpu_ = cpu;
215
216  return percentage;
217}
218
219// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING
220// in your kernel configuration.
221bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
222  // Synchronously reading files in /proc is safe.
223  ThreadRestrictions::ScopedAllowIO allow_io;
224
225  std::string proc_io_contents;
226  FilePath io_file = internal::GetProcPidDir(process_).Append("io");
227  if (!ReadFileToString(io_file, &proc_io_contents))
228    return false;
229
230  (*io_counters).OtherOperationCount = 0;
231  (*io_counters).OtherTransferCount = 0;
232
233  StringTokenizer tokenizer(proc_io_contents, ": \n");
234  ParsingState state = KEY_NAME;
235  StringPiece last_key_name;
236  while (tokenizer.GetNext()) {
237    switch (state) {
238      case KEY_NAME:
239        last_key_name = tokenizer.token_piece();
240        state = KEY_VALUE;
241        break;
242      case KEY_VALUE:
243        DCHECK(!last_key_name.empty());
244        if (last_key_name == "syscr") {
245          StringToInt64(tokenizer.token_piece(),
246              reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount));
247        } else if (last_key_name == "syscw") {
248          StringToInt64(tokenizer.token_piece(),
249              reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount));
250        } else if (last_key_name == "rchar") {
251          StringToInt64(tokenizer.token_piece(),
252              reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount));
253        } else if (last_key_name == "wchar") {
254          StringToInt64(tokenizer.token_piece(),
255              reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount));
256        }
257        state = KEY_NAME;
258        break;
259    }
260  }
261  return true;
262}
263
264ProcessMetrics::ProcessMetrics(ProcessHandle process)
265    : process_(process),
266      last_cpu_time_(0),
267      last_system_time_(0),
268      last_cpu_(0) {
269  processor_count_ = base::SysInfo::NumberOfProcessors();
270}
271
272#if defined(OS_CHROMEOS)
273// Private, Shared and Proportional working set sizes are obtained from
274// /proc/<pid>/totmaps
275bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
276  const {
277  // The format of /proc/<pid>/totmaps is:
278  //
279  // Rss:                6120 kB
280  // Pss:                3335 kB
281  // Shared_Clean:       1008 kB
282  // Shared_Dirty:       4012 kB
283  // Private_Clean:         4 kB
284  // Private_Dirty:      1096 kB
285  // Referenced:          XXX kB
286  // Anonymous:           XXX kB
287  // AnonHugePages:       XXX kB
288  // Swap:                XXX kB
289  // Locked:              XXX kB
290  const size_t kPssIndex = (1 * 3) + 1;
291  const size_t kPrivate_CleanIndex = (4 * 3) + 1;
292  const size_t kPrivate_DirtyIndex = (5 * 3) + 1;
293  const size_t kSwapIndex = (9 * 3) + 1;
294
295  std::string totmaps_data;
296  {
297    FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps");
298    ThreadRestrictions::ScopedAllowIO allow_io;
299    bool ret = ReadFileToString(totmaps_file, &totmaps_data);
300    if (!ret || totmaps_data.length() == 0)
301      return false;
302  }
303
304  std::vector<std::string> totmaps_fields;
305  SplitStringAlongWhitespace(totmaps_data, &totmaps_fields);
306
307  DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]);
308  DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]);
309  DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]);
310  DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]);
311
312  int pss = 0;
313  int private_clean = 0;
314  int private_dirty = 0;
315  int swap = 0;
316  bool ret = true;
317  ret &= StringToInt(totmaps_fields[kPssIndex], &pss);
318  ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean);
319  ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty);
320  ret &= StringToInt(totmaps_fields[kSwapIndex], &swap);
321
322  // On ChromeOS swap is to zram. We count this as private / shared, as
323  // increased swap decreases available RAM to user processes, which would
324  // otherwise create surprising results.
325  ws_usage->priv = private_clean + private_dirty + swap;
326  ws_usage->shared = pss + swap;
327  ws_usage->shareable = 0;
328  ws_usage->swapped = swap;
329  return ret;
330}
331#endif
332
333// Private and Shared working set sizes are obtained from /proc/<pid>/statm.
334bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage)
335    const {
336  // Use statm instead of smaps because smaps is:
337  // a) Large and slow to parse.
338  // b) Unavailable in the SUID sandbox.
339
340  // First we need to get the page size, since everything is measured in pages.
341  // For details, see: man 5 proc.
342  const int page_size_kb = getpagesize() / 1024;
343  if (page_size_kb <= 0)
344    return false;
345
346  std::string statm;
347  {
348    FilePath statm_file = internal::GetProcPidDir(process_).Append("statm");
349    // Synchronously reading files in /proc is safe.
350    ThreadRestrictions::ScopedAllowIO allow_io;
351    bool ret = ReadFileToString(statm_file, &statm);
352    if (!ret || statm.length() == 0)
353      return false;
354  }
355
356  std::vector<std::string> statm_vec;
357  SplitString(statm, ' ', &statm_vec);
358  if (statm_vec.size() != 7)
359    return false;  // Not the format we expect.
360
361  int statm_rss, statm_shared;
362  bool ret = true;
363  ret &= StringToInt(statm_vec[1], &statm_rss);
364  ret &= StringToInt(statm_vec[2], &statm_shared);
365
366  ws_usage->priv = (statm_rss - statm_shared) * page_size_kb;
367  ws_usage->shared = statm_shared * page_size_kb;
368
369  // Sharable is not calculated, as it does not provide interesting data.
370  ws_usage->shareable = 0;
371
372#if defined(OS_CHROMEOS)
373  // Can't get swapped memory from statm.
374  ws_usage->swapped = 0;
375#endif
376
377  return ret;
378}
379
380size_t GetSystemCommitCharge() {
381  SystemMemoryInfoKB meminfo;
382  if (!GetSystemMemoryInfo(&meminfo))
383    return 0;
384  return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached;
385}
386
387// Exposed for testing.
388int ParseProcStatCPU(const std::string& input) {
389  std::vector<std::string> proc_stats;
390  if (!internal::ParseProcStats(input, &proc_stats))
391    return -1;
392
393  if (proc_stats.size() <= internal::VM_STIME)
394    return -1;
395  int utime = GetProcStatsFieldAsInt64(proc_stats, internal::VM_UTIME);
396  int stime = GetProcStatsFieldAsInt64(proc_stats, internal::VM_STIME);
397  return utime + stime;
398}
399
400const char kProcSelfExe[] = "/proc/self/exe";
401
402int GetNumberOfThreads(ProcessHandle process) {
403  return internal::ReadProcStatsAndGetFieldAsInt64(process,
404                                                   internal::VM_NUMTHREADS);
405}
406
407namespace {
408
409// The format of /proc/diskstats is:
410//  Device major number
411//  Device minor number
412//  Device name
413//  Field  1 -- # of reads completed
414//      This is the total number of reads completed successfully.
415//  Field  2 -- # of reads merged, field 6 -- # of writes merged
416//      Reads and writes which are adjacent to each other may be merged for
417//      efficiency.  Thus two 4K reads may become one 8K read before it is
418//      ultimately handed to the disk, and so it will be counted (and queued)
419//      as only one I/O.  This field lets you know how often this was done.
420//  Field  3 -- # of sectors read
421//      This is the total number of sectors read successfully.
422//  Field  4 -- # of milliseconds spent reading
423//      This is the total number of milliseconds spent by all reads (as
424//      measured from __make_request() to end_that_request_last()).
425//  Field  5 -- # of writes completed
426//      This is the total number of writes completed successfully.
427//  Field  6 -- # of writes merged
428//      See the description of field 2.
429//  Field  7 -- # of sectors written
430//      This is the total number of sectors written successfully.
431//  Field  8 -- # of milliseconds spent writing
432//      This is the total number of milliseconds spent by all writes (as
433//      measured from __make_request() to end_that_request_last()).
434//  Field  9 -- # of I/Os currently in progress
435//      The only field that should go to zero. Incremented as requests are
436//      given to appropriate struct request_queue and decremented as they
437//      finish.
438//  Field 10 -- # of milliseconds spent doing I/Os
439//      This field increases so long as field 9 is nonzero.
440//  Field 11 -- weighted # of milliseconds spent doing I/Os
441//      This field is incremented at each I/O start, I/O completion, I/O
442//      merge, or read of these stats by the number of I/Os in progress
443//      (field 9) times the number of milliseconds spent doing I/O since the
444//      last update of this field.  This can provide an easy measure of both
445//      I/O completion time and the backlog that may be accumulating.
446
447const size_t kDiskDriveName = 2;
448const size_t kDiskReads = 3;
449const size_t kDiskReadsMerged = 4;
450const size_t kDiskSectorsRead = 5;
451const size_t kDiskReadTime = 6;
452const size_t kDiskWrites = 7;
453const size_t kDiskWritesMerged = 8;
454const size_t kDiskSectorsWritten = 9;
455const size_t kDiskWriteTime = 10;
456const size_t kDiskIO = 11;
457const size_t kDiskIOTime = 12;
458const size_t kDiskWeightedIOTime = 13;
459
460}  // namespace
461
462SystemMemoryInfoKB::SystemMemoryInfoKB() {
463  total = 0;
464  free = 0;
465  buffers = 0;
466  cached = 0;
467  active_anon = 0;
468  inactive_anon = 0;
469  active_file = 0;
470  inactive_file = 0;
471  swap_total = 0;
472  swap_free = 0;
473  dirty = 0;
474
475  pswpin = 0;
476  pswpout = 0;
477  pgmajfault = 0;
478
479#ifdef OS_CHROMEOS
480  shmem = 0;
481  slab = 0;
482  gem_objects = -1;
483  gem_size = -1;
484#endif
485}
486
487scoped_ptr<Value> SystemMemoryInfoKB::ToValue() const {
488  scoped_ptr<DictionaryValue> res(new base::DictionaryValue());
489
490  res->SetInteger("total", total);
491  res->SetInteger("free", free);
492  res->SetInteger("buffers", buffers);
493  res->SetInteger("cached", cached);
494  res->SetInteger("active_anon", active_anon);
495  res->SetInteger("inactive_anon", inactive_anon);
496  res->SetInteger("active_file", active_file);
497  res->SetInteger("inactive_file", inactive_file);
498  res->SetInteger("swap_total", swap_total);
499  res->SetInteger("swap_free", swap_free);
500  res->SetInteger("swap_used", swap_total - swap_free);
501  res->SetInteger("dirty", dirty);
502  res->SetInteger("pswpin", pswpin);
503  res->SetInteger("pswpout", pswpout);
504  res->SetInteger("pgmajfault", pgmajfault);
505#ifdef OS_CHROMEOS
506  res->SetInteger("shmem", shmem);
507  res->SetInteger("slab", slab);
508  res->SetInteger("gem_objects", gem_objects);
509  res->SetInteger("gem_size", gem_size);
510#endif
511
512  return res.PassAs<Value>();
513}
514
515// exposed for testing
516bool ParseProcMeminfo(const std::string& meminfo_data,
517                      SystemMemoryInfoKB* meminfo) {
518  // The format of /proc/meminfo is:
519  //
520  // MemTotal:      8235324 kB
521  // MemFree:       1628304 kB
522  // Buffers:        429596 kB
523  // Cached:        4728232 kB
524  // ...
525  // There is no guarantee on the ordering or position
526  // though it doesn't appear to change very often
527
528  // As a basic sanity check, let's make sure we at least get non-zero
529  // MemTotal value
530  meminfo->total = 0;
531
532  std::vector<std::string> meminfo_lines;
533  Tokenize(meminfo_data, "\n", &meminfo_lines);
534  for (std::vector<std::string>::iterator it = meminfo_lines.begin();
535       it != meminfo_lines.end(); ++it) {
536    std::vector<std::string> tokens;
537    SplitStringAlongWhitespace(*it, &tokens);
538    // HugePages_* only has a number and no suffix so we can't rely on
539    // there being exactly 3 tokens.
540    if (tokens.size() > 1) {
541      if (tokens[0] == "MemTotal:") {
542        StringToInt(tokens[1], &meminfo->total);
543        continue;
544      } if (tokens[0] == "MemFree:") {
545        StringToInt(tokens[1], &meminfo->free);
546        continue;
547      } if (tokens[0] == "Buffers:") {
548        StringToInt(tokens[1], &meminfo->buffers);
549        continue;
550      } if (tokens[0] == "Cached:") {
551        StringToInt(tokens[1], &meminfo->cached);
552        continue;
553      } if (tokens[0] == "Active(anon):") {
554        StringToInt(tokens[1], &meminfo->active_anon);
555        continue;
556      } if (tokens[0] == "Inactive(anon):") {
557        StringToInt(tokens[1], &meminfo->inactive_anon);
558        continue;
559      } if (tokens[0] == "Active(file):") {
560        StringToInt(tokens[1], &meminfo->active_file);
561        continue;
562      } if (tokens[0] == "Inactive(file):") {
563        StringToInt(tokens[1], &meminfo->inactive_file);
564        continue;
565      } if (tokens[0] == "SwapTotal:") {
566        StringToInt(tokens[1], &meminfo->swap_total);
567        continue;
568      } if (tokens[0] == "SwapFree:") {
569        StringToInt(tokens[1], &meminfo->swap_free);
570        continue;
571      } if (tokens[0] == "Dirty:") {
572        StringToInt(tokens[1], &meminfo->dirty);
573        continue;
574#if defined(OS_CHROMEOS)
575      // Chrome OS has a tweaked kernel that allows us to query Shmem, which is
576      // usually video memory otherwise invisible to the OS.
577      } if (tokens[0] == "Shmem:") {
578        StringToInt(tokens[1], &meminfo->shmem);
579        continue;
580      } if (tokens[0] == "Slab:") {
581        StringToInt(tokens[1], &meminfo->slab);
582        continue;
583#endif
584      }
585    } else
586      DLOG(WARNING) << "meminfo: tokens: " << tokens.size()
587                    << " malformed line: " << *it;
588  }
589
590  // Make sure we got a valid MemTotal.
591  if (!meminfo->total)
592    return false;
593
594  return true;
595}
596
597// exposed for testing
598bool ParseProcVmstat(const std::string& vmstat_data,
599                     SystemMemoryInfoKB* meminfo) {
600  // The format of /proc/vmstat is:
601  //
602  // nr_free_pages 299878
603  // nr_inactive_anon 239863
604  // nr_active_anon 1318966
605  // nr_inactive_file 2015629
606  // ...
607  //
608  // We iterate through the whole file because the position of the
609  // fields are dependent on the kernel version and configuration.
610
611  std::vector<std::string> vmstat_lines;
612  Tokenize(vmstat_data, "\n", &vmstat_lines);
613  for (std::vector<std::string>::iterator it = vmstat_lines.begin();
614       it != vmstat_lines.end(); ++it) {
615    std::vector<std::string> tokens;
616    SplitString(*it, ' ', &tokens);
617    if (tokens.size() == 2) {
618      if (tokens[0] == "pswpin") {
619        StringToInt(tokens[1], &meminfo->pswpin);
620        continue;
621      } if (tokens[0] == "pswpout") {
622        StringToInt(tokens[1], &meminfo->pswpout);
623        continue;
624      } if (tokens[0] == "pgmajfault")
625        StringToInt(tokens[1], &meminfo->pgmajfault);
626    }
627  }
628
629  return true;
630}
631
632bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
633  // Synchronously reading files in /proc is safe.
634  ThreadRestrictions::ScopedAllowIO allow_io;
635
636  // Used memory is: total - free - buffers - caches
637  FilePath meminfo_file("/proc/meminfo");
638  std::string meminfo_data;
639  if (!ReadFileToString(meminfo_file, &meminfo_data)) {
640    DLOG(WARNING) << "Failed to open " << meminfo_file.value();
641    return false;
642  }
643
644  if (!ParseProcMeminfo(meminfo_data, meminfo)) {
645    DLOG(WARNING) << "Failed to parse " << meminfo_file.value();
646    return false;
647  }
648
649#if defined(OS_CHROMEOS)
650  // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a
651  // bind mount into /sys/kernel/debug and synchronously reading the in-memory
652  // files in /sys is fast.
653#if defined(ARCH_CPU_ARM_FAMILY)
654  FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects");
655#else
656  FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects");
657#endif
658  std::string geminfo_data;
659  meminfo->gem_objects = -1;
660  meminfo->gem_size = -1;
661  if (ReadFileToString(geminfo_file, &geminfo_data)) {
662    int gem_objects = -1;
663    long long gem_size = -1;
664    int num_res = sscanf(geminfo_data.c_str(),
665                         "%d objects, %lld bytes",
666                         &gem_objects, &gem_size);
667    if (num_res == 2) {
668      meminfo->gem_objects = gem_objects;
669      meminfo->gem_size = gem_size;
670    }
671  }
672
673#if defined(ARCH_CPU_ARM_FAMILY)
674  // Incorporate Mali graphics memory if present.
675  FilePath mali_memory_file("/sys/class/misc/mali0/device/memory");
676  std::string mali_memory_data;
677  if (ReadFileToString(mali_memory_file, &mali_memory_data)) {
678    long long mali_size = -1;
679    int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
680    if (num_res == 1)
681      meminfo->gem_size += mali_size;
682  }
683#endif  // defined(ARCH_CPU_ARM_FAMILY)
684#endif  // defined(OS_CHROMEOS)
685
686  FilePath vmstat_file("/proc/vmstat");
687  std::string vmstat_data;
688  if (!ReadFileToString(vmstat_file, &vmstat_data)) {
689    DLOG(WARNING) << "Failed to open " << vmstat_file.value();
690    return false;
691  }
692  if (!ParseProcVmstat(vmstat_data, meminfo)) {
693    DLOG(WARNING) << "Failed to parse " << vmstat_file.value();
694    return false;
695  }
696
697  return true;
698}
699
700SystemDiskInfo::SystemDiskInfo() {
701  reads = 0;
702  reads_merged = 0;
703  sectors_read = 0;
704  read_time = 0;
705  writes = 0;
706  writes_merged = 0;
707  sectors_written = 0;
708  write_time = 0;
709  io = 0;
710  io_time = 0;
711  weighted_io_time = 0;
712}
713
714scoped_ptr<Value> SystemDiskInfo::ToValue() const {
715  scoped_ptr<DictionaryValue> res(new base::DictionaryValue());
716
717  // Write out uint64 variables as doubles.
718  // Note: this may discard some precision, but for JS there's no other option.
719  res->SetDouble("reads", static_cast<double>(reads));
720  res->SetDouble("reads_merged", static_cast<double>(reads_merged));
721  res->SetDouble("sectors_read", static_cast<double>(sectors_read));
722  res->SetDouble("read_time", static_cast<double>(read_time));
723  res->SetDouble("writes", static_cast<double>(writes));
724  res->SetDouble("writes_merged", static_cast<double>(writes_merged));
725  res->SetDouble("sectors_written", static_cast<double>(sectors_written));
726  res->SetDouble("write_time", static_cast<double>(write_time));
727  res->SetDouble("io", static_cast<double>(io));
728  res->SetDouble("io_time", static_cast<double>(io_time));
729  res->SetDouble("weighted_io_time", static_cast<double>(weighted_io_time));
730
731  return res.PassAs<Value>();
732}
733
734bool IsValidDiskName(const std::string& candidate) {
735  if (candidate.length() < 3)
736    return false;
737  if (candidate.substr(0,2) == "sd" || candidate.substr(0,2) == "hd") {
738    // [sh]d[a-z]+ case
739    for (size_t i = 2; i < candidate.length(); i++) {
740      if (!islower(candidate[i]))
741        return false;
742    }
743  } else {
744    if (candidate.length() < 7) {
745      return false;
746    }
747    if (candidate.substr(0,6) == "mmcblk") {
748      // mmcblk[0-9]+ case
749      for (size_t i = 6; i < candidate.length(); i++) {
750        if (!isdigit(candidate[i]))
751          return false;
752      }
753    } else {
754      return false;
755    }
756  }
757
758  return true;
759}
760
761bool GetSystemDiskInfo(SystemDiskInfo* diskinfo) {
762  // Synchronously reading files in /proc is safe.
763  ThreadRestrictions::ScopedAllowIO allow_io;
764
765  FilePath diskinfo_file("/proc/diskstats");
766  std::string diskinfo_data;
767  if (!ReadFileToString(diskinfo_file, &diskinfo_data)) {
768    DLOG(WARNING) << "Failed to open " << diskinfo_file.value();
769    return false;
770  }
771
772  std::vector<std::string> diskinfo_lines;
773  size_t line_count = Tokenize(diskinfo_data, "\n", &diskinfo_lines);
774  if (line_count == 0) {
775    DLOG(WARNING) << "No lines found";
776    return false;
777  }
778
779  diskinfo->reads = 0;
780  diskinfo->reads_merged = 0;
781  diskinfo->sectors_read = 0;
782  diskinfo->read_time = 0;
783  diskinfo->writes = 0;
784  diskinfo->writes_merged = 0;
785  diskinfo->sectors_written = 0;
786  diskinfo->write_time = 0;
787  diskinfo->io = 0;
788  diskinfo->io_time = 0;
789  diskinfo->weighted_io_time = 0;
790
791  uint64 reads = 0;
792  uint64 reads_merged = 0;
793  uint64 sectors_read = 0;
794  uint64 read_time = 0;
795  uint64 writes = 0;
796  uint64 writes_merged = 0;
797  uint64 sectors_written = 0;
798  uint64 write_time = 0;
799  uint64 io = 0;
800  uint64 io_time = 0;
801  uint64 weighted_io_time = 0;
802
803  for (size_t i = 0; i < line_count; i++) {
804    std::vector<std::string> disk_fields;
805    SplitStringAlongWhitespace(diskinfo_lines[i], &disk_fields);
806
807    // Fields may have overflowed and reset to zero.
808    if (IsValidDiskName(disk_fields[kDiskDriveName])) {
809      StringToUint64(disk_fields[kDiskReads], &reads);
810      StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged);
811      StringToUint64(disk_fields[kDiskSectorsRead], &sectors_read);
812      StringToUint64(disk_fields[kDiskReadTime], &read_time);
813      StringToUint64(disk_fields[kDiskWrites], &writes);
814      StringToUint64(disk_fields[kDiskWritesMerged], &writes_merged);
815      StringToUint64(disk_fields[kDiskSectorsWritten], &sectors_written);
816      StringToUint64(disk_fields[kDiskWriteTime], &write_time);
817      StringToUint64(disk_fields[kDiskIO], &io);
818      StringToUint64(disk_fields[kDiskIOTime], &io_time);
819      StringToUint64(disk_fields[kDiskWeightedIOTime], &weighted_io_time);
820
821      diskinfo->reads += reads;
822      diskinfo->reads_merged += reads_merged;
823      diskinfo->sectors_read += sectors_read;
824      diskinfo->read_time += read_time;
825      diskinfo->writes += writes;
826      diskinfo->writes_merged += writes_merged;
827      diskinfo->sectors_written += sectors_written;
828      diskinfo->write_time += write_time;
829      diskinfo->io += io;
830      diskinfo->io_time += io_time;
831      diskinfo->weighted_io_time += weighted_io_time;
832    }
833  }
834
835  return true;
836}
837
838#if defined(OS_CHROMEOS)
839scoped_ptr<Value> SwapInfo::ToValue() const {
840  scoped_ptr<DictionaryValue> res(new DictionaryValue());
841
842  // Write out uint64 variables as doubles.
843  // Note: this may discard some precision, but for JS there's no other option.
844  res->SetDouble("num_reads", static_cast<double>(num_reads));
845  res->SetDouble("num_writes", static_cast<double>(num_writes));
846  res->SetDouble("orig_data_size", static_cast<double>(orig_data_size));
847  res->SetDouble("compr_data_size", static_cast<double>(compr_data_size));
848  res->SetDouble("mem_used_total", static_cast<double>(mem_used_total));
849  if (compr_data_size > 0)
850    res->SetDouble("compression_ratio", static_cast<double>(orig_data_size) /
851                                        static_cast<double>(compr_data_size));
852  else
853    res->SetDouble("compression_ratio", 0);
854
855  return res.PassAs<Value>();
856}
857
858void GetSwapInfo(SwapInfo* swap_info) {
859  // Synchronously reading files in /sys/block/zram0 is safe.
860  ThreadRestrictions::ScopedAllowIO allow_io;
861
862  base::FilePath zram_path("/sys/block/zram0");
863  uint64 orig_data_size = ReadFileToUint64(zram_path.Append("orig_data_size"));
864  if (orig_data_size <= 4096) {
865    // A single page is compressed at startup, and has a high compression
866    // ratio. We ignore this as it doesn't indicate any real swapping.
867    swap_info->orig_data_size = 0;
868    swap_info->num_reads = 0;
869    swap_info->num_writes = 0;
870    swap_info->compr_data_size = 0;
871    swap_info->mem_used_total = 0;
872    return;
873  }
874  swap_info->orig_data_size = orig_data_size;
875  swap_info->num_reads = ReadFileToUint64(zram_path.Append("num_reads"));
876  swap_info->num_writes = ReadFileToUint64(zram_path.Append("num_writes"));
877  swap_info->compr_data_size =
878      ReadFileToUint64(zram_path.Append("compr_data_size"));
879  swap_info->mem_used_total =
880      ReadFileToUint64(zram_path.Append("mem_used_total"));
881}
882#endif  // defined(OS_CHROMEOS)
883
884}  // namespace base
885