process_metrics_linux.cc 25.5 KB
Newer Older
gejun's avatar
gejun committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/process/process_metrics.h"

#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "base/file_util.h"
#include "base/logging.h"
#include "base/process/internal_linux.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/sys_info.h"
#include "base/threading/thread_restrictions.h"

namespace base {

namespace {

enum ParsingState {
  KEY_NAME,
  KEY_VALUE
};

#ifdef OS_CHROMEOS
// Read a file with a single number string and return the number as a uint64_t.
static uint64_t ReadFileToUint64(const base::FilePath file) {
  std::string file_as_string;
  if (!ReadFileToString(file, &file_as_string))
    return 0;
  base::TrimWhitespaceASCII(file_as_string, base::TRIM_ALL, &file_as_string);
  uint64_t file_as_uint64 = 0;
  if (!base::StringToUint64(file_as_string, &file_as_uint64))
    return 0;
  return file_as_uint64;
}
#endif

// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure.
// Only works for fields in the form of "Field: value kB".
size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) {
  FilePath stat_file = internal::GetProcPidDir(pid).Append("status");
  std::string status;
  {
    // Synchronously reading files in /proc is safe.
    ThreadRestrictions::ScopedAllowIO allow_io;
    if (!ReadFileToString(stat_file, &status))
      return 0;
  }

  StringTokenizer tokenizer(status, ":\n");
  ParsingState state = KEY_NAME;
  StringPiece last_key_name;
  while (tokenizer.GetNext()) {
    switch (state) {
      case KEY_NAME:
        last_key_name = tokenizer.token_piece();
        state = KEY_VALUE;
        break;
      case KEY_VALUE:
        DCHECK(!last_key_name.empty());
        if (last_key_name == field) {
          std::string value_str;
          tokenizer.token_piece().CopyToString(&value_str);
          std::string value_str_trimmed;
          base::TrimWhitespaceASCII(value_str, base::TRIM_ALL,
                                    &value_str_trimmed);
          std::vector<std::string> split_value_str;
          SplitString(value_str_trimmed, ' ', &split_value_str);
          if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
            NOTREACHED();
            return 0;
          }
          size_t value;
          if (!StringToSizeT(split_value_str[0], &value)) {
            NOTREACHED();
            return 0;
          }
          return value;
        }
        state = KEY_NAME;
        break;
    }
  }
  NOTREACHED();
  return 0;
}

// Get the total CPU of a single process.  Return value is number of jiffies
// on success or -1 on error.
int GetProcessCPU(pid_t pid) {
  // Use /proc/<pid>/task to find all threads and parse their /stat file.
  FilePath task_path = internal::GetProcPidDir(pid).Append("task");

  DIR* dir = opendir(task_path.value().c_str());
  if (!dir) {
    DPLOG(ERROR) << "opendir(" << task_path.value() << ")";
    return -1;
  }

  int total_cpu = 0;
  while (struct dirent* ent = readdir(dir)) {
    pid_t tid = internal::ProcDirSlotToPid(ent->d_name);
    if (!tid)
      continue;

    // Synchronously reading files in /proc is safe.
    ThreadRestrictions::ScopedAllowIO allow_io;

    std::string stat;
    FilePath stat_path =
        task_path.Append(ent->d_name).Append(internal::kStatFile);
    if (ReadFileToString(stat_path, &stat)) {
      int cpu = ParseProcStatCPU(stat);
      if (cpu > 0)
        total_cpu += cpu;
    }
  }
  closedir(dir);

  return total_cpu;
}

}  // namespace

// static
ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) {
  return new ProcessMetrics(process);
}

// On linux, we return vsize.
size_t ProcessMetrics::GetPagefileUsage() const {
  return internal::ReadProcStatsAndGetFieldAsSizeT(process_,
                                                   internal::VM_VSIZE);
}

// On linux, we return the high water mark of vsize.
size_t ProcessMetrics::GetPeakPagefileUsage() const {
  return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024;
}

