ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

Fluent Bit 内置的 CMetrics:用 C 语言构建轻量级指标处理管线的完整指南 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-bitCMetrics 是 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.h、cmt_gauge.h、cmt_histogram.h等具体实现位于 lib/cmetrics/srccmt_counter.c、cmt_gauge.c、cmt_encode_*.c、cmt_decode_*.c等测试用例集中在 lib/cmetrics/testscounter.c、gauge.c、histogram.c、encoding.c、decoding.c、opentelemetry.c等。这种设计让 CMetrics 可以被 Fluent Bit 主程序、各 input/output 插件以及外部 C 项目直接静态链接使用。核心数据模型从上下文到数据点理解 CMetrics 的用法先要理清它的三层核心结构详见 lib/cmetrics/docs/architecture.mdstruct cmt上下文顶层指标上下文持有日志配置、元数据、静态标签以及 counters/gauges/histograms/exp_histograms/summaries/untypeds 六类指标族链表定义见 lib/cmetrics/include/cmetrics/cmetrics.h。struct cmt_map映射每个指标族metric family拥有一个映射负责管理带标签的数据点集合无标签时通过静态 metric 直接访问定义见 lib/cmetrics/include/cmetrics/cmt_map.h。struct cmt_metric数据点单个带时间戳与标签的采样点内部保存数值、直方图桶、指数直方图桶、摘要分位数、时间戳与start_timestamp等字段定义见 lib/cmetrics/include/cmetrics/cmt_metric.h。每个指标族还对应一组选项struct cmt_opts见 lib/cmetrics/include/cmetrics/cmt_opts.h包含namespace、subsystem、name、description并会自动拼接出全限定名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.hGauge可增可减的瞬时值cmt_gauge.hUntyped无类型语义的原始数值cmt_untyped.hHistogram显式边界桶直方图cmt_histogram.hExponential Histogram指数桶直方图OTLP 原生形态cmt_exp_histogram.hSummary带分位数、sum、count 的摘要cmt_summary.h所有指标数据点datapoint都会保存一个纳秒精度的采样timestamp。Counter最常用的累计指标Counter 的核心操作定义在 cmt_counter.hstruct 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计数器加 1cmt_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 HistogramOTLP 原生指数直方图指数直方图见 cmt_exp_histogram.h使用 scale、zero_count、zero_threshold、正负两侧的 offset 与桶计数描述分布是 OTLP 规范的原生形态相比显式桶可以显著减少高基数桶的数量。CMetrics 还提供了cmt_exp_histogram_to_explicit(...)用于将指数直方图转换为显式边界桶表示便于输出到不支持指数直方图的格式。Summary分位数摘要Summary见 cmt_summary.h在设计上只感知最终 quantile 值不做百分位计算创建时需要显式声明quantiles_count与quantiles数组如 0、0.25、0.5、0.75、1并通过cmt_summary_set_default(...)一次性写入分位数、sum 与 count。数据点时间戳与 OTLP start_timestampCMetrics 的每个数据点都带有一个纳秒时间戳。除此之外它还支持可选的原生start_timestamp每个数据点一个主要服务于 OTLP 累积cumulative指标流——在 OTLP 语义中累积计数器的start_time_unix_nano表示该序列开始累积的时刻对下游如 Prometheus rate 计算的正确性至关重要。相关 API 全部声明在 cmt_metric.hAPI作用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_timestampOTLP 编码器优先使用原生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_*.c与cmt_decode_*.c系列文件lib/cmetrics/src头文件一一对应。支持的编码器Encoder格式头文件说明OpenTelemetry MetricsOTLP protobufcmt_encode_opentelemetry.h生成 ExportMetricsServiceRequest protobufPrometheus text expositioncmt_encode_prometheus.h生成# HELP/# TYPE 样本行的文本格式Prometheus Remote Writecmt_encode_prometheus_remote_write.h生成 remote write protobufInflux line protocolcmt_encode_influx.h生成 influx 行协议Splunk HECcmt_encode_splunk_hec.h生成 Splunk HEC JSONCloudWatch EMFcmt_encode_cloudwatch_emf.h生成 CloudWatch Embedded Metric FormatCMetrics msgpack内部格式cmt_encode_msgpack.h用于内部流转/格式转换Text人类可读cmt_encode_text.h便于调试输出支持的解码器Decoder格式头文件OpenTelemetry MetricsOTLP protobufcmt_decode_opentelemetry.hPrometheus text expositioncmt_decode_prometheus.hPrometheus Remote Writecmt_decode_prometheus_remote_write.hStatsDcmt_decode_statsd.hCMetrics msgpack内部格式cmt_decode_msgpack.h值得注意的是Prometheus text 解码器是基于 Flex/Bison 生成的词法/语法解析器cmt_decode_prometheus.l、cmt_decode_prometheus.y这解释了为何 lib/cmetrics/tests 中专门有prometheus_lexer.c与prometheus_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; }示例要点逐行拆解创建上下文cmt_create()返回顶层struct cmt *所有指标族都挂载到该上下文上结束使用后必须cmt_destroy(ctx)释放。创建指标族cmt_counter_create的第 14 个参数依次是 namespace、subsystem、name、help。根据 cmt_opts.h 的fqname逻辑这个示例最终会生成全限定指标名demo_service_requests_total。第 5、6 个参数声明标签数量与标签键数组示例用0, NULL表示无标签。写入采样cmt_counter_set(requests_total, sample_ns, 42.0, 0, NULL)以sample_ns为纳秒时间戳写入值 42.0。sample_ns start_ns 5_000_000_000意味着采样发生在开始后 5 秒。定位数据点cmt_map_metric_get(requests_total-opts, requests_total-map, 0, NULL, CMT_FALSE)返回该无标签系列对应的struct cmt_metric *最后一个参数CMT_FALSE表示只读查找不创建。附加 start 时间戳cmt_metric_set_start_timestamp(sample, start_ns)为该数据点设置 OTLP 累积流的开始时间。编码 OTLPcmt_encode_opentelemetry_create(ctx)将整个上下文编码为一个 OTLP ExportMetricsServiceRequest protobuf返回cfl_sds_tCFL 动态字符串结果通过cfl_sds_len()获取长度最后用cmt_encode_opentelemetry_destroy()释放。编码失败码定义见 cmt_encode_opentelemetry.hCMT_ENCODE_OPENTELEMETRY_ALLOCATION_ERROR、INVALID_ARGUMENT_ERROR、UNEXPECTED_METRIC_TYPE、DATA_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 还额外提供了面向大数据量的分批 APIcmt_encode_opentelemetry_split_payload(...)在不改变 metric/resource/scope 元数据的前提下把已编码的 ExportMetricsServiceRequest 按max_data_points拆分为多个 batchmax_data_points为 0 时返回原始请求作为单个 batchcmt_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_indexed、map、_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 ClientCMetrics 在 API 设计上深度借鉴了 Go Prometheus Clientprometheus/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.c、histogram.c、opentelemetry.c、encoding.c、decoding.c、expire.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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进