procfs_stat_cpu_parser.cc 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright 2021 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. #include "services/device/compute_pressure/procfs_stat_cpu_parser.h"
  5. #include <stdint.h>
  6. #include <limits>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/files/file_path.h"
  10. #include "base/files/file_util.h"
  11. #include "base/sequence_checker.h"
  12. #include "base/strings/string_number_conversions.h"
  13. #include "base/strings/string_piece.h"
  14. #include "base/strings/string_split.h"
  15. #include "base/system/sys_info.h"
  16. namespace device {
  17. constexpr base::FilePath::CharType ProcfsStatCpuParser::kProcfsStatPath[];
  18. ProcfsStatCpuParser::ProcfsStatCpuParser(base::FilePath stat_path)
  19. : stat_path_(std::move(stat_path)) {
  20. core_times_.reserve(base::SysInfo::NumberOfProcessors());
  21. DETACH_FROM_SEQUENCE(sequence_checker_);
  22. }
  23. ProcfsStatCpuParser::~ProcfsStatCpuParser() {
  24. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  25. }
  26. bool ProcfsStatCpuParser::Update() {
  27. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  28. // This implementation takes advantage of the fact that /proc/stat has 8
  29. // lines in addition to the per-core lines (cpu0...cpuN). These 8 lines are
  30. // cpu, intr, ctxt, btime, processes, procs_running, procs_blocked, softirq.
  31. // Each of these lines consists of a small number of tokens. Each
  32. // token has a small upper-bound on its size, because tokens are 64-bit
  33. // base-10 numbers.
  34. //
  35. // This has the following consequences.
  36. // 1) Reading the whole file in memory has a constant size/memory overhead,
  37. // relative to the class' usage of per-core CoreTime structs.
  38. // 2) Splitting the entire file into lines and processing each line has a
  39. // constant size/memory overhead compared to a streaming parser that
  40. // ignores irrelevant data and stops after the last per-core line (cpuN).
  41. std::string stat_bytes;
  42. // This implementation could use base::ReadFileToStringWithMaxSize() to avoid
  43. // the risk that a kernel bug leads to an OOM. The size limit depends on the
  44. // maximum number of cores we'd want to support.
  45. //
  46. // Each CPU line has ~220 bytes, and the other lines should amount to less
  47. // than 10,000 bytes. So, for example, a limit of 2.3Mb should be sufficient
  48. // to support systems up to 10,000 cores.
  49. if (!base::ReadFileToString(stat_path_, &stat_bytes))
  50. return false;
  51. static constexpr base::StringPiece kNewlineSeparator("\n", 1);
  52. std::vector<base::StringPiece> stat_lines = base::SplitStringPiece(
  53. stat_bytes, kNewlineSeparator, base::WhitespaceHandling::KEEP_WHITESPACE,
  54. base::SplitResult::SPLIT_WANT_ALL);
  55. for (base::StringPiece stat_line : stat_lines) {
  56. int core_id = CoreIdFromLine(stat_line);
  57. if (core_id < 0)
  58. continue;
  59. DCHECK_LE(core_times_.size(), size_t{std::numeric_limits<int>::max()});
  60. if (static_cast<int>(core_times_.size()) <= core_id)
  61. core_times_.resize(core_id + 1);
  62. CoreTimes& current_core_times = core_times_[core_id];
  63. UpdateCore(stat_line, current_core_times);
  64. }
  65. return true;
  66. }
  67. // static
  68. int ProcfsStatCpuParser::CoreIdFromLine(base::StringPiece stat_line) {
  69. // The first token of valid lines is cpu<number>. The token is at least 4
  70. // characters ("cpu" plus one digit).
  71. auto space_index = stat_line.find(' ');
  72. if (space_index < 4 || space_index == base::StringPiece::npos)
  73. return -1;
  74. if (stat_line[0] != 'c' || stat_line[1] != 'p' || stat_line[2] != 'u')
  75. return -1;
  76. base::StringPiece core_id_string = stat_line.substr(3, space_index - 3);
  77. int core_id;
  78. if (!base::StringToInt(core_id_string, &core_id) || core_id < 0)
  79. return -1;
  80. return core_id;
  81. }
  82. // static
  83. void ProcfsStatCpuParser::UpdateCore(base::StringPiece core_line,
  84. CoreTimes& core_times) {
  85. DCHECK_GE(CoreIdFromLine(core_line), 0);
  86. static constexpr base::StringPiece kSpaceSeparator(" ", 1);
  87. std::vector<base::StringPiece> tokens = base::SplitStringPiece(
  88. core_line, kSpaceSeparator, base::WhitespaceHandling::KEEP_WHITESPACE,
  89. base::SplitResult::SPLIT_WANT_ALL);
  90. // Accept lines with more than 10 numbers, so the code keeps working if
  91. // /proc/stat is extended with new per-core metrics.
  92. //
  93. // The first token on the line is the "cpuN" core ID. One core ID plus 10
  94. // numbers equals 11 tokens.
  95. if (tokens.size() < 11)
  96. return;
  97. std::vector<uint64_t> parsed_numbers(10, 0);
  98. for (int i = 0; i < 10; ++i) {
  99. uint64_t parsed_number;
  100. if (!base::StringToUint64(tokens[i + 1], &parsed_number))
  101. break;
  102. parsed_numbers[i] = parsed_number;
  103. }
  104. core_times.set_user(parsed_numbers[0]);
  105. core_times.set_nice(parsed_numbers[1]);
  106. core_times.set_system(parsed_numbers[2]);
  107. core_times.set_idle(parsed_numbers[3]);
  108. core_times.set_iowait(parsed_numbers[4]);
  109. core_times.set_irq(parsed_numbers[5]);
  110. core_times.set_softirq(parsed_numbers[6]);
  111. core_times.set_steal(parsed_numbers[7]);
  112. core_times.set_guest(parsed_numbers[8]);
  113. core_times.set_guest_nice(parsed_numbers[9]);
  114. }
  115. } // namespace device