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
|
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/system_cpu/procfs_stat_cpu_parser.h"
#include <stdint.h>
#include <limits>
#include <string_view>
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/system/sys_info.h"
namespace system_cpu {
constexpr base::FilePath::CharType ProcfsStatCpuParser::kProcfsStatPath[];
ProcfsStatCpuParser::ProcfsStatCpuParser(base::FilePath stat_path)
: stat_path_(std::move(stat_path)) {
core_times_.reserve(base::SysInfo::NumberOfProcessors());
DETACH_FROM_SEQUENCE(sequence_checker_);
}
ProcfsStatCpuParser::~ProcfsStatCpuParser() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool ProcfsStatCpuParser::Update() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This implementation takes advantage of the fact that /proc/stat has 8
// lines in addition to the per-core lines (cpu0...cpuN). These 8 lines are
// cpu, intr, ctxt, btime, processes, procs_running, procs_blocked, softirq.
// Each of these lines consists of a small number of tokens. Each
// token has a small upper-bound on its size, because tokens are 64-bit
// base-10 numbers.
//
// This has the following consequences.
// 1) Reading the whole file in memory has a constant size/memory overhead,
// relative to the class' usage of per-core CoreTime structs.
// 2) Splitting the entire file into lines and processing each line has a
// constant size/memory overhead compared to a streaming parser that
// ignores irrelevant data and stops after the last per-core line (cpuN).
std::string stat_bytes;
// This implementation could use base::ReadFileToStringWithMaxSize() to avoid
// the risk that a kernel bug leads to an OOM. The size limit depends on the
// maximum number of cores we'd want to support.
//
// Each CPU line has ~220 bytes, and the other lines should amount to less
// than 10,000 bytes. So, for example, a limit of 2.3Mb should be sufficient
// to support systems up to 10,000 cores.
if (!base::ReadFileToString(stat_path_, &stat_bytes)) {
return false;
}
static constexpr std::string_view kNewlineSeparator("\n", 1);
std::vector<std::string_view> stat_lines = base::SplitStringPiece(
stat_bytes, kNewlineSeparator, base::WhitespaceHandling::KEEP_WHITESPACE,
base::SplitResult::SPLIT_WANT_ALL);
for (std::string_view stat_line : stat_lines) {
int core_id = CoreIdFromLine(stat_line);
if (core_id < 0) {
continue;
}
CHECK_LE(core_times_.size(), size_t{std::numeric_limits<int>::max()});
if (static_cast<int>(core_times_.size()) <= core_id) {
core_times_.resize(core_id + 1);
}
CoreTimes& current_core_times = core_times_[core_id];
UpdateCore(stat_line, current_core_times);
}
return true;
}
// static
int ProcfsStatCpuParser::CoreIdFromLine(std::string_view stat_line) {
// The first token of valid lines is cpu<number>. The token is at least 4
// characters ("cpu" plus one digit).
auto space_index = stat_line.find(' ');
if (space_index < 4 || space_index == std::string_view::npos) {
return -1;
}
if (stat_line[0] != 'c' || stat_line[1] != 'p' || stat_line[2] != 'u') {
return -1;
}
std::string_view core_id_string = stat_line.substr(3, space_index - 3);
int core_id;
if (!base::StringToInt(core_id_string, &core_id) || core_id < 0) {
return -1;
}
return core_id;
}
// static
void ProcfsStatCpuParser::UpdateCore(std::string_view core_line,
CoreTimes& core_times) {
CHECK_GE(CoreIdFromLine(core_line), 0);
static constexpr std::string_view kSpaceSeparator(" ", 1);
std::vector<std::string_view> tokens = base::SplitStringPiece(
core_line, kSpaceSeparator, base::WhitespaceHandling::KEEP_WHITESPACE,
base::SplitResult::SPLIT_WANT_ALL);
// Accept lines with more than 10 numbers, so the code keeps working if
// /proc/stat is extended with new per-core metrics.
//
// The first token on the line is the "cpuN" core ID. One core ID plus 10
// numbers equals 11 tokens.
if (tokens.size() < 11) {
return;
}
std::vector<uint64_t> parsed_numbers(10, 0);
for (int i = 0; i < 10; ++i) {
uint64_t parsed_number;
if (!base::StringToUint64(tokens[i + 1], &parsed_number)) {
break;
}
parsed_numbers[i] = parsed_number;
}
core_times.set_user(parsed_numbers[0]);
core_times.set_nice(parsed_numbers[1]);
core_times.set_system(parsed_numbers[2]);
core_times.set_idle(parsed_numbers[3]);
core_times.set_iowait(parsed_numbers[4]);
core_times.set_irq(parsed_numbers[5]);
core_times.set_softirq(parsed_numbers[6]);
core_times.set_steal(parsed_numbers[7]);
core_times.set_guest(parsed_numbers[8]);
core_times.set_guest_nice(parsed_numbers[9]);
}
} // namespace system_cpu
|