spike 是我实现的一个 graphite_api 的 proxy .

通过 graphite_api 查询 whishper 数据的时候,query 的形式可能会很复杂,涉及到函数嵌套等问题,所以 spike 其实只做了一个工作:从线上的 graphite_api 中读取数据,再调用 graphite_api 进行下一步的处理。实质上,从 graphite_api 读取的数据是时间戳-数据值点对,这些数据点对是 graphite_api 最终处理的结果,spike 读取这些数据之后,需要对它们作逆映射,转换成 graphite_api 定义的 TimeSeries 实例。 总的来说,可以把 spike 看作一个从其它线上 whisper 读取数据的 graphite_api (很惭愧,spike 只是做了一点微不足道的工作)。

昨天 @wzyboy 跟我提了一个 bug : 同样的查询语句直接查询 graphite_api 可以正常地获取数据,但是访问 spike 却会返回500错误。于是我开始排查原因。

这个问题有点诡异,因为我无法在本地复现。错误信息最终定位到 graphite_apidoDrawImage 函数,我最初想会不会是 graphite_api 的版本问题。我本地的 graphite_api 是1.1.2版本,而线上的是1.1.3。于是在依赖里面指定了版本,上了一个新的容器,发现问题并没有解决。

然后我又开了一个 cell 的测试环境,在上面启动 spike, 这个 spike 同样无法复现前面的错误。最后我只能钻进容器里面,找到容器中的 graphite_api 在可疑的地方插入 print 排查有错误的地方。

这个错误是发生在 graphite_apiGraph 类的 drawText() 函数里面,问题在于 drawText() 函数被很多别的函数调用了,我不知道它是在哪一个函数调用的时候出的错。最后我是用 try catch 强行让 spike “正常”地返回一个结果,再跟正确的结果(之所以可以这样做,是因为这个查询返回的是一个图,对比两个图其实并不难)对比,最后将错误定位到 drawLabel() 函数里面。这个函数的作用是画出图的横坐标。

定位到错误之后,我发现 drawLabel() 是用时间来做横坐标的,它会计算适当的时间点(一个 datetime 对象),然后调用 strftime("%a %l%p") 转换成字符串。但是线上容器进行到这一步的时候,传了一个空字符串到 drawText() 导致了函数出错。令我感到奇怪的是,为什么同样的函数调用,再线上会出错呢?我在容器里面开 python 解释器,发现线上的 python 就是将 datetime 对象转换成了空字符串了!

感谢 @wzyboy 他很快告诉我这是 musl/libc问题,总的来说,这个 bug 的问题是这样的:线上 spike 是用 alpine 作为容器镜像的,而 alpine 没有用 glibc 而是用了 musl/libcmusl/libc 不支持 %l 这种日期格式(WHY!!!!),最终导致了这个诡异的错误。

找到问题所在,修复就是小菜一碟了,将 spike 的镜像换回 centos 就解决了。