TT Lab
开始
学习 学习路径 课程

Grafana — 仪表盘是一个问题

这一个仪表盘每分钟要发出多少次查询

在 TT Lab 中继续学习

目标

实际测量一个仪表板的查询次数、序列数量和点的数量来给它估值,把预算写成文件和检查器,然后制作一个落在该预算之内的版本并提交。

为什么重要

仪表板看起来是免费的。多加一个面板只需要点三下,而开销却发生在别的团队的 Prometheus 上。所以仪表板总是只朝着变重的方向生长。估值的算法本身并不难——把每个面板带的查询加起来,再加上向服务器询问列表的变量,就是打开一次时的查询数;再乘以自动刷新周期,就是每分钟的查询数。十个查询、10 秒刷新,一分钟六十次,一天八万次,而且在没有人看那块屏幕的夜里也同样发出。数过一次这个数字的团队,和没有数过的团队,对待仪表板的态度是不同的。

步骤

  1. 用 lab-start-grafana 启动 Grafana,并把 /opt/lab/gfd/gfd-perf/heavy.json 不做修改、原样上传到 Grafana(uid 是文件中写的 gfd-perf,共 8 个面板)。用 curl 向 /api/dashboards/db 发送 POST 即可。这个仪表板在本实验结束之前要原样保留作为原始版本——缩减后的版本在最后一步用另一个 uid 单独保存。
  2. 把已上传到 Grafana 的仪表板下载下来数一数。在 /root/gfd-perf/02-count.txt 中写 panels=、targets=、var_queries=、open= 四行。panels 是去掉行(row)后的面板数,targets 是所有面板的查询数,var_queries 是 templating.list 中 type 为 query 的变量个数,open 是 targets 与 var_queries 相加的值——也就是打开一次仪表板时的查询数。
  3. 把每个面板带的查询实际发出,测出返回的序列数量。选一个已经过去的时刻(比现在早 5 分钟以上,在 6 小时之内),把所有查询按那个时刻的瞬时值发出。在 /root/gfd-perf/03-series.txt 中写 at=、total=、top_id=、top_series= 四行——at 是所选时刻的 epoch 秒,total 是仪表板所有查询返回的序列数之和,top_id 是返回序列最多的面板的 id,top_series 是该面板的序列数。想一想最重的那个面板的图表上会画出多少条线。
  4. 选一个已经过去的区间(2 小时以上,结束时间早于现在),对 3 号面板的 p95 查询在同一个区间只改变 step,发出两次——一次 step=15,一次 step=300。在 /root/gfd-perf/04-points.txt 中写 start=、end=、points_fine=、points_coarse= 四行。points_fine 是 step 为 15 时、points_coarse 是 step 为 300 时,一个序列返回的点的数量。
  5. 读取仪表板 JSON 中的自动刷新周期,在 /root/gfd-perf/05-rate.txt 中写 refresh=、refresh_sec=、targets=、per_min= 四行。refresh 是 JSON 中写的字符串原样,refresh_sec 是把它换算成秒的整数,targets 是第 2 步数出的查询数,per_min 是 targets 乘以 60 / refresh_sec。在本实验的算法中,认为自动刷新只重新发出面板查询,而不重新发出变量查询。
  6. 3 号面板的 p95 查询要读取四十八个桶序列,每次都计算分位数。在 /etc/prometheus/rules/gfd-perf.yml 中放入分组名 gfd-perf、interval 为 15 秒、一条记录规则——record 是 handler:http_request_duration_seconds:p95,expr 是 3 号面板的 p95 查询原样。用 promtool check rules /etc/prometheus/rules/gfd-perf.yml 确认后,用 curl -X POST http://127.0.0.1:9090/-/reload 应用,等待 20 秒以上,确认 promq 'handler:http_request_duration_seconds:p95' 能返回值,再进入下一步。
  7. 在 /root/gfd-perf/budget.txt 中用五行写下预算——max_panels=6、max_targets=6、max_var_queries=0、min_refresh_sec=60、max_queries_per_min=6。然后创建 /root/gfd-perf/budget.py。它接收预算文件路径和仪表板 JSON 路径作为参数,把违规每行输出一条(以 B1 到 B5 开头),只要有一处违规,就以退出码 1 结束。B1 面板数超出 · B2 查询数超出 · B3 变量查询数超出 · B4 开启了刷新但周期太短 · B5 每分钟查询数超出。请在原始文件 /opt/lab/gfd/gfd-perf/heavy.json 上运行,确认五种违规全部都能被抓出来。
  8. 保持原始的 gfd-perf 不动,把落在预算之内的新仪表板用 uid gfd-perf-slim 保存。缩减方法不限,但下面三项必须做到——去掉没有任何面板使用的变量查询,把自动刷新周期改为 60 秒以上,以及把 p95 面板的查询改为第 6 步创建的 handler:http_request_duration_seconds:p95。保存之后,把该仪表板原样下载,保存到 /root/gfd-perf/fixed.json(只取 .dashboard 正文),运行第 7 步的检查器,确认违规为 0、退出码为 0。然后在 /root/gfd-perf/08-review.md 中用 B1= 到 B5= 五行,分别用不少于 30 个字符写明每一项是如何落在预算之内的。

