From ec4b77dd5bf78292e45e94a8764ccaaffb0d8175 Mon Sep 17 00:00:00 2001 From: resserops Date: Wed, 10 Jun 2026 22:34:09 +0800 Subject: [PATCH] feat(profiling/tool): add system-wide cpu usage measurement --- module/common/include/oops/str.h | 4 +- module/profiling/include/oops/cpu_timer.h | 41 ++++++++++++--- module/profiling/tool/monitor/monitor.cpp | 64 +++++++++++------------ 3 files changed, 68 insertions(+), 41 deletions(-) diff --git a/module/common/include/oops/str.h b/module/common/include/oops/str.h index f6039d9..5265493 100644 --- a/module/common/include/oops/str.h +++ b/module/common/include/oops/str.h @@ -63,14 +63,14 @@ class SplitView { } bool operator==(const Iterator &other) const { - // 短路和迭代器的比较 + // 尾迭代器参与的快速比较 if ((token_.data() == nullptr) ^ (other.token_.data() == nullptr)) { return false; // 一个迭代器是尾迭代器,另一个不是 } if (token_.data() == nullptr) { return true; // 均是尾迭代器 } - // 非尾迭代器的严格匹配 + // 两个正常迭代器的严格比较 return (token_.data() == other.token_.data()) && (token_.size() == other.token_.size()) && (s_.data() == other.s_.data()) && (s_.size() == other.s_.size()) && (delim_ == other.delim_) && (any_of_delims_ == other.any_of_delims_) && (skip_empty_ == other.skip_empty_); diff --git a/module/profiling/include/oops/cpu_timer.h b/module/profiling/include/oops/cpu_timer.h index 35be17e..95c0541 100644 --- a/module/profiling/include/oops/cpu_timer.h +++ b/module/profiling/include/oops/cpu_timer.h @@ -17,8 +17,8 @@ inline auto GetTicksPerSec() { class CpuTimer { struct CpuTicks { std::uintmax_t TotalTicks() const { return user_ticks + kernel_ticks; }; - std::uintmax_t user_ticks; - std::uintmax_t kernel_ticks; + std::uintmax_t user_ticks{}; + std::uintmax_t kernel_ticks{}; }; using CpuTimePoint = std::variant; @@ -34,10 +34,17 @@ class CpuTimer { double elapsed_time{}; }; + static constexpr pid_t SYSTEM{-1}; + static std::string GetPath(pid_t pid) { + if (pid == SYSTEM) { + return "/proc/stat"; + } + return "/proc/" + std::to_string(pid) + "/stat"; + } + CpuTimer() : cpu_t0_{GetCpuTimePoint()}, elapsed_t0_{std::chrono::steady_clock::now()} {} CpuTimer(pid_t pid) - : stat_path_{"/proc/" + std::to_string(pid) + "/stat"}, cpu_t0_{GetCpuTimePoint()}, - elapsed_t0_{std::chrono::steady_clock::now()} {} + : stat_path_{GetPath(pid)}, cpu_t0_{GetCpuTimePoint()}, elapsed_t0_{std::chrono::steady_clock::now()} {} void Reset() { cpu_t0_ = GetCpuTimePoint(); @@ -76,20 +83,42 @@ class CpuTimer { return spec; } - // 文件快照(/proc/pid/stat)获取cpu时间,Ticks级精度(约10ms) + // 文件快照(/proc/*/stat)获取进程或system cpu时间,Ticks级精度(约10ms) // 解析效率要求高,使用栈缓冲 char buf[512]; std::FILE *f{std::fopen(stat_path_.c_str(), "r")}; if (!f) { return std::monostate{}; // 无法打开文件,返回空类型 } - auto res{std::fgets(buf, sizeof(buf), f)}; std::fclose(f); if (res == nullptr) { return std::monostate{}; // 读取失败,返回空类型 } + if (stat_path_.size() == 10) { + // 文件快照(/proc/stat)获取系统级cpu时间 + char *p{buf}; + while ((*p < '0') || (*p > '9')) { + ++p; + } + CpuTicks cpu_ticks; + cpu_ticks.user_ticks += std::strtoull(p, &p, 10); // 匹配第1个字段user + cpu_ticks.user_ticks += std::strtoull(p + 1, &p, 10); // 匹配第2个字段nice + + cpu_ticks.kernel_ticks += std::strtoull(p + 1, &p, 10); // 匹配第3个字段system + + // 跳过第4个字段idle和第5个字段iowait + int rem{2}; + while (rem-- > 0) { + p = std::strchr(p + 1, ' '); + assert(p != nullptr); + } + + cpu_ticks.kernel_ticks += std::strtoull(p + 1, &p, 10); // 匹配第6个字段irq + cpu_ticks.kernel_ticks += std::strtoull(p + 1, &p, 10); // 匹配第7个字段softirq + return cpu_ticks; + } // 查找第2个字段的右括号 char *p{std::strrchr(buf, ')')}; assert(p != nullptr); diff --git a/module/profiling/tool/monitor/monitor.cpp b/module/profiling/tool/monitor/monitor.cpp index d55e49b..01640d6 100644 --- a/module/profiling/tool/monitor/monitor.cpp +++ b/module/profiling/tool/monitor/monitor.cpp @@ -167,7 +167,7 @@ void Measure() { std::cout << MakeHeaderRow() << std::endl; std::vector values(oops::ToUnderlying(Metrics::COUNT)); - while (!STOP.load(std::memory_order_relaxed) && ProcExist(ARGS.measure.pid)) { + while (!STOP.load(std::memory_order_relaxed) && (ProcExist(ARGS.measure.pid) || ARGS.measure.pid == -1)) { std::this_thread::sleep_until(next_time); // 根据选项配置完成测量 @@ -433,9 +433,6 @@ void AlignTimeline() { double range_l_fidx{std::ceil(std::chrono::duration{range_l - raw_l}.count()) / r_entry.interval}; double range_r_fidx{std::ceil(std::chrono::duration{range_r - raw_l}.count()) / r_entry.interval}; - std::cout << "lidx: " << range_l_fidx << std::endl; - std::cout << "ridx: " << range_r_fidx << std::endl; - // 计算range对应的下标,左闭右开 std::size_t range_l_idx{std::max(range_l_fidx, 0)}; std::size_t range_r_idx{std::min(range_r_fidx, size)}; @@ -469,38 +466,39 @@ void AlignTimeline() { } } -void PlotCpu() { - if (RAW_DATA_TABLE.empty()) { - return; - } +auto GetMetricEntries(Metrics m) { + struct Entries { + RawDataEntry &r_entry; + MetricDataEntry &m_entry; + }; - // 识别记录了CPU利用率的raw data idx及对应CPU利用率的metric data dix - std::vector> valid_idxs; - for (std::size_t i{0}; i < RAW_DATA_TABLE.size(); ++i) { - const auto &r_entry{RAW_DATA_TABLE[i]}; - for (std::size_t j{0}; j < r_entry.metric_data_table.size(); ++j) { - const auto &m_entry{r_entry.metric_data_table.at(j)}; - if (m_entry.name == METRIC_TABLE[oops::ToUnderlying(Metrics::CPU_USAGE)].name && !m_entry.values.empty()) { - valid_idxs.emplace_back(i, j); + std::vector res; + for (auto &r_entry : RAW_DATA_TABLE) { + for (auto &m_entry : r_entry.metric_data_table) { + if (m_entry.name == METRIC_TABLE[oops::ToUnderlying(m)].name && !m_entry.values.empty()) { + res.push_back({r_entry, m_entry}); } } } - if (valid_idxs.empty()) { + return res; +} + +void PlotCpu() { + assert(!RAW_DATA_TABLE.empty()); + + auto entries{GetMetricEntries(Metrics::CPU_USAGE)}; + if (entries.empty()) { return; } // 根据downsample_rate,合并数据点,cpu利用率采取平均值 - for (const auto &[r_idx, m_idx] : valid_idxs) { - auto &r_entry{RAW_DATA_TABLE[r_idx]}; + for (const auto &[r_entry, m_entry] : entries) { if (r_entry.downsample_rate <= 1) { // 不需要合并 continue; } - std::cout << r_entry.downsample_rate << std::endl; - // 计算合并后的新数据点数 - auto &m_entry{r_entry.metric_data_table[m_idx]}; std::size_t new_i{0}; for (std::size_t i{0}; i + r_entry.downsample_rate - 1 < m_entry.values.size(); i += r_entry.downsample_rate) { double sum{}; @@ -508,7 +506,6 @@ void PlotCpu() { sum += m_entry.values[j]; } m_entry.values[new_i++] = sum / r_entry.downsample_rate; - std::cout << "avg: " << sum / r_entry.downsample_rate << std::endl; } m_entry.values.resize(new_i); r_entry.interval *= r_entry.downsample_rate; @@ -516,31 +513,30 @@ void PlotCpu() { // 最大数值 double max_value{std::numeric_limits::min()}; - for (const auto &[r_idx, m_idx] : valid_idxs) { - for (double value : RAW_DATA_TABLE[r_idx].metric_data_table[m_idx].values) { + for (const auto &e : entries) { + for (double value : e.m_entry.values) { max_value = std::max(max_value, value); } } - std::cout << "max_value: " << max_value << std::endl; py::scoped_interpreter guard{}; // 必须放在try-catch块外侧,否则无法捕获py::error_already_set异常 try { auto plt{py::module_::import("matplotlib.pyplot")}; auto subplots{plt.attr("subplots")( - valid_idxs.size(), 1, py::arg("sharex") = true, + entries.size(), 1, py::arg("sharex") = true, py::arg("figsize") = py::make_tuple( - 16, std::max(std::min(9, 3 * valid_idxs.size()), valid_idxs.size())))}; + 16, std::max(std::min(9, 3 * entries.size()), entries.size())))}; py::list axes; // 存储所有子图,使用vector反而会有拷贝和引用计数开销 - if (valid_idxs.size() == 1) { + if (entries.size() == 1) { axes.append(subplots[py::int_(1)]); } else { axes = subplots[py::int_(1)].cast(); } - for (std::size_t i{0}; i < valid_idxs.size(); ++i) { - const auto &r_entry{RAW_DATA_TABLE[valid_idxs[i].first]}; - const auto &m_entry{r_entry.metric_data_table[valid_idxs[i].second]}; + for (std::size_t i{0}; i < entries.size(); ++i) { + const auto &r_entry{entries[i].r_entry}; + const auto &m_entry{entries[i].m_entry}; double offset{std::chrono::duration{r_entry.start_time - TOTAL_START_TIME}.count()}; // 构造时间数组,由于offset差异,每个raw data需要单独构造 @@ -557,7 +553,6 @@ void PlotCpu() { ax.attr("margins")(py::arg("x") = 0); ax.attr("grid")(true, py::arg("linestyle") = "--", py::arg("alpha") = 0.5); double ylim{std::max(100., max_value)}; - std::cout << "ylim " << ylim << std::endl; ax.attr("set_ylim")(-ylim / 20, ylim + ylim / 20); // 对齐每个子图y坐标范围 // 绘制每个子图左侧描述raw data的标签 ax.attr("set_ylabel")( @@ -592,6 +587,9 @@ void Report() { // 对齐时间轴,range裁剪,分辨率缩放 AlignTimeline(); + if (RAW_DATA_TABLE.empty()) { + return; + } // 绘制CPU利用率折线图 PlotCpu();