Firecracker Metrics 监控体系完全指南:从 PUT /metrics 配置到 JSON 指标字段深度解析
Firecracker 的 Metrics 系统为每台 microVM 提供统一、低开销的运行时监控能力:它以 JSON 格式按固定周期(默认每 60 秒)或按需向命名管道或普通文件冲刷指标,覆盖 API Server、virtio 设备、vCPU、MMDS、seccomp、信号处理等全部核心子系统。本文以 docs/metrics.md 为主线,结合 vmm 与 firecracker 源码,系统讲解 Metrics 系统的两种配置入口(CLI 与 PUT /metrics)、可选字段(emit_id、properties)、刷新机制,以及如何从源码层解读每一条 JSON 指标行的含义与单位,帮助读者搭建可落地的 Firecracker 可观测性方案。
Metrics 系统整体架构:统一入口与"启动即固定"的生命周期
Firecracker 的全过程监控依赖一套单一的 Metrics 系统(METRICS 全局实例),它与其他资源(如 vCPU、内存、设备)的配置相互独立。根据 docs/metrics.md 的说明,该系统可以通过两种等价的方式初始化:
- 向 API Server 发送
PUT请求到/metrics路径(经 src/firecracker/src/api_server/request/metrics.rs 的parse_put_metrics解析为VmmAction::ConfigureMetrics,最终调用vmm_config::metrics::init_metrics); - 在启动命令行中通过
--metrics-pathCLI 选项指定(对应 src/firecracker/src/main.rs 中定义的Argument::new("metrics-path"),help 文案为 "Path to a fifo or a file used for configuring the metrics on startup.")。
需要注意两条重要的生命周期约束:
- Metrics 配置不属于 Guest 配置的一部分,不会在快照(snapshot)恢复时被还原。也就是说,快照恢复后的新进程需要重新配置其 Metrics 输出目标。
- 所有 Metrics 配置(包括可选的
emit_id、properties)都是在 microVM boot 前设置一次,并在整台 microVM 的生命周期内保持固定,不提供运行时热更新。
从源码看,这套系统的设计目标(src/vmm/src/logger/metrics.rs 的模块文档)可以总结为四点:尽量采用无需锁的原子读写操作;借助内部可变性与原子类型的 Sync 特性,使所有方法(包括语义上可变的写操作)都能作用于一个 static 全局量;序列化交给 serde 完成;全部指标从 0 开始,统一派生 Default。而对外暴露的全局实例定义在 src/vmm/src/logger/metrics.rs 中:
pub static METRICS: Metrics<FirecrackerMetrics, FcLineWriter> =
Metrics::<FirecrackerMetrics, FcLineWriter>::new(FirecrackerMetrics::new());
其内部通过 OnceLock<Mutex<M>> 保存输出 writer,保证 init 只被成功执行一次,重复初始化会返回 MetricsError::AlreadyInitialized。
前置准备:创建指标输出目标
Metrics 以每行一个完整 JSON 对象的形式写入输出目标。输出目标可以是命名管道(FIFO)或普通文件。配置 Metrics 之前需要先创建对应资源(docs/metrics.md):
# 创建所需的命名管道:
mkfifo metrics.fifo
# Metrics 系统同样支持普通文件:
touch metrics.file
选择命名管道的典型场景是:由宿主侧的独立消费者进程(如日志采集器)实时读取每一条指标;选择普通文件的场景则是希望指标落盘、事后用 cat/采集器批量处理。源码中 writer 使用 utils::open_file_nonblock 打开路径并以 FcLineWriter 包装(src/vmm/src/vmm_config/metrics.rs),因此目标文件在每次写指标时都是以追加一行的方式输出。
通过 CLI 配置 Metrics
启动 Firecracker 时直接传入 --metrics-path 即可把 Metrics 输出指向指定文件或 FIFO(docs/metrics.md):
./firecracker --metrics-path metrics.fifo
注意 --metrics-path 只负责设置输出路径,不会处理 emit_id/properties 等可选字段;若要配置这些字段,需要使用下述 API 方式或 --config-file 的 metrics 块。
通过 API(PUT /metrics)配置 Metrics
API 方式是功能最完整的配置入口,示例(docs/metrics.md):
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT "http://localhost/metrics" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-d "{
\"metrics_path\": \"metrics.fifo\"
}"
请求体被反序列化为强类型的 MetricsConfig 结构(定义在 src/vmm/src/vmm_config/metrics.rs),其字段如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
metrics_path |
string | 是 | 命名管道或文件的路径,JSON 格式的指标将冲刷到此 |
emit_id |
boolean | 否(默认 false) |
是否在每条指标行的顶层输出 id 字段 |
properties |
object(string→string) | 否 | 由运维自定义的键值对,原样输出到顶层 properties 字段 |
这与 src/firecracker/swagger/firecracker.yaml 中 Metrics 定义(Swagger 中 Metrics 组件的 definitions)完全对应:metrics_path 是唯一必填项,emit_id 默认 false,properties 为字符串到字符串的 map。字段的默认值行为也有对应的单元测试佐证(test_emit_id_defaults_to_false),即缺省时 emit_id 为 false、properties 为 None。配置完成后,Metrics 会以 JSON 格式持续写入 metrics_path。
可选顶层字段:emit_id 与 properties
默认情况下,每条指标行只携带时间戳加各组件指标。为了在多实例汇聚场景中区分指标来源,Firecracker 提供了两个各自独立开启、默认关闭的可选顶层字段(docs/metrics.md):
id:标记 microVM 实例
将 emit_id 设为 true 后,每条指标行的顶层会携带 id 字段,其值即启动时传给 --id 的实例 id(缺省为 anonymous-instance)。当多台 microVM 的指标被汇聚到同一目的地时,通过 id 即可把每行指标归属到具体实例。
从源码实现看,这个字段由 InstanceIdField 负责(src/vmm/src/logger/metrics.rs):内部是一个 AtomicBool enabled 与全局 INSTANCE_ID(取不到时回退 DEFAULT_INSTANCE_ID),仅在开启时通过 serde 的 flatten 序列化输出一个 "id" 键;MetricsConfig 中 emit_id == true 时会调用 METRICS.id.enable()。对应的序列化单元测试 test_instance_id_serialize_disabled / test_instance_id_serialize_enabled 也验证了"未开启时输出 {}、开启后包含 "id""的两种形态。
properties:运维自定义标签
设置 properties 后,该 map 会被原样输出到顶层 properties 字段(注意 map 经 BTreeMap 存储,输出按键排序)。源码中对应 MetricsProperties 结构(src/vmm/src/logger/metrics.rs),其内部用 OnceLock<BTreeMap<String, String>> 保存、同样只能 set 一次,重复设置返回 AlreadyInitialized。测试 test_metrics_properties_serialize_set 精确验证了输出形如 {"properties":{"bundle_id":"fn-abc","customer_id":"1234"}}。
配置示例与输出对比
通过 API 请求体同时配置两个字段(docs/metrics.md):
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT "http://localhost/metrics" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-d "{
\"metrics_path\": \"metrics.fifo\",
\"emit_id\": true,
\"properties\": {
\"customer_id\": \"1234\",
\"bundle_id\": \"fn-abc\"
}
}"
同样的字段可以放进 --config-file 启动配置文件的 metrics 块(docs/metrics.md):
{
"metrics": {
"metrics_path": "metrics.fifo",
"emit_id": true,
"properties": {
"customer_id": "1234",
"bundle_id": "fn-abc"
}
}
}
两个字段都不配置时,一行指标只有默认键,大致形如:
{"utc_timestamp_ms": 1739000000000, "api_server": {"...": 0}}
两个字段都配置后,同一行会额外携带实例 id 与自定义属性(docs/metrics.md):
{"utc_timestamp_ms": 1739000000000, "id": "my-instance", "properties": {"bundle_id": "fn-abc", "customer_id": "1234"}, "api_server": {"...": 0}}
指标刷新机制:60 秒定时冲刷与按需 FlushMetrics
指标通过两种方式被冲刷到输出目标(docs/metrics.md):
- 无需人工干预:Firecracker 内部每 60 秒自动冲刷一次;
- 按用户需求:通过
PUT /actions发送FlushMetrics请求触发即时冲刷。
源码证据位于 src/firecracker/src/metrics.rs:常量 WRITE_METRICS_PERIOD_MS: u64 = 60000,主流程用一个 TimerFd(write_metrics_event_fd)注册到事件管理器,定时到期后调用 METRICS.write() 写出一行完整 JSON;FlushMetrics 则直接对应 docs/api_requests/actions.md 中描述的 action:
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT "http://localhost/actions" \
-d '{ "action_type": "FlushMetrics" }'
METRICS.write()(src/vmm/src/logger/metrics.rs)的具体动作是:通过 serde_json::to_writer 将整个 FirecrackerMetrics 序列化为 JSON,随后再写一个换行符 \n,因此输出目标的每条记录都是独立的单行 JSON,天然适合按行解析的采集管线。
消费指标输出的两种方式
如果配置的是命名管道,可以用下面的脚本持续读取(docs/metrics.md):
metrics=metrics.fifo
while true
do
if read line <$metrics; then
echo $line
fi
done
echo "Reader exiting"
如果输出目标是普通文件,直接查看即可(docs/metrics.md):
cat metrics.file
单行 JSON 指标中可能出现的顶层键
Firecracker 每条指标 JSON 对象可能包含以下键(docs/metrics.md):
"api_server"
"balloon"
"block"
"deprecated_api"
"entropy"
"get_api_requests"
"i8042"
"latencies_us"
"logger"
"mmds"
"net"
"patch_api_requests"
"put_api_requests"
"rtc"
"seccomp"
"signals"
"uart"
"vcpu"
"vhost_user_block"
"vmm"
"vsock"
从源码视角看,这些键几乎一一对应 src/vmm/src/logger/metrics.rs 中 FirecrackerMetrics 结构的字段。其中 utc_timestamp_ms 是每一行的第一个键,由 SerializeToUtcTimestampMs 在序列化时用实时时钟(ClockType::Real)即时生成(毫秒精度)。设备类指标(balloon/block/net/vsock 等)则通过 serde 的 #[serde(flatten)] 代理结构(如 create_serialize_proxy! 宏生成的 BlockMetricsSerializeProxy)在序列化过程中调用各设备 metrics 模块的 flush_metrics,从而把多设备指标扁平地合并进同一 JSON 对象。
各指标的源码定义位置
下表给出了各指标键对应的设备与定义文件(docs/metrics.md,链接已转换为仓库根目录相对路径):
| Metrics key | Device | Additional comments |
|---|---|---|
| balloon | BalloonDeviceMetrics | 表示 Balloon 设备的指标。 |
| block | BlockDeviceMetrics | 表示 Virtio Block 设备的聚合指标。 |
| block_{block_drive_id} | BlockDeviceMetrics | 表示 /drives/{drive_id} 端点对应 Virtio Block 设备的指标,例如 "block_rootfs": 表示端点 /drives/rootfs 的指标。 |
| i8042 | I8042DeviceMetrics | 表示 i8042 设备特有的指标。 |
| net | NetDeviceMetrics | 表示 Virtio Net 设备的聚合指标。 |
| net_{iface_id} | NetDeviceMetrics | 表示 /network-interfaces/{iface_id} 端点对应 Virtio Net 设备的指标,例如 net_eth0 表示端点 /network-interfaces/eth0 的指标。 |
| rtc | RTCDeviceMetrics | 表示 RTC 设备特有的指标。注意:仅在 aarch64 上输出。 |
| uart | SerialDeviceMetrics | 表示串口设备特有的指标。 |
| vhost_user_{dev}_{dev_id} | VhostUserDeviceMetrics | 表示设备 dev 与设备 id dev_id 的 Vhost-user 设备指标,例如 "vhost_user_block_rootfs": 表示端点 /drives/rootfs 的 vhost-user block 设备指标。 |
| vsock | VsockDeviceMetrics | 表示 vsock 设备特有的指标。 |
| entropy | EntropyDeviceMetrics | 表示 entropy(virtio-rng)设备特有的指标。 |
| "api_server" "deprecated_api" "get_api_requests" "latencies_us" "logger" "mmds" "patch_api_requests" "put_api_requests" "seccomp" "signals" "vcpu" "vmm" |
metrics.rs | 其余指标均定义在同一文件 metrics.rs 中。 |
关键行为:所有组件键都会被输出
Firecracker 会无条件输出全部上述指标键,无论对应组件是否真的挂载(docs/metrics.md)。例如即使 microVM 没有挂载 vsock 设备,输出中仍会出现键 vsock,其值为 VsockDeviceMetrics 中定义的所有指标全部为 0。这一"全量骨架、零值填充"的设计让下游解析器可以按固定 schema 处理,无需感知每台实例的差异。同理,rtc 仅在 aarch64 平台输出(源码层面表现为 LegacyDevMetricsSerializeProxy 在 aarch64/x86_64 上注册的 legacy 设备集合不同,src/vmm/src/devices/legacy 中 serial.rs 同时承载了 RTC 与串口两种设备的指标结构)。
指标计数语义:SharedIncMetric 与 SharedStoreMetric
想要正确解读数值,需要理解 Firecracker 底层两类指标原语(src/vmm/src/logger/metrics.rs):
SharedIncMetric(增量计数指标):内部保存"当前值 + 上一次冲刷值"两个AtomicU64。序列化(即冲刷)时输出二者之差current - previous,成功后把previous更新为current。因此这类指标(如各类 API 请求次数、设备事件计数)在每次冲刷后自动归零,两次输出之间的差值即这段时间的新增量。SharedStoreMetric(持久存储指标):只保存单一值,冲刷时原样输出、不清零。适合"状态/瞬时值"语义,例如api_server.process_startup_time_us、seccomp 的num_faults、vmm 的panic_count,以及vmm级的latencies_us下各类快照操作耗时。
此外还有聚合型延迟指标 LatencyAggregateMetrics,以 min_us / max_us / sum_us 三元组记录某一操作的耗时极值与累计,sum_us 是增量计数的(冲刷清零),min_us/max_us 是持久存储的。使用方只需 metrics.record_latency_metrics() 生成一个 RAII 风格的 recorder,drop 时自动把时间差写回(这正是单元测试之外最常用的埋点方式)。一个典型示例是 vCPU 指标中的 exit_io_in_agg:用 exit_io_in_agg.sum_us 除以 exit_io_in 即可算出单次 KVM I/O 退出处理的平均耗时。
由于 vCPU 指标可能被多个 vCPU 线程同时递增,SharedIncMetric/SharedStoreMetric 均基于 AtomicU64 实现(fetch_add/store,Ordering::Relaxed),无需额外加锁即可并发安全。需要注意一个附带效应:任何一次序列化都会同时执行增量计数器的"复位",因此对指标对象的任何打印/序列化都应视为一次冲刷。
从指标名提取单位:内置的命名约定
Firecracker 指标的单位直接内嵌在字段名后缀中,无需额外元数据表。官方给出的提取伪代码(docs/metrics.md)如下:
if substring "_bytes" or "_bytes_count" is present in any subkey of full_key
Unit is "Bytes"
else substring "_ms" is present in any subkey of full_key
Unit is "Milliseconds"
else substring "_us" is present in any subkey of full_key
Unit is "Microseconds"
else
Unit is "Count"
判断顺序是从"字节/毫秒/微秒"逐步收窄到最后默认的"次数"。例如:
- 顶层
utc_timestamp_ms—— 含_ms,单位为毫秒(Unix 毫秒时间戳); - 全文键
vcpu.exit_io_in_agg.min_us—— 含_us,单位为微秒; - 含
_bytes或_bytes_count的字段(如 MMDS 的tx_bytes)—— 单位为字节; - 其余不含任何单位后缀的字段 —— 单位为次数(Count)。
以真实的 vcpu.exit_io_in_agg.min_us 为例,规则会匹配到 _us 而判定为微秒,这与代码中 LatencyAggregateMetrics 字段名 min_us/max_us/sum_us 以及 PerformanceMetrics(即顶层 latencies_us)中 *_snapshot 等以 _us 结尾的字段完全一致。
归纳:一条指标行的完整解读
综合以上内容,一条典型的 Firecracker 指标行可以按"时间戳 + 可选标签 + 各组件数值"来解读:
{
"utc_timestamp_ms": 1739000000000,
"id": "my-instance",
"properties": { "bundle_id": "fn-abc", "customer_id": "1234" },
"api_server": { "process_startup_time_us": 0, "process_startup_time_cpu_us": 0 },
"put_api_requests": { "metrics_count": 1, "metrics_fails": 0 },
"vcpu": { "exit_io_in": 0, "exit_io_in_agg": { "min_us": 0, "max_us": 0, "sum_us": 0 } },
"block_rootfs": { "...": 0 },
"vsock": { "...": 0 }
}
- 顶层的
utc_timestamp_ms标明冲刷时刻;id与properties由emit_id/properties可选开启,用于多实例汇聚溯源; api_server、*_api_requests、logger、seccomp、signals、mmds、vcpu、latencies_us等键定义于 src/vmm/src/logger/metrics.rs;- 设备相关键(
block、block_rootfs、net、net_eth0、balloon、vsock、entropy、vhost-user 系列)由对应设备 metrics 模块在序列化时合并进来,未挂载的设备一律输出零值; - 每次输出后,
SharedIncMetric型计数自动复位,适合按 60 秒窗口做增量聚合;SharedStoreMetric型持久值则适合读取瞬时状态与累计量。
配置与排障的落地建议:优先使用 --config-file 中的 metrics 块或 PUT /metrics 一次性配置好 metrics_path(必要时开启 emit_id/properties),在 microVM boot 之前完成;启动后通过"读取 FIFO/文件 + 60 秒周期或 FlushMetrics"持续获得观测数据;解析时依据上文单位规则与计数语义,即可准确换算为字节/毫秒/微秒/次数,用于告警、容量评估与性能分析。
如需进一步了解字段的 API 定义细节,可直接查阅 Swagger 定义 src/firecracker/swagger/firecracker.yaml 中 Metrics 组件的 schema;关于 FlushMetrics action 的完整用法可参考 docs/api_requests/actions.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00