Cloud Hypervisor 启动追踪(Tracing)基础设施实战指南:从 `trace_scoped!` 埋点到 SVG 可视化
2026/9/17 23:11:44 网站建设 项目流程

Cloud Hypervisor 启动追踪(Tracing)基础设施实战指南:从trace_scoped!埋点到 SVG 可视化

【免费下载链接】cloud-hypervisorA Virtual Machine Monitor for modern Cloud workloads. Features include CPU, memory and device hotplug, support for running Windows and Linux guests, device offload with vhost-user and a minimal compact footprint. Written in Rust with a strong focus on security.项目地址: https://gitcode.com/GitHub_Trending/cl/cloud-hypervisor

导读

Cloud Hypervisor 内置了一套轻量级追踪(tracing)基础设施,专门用于观察虚拟机**初始启动(initial VM setup)**过程中的耗时分布与调用层次。本文将完整介绍如何通过tracing编译特性开启追踪、解读输出的 JSON trace 文件、使用官方脚本生成 SVG 时间线图,并结合tracercrate 与vmm中的真实埋点源码,讲清trace_scoped!tracer::start()/tracer::end()trace_point!等 API 的底层实现原理,让你能够为代码库添加自定义埋点并输出可读性强的启动性能画像。

Cloud Hypervisor 的追踪设施定位

Cloud Hypervisor 的追踪基础设施刻意保持"基础(basic)"的定位:它不追求像 Tracing/OpenTelemetry 那样完备的分布式追踪能力,而是聚焦于一个非常具体的目标——VM 启动流程的性能观察。通过极少的 API 面(两个宏、两个函数)即可在启动代码路径上打点,最终产出一份 JSON 报告和一张 SVG 时间线图。

整个设施由工作区成员 cratetracer提供,其核心代码量非常小:

  • tracer/src/lib.rs:按编译特性门控的模块分发入口;
  • tracer/src/tracer.rs:开启tracing特性后的完整实现;
  • tracer/src/tracer_noop.rs:关闭特性后的空实现(编译期剔除)。

快速上手:开启追踪并生成 trace 文件

编译:通过 Cargo feature 开关追踪

追踪功能以 Cargo featuretracing形式提供。构建 Cloud Hypervisor 时带上该特性即可:

cargo build --features "tracing"

关键在于:不带该特性编译时,追踪代码会被完整编译掉(compiled out),不会产生任何运行时开销。这一机制由tracercrate 的模块级条件编译实现——tracer/src/lib.rs 中的逻辑是:

#[cfg(not(feature = "tracing"))] mod tracer_noop; #[cfg(not(feature = "tracing"))] pub use tracer_noop::*; #[cfg(feature = "tracing")] mod tracer; #[cfg(feature = "tracing")] pub use tracer::*;

feature 的传导链在三个Cargo.toml中逐级串联:

  • cloud-hypervisor/Cargo.toml:tracing = ["tracer/tracing", "vmm/tracing"]
  • vmm/Cargo.toml:tracing = ["tracer/tracing"]
  • tracer/Cargo.toml:tracing = ["dep:libc", "dep:log", "dep:serde", "dep:serde_json"]

也就是说,只有在顶层显式开启tracing时,libclogserdeserde_json这些仅用于追踪的依赖才会被引入;否则启用的是 tracer_noop.rs 中的全空实现(trace_scoped!/trace_point!为空宏,start()/end()为空函数)。这是典型的"零成本抽象"设计:不开启特性,追踪路径上几乎不存在任何指令。

运行:trace 文件落在哪里

按常规方式运行编译出的 Cloud Hypervisor 即可,无需额外命令行参数。追踪结束后,trace 会以 JSON 格式写入当前工作目录,文件名为:

cloud-hypervisor-<pid>.trace

其中<pid>是 Cloud Hypervisor 进程自身的 PID。该命名逻辑来自 tracer/src/tracer.rs 的Tracer::end():它通过libc::getpid()取得进程号并拼接文件名,随后用serde_json::to_writer_pretty写出格式化 JSON,并打出一条 WARN 级别的日志提示输出路径:

let path = format!("cloud-hypervisor-{}.trace", unsafe { libc::getpid() }); // ... warn!("Trace output: {path}");

这个文件是标准 JSON,你可以直接用任意工具(jq、Python、VS Code)自行检查分析。

trace 文件的数据结构

一份 trace 文件的顶层包含两个字段:

字段类型含义
durationDurationsecs+nanostracer::start()tracer::end()的总耗时
eventsMap<线程名, Vec<TraceEvent>>线程名分组的追踪事件列表