参考

上传要估值的仪表板

用 lab-start-grafana 启动 Grafana,并把 /opt/lab/gfd/gfd-perf/heavy.json 不做修改、原样上传到 Grafana(uid 是文件中写的 gfd-perf,共 8 个面板)。用 curl 向 /api/dashboards/db 发送 POST 即可。这个仪表板在本实验结束之前要原样保留作为原始版本——缩减后的版本在最后一步用另一个 uid 单独保存。

保存 API 把仪表板正文放在 dashboard 键中,并同时发送 overwrite。从文件构造这种形式,用 jq -n --slurpfile 比较方便。Grafana 启动需要几十秒,请先看 /api/health 是否有响应。

打开一次会发出多少次查询

把已上传到 Grafana 的仪表板下载下来数一数。在 /root/gfd-perf/02-count.txt 中写 panels=、targets=、var_queries=、open= 四行。panels 是去掉行(row)后的面板数,targets 是所有面板的查询数,var_queries 是 templating.list 中 type 为 query 的变量个数,open 是 targets 与 var_queries 相加的值——也就是打开一次仪表板时的查询数。

折叠在行里的面板也是面板。要用 jq 展开,可以用 .panels[] | (., (.panels[]?)),再把 type 为 row 的过滤掉。查询在每个面板的 targets 数组中,一个面板可以有两个以上。变量只数向服务器询问列表的——手写的常量列表不会产生查询。

哪个面板最重

把每个面板带的查询实际发出,测出返回的序列数量。选一个已经过去的时刻(比现在早 5 分钟以上,在 6 小时之内),把所有查询按那个时刻的瞬时值发出。在 /root/gfd-perf/03-series.txt 中写 at=、total=、top_id=、top_series= 四行——at 是所选时刻的 epoch 秒,total 是仪表板所有查询返回的序列数之和,top_id 是返回序列最多的面板的 id,top_series 是该面板的序列数。想一想最重的那个面板的图表上会画出多少条线。

序列数量就是 /api/v1/query 响应中 data.result 的长度。要固定时刻,请同时发送 time=——这样以后再数,才能得到相同的答案。如果把查询从仪表板 JSON 中提取出来用循环运行,就不必手工抄写。一个面板有两个查询时,把两者相加就是该面板的序列数量。

改变分辨率,返回的点会变成几倍

选一个已经过去的区间(2 小时以上,结束时间早于现在),对 3 号面板的 p95 查询在同一个区间只改变 step,发出两次——一次 step=15,一次 step=300。在 /root/gfd-perf/04-points.txt 中写 start=、end=、points_fine=、points_coarse= 四行。points_fine 是 step 为 15 时、points_coarse 是 step 为 300 时,一个序列返回的点的数量。

区间查询是 /api/v1/query_range,需要同时发送 start、end、step。返回的 JSON 中 data.result[0].values 的长度就是一个序列的点的数量。算出两个数字的比值,就能看出分辨率会原样乘到开销上。必须把区间用已经过去的绝对时间固定下来,以后重新量时才会得到相同的值。

