Fluent Bit 内置的 CMetrics:用 C 语言构建轻量级指标处理管线的完整指南
2026/9/16 22:26:56 网站建设 项目流程

Fluent Bit 内置的 CMetrics:用 C 语言构建轻量级指标处理管线的完整指南

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

CMetrics 是 Fluent Bit 仓库中内置的一个独立 C 指标库(位于 lib/cmetrics),负责指标的创建、变更、聚合、编码与解码,是 Fluent Bit 采集与输出 CPU、内存、网络等指标的核心数据层。读完本文,你将掌握 CMetrics 支持的全部指标类型与编解码格式、start_timestamp在 OTLP 累积流中的用法,并能基于公开 C API 编写一个完整的指标采集与 OTLP 编码程序。

说明:当前仓库中的 CMetrics 仍处于积极开发阶段(README 明确标注 "THIS LIBRARY IS STILL IN ACTIVE DEVELOPMENT"),本文描述的 API 与行为均以当前仓库源码为准。

CMetrics 是什么:指标上下文的一站式 C 库

CMetrics 是一个独立的 C 库,用于"创建、变更、聚合、编码和解码指标上下文(metrics contexts)"。它不依赖任何运行时或脚本语言,通过一套以cmt_前缀命名的 C API 对外提供服务。

从源码结构看,整个库遵循"公共 API 在头文件、实现分散在源文件"的经典布局:

  • 公共 API 声明集中在 lib/cmetrics/include/cmetrics 下的头文件中(cmt_counter.hcmt_gauge.hcmt_histogram.h等);
  • 具体实现位于 lib/cmetrics/src(cmt_counter.ccmt_gauge.ccmt_encode_*.ccmt_decode_*.c等);
  • 测试用例集中在 lib/cmetrics/tests(counter.cgauge.chistogram.cencoding.cdecoding.copentelemetry.c等)。

这种设计让 CMetrics 可以被 Fluent Bit 主程序、各 input/output 插件以及外部 C 项目直接静态链接使用。

核心数据模型:从上下文到数据点

理解 CMetrics 的用法,先要理清它的三层核心结构(详见 lib/cmetrics/docs/architecture.md):

  1. struct cmt(上下文):顶层指标上下文,持有日志配置、元数据、静态标签,以及 counters/gauges/histograms/exp_histograms/summaries/untypeds 六类指标族链表(定义见 lib/cmetrics/include/cmetrics/cmetrics.h)。
  2. struct cmt_map(映射):每个指标族(metric family)拥有一个映射,负责管理带标签的数据点集合;无标签时通过静态 metric 直接访问(定义见 lib/cmetrics/include/cmetrics/cmt_map.h)。
  3. struct cmt_metric(数据点):单个带时间戳与标签的采样点,内部保存数值、直方图桶、指数直方图桶、摘要分位数、时间戳与start_timestamp等字段(定义见 lib/cmetrics/include/cmetrics/cmt_metric.h)。

每个指标族还对应一组选项struct cmt_opts(见 lib/cmetrics/include/cmetrics/cmt_opts.h),包含namespacesubsystemnamedescription,并会自动拼接出全限定名fqname,格式为namespace_subsystem_name

另外,cmetrics.h 还定义了指标类型的枚举常量与聚合类型:

#define CMT_COUNTER 0 #define CMT_GAUGE 1 #define CMT_HISTOGRAM 2 #define CMT_SUMMARY 3 #define CMT_UNTYPED 4 #define CMT_EXP_HISTOGRAM 5 #define CMT_AGGREGATION_TYPE_UNSPECIFIED 0 #define CMT_AGGREGATION_TYPE_DELTA 1 #define CMT_AGGREGATION_TYPE_CUMULATIVE 2

其中聚合类型(DELTA/CUMULATIVE)是 OTLP 语义的重要组成部分,cumulative 流正是start_timestamp发挥作用的地方。

支持的指标类型(六种)

CMetrics 支持以下六种指标类型,覆盖了 Prometheus 数据模型与 OTLP 指标规范的主要类型:

