注意

本文档适用于 Ceph 的开发版本。

CpuTrace

CpuTrace 是一个测量执行 CPU 开销的开发人员工具。它对于新代码的算法选择和验证性能改进非常有用。CpuTrace 测量 CPU 指令、时钟周期、分支预测错误、缓存未命中和线程重新调度。

集成到 Ceph

要启用 CpuTrace,请使用 WITH_CPUTRACE 标志进行构建

./do_cmake.sh -DWITH_CPUTRACE=1

一旦使用 CpuTrace 支持构建完成,您就可以使用提供的宏和辅助类来注解特定的函数或代码区域。

要在代码中启用性能分析,请包含 CpuTrace 头文件

#include "common/cputrace.h"

然后您可以使用提供的辅助函数来标记需要进行性能分析的函数。

原始计数器模式

CpuTrace 使用 Linux perf_event_open 系统调用。您可以将此工具用作一个简单的辅助工具来访问硬件 perf 计数器。

// I am profiling my code and want to know
// how many clock cycles and how many thread switches it takes
HW_ctx hw = HW_ctx_empty;
HW_init(&hw, HW_PROFILE_SWI|HW_PROFILE_CYC);
sample_t start, end;
HW_read(&hw, &start);
// my code starts
// .....
// my code ends
HW_read(&hw, &end);
// task_switches = end.swi - start.swi;
// clock_cycles  = end.cyc - start.cyc;
HW_clean(&hw);

通过检查 task_switchesclock_cycles,开发人员可以得知 10ms 的实际时钟执行时间只有 1M 时钟周期,但发生了 2 次任务切换。

聚合样本

单个执行时间读数通常不够。我们需要更多样本才能更真实地测量实际执行开销。

// a variable to hold my measurement
static measurement_t my_code_time;
sample_t start, end, elapsed;
// hw initialized somewhere else
HW_read(&hw, &start);
// my code starts
// .....
// my code ends
HW_read(&hw, &end);
elapsed = end - start;
// add new sample to the whole measurement
my_code_time.sample(elapsed);

measurement_t

measurement_t 类型聚合收集到的样本并计算执行的测量次数。

它生成包括以下内容的摘要统计信息

  • count:总测量次数

  • average:所有样本的平均值

  • zero / non-zero split:测量结果恰好为零和大于零的次数(仅适用于上下文切换指标)

这些统计信息提供了性能测量的紧凑而清晰的视图。

measurement_t 还可以以两种格式导出结果

  • Ceph Formatter(用于结构化的 JSON/YAML/XML 输出)

    ceph::Formatter* jf;
    m->dump(jf, HW_PROFILE_CYC|HW_PROFILE_INS); // Select which stats to output
    
  • String stream(用于纯文本日志记录)

    std::stringstream ss;
    m->dump_to_stringstream(ss, HW_PROFILE_CYC|HW_PROFILE_INS); // Select which stats to output
    std::cout << ss.str();
    

这使得将测量结果集成到 Ceph 的结构化输出管道中或将其作为人类可读的文本转储进行调试变得容易。

RAII 样本

使用 RAII 收集样本通常最方便。使用 RAII,当创建守卫对象时测量自动开始,当它超出范围时结束,因此不需要显式的开始/停止调用。

在创建守卫之前,必须先初始化硬件上下文(HW_ctx)一次。初始化后,同一上下文可以在多次测量中重用。

HW_guard 接受两个参数

  • HW_ctx* ctx 指向已初始化硬件上下文的指针。

  • measurement_t* m 指向将存储结果的测量对象的指针。

示例

// variable to hold measurement results
static measurement_t my_code_time;
{
  HW_guard guard(&hw, &my_code_time);
  // code to be measured
  // ...
}

命名测量

可以使用 named guard 来测量代码区域。每个 HW_named_guard 在构造时自动开始测量,并在离开范围时停止。

{
  HW_named_guard("function", &hw);
  // my code starts
  // ...
  // my code ends
}

此示例记录 function 的执行时间。

守卫需要一个指向先前初始化的 HW_ctx 的指针。必须在可以使用守卫之前创建并设置此上下文(例如,在程序初始化期间)。

命名守卫提供了一种简单一致的方式来跟踪性能指标。