其中单个TraceEvent(见 tracer/src/tracer.rs)包含四个字段:

  • timestamp:事件开始时间(相对tracer::start()的时刻),类型为Duration
  • event:事件名称(&'static str,即trace_scoped!/trace_point!传入的字符串字面量);
  • end_timestamp:事件结束时间。作用域块(scope block)事件有值,瞬时 trace point 为None
  • depth:事件在该线程内的嵌套深度(由thread_depths维护的调用栈深度)。

events之所以按线程分组,是因为add_eventthread::current().name()作为键存储事件(见 tracer.rs)。这意味着未命名的线程会落入空字符串键下——这一点在自定义线程埋点时值得留意。

可视化:用 ch-trace-visualiser.py 生成 SVG 时间线

JSON 文件虽然信息完整,但直接阅读启动流程的耗时分布并不直观。仓库在 scripts/ch-trace-visualiser.py 提供了官方可视化脚本,可将 trace 文件转换成 SVG 时间线图:

scripts/ch-trace-visualiser.py cloud-hypervisor-39466.trace output.svg

其中第一个参数是输入的 trace 文件,第二个参数是输出的 SVG 路径。

脚本的工作原理(从 ch-trace-visualiser.py 源码可以确认):

  • 解析 JSON 中的duration,将纳秒时间按比例映射到 1000×200 的 SVG 画布横向坐标;
  • 每个线程占据一行,行内按timestamp排序事件;
  • 每个作用域块渲染为一个带随机颜色的矩形,宽度与事件耗时成正比,矩形上标注事件名与耗时毫秒数(如vm_boot (123ms));
  • 通过事件的depth字段控制纵向偏移(每层 18px),从而直观呈现嵌套关系;
  • 矩形文字使用clipPath裁剪,超长事件名不会溢出。

可以看到,可视化完全依赖TraceEventtimestampend_timestampdepth三个字段,这也是设计TraceEvent时"为可视化服务"的直接体现。

现有埋点全景:启动流程中的 trace_scoped!

追踪设施已预先在 VM 启动路径上埋好了一批作用域块,覆盖了从设备创建、内存管理、ACPI 表生成到引导装载的各个环节。以下是从vmm源码中检索到的全部trace_scoped!调用点:

埋点位置文件与行号事件名追踪内容
启动总入口vmm/src/lib.rsvm_boot整个 boot 流程顶层作用域
设备创建vmm/src/device_manager.rsDeviceManager::new设备管理器初始化
设备创建vmm/src/device_manager.rscreate_devices创建设备
内存管理vmm/src/memory_manager.rsMemoryManager::new内存管理器初始化
VM 构造vmm/src/vm.rsVm::new_from_memory_manager从内存管理器创建 VM
VM 构造vmm/src/vm.rsVm::newVM 对象构造
引导装载vmm/src/vm.rsload_payload装载 guest 负载(内核/固件)
系统配置vmm/src/vm.rsconfigure_system系统寄存器与内存配置
ACPI 表vmm/src/acpi.rscreate_dsdt_tableDSDT 表生成
ACPI 表vmm/src/acpi.rscreate_facp_tableFADT 表生成
ACPI 表vmm/src/acpi.rscreate_acpi_tablesACPI 表整体生成
vCPU 创建vmm/src/cpu.rscreate_boot_vcpus创建启动 vCPU
入口点vmm/src/vm.rsentry_point设置 guest 入口
启动vmm/src/vm.rsVm::bootVM 启动

这些埋点大多互相嵌套(例如vm_boot内嵌套Vm::newVm::bootVm::new内嵌套MemoryManager::newDeviceManager::new),配合depth字段即可在 SVG 中呈现出一棵清晰的启动调用树。

tracer::start()tracer::end()已经就位,它们包住了整个 boot 流程。见 vmm/src/lib.rs 的vm_boot()

fn vm_boot(&mut self) -> result::Result<(), VmError> { match &self.vm { VmOwnership::Owned(_) => Err(VmError::VmAlreadyCreated), VmOwnership::Migration { .. } => Err(VmError::VmMigrating), VmOwnership::None => { tracer::start(); info!("Booting VM"); // ... let r = (|| { trace_scoped!("vm_boot"); // ... 创建 Vm、调用 vm.boot() ... })(); tracer::end(); // ... } } }

即:进入VmOwnership::None分支、开始真正启动时调用tracer::start(),整个 boot 逻辑(含所有嵌套的trace_scoped!)执行完毕后调用tracer::end()落盘。文档明确指出,这两个函数的位置是可以移动的——如果需要把追踪聚焦到代码库中某个更窄的部分(例如只想测load_payload或某张 ACPI 表的生成),可以把start()/end()挪到相应位置重新编译即可。

底层原理:tracer crate 是如何工作的

全局单例与线程深度记账

tracer.rs 使用一个static mut TRACER: OnceCell<Tracer>作为全局追踪器,Tracer结构持有三块状态:

  • events: Arc<Mutex<HashMap<String, Vec<TraceEvent>>>>:按线程名分组的事件表;
  • thread_depths: HashMap<String, Arc<AtomicU64>>:每个线程当前的作用域嵌套深度;
  • start: Instant:追踪起点,用于计算所有事件的时间戳。

start()在其它线程启动之前完成TRACER的初始化(OnceCell::set),end()在其它线程结束后输出,这个时序约束在源码注释中有明确说明。

trace_scoped!:RAII 作用域块

trace_scoped!宏(tracer.rs)展开为:

macro_rules! trace_scoped { ($event:expr) => { let _trace_scoped = $crate::TraceBlock::new($event); }; }

它在当前作用域声明一个TraceBlockTraceBlock::new()会先把当前线程深度 +1 并记录Instant::now()起始时刻;当作用域结束时(变量析构),Drop实现会构造一个带有timestampend_timestampTraceEvent写入事件表,并把线程深度 -1。因此:

  • 开发者只需提供一个有意义的事件名,块的开始、结束、耗时、嵌套深度全部自动完成;
  • 由于是 RAII,即使作用域内 panic 或提前returnDrop依然会执行,不会漏记结束时间。

这也是文档所说"除了提供有用的事件名,开发者无需做任何其它事"的底层含义。

trace_point!:瞬时埋点

trace_point!宏(tracer.rs)调用trace_point_log,构造end_timestamp: None的瞬时事件。它记录一个"时刻"而非一个"区间"。

文档特别指出了它的两个现状:

  1. 当前代码库中没有使用——搜索整个仓库,trace_point!/trace_point_log只存在于tracercrate 自身定义中,vmm内没有任何调用点;
  2. 可视化脚本没有处理它——由于瞬时事件没有end_timestamp,无法在 SVG 中渲染成带宽度的块,ch-trace-visualiser.py 的add_traced_block依赖end_timestamp计算矩形宽度,因此这类事件不会被绘制。

所以如果你要用trace_point!做瞬时打点,需要自行解析 JSON 中的这类事件(end_timestampnull),或者扩展可视化脚本。这也是"基础基础设施"定位的体现——它留下的是扩展空间,而非完备的现成方案。

为代码库添加自定义埋点

要在你的 Cloud Hypervisor 定制版本中做更细粒度的追踪,步骤非常简单:

第一步:确认构建时携带tracing特性:

cargo build --features "tracing"

第二步:在需要测量耗时的函数入口(或代码块开头)插入作用域埋点:

fn my_expensive_operation(&mut self) { trace_scoped!("my_expensive_operation"); // ... 原有逻辑 ... }

事件名是&'static str,推荐使用与函数/操作语义一致的描述性名称,例如仓库现有埋点风格的create_devicesload_payloadconfigure_system

第三步:如需缩小追踪范围,把tracer::start()/tracer::end()从 vmm/src/lib.rs 与 vmm/src/lib.rs 移到目标代码路径的前后。

第四步:运行后检查当前目录生成的cloud-hypervisor-<pid>.trace,并用可视化脚本输出 SVG:

scripts/ch-trace-visualiser.py cloud-hypervisor-<pid>.trace output.svg

局限性与注意事项

基于源码事实,使用这套追踪设施时有几点需要了解:

  • 追踪窗口默认仅覆盖启动start()/end()包住的是vm_boot(),运行时(runtime)阶段的设备 I/O 不在默认追踪范围内,需要自行移动起止点;
  • 按线程分组:事件以线程名聚合,匿名线程会归入空字符串分组,命名线程时使用有辨识度的名字更利于解读;
  • trace_point!未被可视化:瞬时事件不会出现在 SVG 中,且仓库内尚无使用案例;
  • 输出文件覆盖风险:每次end()都直接File::create同名文件(cloud-hypervisor-<pid>.trace),同一 PID 不会复用,但重复启动同名进程时会按各自 PID 区分文件;
  • 深度记账的不对称风险decrease_thread_depth在没有对应深度记录时会panic!("Unmatched decrease for thread: {thread_name}"),正常 RAII 配对不会触发,但任何绕过TraceBlock的手工操作都可能破坏深度平衡。

总结

Cloud Hypervisor 的追踪设施是一套"小而美"的启动性能观测方案:一条 Cargo feature 开关、两个宏、两个函数,即可在零负担的前提下获得按线程分组、带嵌套深度的启动耗时 JSON,并经官方脚本一键渲染为 SVG 时间线。通过本文的源码级拆解,你已经掌握了trace_scoped!的 RAII 语义、TraceEvent的数据结构、feature 的条件编译传导链,以及仓库中全部 15 处现有埋点的分布——无论是理解vm_boot期间各阶段(设备创建、内存管理、ACPI 表生成、负载装载、vCPU 创建)的时间占比,还是为特定热点添加自定义埋点,都可以立即动手实践。

相关参考文件:

  • 官方追踪文档
  • tracer crate 完整实现
  • tracer crate 空实现
  • tracer crate 特性定义
  • 可视化脚本
  • vmm 中 start/end 与 vm_boot 埋点
  • vmm 中各 trace_scoped! 埋点

【免费下载链接】cloud-hypervisorA Virtual Machine Monitor for modern Cloud workloads. Features include CPU, memory and device hotplug, support for running Windows and Linux guests, device offload with vhost-user and a minimal compact footprint. Written in Rust with a strong focus on security.项目地址: https://gitcode.com/GitHub_Trending/cl/cloud-hypervisor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询