指标类型语义对应头文件
Counter只增不减的累计计数器(可配置允许重置)cmt_counter.h
Gauge可增可减的瞬时值cmt_gauge.h
Untyped无类型语义的原始数值cmt_untyped.h
Histogram显式边界桶直方图cmt_histogram.h
Exponential Histogram指数桶直方图(OTLP 原生形态)cmt_exp_histogram.h
Summary带分位数、sum、count 的摘要cmt_summary.h

所有指标数据点(datapoint)都会保存一个纳秒精度的采样timestamp

Counter:最常用的累计指标

Counter 的核心操作定义在 cmt_counter.h:

struct cmt_counter *cmt_counter_create(struct cmt *cmt, char *ns, char *subsystem, char *name, char *help, int label_count, char **label_keys); int cmt_counter_inc(struct cmt_counter *counter, uint64_t timestamp, int labels_count, char **label_vals); int cmt_counter_add(struct cmt_counter *counter, uint64_t timestamp, double val, int labels_count, char **label_vals); int cmt_counter_set(struct cmt_counter *counter, uint64_t timestamp, double val, int labels_count, char **label_vals); int cmt_counter_get_val(struct cmt_counter *counter, int labels_count, char **label_vals, double *out_val);
  • cmt_counter_inc:计数器加 1;
  • cmt_counter_add:计数器增加指定值;
  • cmt_counter_set:直接设置计数器当前值(例如在解码、重启恢复或"允许重置"场景中使用);
  • cmt_counter_allow_reset:允许计数器在检测到重置时回退。

Histogram:显式边界桶

直方图通过 cmt_histogram.h 提供,支持三种桶构建方式:

struct cmt_histogram_buckets *cmt_histogram_buckets_create(size_t count, ...); /* 可变参数直接指定边界 */ struct cmt_histogram_buckets *cmt_histogram_buckets_linear_create(double start, double width, size_t count); /* 线性边界 */ struct cmt_histogram_buckets *cmt_histogram_buckets_exponential_create(double start, double factor, size_t count); /* 指数边界 */ struct cmt_histogram_buckets *cmt_histogram_buckets_default_create(); /* 默认桶 */

创建后通过cmt_histogram_observe(...)观察一个新观测值,库会按桶边界自动累加计数。

Exponential Histogram:OTLP 原生指数直方图

指数直方图(见 cmt_exp_histogram.h)使用 scale、zero_count、zero_threshold、正负两侧的 offset 与桶计数描述分布,是 OTLP 规范的原生形态,相比显式桶可以显著减少高基数桶的数量。CMetrics 还提供了cmt_exp_histogram_to_explicit(...)用于将指数直方图转换为显式边界桶表示,便于输出到不支持指数直方图的格式。

Summary:分位数摘要

Summary(见 cmt_summary.h)在设计上"只感知最终 quantile 值,不做百分位计算",创建时需要显式声明quantiles_countquantiles数组(如 0、0.25、0.5、0.75、1),并通过cmt_summary_set_default(...)一次性写入分位数、sum 与 count。

数据点时间戳与 OTLP start_timestamp

CMetrics 的每个数据点都带有一个纳秒时间戳。除此之外,它还支持可选的原生start_timestamp(每个数据点一个),主要服务于 OTLP 累积(cumulative)指标流——在 OTLP 语义中,累积计数器的start_time_unix_nano表示该序列开始累积的时刻,对下游(如 Prometheus rate 计算)的正确性至关重要。

相关 API 全部声明在 cmt_metric.h:

API作用
cmt_metric_set_start_timestamp(metric, start_ns)为数据点设置 start 时间戳
cmt_metric_unset_start_timestamp(metric)移除已设置的 start 时间戳
cmt_metric_has_start_timestamp(metric)判断数据点是否带 start 时间戳
cmt_metric_get_start_timestamp(metric)读取数据点的 start 时间戳

兼容性方面,README 明确保证:只使用timestamp的既有代码行为完全不变start_timestamp是纯增量特性。

在编码/解码流程中,start_timestamp的传递规则为:

  • OTLP 解码器:从start_time_unix_nano字段填充原生start_timestamp
  • OTLP 编码器:优先使用原生start_timestamp,缺失时才回退到 OTLP 元数据;
  • CMetrics 内部 msgpack:通过可选的start_ts字段在内部编码/解码流程中保留该值。