要稍后访问收集到的给定名称的测量结果,请使用

measurement_t* m = get_named_measurement("function");
if (m) {
  // inspect m->sum_cyc, m->sum_ins.
  // m->dump_to_stringstream(ss, HW_PROFILE_INS|HW_PROFILE_CYC);
}

Admin socket 集成

除了代码中的直接插桩之外,CpuTrace 还可以通过 admin socket 接口在运行时控制。这允许开发人员在运行中的 Ceph 守护进程中启动、停止和检查性能分析,而无需重新构建或重新启动它们。

要对函数进行性能分析,请使用提供的宏进行注解

HWProfileFunctionF(profile, __func__,
                   HW_PROFILE_CYC  | HW_PROFILE_CMISS |
                   HW_PROFILE_INS  | HW_PROFILE_BMISS |
                   HW_PROFILE_SWI);
  • profile 是性能分析器对象的局部变量名,只需要在性能分析范围内唯一即可。

  • __func__(或您作为名称传入的任何字符串)是此性能分析范围的唯一锚点名称。

每个唯一的名称都会创建一个单独的锚点。在多个地方重用同一个名称将触发断言失败。

此宏自动将性能分析器附加到函数范围,并在每次函数执行时收集指定的硬件计数器。

您可以组合任何可用的标志

  • HW_PROFILE_CYC – CPU 周期

  • HW_PROFILE_CMISS – 缓存未命中

  • HW_PROFILE_BMISS – 分支预测错误

  • HW_PROFILE_INS – 退役指令

  • HW_PROFILE_SWI – 上下文切换

可用命令

  • cputrace start – 使用配置的组/计数器开始性能分析

  • cputrace stop – 停止性能分析并冻结结果

  • cputrace dump – 转储所有收集到的指标(以 JSON 或纯文本形式)

  • cputrace reset – 重置所有捕获的数据

性能分析计数器是累积的。cputrace stop 暂停性能分析而不重置值。cputrace start 恢复累积。使用 cputrace reset 清除所有收集到的指标。

命令行使用示例

# Start profiling on OSD.0
ceph tell osd.0 cputrace start

# Stop profiling
ceph tell osd.0 cputrace stop

# Dump results
ceph tell osd.0 cputrace dump

# Reset counters
ceph tell osd.0 cputrace reset

这些命令可以重复多次:开发人员通常在工作负载之前 start,之后 stop,然后 dump 结果进行分析。

cputrace dump 支持可选参数来按记录器或计数器过滤,因此在需要时可以只报告指标的一个子集。

cputrace reset 清除所有数据,为新一轮的性能分析做准备。

API 参考

枚举

enum cputrace_flags {
    HW_PROFILE_SWI   = (1ULL << 0), // Context switches
    HW_PROFILE_CYC   = (1ULL << 1), // CPU cycles
    HW_PROFILE_CMISS = (1ULL << 2), // Cache misses
    HW_PROFILE_BMISS = (1ULL << 3), // Branch mispredictions
    HW_PROFILE_INS   = (1ULL << 4), // Instructions retired
};

按位 | 运算符可用于组合这些标志。

数据结构

sample_t – 保存单个硬件计数器快照。

struct sample_t {
  uint64_t swi;   //context switches
  uint64_t cyc;   //clock cycles
  uint64_t cmiss; //cache misses
  uint64_t bmiss; //branch misses
  uint64_t ins;   //instructions
};

measurement_t – 累积多个样本并计算总计/平均值和其他有用指标。

struct measurement_t {
  uint64_t call_count = 0;
  uint64_t sample_count = 0;
  uint64_t sum_swi = 0, sum_cyc = 0, sum_cmiss = 0, sum_bmiss = 0, sum_ins = 0;
  uint64_t non_zero_swi_count = 0;
  uint64_t zero_swi_count = 0;
};

HW_ctx – 封装一个测量上下文的 perf-event 文件描述符。

extern HW_ctx HW_ctx_empty;

底层 API

  • void HW_init(HW_ctx* ctx, cputrace_flags flags) – 初始化 perf 计数器。

  • void HW_read(HW_ctx* ctx, sample_t* out) – 读取当前计数器值。

  • void HW_clean(HW_ctx* ctx) – 释放 perf 计数器。

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入