// On linux, we return RSS.
size_t ProcessMetrics::GetWorkingSetSize() const {
  return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
      getpagesize();
}

// On linux, we return the high water mark of RSS.
size_t ProcessMetrics::GetPeakWorkingSetSize() const {
  return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024;
}

bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
                                    size_t* shared_bytes) {
  WorkingSetKBytes ws_usage;
  if (!GetWorkingSetKBytes(&ws_usage))
    return false;

  if (private_bytes)
    *private_bytes = ws_usage.priv * 1024;

  if (shared_bytes)
    *shared_bytes = ws_usage.shared * 1024;

  return true;
}

bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
#if defined(OS_CHROMEOS)
  if (GetWorkingSetKBytesTotmaps(ws_usage))
    return true;
#endif
  return GetWorkingSetKBytesStatm(ws_usage);
}

double ProcessMetrics::GetCPUUsage() {
  TimeTicks time = TimeTicks::Now();

  if (last_cpu_ == 0) {
    // First call, just set the last values.
    last_cpu_time_ = time;
    last_cpu_ = GetProcessCPU(process_);
    return 0;
  }

  int64_t time_delta = (time - last_cpu_time_).InMicroseconds();
  DCHECK_NE(time_delta, 0);
  if (time_delta == 0)
    return 0;

  int cpu = GetProcessCPU(process_);

  // We have the number of jiffies in the time period.  Convert to percentage.
  // Note this means we will go *over* 100 in the case where multiple threads
  // are together adding to more than one CPU's worth.
  TimeDelta cpu_time = internal::ClockTicksToTimeDelta(cpu);
  TimeDelta last_cpu_time = internal::ClockTicksToTimeDelta(last_cpu_);
  int percentage = (int)(100 * (cpu_time - last_cpu_time).InSecondsF() /
                         TimeDelta::FromMicroseconds(time_delta).InSecondsF());

  last_cpu_time_ = time;
  last_cpu_ = cpu;

  return percentage;
}

// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING
// in your kernel configuration.
bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
  // Synchronously reading files in /proc is safe.
  ThreadRestrictions::ScopedAllowIO allow_io;

  std::string proc_io_contents;
  FilePath io_file = internal::GetProcPidDir(process_).Append("io");
  if (!ReadFileToString(io_file, &proc_io_contents))
    return false;

  (*io_counters).OtherOperationCount = 0;
  (*io_counters).OtherTransferCount = 0;

  StringTokenizer tokenizer(proc_io_contents, ": \n");
  ParsingState state = KEY_NAME;
  StringPiece last_key_name;
  while (tokenizer.GetNext()) {
    switch (state) {
      case KEY_NAME:
        last_key_name = tokenizer.token_piece();
        state = KEY_VALUE;
        break;
      case KEY_VALUE:
        DCHECK(!last_key_name.empty());
        if (last_key_name == "syscr") {
          StringToInt64(tokenizer.token_piece(),
              reinterpret_cast<int64_t*>(&(*io_counters).ReadOperationCount));
        } else if (last_key_name == "syscw") {
          StringToInt64(tokenizer.token_piece(),
              reinterpret_cast<int64_t*>(&(*io_counters).WriteOperationCount));
        } else if (last_key_name == "rchar") {
          StringToInt64(tokenizer.token_piece(),
              reinterpret_cast<int64_t*>(&(*io_counters).ReadTransferCount));
        } else if (last_key_name == "wchar") {
          StringToInt64(tokenizer.token_piece(),
              reinterpret_cast<int64_t*>(&(*io_counters).WriteTransferCount));
        }
        state = KEY_NAME;
        break;
    }
  }
  return true;
}

ProcessMetrics::ProcessMetrics(ProcessHandle process)
    : process_(process),
      last_system_time_(0),
      last_cpu_(0) {
  processor_count_ = base::SysInfo::NumberOfProcessors();
}