非 OTLP 格式(如 Prometheus text、Influx、Splunk HEC、CloudWatch EMF)没有 OTLP 风格的 start 时间戳字段,因此只序列化采样时间戳。这与各格式协议本身的能力边界一致,源码实现可参见 src/cmt_decode_opentelemetry.c 与 src/cmt_encode_opentelemetry.c。

编码器与解码器全景

CMetrics 的协议边界集中在cmt_encode_*.ccmt_decode_*.c系列文件(lib/cmetrics/src),头文件一一对应。

支持的编码器(Encoder)

格式头文件说明
OpenTelemetry Metrics(OTLP protobuf)cmt_encode_opentelemetry.h生成 ExportMetricsServiceRequest protobuf
Prometheus text expositioncmt_encode_prometheus.h生成# HELP/# TYPE+ 样本行的文本格式
Prometheus Remote Writecmt_encode_prometheus_remote_write.h生成 remote write protobuf
Influx line protocolcmt_encode_influx.h生成 influx 行协议
Splunk HECcmt_encode_splunk_hec.h生成 Splunk HEC JSON
CloudWatch EMFcmt_encode_cloudwatch_emf.h生成 CloudWatch Embedded Metric Format
CMetrics msgpack(内部格式)cmt_encode_msgpack.h用于内部流转/格式转换
Text(人类可读)cmt_encode_text.h便于调试输出

支持的解码器(Decoder)

格式头文件
OpenTelemetry Metrics(OTLP protobuf)cmt_decode_opentelemetry.h
Prometheus text expositioncmt_decode_prometheus.h
Prometheus Remote Writecmt_decode_prometheus_remote_write.h
StatsDcmt_decode_statsd.h
CMetrics msgpack(内部格式)cmt_decode_msgpack.h

值得注意的是,Prometheus text 解码器是基于 Flex/Bison 生成的词法/语法解析器(cmt_decode_prometheus.l、cmt_decode_prometheus.y),这解释了为何 lib/cmetrics/tests 中专门有prometheus_lexer.cprometheus_parser.c两组测试。OTLP 与 Remote Write 则基于仓库内生成的 protobuf-C 定义(lib/cmetrics/src/external)。

在 Fluent Bit 生态中,这个编码器/解码器矩阵意味着:无论上游是 Prometheus 抓取、StatsD 还是 OTLP 上报,进入 CMetrics 后都可以被统一转换为任意下游格式输出——这正是 Fluent Bit 多格式互转能力的底层支撑。

完整 C 使用示例:创建 Counter 并编码 OTLP

下面是从 README 继承的完整示例,演示了 CMetrics 的典型使用闭环:创建上下文 → 创建 Counter → 写入采样点 → 附加start_timestamp→ 编码为 OTLP payload。该示例在结构上直接对应 Fluent Bit 内部"采集指标 → 聚合 → OTLP 输出"的调用链。

#include <stdint.h> #include <stdio.h> #include <cmetrics/cmetrics.h> #include <cmetrics/cmt_counter.h> #include <cmetrics/cmt_map.h> #include <cmetrics/cmt_metric.h> #include <cmetrics/cmt_encode_opentelemetry.h> int main(void) { struct cmt *ctx; struct cmt_counter *requests_total; struct cmt_metric *sample; cfl_sds_t otlp_payload; uint64_t start_ns; uint64_t sample_ns; ctx = cmt_create(); if (ctx == NULL) { return 1; } requests_total = cmt_counter_create(ctx, "demo", /* namespace */ "service", /* subsystem */ "requests_total", "Total requests", 0, /* label keys */ NULL); if (requests_total == NULL) { cmt_destroy(ctx); return 1; } start_ns = 1700000000000000000ULL; sample_ns = start_ns + 5000000000ULL; /* Write sample value (cumulative stream example). */ if (cmt_counter_set(requests_total, sample_ns, 42.0, 0, NULL) != 0) { cmt_destroy(ctx); return 1; } /* Access the same datapoint and attach native start timestamp. */ sample = cmt_map_metric_get(&requests_total->opts, requests_total->map, 0, NULL, CMT_FALSE); if (sample == NULL) { cmt_destroy(ctx); return 1; } cmt_metric_set_start_timestamp(sample, start_ns); /* Encode OTLP metrics payload. */ otlp_payload = cmt_encode_opentelemetry_create(ctx); if (otlp_payload == NULL) { cmt_destroy(ctx); return 1; } printf("Encoded OTLP payload size: %zu bytes\n", cfl_sds_len(otlp_payload)); cmt_encode_opentelemetry_destroy(otlp_payload); cmt_destroy(ctx); return 0; }

