同样的 0.42,有人读成 420 毫秒,有人读成 0.42 毫秒
目标
把面板上传到真正的 Grafana,亲手填入单位标识符,用查询确认查询给出的值的大小与单位是否匹配,亲手调整轴的范围和对数轴,然后修正从生产环境取回的仪表板中四个单位和轴的缺陷并提交。
为什么重要
仪表板上的数字,即使查询正确,在界面上也可能是错的。Grafana 的单位是显示规则而不是转换规则,所以给以秒为单位输出的值加上毫秒单位,值保持不变,只有名称变小了一千倍。比率中,0..1 和 0..100 也是不同的单位,字节中,按 1024 缩写的和按 1000 缩写的也是不同的单位。还要知道界面上显示的名称与进入 JSON 的标识符不同,才能把这件事留成文件。轴是接下来的地方——不把底部固定为 0,连两倍都不到的波动看起来也像悬崖;把天花板钉死,事故就会被整个截掉。本实验的目的不是背单位名称,而是学会看着一个面板,去问“这个数字会被读成什么”。
步骤
- 用
lab-start-grafana启动 Grafana,并在/root/gfd-units/dash.json中创建 uid 为gfd-units的仪表板,上传到 Grafana。面板只有一个,id是1,类型是timeseries,标题是p99 응답 시간 (단위 없음 - 비교용)(韩文,意为“p99 响应时间(无单位——用于对比)”),查询是histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[6h])))。这个面板不要写单位(后面的步骤中也保持原样——它是用于对比的)。然后在/root/gfd-units/01-readings.tsv中写三行,没有表头,每行用制表符分成两列<가정한 단위 식별자> <그 가정대로면 실제로 몇 초인가>(占位符依次为所假设的单位标识符、按该假设实际是多少秒)。要假设的单位依次为s(秒)、ms(毫秒)、m(分钟),换算成秒的值写到小数点后六位。 - 在同一个仪表板中再添加三个面板。
id2 的标题是p99 응답 시간(韩文,意为“p99 响应时间”),查询是histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[6h])));id3 的标题是5xx 비율(韩文,意为“5xx 比率”),查询是sum(rate(http_requests_total{job="shop-api",status=~"5.."}[1h])) / sum(rate(http_requests_total{job="shop-api"}[1h]));id4 的标题是남은 디스크(韩文,意为“剩余磁盘”),查询是node_filesystem_avail_bytes{job="node",mountpoint="/data"}。三个面板的类型都是timeseries,并在每个面板的fieldConfig.defaults.unit中写下与该值相匹配的 Grafana 单位标识符。延迟以秒为单位,比率介于 0 到 1 之间,磁盘以字节输出。字节请使用按 1024 缩写的一类(IEC)。然后在/root/gfd-units/02-units.tsv中写三行,没有表头,每行用制表符分成三列<패널 id> <단위 식별자> <이 단위를 고른 이유 15자 이상>(占位符依次为面板 id、单位标识符、选择这个单位的理由(不少于 15 个字符))。 - 在同一个仪表板中再添加一个
id为5的 timeseries 面板。标题是p99 응답 시간 (ms)(韩文,意为“p99 响应时间(ms)”),使用把同一个 p99 以毫秒数字输出的查询,单位标识符选毫秒一类。然后在/root/gfd-units/03-scale.tsv中写两行,没有表头,每行用制表符分成两列<단위 식별자> <그 패널의 쿼리가 실제로 내는 값>(占位符依次为单位标识符、该面板的查询实际给出的值)。第一行是第 2 步的秒面板,第二行是本次的面板,值写到小数点后六位。 - 在同一个仪表板中再添加一个
id为6的 timeseries 面板。标题是초당 요청 수(韩文,意为“每秒请求数”),查询是sum(rate(http_requests_total{job="shop-api"}[5m])),单位标识符是吞吐量一类的requests/sec (rps),并把fieldConfig.defaults.min固定为0。相反,第 2 步创建的id2 面板中不要放入max(如果放入了,就删除)。然后在/root/gfd-units/04-axis.tsv中写三行,没有表头,每行用制表符分成两列,依次写rps_min、rps_max、swing_pct。前两个是这个查询在最近 12 小时内给出的最小值和最大值(三位小数),swing_pct是(最大值减最小值)除以最大值再乘以 100(两位小数)。 - 在同一个仪表板中再添加一个
id为7的 timeseries 面板。标题是지연과 오류 비율(韩文,意为“延迟与错误比率”),查询有两个——refId为A的histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[6h])))(图例名称p99)和refId为B的sum(rate(http_requests_total{job="shop-api",status=~"5.."}[1h])) / sum(rate(http_requests_total{job="shop-api"}[1h]))(图例名称5xx)。面板整体的单位设为秒,在fieldConfig.overrides中只选出名称为5xx的序列,把单位指定为 0..1 比率,把custom.axisPlacement指定为right。然后在/root/gfd-units/05-override.tsv中写两行,没有表头,每行用制表符分成三列<범례 이름> <그 계열에 적용되는 단위 식별자> <축 위치>(占位符依次为图例名称、适用于该序列的单位标识符、轴的位置)。轴的位置是left或right。 - 在同一个仪表板中再添加一个
id为8的 timeseries 面板。标题是핸들러별 초당 요청 수(韩文,意为“按处理器的每秒请求数”),查询是sum by (handler) (rate(http_requests_total{job="shop-api"}[1h])),单位是吞吐量一类的requests/sec (rps),并把fieldConfig.defaults.custom.scaleDistribution指定为{"type": "log", "log": 10}。这个面板不要把最小值固定为 0。然后在/root/gfd-units/06-log.txt中写四行——top=<가장 큰 핸들러의 값>、bottom=<가장 작은 핸들러의 값>(两个都保留三位小数)、ratio=<top 나누기 bottom, 소수 두 자리>、loss=<로그 축으로 바꾸면서 잃는 것, 40자 이상>(占位符依次为最大处理器的值、最小处理器的值、top 除以 bottom 的结果(保留两位小数)、换成对数轴时失去的东西(不少于 40 个字符))。 - 在同一个仪表板中再添加两个面板。
id9 的标题是디스크가 줄어드는 속도(韩文,意为“磁盘减少的速度”),查询是- deriv(node_filesystem_avail_bytes{job="node",mountpoint="/data"}[1h]),单位是按 1024 缩写一类的每秒字节数。id10 的标题是디스크가 바닥날 때까지(韩文,意为“直到磁盘耗尽”),查询是剩余字节数除以减少的速度,单位是秒。两个面板的类型都是timeseries。然后在/root/gfd-units/07-derived.tsv中写三行,没有表头,每行用制表符分成两列<단위 식별자> <그 쿼리가 내는 값>(占位符依次为单位标识符、该查询给出的值)。依次是剩余字节数、减少的速度、剩余时间,值写到小数点后三位。 /opt/lab/gfd/gfd-units/broken.json是从生产环境取回的仪表板(uid 为gfd-units-fix)。四个面板的单位或轴都有缺陷。查询可以保持不变,也可以修改,但界面上显示的值与单位必须相互匹配。把修正后的仪表板以 uidgfd-units-fix上传到 Grafana。4 号面板(每秒请求数)的轴要把最小值固定为 0,并撤掉天花板。然后在/root/gfd-units/08-report.tsv中写四行,没有表头,每行用制表符分成三列<패널 id> <결함 코드> <무엇이 틀렸었나, 20자 이상이고 숫자를 하나 이상 포함>(占位符依次为面板 id、缺陷代码、原来错在哪里(不少于 20 个字符且至少包含一个数字))。缺陷代码是scale(值的大小与单位错位)、category(单位的类别错误)、axis(因轴而被截断)三者之一,各行按面板 id 的顺序排列。
参考
- 工作目录是
/root/gfd-units。Grafana 用lab-start-grafana启动,也可以通过网页预览的 3000 端口用眼睛查看。 - 仪表板可以在界面上创建,也可以通过 API 上传。评分器不问是用哪种方式创建的,只看已经上传到 Grafana 的结果。上传后的结果用
curl -s http://127.0.0.1:3000/api/dashboards/uid/gfd-units | jq '.dashboard.panels'确认。 - 请把面板的
datasource留空。数据源 uid 在每个 Pod 中生成的都不同,留空则使用默认数据源(Prometheus)。如果非写不可,用curl -s localhost:3000/api/datasources | jq -r '.[0].uid'获得。 - 单位标识符表在
/opt/lab/gfd/gfd-units/unit-picker.md中——它是从这个 Pod 的 Grafana 中原样提取的,所以与界面下拉框的列表相同。第 8 步要修正的仪表板是 /opt/lab/gfd/gfd-units/broken.json。 - 这个环境无法判定面板实际绘制出来的图像(没有图像渲染器)。评分器只看仪表板 JSON 模型和查询结果。需要用眼睛看的内容,请自己通过网页预览打开。
- 常见错误:只改单位,却保持查询不变。单位不会转换值。
- 常见错误:为了“好看”而把轴的最大值钉死。事故总是发生在那条天花板之上。
- Configure standard options · Configure overrides · Time series - Axis options · Dashboard JSON model · Prometheus - Query functions
上传一个没有写单位的面板,把同一个数字读成三种
用 lab-start-grafana 启动 Grafana,并在 /root/gfd-units/dash.json 中创建 uid 为 gfd-units 的仪表板,上传到 Grafana。面板只有一个,id 是 1,类型是 timeseries,标题是 p99 응답 시간 (단위 없음 - 비교용)(韩文,意为“p99 响应时间(无单位——用于对比)”),查询是 histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[6h])))。这个面板不要写单位(后面的步骤中也保持原样——它是用于对比的)。然后在 /root/gfd-units/01-readings.tsv 中写三行,没有表头,每行用制表符分成两列 <가정한 단위 식별자> <그 가정대로면 실제로 몇 초인가>(占位符依次为所假设的单位标识符、按该假设实际是多少秒)。要假设的单位依次为 s(秒)、ms(毫秒)、m(分钟),换算成秒的值写到小数点后六位。
仪表板可以在网页预览(3000 端口)上创建,也可以通过 API 上传。API 是 curl -s -XPOST -H 'Content-Type: application/json' -d @파일 http://127.0.0.1:3000/api/dashboards/db(占位符为文件名),发送的正文形式为 {"dashboard": {...}, "overwrite": true}。把面板的 datasource 留空,就会使用默认数据源(Prometheus)。换算只需一次乘法——假设是分钟,就是那个数字这么多分钟,所以乘以 60。
实际填入单位标识符
在同一个仪表板中再添加三个面板。id 2 的标题是 p99 응답 시간(韩文,意为“p99 响应时间”),查询是 histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[6h])));id 3 的标题是 5xx 비율(韩文,意为“5xx 比率”),查询是 sum(rate(http_requests_total{job="shop-api",status=~"5.."}[1h])) / sum(rate(http_requests_total{job="shop-api"}[1h]));id 4 的标题是 남은 디스크(韩文,意为“剩余磁盘”),查询是 node_filesystem_avail_bytes{job="node",mountpoint="/data"}。三个面板的类型都是 timeseries,并在每个面板的 fieldConfig.defaults.unit 中写下与该值相匹配的 Grafana 单位标识符。延迟以秒为单位,比率介于 0 到 1 之间,磁盘以字节输出。字节请使用按 1024 缩写的一类(IEC)。然后在 /root/gfd-units/02-units.tsv 中写三行,没有表头,每行用制表符分成三列 <패널 id> <단위 식별자> <이 단위를 고른 이유 15자 이상>(占位符依次为面板 id、单位标识符、选择这个单位的理由(不少于 15 个字符))。
界面上显示的名称与进入 JSON 的标识符是不同的。/opt/lab/gfd/gfd-units/unit-picker.md 中有一张从这个 Pod 的 Grafana 中原样提取的表。比率取决于是介于 0 到 1 之间还是介于 0 到 100 之间,字节则是按 1024 缩写的和按 1000 缩写的使用不同的标识符。如果想知道值的大小,请先用 promq "<쿼리>"(占位符为查询语句)发出查询试一试。
单位不会改变值——要显示成毫秒,需要乘以什么
在同一个仪表板中再添加一个 id 为 5 的 timeseries 面板。标题是 p99 응답 시간 (ms)(韩文,意为“p99 响应时间(ms)”),使用把同一个 p99 以毫秒数字输出的查询,单位标识符选毫秒一类。然后在 /root/gfd-units/03-scale.tsv 中写两行,没有表头,每行用制表符分成两列 <단위 식별자> <그 패널의 쿼리가 실제로 내는 값>(占位符依次为单位标识符、该面板的查询实际给出的值)。第一行是第 2 步的秒面板,第二行是本次的面板,值写到小数点后六位。
单位是显示规则,而不是转换规则。给以秒输出的值只加上毫秒单位,界面上的数字不变,只是名称改变——这个面板会被读成小一千倍。要把值变成毫秒,必须在查询中相乘。两个值必须恰好相差 1000 倍。
必须从 0 开始的轴,和不能加天花板的轴
在同一个仪表板中再添加一个 id 为 6 的 timeseries 面板。标题是 초당 요청 수(韩文,意为“每秒请求数”),查询是 sum(rate(http_requests_total{job="shop-api"}[5m])),单位标识符是吞吐量一类的 requests/sec (rps),并把 fieldConfig.defaults.min 固定为 0。相反,第 2 步创建的 id 2 面板中不要放入 max(如果放入了,就删除)。然后在 /root/gfd-units/04-axis.tsv 中写三行,没有表头,每行用制表符分成两列,依次写 rps_min、rps_max、swing_pct。前两个是这个查询在最近 12 小时内给出的最小值和最大值(三位小数),swing_pct 是(最大值减最小值)除以最大值再乘以 100(两位小数)。
12 小时内的最小值、最大值用子查询求得——写成 min_over_time((<쿼리>)[12h:5m])(占位符为查询语句)的形式。如果让轴保持自动,y 轴就会从最小值开始,连两倍都不到的波动也会占满整个界面高度。相反,如果把最大值钉死,在它之上发生的事故就会被整个截掉——想让线条少一些起伏,就不要用硬性最大值,而是使用 Soft max。
在一个面板中放入两个单位不同的东西
在同一个仪表板中再添加一个 id 为 7 的 timeseries 面板。标题是 지연과 오류 비율(韩文,意为“延迟与错误比率”),查询有两个——refId 为 A 的 histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{job="shop-api"}[6h])))(图例名称 p99)和 refId 为 B 的 sum(rate(http_requests_total{job="shop-api",status=~"5.."}[1h])) / sum(rate(http_requests_total{job="shop-api"}[1h]))(图例名称 5xx)。面板整体的单位设为秒,在 fieldConfig.overrides 中只选出名称为 5xx 的序列,把单位指定为 0..1 比率,把 custom.axisPlacement 指定为 right。然后在 /root/gfd-units/05-override.tsv 中写两行,没有表头,每行用制表符分成三列 <범례 이름> <그 계열에 적용되는 단위 식별자> <축 위치>(占位符依次为图例名称、适用于该序列的单位标识符、轴的位置)。轴的位置是 left 或 right。
一个覆盖项的形式是 {"matcher": {"id": "byName", "options": "<범례 이름>"}, "properties": [{"id": "unit", "value": "..."}, ...]}(占位符为图例名称)。图例名称由目标的 legendFormat 决定。如果不做覆盖直接叠在一起,两个序列就会共用一个轴,0.004 的比率在 0.3 的延迟旁边就会成为贴在底部的一条直线。
使用对数轴的地方,以及那时失去的东西
在同一个仪表板中再添加一个 id 为 8 的 timeseries 面板。标题是 핸들러별 초당 요청 수(韩文,意为“按处理器的每秒请求数”),查询是 sum by (handler) (rate(http_requests_total{job="shop-api"}[1h])),单位是吞吐量一类的 requests/sec (rps),并把 fieldConfig.defaults.custom.scaleDistribution 指定为 {"type": "log", "log": 10}。这个面板不要把最小值固定为 0。然后在 /root/gfd-units/06-log.txt 中写四行——top=<가장 큰 핸들러의 값>、bottom=<가장 작은 핸들러의 값>(两个都保留三位小数)、ratio=<top 나누기 bottom, 소수 두 자리>、loss=<로그 축으로 바꾸면서 잃는 것, 40자 이상>(占位符依次为最大处理器的值、最小处理器的值、top 除以 bottom 的结果(保留两位小数)、换成对数轴时失去的东西(不少于 40 个字符))。
四个处理器的值可以用 promq "sum by (handler) (rate(http_requests_total{job="shop-api"}[1h]))" 一次看到,最大值和最小值只需用 max(...)、min(...) 包起来就能得到。对数轴上无法画出 0——所以把最小值固定为 0 与对数轴不能同时使用。写失去的东西时,想一想“相同的纵向距离会表示什么”。
应用 ① ——除法的结果带什么单位
在同一个仪表板中再添加两个面板。id 9 的标题是 디스크가 줄어드는 속도(韩文,意为“磁盘减少的速度”),查询是 - deriv(node_filesystem_avail_bytes{job="node",mountpoint="/data"}[1h]),单位是按 1024 缩写一类的每秒字节数。id 10 的标题是 디스크가 바닥날 때까지(韩文,意为“直到磁盘耗尽”),查询是剩余字节数除以减少的速度,单位是秒。两个面板的类型都是 timeseries。然后在 /root/gfd-units/07-derived.tsv 中写三行,没有表头,每行用制表符分成两列 <단위 식별자> <그 쿼리가 내는 값>(占位符依次为单位标识符、该查询给出的值)。依次是剩余字节数、减少的速度、剩余时间,值写到小数点后三位。
字节除以每秒字节数,剩下的是秒——单位跟随查询的算术。减少的速度用 deriv 求得,值会是负数,所以在前面加上减号让它变成正数。每秒字节数与字节是不同类的单位(不是 Data 而是 Data rate)。如果直接加上字节单位,界面会说“4 GiB”,但实际含义是“每秒 4 GiB”。
应用 ② ——把生产仪表板的单位和轴的缺陷全部修正并提交
/opt/lab/gfd/gfd-units/broken.json 是从生产环境取回的仪表板(uid 为 gfd-units-fix)。四个面板的单位或轴都有缺陷。查询可以保持不变,也可以修改,但界面上显示的值与单位必须相互匹配。把修正后的仪表板以 uid gfd-units-fix 上传到 Grafana。4 号面板(每秒请求数)的轴要把最小值固定为 0,并撤掉天花板。然后在 /root/gfd-units/08-report.tsv 中写四行,没有表头,每行用制表符分成三列 <패널 id> <결함 코드> <무엇이 틀렸었나, 20자 이상이고 숫자를 하나 이상 포함>(占位符依次为面板 id、缺陷代码、原来错在哪里(不少于 20 个字符且至少包含一个数字))。缺陷代码是 scale(值的大小与单位错位)、category(单位的类别错误)、axis(因轴而被截断)三者之一,各行按面板 id 的顺序排列。
1 号面板是以秒输出的值带着毫秒,2 号面板是 0..1 的比率带着 0..100 的百分比。两者的修正方法都有两种——让单位适配值,或者让值乘以适配单位的系数。哪一种都可以。3 号面板是每秒字节数,却带着字节单位(类别不同)。4 号面板单位是对的,但轴上被加了天花板,实际流量被截断——请先量一下 12 小时的最大值。用 cp /opt/lab/gfd/gfd-units/broken.json /root/gfd-units/fixed.json 复制一份副本,修改后上传即可。