#if defined(OS_CHROMEOS)
// Private, Shared and Proportional working set sizes are obtained from
// /proc/<pid>/totmaps
bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
  const {
  // The format of /proc/<pid>/totmaps is:
  //
  // Rss:                6120 kB
  // Pss:                3335 kB
  // Shared_Clean:       1008 kB
  // Shared_Dirty:       4012 kB
  // Private_Clean:         4 kB
  // Private_Dirty:      1096 kB
  // Referenced:          XXX kB
  // Anonymous:           XXX kB
  // AnonHugePages:       XXX kB
  // Swap:                XXX kB
  // Locked:              XXX kB
  const size_t kPssIndex = (1 * 3) + 1;
  const size_t kPrivate_CleanIndex = (4 * 3) + 1;
  const size_t kPrivate_DirtyIndex = (5 * 3) + 1;
  const size_t kSwapIndex = (9 * 3) + 1;

  std::string totmaps_data;
  {
    FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps");
    ThreadRestrictions::ScopedAllowIO allow_io;
    bool ret = ReadFileToString(totmaps_file, &totmaps_data);
    if (!ret || totmaps_data.length() == 0)
      return false;
  }

  std::vector<std::string> totmaps_fields;
  SplitStringAlongWhitespace(totmaps_data, &totmaps_fields);

  DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]);
  DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]);
  DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]);
  DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]);

  int pss = 0;
  int private_clean = 0;
  int private_dirty = 0;
  int swap = 0;
  bool ret = true;
  ret &= StringToInt(totmaps_fields[kPssIndex], &pss);
  ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean);
  ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty);
  ret &= StringToInt(totmaps_fields[kSwapIndex], &swap);

  // On ChromeOS swap is to zram. We count this as private / shared, as
  // increased swap decreases available RAM to user processes, which would
  // otherwise create surprising results.
  ws_usage->priv = private_clean + private_dirty + swap;
  ws_usage->shared = pss + swap;
  ws_usage->shareable = 0;
  ws_usage->swapped = swap;
  return ret;
}
#endif

// Private and Shared working set sizes are obtained from /proc/<pid>/statm.
bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage)
    const {
  // Use statm instead of smaps because smaps is:
  // a) Large and slow to parse.
  // b) Unavailable in the SUID sandbox.

  // First we need to get the page size, since everything is measured in pages.
  // For details, see: man 5 proc.
  const int page_size_kb = getpagesize() / 1024;
  if (page_size_kb <= 0)
    return false;

  std::string statm;
  {
    FilePath statm_file = internal::GetProcPidDir(process_).Append("statm");
    // Synchronously reading files in /proc is safe.
    ThreadRestrictions::ScopedAllowIO allow_io;
    bool ret = ReadFileToString(statm_file, &statm);
    if (!ret || statm.length() == 0)
      return false;
  }

  std::vector<std::string> statm_vec;
  SplitString(statm, ' ', &statm_vec);
  if (statm_vec.size() != 7)
    return false;  // Not the format we expect.

  int statm_rss, statm_shared;
  bool ret = true;
  ret &= StringToInt(statm_vec[1], &statm_rss);
  ret &= StringToInt(statm_vec[2], &statm_shared);

  ws_usage->priv = (statm_rss - statm_shared) * page_size_kb;
  ws_usage->shared = statm_shared * page_size_kb;

  // Sharable is not calculated, as it does not provide interesting data.
  ws_usage->shareable = 0;

#if defined(OS_CHROMEOS)
  // Can't get swapped memory from statm.
  ws_usage->swapped = 0;
#endif

  return ret;
}

size_t GetSystemCommitCharge() {
  SystemMemoryInfoKB meminfo;
  if (!GetSystemMemoryInfo(&meminfo))
    return 0;
  return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached;
}