示例要点逐行拆解

  1. 创建上下文cmt_create()返回顶层struct cmt *,所有指标族都挂载到该上下文上;结束使用后必须cmt_destroy(ctx)释放。
  2. 创建指标族cmt_counter_create的第 1~4 个参数依次是 namespace、subsystem、name、help。根据 cmt_opts.h 的fqname逻辑,这个示例最终会生成全限定指标名demo_service_requests_total。第 5、6 个参数声明标签数量与标签键数组,示例用0, NULL表示无标签。
  3. 写入采样cmt_counter_set(requests_total, sample_ns, 42.0, 0, NULL)sample_ns为纳秒时间戳写入值 42.0。sample_ns = start_ns + 5_000_000_000意味着采样发生在开始后 5 秒。
  4. 定位数据点cmt_map_metric_get(&requests_total->opts, requests_total->map, 0, NULL, CMT_FALSE)返回该无标签系列对应的struct cmt_metric *,最后一个参数CMT_FALSE表示只读查找(不创建)。
  5. 附加 start 时间戳cmt_metric_set_start_timestamp(sample, start_ns)为该数据点设置 OTLP 累积流的开始时间。
  6. 编码 OTLPcmt_encode_opentelemetry_create(ctx)将整个上下文编码为一个 OTLP ExportMetricsServiceRequest protobuf,返回cfl_sds_t(CFL 动态字符串);结果通过cfl_sds_len()获取长度,最后用cmt_encode_opentelemetry_destroy()释放。编码失败码定义见 cmt_encode_opentelemetry.h(CMT_ENCODE_OPENTELEMETRY_ALLOCATION_ERRORINVALID_ARGUMENT_ERRORUNEXPECTED_METRIC_TYPEDATA_POINT_INIT_ERROR)。

编码错误处理

OTLP 编码器可能返回以下错误码(定义于 cmt_encode_opentelemetry.h):

#define CMT_ENCODE_OPENTELEMETRY_SUCCESS 0 #define CMT_ENCODE_OPENTELEMETRY_ALLOCATION_ERROR 1 #define CMT_ENCODE_OPENTELEMETRY_INVALID_ARGUMENT_ERROR 2 #define CMT_ENCODE_OPENTELEMETRY_UNEXPECTED_METRIC_TYPE 3 #define CMT_ENCODE_OPENTELEMETRY_DATA_POINT_INIT_ERROR 4

生产代码应当检查返回值,并在失败时逐层释放已分配的资源(参考示例中的cmt_destroy清理路径)。

大数据量下的 OTLP 分批编码

cmt_encode_opentelemetry.h 还额外提供了面向大数据量的分批 API:

  • cmt_encode_opentelemetry_split_payload(...):在不改变 metric/resource/scope 元数据的前提下,把已编码的 ExportMetricsServiceRequest 按max_data_points拆分为多个 batch;max_data_points为 0 时返回原始请求作为单个 batch;
  • cmt_encode_opentelemetry_create_batches(...):直接从上下文按数据点上限分批生成;
  • cmt_encode_opentelemetry_destroy_batches(...):释放批次集合,返回的 payload 由批次集合统一拥有。

当一次上报的数据点数量很大、需要控制单个 OTLP 请求大小时,这套 API 可以直接复用(Fluent Bit 的 OTLP 输出路径中即存在此类分批需求)。

指标过期清理与并发查找

除了 README 提到的内容,从源码还可以看到两个生产环境必须了解的机制:

  • 过期清理cmt_expire(cmt, expiration)(声明于 cmetrics.h)配合cmt_map_metrics_expire(...)可以按时间戳淘汰过期数据点,防止动态标签系列无限增长——这对于高基数标签场景(如按 Pod/进程维度的指标)非常关键。对应的测试见 lib/cmetrics/tests/expire.c。
  • 并发安全查找cmt_map_metric_get(...)内部对并发查找与创建做了序列化(头文件注释明确说明 "Concurrent lookups and metric creation are serialized internally"),返回的 metric 在 map 未过期或销毁前可用。struct cmt_metric中还带有内部查找索引字段(hash_indexedmap_hash_head),配合 cmt_map.h 中的metric_buckets哈希桶结构提升带标签系列检索效率。

