Categraf SNMP 插件优化:解析带单位的监控指标

孔飞@快猫星云 2025-12-26 09:46:28

在监控领域,通过 SNMP 协议采集硬件设备的指标是非常常见的需求。然而,不同厂商的设备(如服务器、网络设备)返回的 SNMP 数据格式千差万别。最近,我们在开源项目 Categraf 中解决了一个有趣的数据解析问题,通过引入启发式提取算法,让 SNMP 插件能够自动处理带有单位后缀的复杂字符串指标。

背景:当数值带上了 “尾巴”

Issue #1314 提出反馈,在使用 Categraf 采集某些服务器(例如浪潮机器)的 SNMP 指标时,设备返回的数据并不是纯粹的数字,而是包含了单位描述的字符串。

典型的返回结果如下:

SNMPv2-SMI::enterprises.37945.2.1.2.1.1.1.4.8.8... = STRING: "60 degree Celsius"
SNMPv2-SMI::enterprises.37945.2.3.1.1.1.10.0.4... = STRING: "0.48 A"

对于流行的时序数据库来说,我们需要的是 60 或 0.48 这样的浮点数,以便进行存储、绘图和告警。“60 degree Celsius” 这样的字符串直接转换会失败,导致指标采集丢失。

挑战

Categraf 的 SNMP 插件在处理数据时,通常会尝试将获取到的值转换为浮点数(float64)。 在 Go 语言中,我们通常使用 strconv.ParseFloat 来完成这项工作。但是,这个标准库函数非常严格,一旦字符串中包含非数字字符(比如 " degree Celsius"),它就会直接报错并返回错误。

在面对数以千计的设备型号时,我们无法为每一种特殊的返回格式编写特定的正则规则,我们需要一种更通用的、能 “猜” 出数值的方法。

解决方案:启发式数据提取

之前社区和交流群里也有小伙伴多次提到数值转换问题,这次我们想找一个更通用的解决方案。 在PR#1317,引入了一个名为 heuristicsDataExtract 的通用处理函数。

核心逻辑

这个方案的核心思想不再是 “验证字符串是否为数字”,而是 “从字符串中提取出最像数字的部分”。

新的解析逻辑采用了字符遍历的方式,实现了以下功能:

智能扫描:逐个字符扫描字符串,寻找可能是数字的起始点。

兼容性支持:支持识别正负号(+, -)、小数点(.)以及科学计数法(e, E)。

提取有效载荷:一旦识别到合法的数字序列,就将其提取出来。

例如,对于输入 “Current: 0.48 A (Normal)",算法会自动定位到 0.48 并将其提取转换。

代码实现片段

1. 转换入口:fieldConvert 的平滑升级

// fieldConvert 代码片段
if _, err := fmt.Sscanf(conv, "float(%d)", &d); err == nil || conv == "float" {
    floatConv := func(vt string) float64 {
        var ret float64
        // 优先使用启发式提取,从脏数据中“淘”出数字
        floatVal, err := heuristicDataExtract(vt)
        if err != nil {
            // 如果提取失败,记录日志并尝试最后的兜底(直接转换)
            log.Printf("E! failed to extract float from string: %s, error: %v", vt, err)
            vf, _ := strconv.ParseFloat(vt, 64)
            ret = vf / math.Pow10(d)
        } else {
            ret = floatVal / math.Pow10(d)
        }
        return ret
    }
    // ... (后续类型断言逻辑)
}

这种设计保证了向后兼容性:如果字符串本身就是纯数字,启发式提取依然有效;如果是复杂的带单位字符串,新逻辑则能大显身手。

2. 核心算法:heuristicDataExtract

这是本次优化的核心。为了高效地从字符串中剥离出浮点数,我们没有使用性能开销较大的正则表达式,而是实现了一个基于字符扫描的状态机。

这个函数模拟了人类阅读数字的逻辑,主要流程如下:

A. 寻找“数字锚点” (Start Detection)

算法首先遍历字符串,寻找数字的起始位置。它不仅寻找数字 0-9,还能智能识别正负号和小数点。

特别值得一提的是智能负号识别逻辑。在 SNMP 返回的数据中,连字符(-)非常常见(例如 limit-value)。算法通过判断前一个字符(prev),来区分“减号/负号”和“连接符”:

// 代码片段:判断 '-' 是否代表负数
if c == '-' && i > 0 {
    prev := s[i-1]
    // 只有当前面是空格、制表符、冒号等分隔符时,'-' 才被视为负号
    if prev != ' ' && prev != '\t' && prev != ':' && prev != '=' && prev != '(' && prev != ',' {
        i++ // 否则视为连接符,跳过
        continue
    }
}

B. 贪婪匹配与状态锁定 (Scanning & Locking)

一旦确定了数字的 start 位置,算法进入“锁定”状态,开始贪婪匹配后续字符,直到遇到非数字字符为止。在此过程中,它维护了几个关键状态标志:

  • hasDot: 确保一个数字中只有一个小数点。

  • hasExp: 支持科学计数法(如 -12.7e3),并允许 e 后面紧跟 + 或 - 号。

C. 切片与转换 (Extraction)

当扫描遇到非法字符(如 degree 中的 d,或 Amp 中的 A)时,循环终止。算法直接截取 s[start:end] 子串。

// 最终提取
numStr := s[start:end]
val, err := strconv.ParseFloat(numStr, 64)

实际案例分析

基于这段代码,我们可以看到它是如何处理各种边缘情况的: 输入字符串,提取逻辑分析,结果

输入字符串 提取逻辑分析 结果
“60 degree Celsius” 扫描到 ‘6’ 开始,扫描到 ’ ’ 结束。截取 “60” 60.0
“Current: -0.48A” 扫描到 ‘-’ (前面是空格) 开始,扫描到 ‘A’ 结束。截取”-0.48" -0.48
“Voltage: 2.2e+2V” 识别 ’e’ 和后面的 ‘+’ 为合法科学计数法。截取 “2.2e+2” 220.0
“key-value: 10” 遇到第一个 - 时,因为前面是字母 y,判定为连接符跳过;继续扫描找到 10 10.0

这种实现方式既避免了复杂的正则回溯攻击(ReDoS)风险,又保证了执行效率,非常适合作为监控采集端的底层处理逻辑。用户无需编写复杂的预处理脚本,即可直接监控那些带单位字符串的设备指标,提升了开箱即用的体验。

总结 在Categraf的开发中,兼容性往往是最大的挑战之一。通过从严格解析转向启发式提取,我们在保持性能的同时,用较小的改动解决了一大类设备的数据兼容问题。

如果你也遇到了类似设备指标采集的问题,欢迎升级 Categraf(v0.4.23+)体验这个新特性!

相关链接:

Issue: #1314 snmp插件不能解析包含电流温度等单位的字符串

PR: #1317 feat(snmp): heuristics data extract

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat
Flashduty
Flashduty