// Exposed for testing.
int ParseProcStatCPU(const std::string& input) {
  std::vector<std::string> proc_stats;
  if (!internal::ParseProcStats(input, &proc_stats))
    return -1;

  if (proc_stats.size() <= internal::VM_STIME)
    return -1;
  int utime = GetProcStatsFieldAsInt64(proc_stats, internal::VM_UTIME);
  int stime = GetProcStatsFieldAsInt64(proc_stats, internal::VM_STIME);
  return utime + stime;
}

const char kProcSelfExe[] = "/proc/self/exe";

int GetNumberOfThreads(ProcessHandle process) {
  return internal::ReadProcStatsAndGetFieldAsInt64(process,
                                                   internal::VM_NUMTHREADS);
}

namespace {

// The format of /proc/diskstats is:
//  Device major number
//  Device minor number
//  Device name
//  Field  1 -- # of reads completed
//      This is the total number of reads completed successfully.
//  Field  2 -- # of reads merged, field 6 -- # of writes merged
//      Reads and writes which are adjacent to each other may be merged for
//      efficiency.  Thus two 4K reads may become one 8K read before it is
//      ultimately handed to the disk, and so it will be counted (and queued)
//      as only one I/O.  This field lets you know how often this was done.
//  Field  3 -- # of sectors read
//      This is the total number of sectors read successfully.
//  Field  4 -- # of milliseconds spent reading
//      This is the total number of milliseconds spent by all reads (as
//      measured from __make_request() to end_that_request_last()).
//  Field  5 -- # of writes completed
//      This is the total number of writes completed successfully.
//  Field  6 -- # of writes merged
//      See the description of field 2.
//  Field  7 -- # of sectors written
//      This is the total number of sectors written successfully.
//  Field  8 -- # of milliseconds spent writing
//      This is the total number of milliseconds spent by all writes (as
//      measured from __make_request() to end_that_request_last()).
//  Field  9 -- # of I/Os currently in progress
//      The only field that should go to zero. Incremented as requests are
//      given to appropriate struct request_queue and decremented as they
//      finish.
//  Field 10 -- # of milliseconds spent doing I/Os
//      This field increases so long as field 9 is nonzero.
//  Field 11 -- weighted # of milliseconds spent doing I/Os
//      This field is incremented at each I/O start, I/O completion, I/O
//      merge, or read of these stats by the number of I/Os in progress
//      (field 9) times the number of milliseconds spent doing I/O since the
//      last update of this field.  This can provide an easy measure of both
//      I/O completion time and the backlog that may be accumulating.

const size_t kDiskDriveName = 2;
const size_t kDiskReads = 3;
const size_t kDiskReadsMerged = 4;
const size_t kDiskSectorsRead = 5;
const size_t kDiskReadTime = 6;
const size_t kDiskWrites = 7;
const size_t kDiskWritesMerged = 8;
const size_t kDiskSectorsWritten = 9;
const size_t kDiskWriteTime = 10;
const size_t kDiskIO = 11;
const size_t kDiskIOTime = 12;
const size_t kDiskWeightedIOTime = 13;

}  // namespace

SystemMemoryInfoKB::SystemMemoryInfoKB() {
  total = 0;
  free = 0;
  buffers = 0;
  cached = 0;
  active_anon = 0;
  inactive_anon = 0;
  active_file = 0;
  inactive_file = 0;
  swap_total = 0;
  swap_free = 0;
  dirty = 0;

  pswpin = 0;
  pswpout = 0;
  pgmajfault = 0;

#ifdef OS_CHROMEOS
  shmem = 0;
  slab = 0;
  gem_objects = -1;
  gem_size = -1;
#endif
}