乘以自动刷新,得出每分钟的查询数

读取仪表板 JSON 中的自动刷新周期,在 /root/gfd-perf/05-rate.txt 中写 refresh=、refresh_sec=、targets=、per_min= 四行。refresh 是 JSON 中写的字符串原样,refresh_sec 是把它换算成秒的整数,targets 是第 2 步数出的查询数,per_min 是 targets 乘以 60 / refresh_sec。在本实验的算法中,认为自动刷新只重新发出面板查询,而不重新发出变量查询。

自动刷新周期在仪表板 JSON 最上层的 refresh 键中,是 10s、1m 这样的字符串。最后一个字符是单位,前面是数字。如果对这个数字为什么重要没有感觉,请乘以 24 算出一整天的量——在没有人看的夜里,同样的数量也会被发出。

把沉重的计算迁移为记录规则

3 号面板的 p95 查询要读取四十八个桶序列,每次都计算分位数。在 /etc/prometheus/rules/gfd-perf.yml 中放入分组名 gfd-perf、interval 为 15 秒、一条记录规则——record 是 handler:http_request_duration_seconds:p95,expr 是 3 号面板的 p95 查询原样。用 promtool check rules /etc/prometheus/rules/gfd-perf.yml 确认后,用 curl -X POST http://127.0.0.1:9090/-/reload 应用,等待 20 秒以上,确认 promq 'handler:http_request_duration_seconds:p95' 能返回值,再进入下一步。

记录规则的名称有既定的惯例——写成 수준:지표:연산(占位符依次为级别、指标、运算)的形式,用两个冒号。分组的 interval 就是计算周期,刚应用之后还没有计算过一次,所以要等这个周期过去一次才会有值。可以用 curl -s http://127.0.0.1:9090/api/v1/rules | jq 查看规则是否已载入。

写下预算,并制作检查该预算的工具

在 /root/gfd-perf/budget.txt 中用五行写下预算——max_panels=6、max_targets=6、max_var_queries=0、min_refresh_sec=60、max_queries_per_min=6。然后创建 /root/gfd-perf/budget.py。它接收预算文件路径和仪表板 JSON 路径作为参数,把违规每行输出一条(以 B1 到 B5 开头),只要有一处违规,就以退出码 1 结束。B1 面板数超出 · B2 查询数超出 · B3 变量查询数超出 · B4 开启了刷新但周期太短 · B5 每分钟查询数超出。请在原始文件 /opt/lab/gfd/gfd-perf/heavy.json 上运行,确认五种违规全部都能被抓出来。

只把预算写成文字,就没有人会遵守。请写成能运行的代码。别忘了折叠在行(row)里的面板也是面板,文件也可能被 {"dashboard": ...} 包着。如果没有 refresh 或者已经关闭,就是没有自动刷新,所以 B4 和 B5 不应触发。

缩减到预算之内,作为新版本提交

保持原始的 gfd-perf 不动,把落在预算之内的新仪表板用 uid gfd-perf-slim 保存。缩减方法不限,但下面三项必须做到——去掉没有任何面板使用的变量查询,把自动刷新周期改为 60 秒以上,以及把 p95 面板的查询改为第 6 步创建的 handler:http_request_duration_seconds:p95。保存之后,把该仪表板原样下载,保存到 /root/gfd-perf/fixed.json(只取 .dashboard 正文),运行第 7 步的检查器,确认违规为 0、退出码为 0。然后在 /root/gfd-perf/08-review.md 中用 B1= 到 B5= 五行,分别用不少于 30 个字符写明每一项是如何落在预算之内的。

用新的 uid 保存时,必须从正文中去掉 id——如果留着,Grafana 会改动那个 id 对应的已有仪表板。减少查询数的方法不只是删除面板。请看看有没有可以把两个查询合并成一个表达式的面板——把两个计数分别取回、再靠眼睛相除的面板就是这样。对于返回四十八个序列的面板,请先问一问,从那个界面上是否什么也读不出来。