长标签值处理与安全边界(设计参考)

README 引用的设计文档 lib/cmetrics/docs/label-value-handling.md 记录了一次真实问题的完整复盘,对理解 CMetrics 的数据完整性策略很有价值:

  • 背景:旧版本 CMetrics 在解码内部 MessagePack 时,拒绝任何超过 1024 字节的字符串,导致超过 1024 字节的 Prometheus 标签值(如process_command_line)在"Prometheus 解析 → CMetrics msgpack 往返"过程中被整体丢弃(对应 Fluent Bit issue #9297)。
  • 结论:不能通过静默截断来"修复"——因为 Prometheus 用"指标名 + 完整标签集"标识时间序列,两个共享 1024 字节前缀、仅尾部不同的标签值若都被截断为...,会把不同序列合并成同一个,造成错误结果;固定字节偏移还可能切断多字节 UTF-8 字符。
  • CMetrics 采取的原则:内部 MessagePack 解码器默认无损解码有效字符串;资源限制(如分配上限)与字符串内容/指标语义分离处理;解码前先校验声明的字节数是否真实存在,再一次性复制完整值,防止恶意长度字段触发超量分配,同时保证合法长字符串往返不被修改。
  • 建议的摄入策略(由调用方实现,而非解码器)preserve(完整保留,符合 Prometheus 默认行为)、reject(带诊断信息拒绝批次)、drop(仅丢弃违规序列并递增错误计数)、truncate_hash(在合法 UTF-8 边界截断并追加完整值哈希以尽量保持序列身份)。
  • 回归测试覆盖:1023/1024/1025/2048/65536 字节值、相同前缀不同后缀的两个值、跨边界 UTF-8 字符、长字符串后的多字段同步性、声明长度大于实际输入、接近整数/分配上限的长度、分配失败清理路径以及端到端 Prometheus 抓取链路等,并建议配合 AddressSanitizer、UndefinedBehaviorSanitizer 与 Valgrind 做内存安全验证。

这个案例提醒所有集成方:呈现层需求不应通过修改内部数据模型来实现——编码器可以为了展示而缩写,但存储的值必须保持不变。

设计渊源:Go Prometheus Client

CMetrics 在 API 设计上深度借鉴了 Go Prometheus Client(prometheus/client_golang)的公开设计:namespace / subsystem / name的三段式命名、HELP文本、label 键值对、Counter/Gauge/Histogram/Summary 的家族划分等,都能在 Go 版客户端中找到对应物。这意味着熟悉 Prometheus 生态的开发者可以快速上手,而 Fluent Bit 的指标插件也因此天然贴合 Prometheus 的指标语义。Fluent Bit 内部"采集 → 指标上下文 → 多格式输出"的完整链路,正是围绕这一数据模型组织起来的。

小结

CMetrics 为 Fluent Bit 提供了统一的指标数据模型与协议转换能力:

  • 六种指标类型(Counter/Gauge/Untyped/Histogram/Exponential Histogram/Summary)覆盖主流监控数据形态;
  • 统一纳秒时间戳,并支持 OTLP 累积流所需的原生start_timestamp(与既有timestamp完全向后兼容);
  • 八种编码器、五种解码器,打通 Prometheus、OTLP、Influx、Splunk、CloudWatch EMF 等格式之间的互转;
  • 公开的 C API可直接在自定义插件中创建指标、写入采样、附加时间戳并编码输出;
  • 同时提供指标过期清理、并发安全的 map 查找与安全的数据解码边界,适合作为长期运行的数据采集管线底座。

如需深入阅读实现与测试,推荐依次查看 lib/cmetrics/docs/architecture.md、lib/cmetrics/src 下的编码/解码源码,以及 lib/cmetrics/tests 中的counter.chistogram.copentelemetry.cencoding.cdecoding.cexpire.c等测试文件。

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

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

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

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

立即咨询