// exposed for testing
bool ParseProcMeminfo(const std::string& meminfo_data,
                      SystemMemoryInfoKB* meminfo) {
  // The format of /proc/meminfo is:
  //
  // MemTotal:      8235324 kB
  // MemFree:       1628304 kB
  // Buffers:        429596 kB
  // Cached:        4728232 kB
  // ...
  // There is no guarantee on the ordering or position
  // though it doesn't appear to change very often

  // As a basic sanity check, let's make sure we at least get non-zero
  // MemTotal value
  meminfo->total = 0;

  std::vector<std::string> meminfo_lines;
  Tokenize(meminfo_data, "\n", &meminfo_lines);
  for (std::vector<std::string>::iterator it = meminfo_lines.begin();
       it != meminfo_lines.end(); ++it) {
    std::vector<std::string> tokens;
    SplitStringAlongWhitespace(*it, &tokens);
    // HugePages_* only has a number and no suffix so we can't rely on
    // there being exactly 3 tokens.
    if (tokens.size() > 1) {
      if (tokens[0] == "MemTotal:") {
        StringToInt(tokens[1], &meminfo->total);
        continue;
      } if (tokens[0] == "MemFree:") {
        StringToInt(tokens[1], &meminfo->free);
        continue;
      } if (tokens[0] == "Buffers:") {
        StringToInt(tokens[1], &meminfo->buffers);
        continue;
      } if (tokens[0] == "Cached:") {
        StringToInt(tokens[1], &meminfo->cached);
        continue;
      } if (tokens[0] == "Active(anon):") {
        StringToInt(tokens[1], &meminfo->active_anon);
        continue;
      } if (tokens[0] == "Inactive(anon):") {
        StringToInt(tokens[1], &meminfo->inactive_anon);
        continue;
      } if (tokens[0] == "Active(file):") {
        StringToInt(tokens[1], &meminfo->active_file);
        continue;
      } if (tokens[0] == "Inactive(file):") {
        StringToInt(tokens[1], &meminfo->inactive_file);
        continue;
      } if (tokens[0] == "SwapTotal:") {
        StringToInt(tokens[1], &meminfo->swap_total);
        continue;
      } if (tokens[0] == "SwapFree:") {
        StringToInt(tokens[1], &meminfo->swap_free);
        continue;
      } if (tokens[0] == "Dirty:") {
        StringToInt(tokens[1], &meminfo->dirty);
        continue;
#if defined(OS_CHROMEOS)
      // Chrome OS has a tweaked kernel that allows us to query Shmem, which is
      // usually video memory otherwise invisible to the OS.
      } if (tokens[0] == "Shmem:") {
        StringToInt(tokens[1], &meminfo->shmem);
        continue;
      } if (tokens[0] == "Slab:") {
        StringToInt(tokens[1], &meminfo->slab);
        continue;
#endif
      }
    } else
      DLOG(WARNING) << "meminfo: tokens: " << tokens.size()
                    << " malformed line: " << *it;
  }

  // Make sure we got a valid MemTotal.
  if (!meminfo->total)
    return false;

  return true;
}

// exposed for testing
bool ParseProcVmstat(const std::string& vmstat_data,
                     SystemMemoryInfoKB* meminfo) {
  // The format of /proc/vmstat is:
  //
  // nr_free_pages 299878
  // nr_inactive_anon 239863
  // nr_active_anon 1318966
  // nr_inactive_file 2015629
  // ...
  //
  // We iterate through the whole file because the position of the
  // fields are dependent on the kernel version and configuration.

  std::vector<std::string> vmstat_lines;
  Tokenize(vmstat_data, "\n", &vmstat_lines);
  for (std::vector<std::string>::iterator it = vmstat_lines.begin();
       it != vmstat_lines.end(); ++it) {
    std::vector<std::string> tokens;
    SplitString(*it, ' ', &tokens);
    if (tokens.size() == 2) {
      if (tokens[0] == "pswpin") {
        StringToInt(tokens[1], &meminfo->pswpin);
        continue;
      } if (tokens[0] == "pswpout") {
        StringToInt(tokens[1], &meminfo->pswpout);
        continue;
      } if (tokens[0] == "pgmajfault")
        StringToInt(tokens[1], &meminfo->pgmajfault);
    }
  }

  return true;
}

bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
  // Synchronously reading files in /proc is safe.
  ThreadRestrictions::ScopedAllowIO allow_io;

  // Used memory is: total - free - buffers - caches
  FilePath meminfo_file("/proc/meminfo");
  std::string meminfo_data;
  if (!ReadFileToString(meminfo_file, &meminfo_data)) {
    DLOG(WARNING) << "Failed to open " << meminfo_file.value();
    return false;
  }

  if (!ParseProcMeminfo(meminfo_data, meminfo)) {
    DLOG(WARNING) << "Failed to parse " << meminfo_file.value();
    return false;
  }

#if defined(OS_CHROMEOS)
  // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a
  // bind mount into /sys/kernel/debug and synchronously reading the in-memory
  // files in /sys is fast.
#if defined(ARCH_CPU_ARM_FAMILY)
  FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects");
#else
  FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects");
#endif
  std::string geminfo_data;
  meminfo->gem_objects = -1;
  meminfo->gem_size = -1;
  if (ReadFileToString(geminfo_file, &geminfo_data)) {
    int gem_objects = -1;
    long long gem_size = -1;
    int num_res = sscanf(geminfo_data.c_str(),
                         "%d objects, %lld bytes",
                         &gem_objects, &gem_size);
    if (num_res == 2) {
      meminfo->gem_objects = gem_objects;
      meminfo->gem_size = gem_size;
    }
  }

#if defined(ARCH_CPU_ARM_FAMILY)
  // Incorporate Mali graphics memory if present.
  FilePath mali_memory_file("/sys/class/misc/mali0/device/memory");
  std::string mali_memory_data;
  if (ReadFileToString(mali_memory_file, &mali_memory_data)) {
    long long mali_size = -1;
    int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
    if (num_res == 1)
      meminfo->gem_size += mali_size;
  }
#endif  // defined(ARCH_CPU_ARM_FAMILY)
#endif  // defined(OS_CHROMEOS)

  FilePath vmstat_file("/proc/vmstat");
  std::string vmstat_data;
  if (!ReadFileToString(vmstat_file, &vmstat_data)) {
    DLOG(WARNING) << "Failed to open " << vmstat_file.value();
    return false;
  }
  if (!ParseProcVmstat(vmstat_data, meminfo)) {
    DLOG(WARNING) << "Failed to parse " << vmstat_file.value();
    return false;
  }

  return true;
}

SystemDiskInfo::SystemDiskInfo() {
  reads = 0;
  reads_merged = 0;
  sectors_read = 0;
  read_time = 0;
  writes = 0;
  writes_merged = 0;
  sectors_written = 0;
  write_time = 0;
  io = 0;
  io_time = 0;
  weighted_io_time = 0;
}

bool IsValidDiskName(const std::string& candidate) {
  if (candidate.length() < 3)
    return false;
  if (candidate.substr(0,2) == "sd" || candidate.substr(0,2) == "hd") {
    // [sh]d[a-z]+ case
    for (size_t i = 2; i < candidate.length(); i++) {
      if (!islower(candidate[i]))
        return false;
    }
  } else {
    if (candidate.length() < 7) {
      return false;
    }
    if (candidate.substr(0,6) == "mmcblk") {
      // mmcblk[0-9]+ case
      for (size_t i = 6; i < candidate.length(); i++) {
        if (!isdigit(candidate[i]))
          return false;
      }
    } else {
      return false;
    }
  }

  return true;
}

bool GetSystemDiskInfo(SystemDiskInfo* diskinfo) {
  // Synchronously reading files in /proc is safe.
  ThreadRestrictions::ScopedAllowIO allow_io;

  FilePath diskinfo_file("/proc/diskstats");
  std::string diskinfo_data;
  if (!ReadFileToString(diskinfo_file, &diskinfo_data)) {
    DLOG(WARNING) << "Failed to open " << diskinfo_file.value();
    return false;
  }

  std::vector<std::string> diskinfo_lines;
  size_t line_count = Tokenize(diskinfo_data, "\n", &diskinfo_lines);
  if (line_count == 0) {
    DLOG(WARNING) << "No lines found";
    return false;
  }

  diskinfo->reads = 0;
  diskinfo->reads_merged = 0;
  diskinfo->sectors_read = 0;
  diskinfo->read_time = 0;
  diskinfo->writes = 0;
  diskinfo->writes_merged = 0;
  diskinfo->sectors_written = 0;
  diskinfo->write_time = 0;
  diskinfo->io = 0;
  diskinfo->io_time = 0;
  diskinfo->weighted_io_time = 0;

  uint64_t reads = 0;
  uint64_t reads_merged = 0;
  uint64_t sectors_read = 0;
  uint64_t read_time = 0;
  uint64_t writes = 0;
  uint64_t writes_merged = 0;
  uint64_t sectors_written = 0;
  uint64_t write_time = 0;
  uint64_t io = 0;
  uint64_t io_time = 0;
  uint64_t weighted_io_time = 0;

  for (size_t i = 0; i < line_count; i++) {
    std::vector<std::string> disk_fields;
    SplitStringAlongWhitespace(diskinfo_lines[i], &disk_fields);

    // Fields may have overflowed and reset to zero.
    if (IsValidDiskName(disk_fields[kDiskDriveName])) {
      StringToUint64(disk_fields[kDiskReads], &reads);
      StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged);
      StringToUint64(disk_fields[kDiskSectorsRead], &sectors_read);
      StringToUint64(disk_fields[kDiskReadTime], &read_time);
      StringToUint64(disk_fields[kDiskWrites], &writes);
      StringToUint64(disk_fields[kDiskWritesMerged], &writes_merged);
      StringToUint64(disk_fields[kDiskSectorsWritten], &sectors_written);
      StringToUint64(disk_fields[kDiskWriteTime], &write_time);
      StringToUint64(disk_fields[kDiskIO], &io);
      StringToUint64(disk_fields[kDiskIOTime], &io_time);
      StringToUint64(disk_fields[kDiskWeightedIOTime], &weighted_io_time);

      diskinfo->reads += reads;
      diskinfo->reads_merged += reads_merged;
      diskinfo->sectors_read += sectors_read;
      diskinfo->read_time += read_time;
      diskinfo->writes += writes;
      diskinfo->writes_merged += writes_merged;
      diskinfo->sectors_written += sectors_written;
      diskinfo->write_time += write_time;
      diskinfo->io += io;
      diskinfo->io_time += io_time;
      diskinfo->weighted_io_time += weighted_io_time;
    }
  }

  return true;
}

#if defined(OS_CHROMEOS)
void GetSwapInfo(SwapInfo* swap_info) {
  // Synchronously reading files in /sys/block/zram0 is safe.
  ThreadRestrictions::ScopedAllowIO allow_io;

  base::FilePath zram_path("/sys/block/zram0");
  uint64_t orig_data_size = ReadFileToUint64(zram_path.Append("orig_data_size"));
  if (orig_data_size <= 4096) {
    // A single page is compressed at startup, and has a high compression
    // ratio. We ignore this as it doesn't indicate any real swapping.
    swap_info->orig_data_size = 0;
    swap_info->num_reads = 0;
    swap_info->num_writes = 0;
    swap_info->compr_data_size = 0;
    swap_info->mem_used_total = 0;
    return;
  }
  swap_info->orig_data_size = orig_data_size;
  swap_info->num_reads = ReadFileToUint64(zram_path.Append("num_reads"));
  swap_info->num_writes = ReadFileToUint64(zram_path.Append("num_writes"));
  swap_info->compr_data_size =
      ReadFileToUint64(zram_path.Append("compr_data_size"));
  swap_info->mem_used_total =
      ReadFileToUint64(zram_path.Append("mem_used_total"));
}
#endif  // defined(OS_CHROMEOS)

}  // namespace base