前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文搞懂 bosun 查询

一文搞懂 bosun 查询

原创
作者头像
王磊-字节跳动
发布2021-05-16 14:21:37
10.7K0
发布2021-05-16 14:21:37
举报
文章被收录于专栏:01ZOO01ZOO

背景

bosun 是一个由 Stack Exchange 开源的监控和告警系统,可以对标的工具有 prometheus 的 alertmanager. bosun 的设计目的是用于配合各种 tsdb 配置监控告警系统,但是 bosun 同时又提供了一套 dsl 用于查询监控、评估指标,使得 bosun 本身也是一种 tsdb 无关(目前支持如 opentsdb, prometheus, influxdb, es 等多种 tsdb 后端)的指标查询语言。要理解 bosun 是如何生成告警,或者仅仅是利用他的指标查询能力,配合如 grafana 这样的监控前端来展示指标,那么就必须要了解这门语言。

bosun 并不是一个非常火热的项目,目前有 3.1k star, 市面上介绍他的文档也比较少,大部分是对官方文档的直译。本文的目的是从概念角度介绍 bosun 查询的方式(主要针对后端为 opentsdb),以及一些查询的技巧。

概念

首先要了解 bosun 查询中的一些类型概念:

  1. Scalar 就是一个数字
  2. NumberSet 和 Scalar 基本是一回事,但是多了一组 tag,空 {} 也算是 tag
  3. SeriesSet 是 表征原始指标最常见的格式,和 NumberSet 不同,它对应的值不是一个数字,而是一组关联的时间戳的值,比如 时间 100 下的值 3.14, 时间 200 下的值 3.28
  4. Results 不是一个文档中介绍的概念,却是实际查询中最常见的类型,它代表一次查询的最常见的结果:即一组 tag 不同的 SeriesSet 或者 NumberSet 等的集合。文档里面把不同的 tags 组合又叫 group。

查询

针对 opentsdb 中的查询,bosun 提供了几种查询方式

q

q(query string, startDuration string, endDuration string) seriesSet

这是最常用的查询方式,大部分的告警也都是用这个语句查询出来的,这个语句非常简单,其中 query 是 opentsdb 的查询语句,startDuration 和 endDuration 为启止查询时间,比如 q(sum:rate{counter}:sys.cpu.user, 5m, 1m), 表示查询 sys.cpu.user 指标 5m前到 1m前这段时间的 sum:rate。 由于指标一般收集有延迟,endDuration 一般推荐为至少 1m 前。

代码语言:txt
复制
# 例子 
q("sum:rate{counter}:${service}.rpc.calledby.success.throughput", "5m", "1m")

group	result	computations
{ }	
{
  "1620196950": 2.016666666666667,
  "1620196980": 2.3666666666666667,
  "1620197010": 1.0999999999999999,
  "1620197040": 1.8333333333333335,
  "1620197070": 2.7333333333333343,
  "1620197100": 2.5,
  "1620197130": 1.7000000000000002,
  "1620197160": 0.9666666666666666
}

bandQuery/overQuery

bandQuery(query string, duration string, period string, eduration string, num scalar) seriesSet band(query string, duration string, period string, num scalar) seriesSet

  • bandQuery 的意思是 用 query 语句进行多次 (num 次) 查询,每次查询的时间范围由 duration/period 决定,
  • band 是 bandQuery 的特殊形式,相当于设置了 eduration = period,比如 band("avg:os.cpu", "1h", "1d", 3) 相当于查询了以下三个语句 q("avg:os.cpu", "25h", "1d"), q("avg:os.cpu", "49h", "2d"), q("avg:os.cpu", "73h", "3d"), 因为设置了 eduration=period,所以最近的那个周期是 (period+duration,period)

overQuery(query string, duration string, period string, eduration string, num scalar) seriesSet over(query string, duration string, period string, num scalar) seriesSet shiftBand(query string, duration string, period string, num scalar) seriesSet

  • overQuery 是 over 和 shiftBand 的通用形式,和 bandQuery 的不同之处在于,查询之后会对查询结果加上查询偏移相关的标签 "shifted"
  • over 和 shiftBand 只是overQuery 的特殊形式,只是相当于分别给 overQuery 的 eduration 设置为 period 和 当前时间(即不填写)
代码语言:txt
复制
# 例子,由于 剩下几种只是 bandQuery 和 overQuery 的特殊形式,这里只给出这两种查询的例子

> bandQuery("sum:rate{counter}:${service}.rpc.calledby.success.throughput", "5m", "60m", "1m", 2)


group	result	computations
{ }	
{
  "1620195120": 69.96666666666665,
  "1620195150": 5.816666666666666,
  "1620195180": 5.766666666666667,
  "1620195210": 4.3,
  "1620195240": 5.7666666666666675,
  "1620195270": 3.7666666666666675,
  "1620195300": 4.4,
  "1620195330": 4.933333333333334,
  "1620195360": 4.033333333333334,
  "1620195390": 1.7000000000000002,
  "1620198720": 69.93333333333334,
  "1620198750": 11.7,
  "1620198780": 1.2999999999999998,
  "1620198810": 1.8500000000000008,
  "1620198840": 2.766666666666667,
  "1620198870": 4.633333333333333,
  "1620198900": 4.833333333333334,
  "1620198930": 2.366666666666667,
  "1620198960": 2.366666666666667,
  "1620198990": 2.2666666666666666
}



> overQuery("sum:rate{counter}:${service}.rpc.calledby.success.throughput", "5m", "60m", "1m", 2)

group	result	computations
{ shift=1m0s }	
{
  "1620198780": 69.93333333333334,
  "1620198810": 11.7,
  "1620198840": 1.2999999999999998,
  "1620198870": 1.8500000000000008,
  "1620198900": 2.766666666666667,
  "1620198930": 4.633333333333333,
  "1620198960": 4.833333333333334,
  "1620198990": 2.366666666666667,
  "1620199020": 2.366666666666667,
  "1620199050": 2.2666666666666666
}
{ shift=1h1m0s }	
{
  "1620198780": 69.96666666666665,
  "1620198810": 5.816666666666666,
  "1620198840": 5.766666666666667,
  "1620198870": 4.3,
  "1620198900": 5.7666666666666675,
  "1620198930": 3.7666666666666675,
  "1620198960": 4.4,
  "1620198990": 4.933333333333334,
  "1620199020": 4.033333333333334,
  "1620199050": 1.7000000000000002
}

bandQuery 和 overQuery 对于查询一个周期相同时间段(比如每天的这个时间)的指标很有用,而且很有意思的是 bandQuery 并不会产生 unjoined group, 这点在下面的小技巧里面进一步说明。

window

window(query string, duration string, period string, num scalar, funcName string) seriesSet

比起 bandQuery 和 overQuery,window 对于展示用途的查询更有用, window 会对每次查询的结果进行 funcName 的 reduction 计算,返回的值和时间戳生成一个新的时间序列。举个例子,你想查询过去 6个小时内每小时的请求数量,就可以用下面的计算方式:

代码语言:txt
复制
> window("sum:rate{counter}:${service}.rpc.calledby.success.throughput", "60m", "60m", 6, "sum")

group	result	computations
{ }	
{
  "1620175620": 356260.0166666666,
  "1620179220": 370473.99999999965,
  "1620182820": 391460.0166666665,
  "1620186420": 405893.36666666664,
  "1620190020": 364280.9166666666,
  "1620193620": 380179.3833333336
}

配合 grafana 就可以画出这样的一个曲线或者柱状图

count/change

count 表示查询返回的 Results 长度,而 change 表示变化, change("avg:rate:net.bytes", "60m", "") = avg(q("avg:rate:net.bytes", "60m", "")) * 60 * 60

计算

bosun 的计算方式可能是最让人困扰的一部分,要理解这个,首先要结合第一节讲的概念理解几个核心:

  1. 查询大部分的返回结果是一组 SeriesSet 或者 NumberSet 即 Results,比如我们在查询的时候使用 这样的 query: avg:rate:net.bytes{host=*}, 就会自动产生多个 group 的 SeriesSet ( 如果不希望产生,只是筛选可以这么写 avg:rate:net.bytes{}{host=1.2.3.4})
  2. bosun 文档中的大部分函数是针对单个 group 的 SeriesSet,即对查询结果应用函数的时候,是对每个 group 按个应用函数,比如 avg(q("avg:rate:net.bytes{host=*}", "60m", "")) 查询返回的结果有 {host=a}, {host=b} 等等,那么对多个 group 分别应用 avg 函数
  3. 不同的 Results 相互计算,举个例子 +, 是对所有的 group 组合分别应用 + 进行计算,但是并不是所有的 group 组合都能互相计算,只有互相为子集或者相等的 group 才能计算,所以就会产生 unjoined group, 没有参与计算的 group 就会产生一个 unjoined group, 这个计算有点抽象,可以看下面的例子帮助理解。在看结果之前可以猜测一下结果,确认自己的理解对不对。
代码语言:txt
复制
# 两个 results 之间的运算方式
for g1 in Result1:
    for g2 in Result2:
        if g1 == g2 || g1 is subset of g2 || g2 is subset of g1:
            计算

for g1 in Result1:
    if g1 没有参与计算:
        生成一个 unjoined group
for g2 in Result2:
    if g2 没有参与计算:
        生成一个 unjoined group

例子1

代码语言:txt
复制
$a = series("X=a1,Y=b1", 100, 1, 200, 2)
$b = series("X=a2,Y=b2", 100, 2, 200, 3)


$x = series("X=a1", 100, 2, 200, 1)
$y = series("X=a1,Y=b2", 100, 3, 200, 5)
$z = series("X=a2,Y=b2", 100, 3, 200, 2)

# {X=a1,Y=b1} {X=a2,Y=b2}
$ab = merge($a, $b)

# {X=a1} {X=a1,Y=b2} {X=a2,Y=b2}
$xyz = merge($x, $y, $z)


# 这里可以参与计算的组合有 ({X=a1,Y=b1}, {X=a1}), ({X=a2,Y=b2}, {X=a2,Y=b2}), 由于 {X=a1,Y=b2} 没有参与计算,所以会生成一个 unjoined group
$ab+$xyz


-----------------------------------
group	result	computations
{ X=a1, Y=b1 }	
{
  "100": 3,
  "200": 3
}
{ X=a2, Y=b2 }	
{
  "100": 5,
  "200": 5
}
{ X=a1, Y=b2 }	
{
  "100": "NaN",
  "200": "NaN"
}
merge(series("X=a1,Y=b1", 100, 1, 200, 2), series("X=a2,Y=b2", 100, 2, 200, 3)) + merge(series("X=a1", 100, 2, 200, 1), series("X=a1,Y=b2", 100, 3, 200, 5), series("X=a2,Y=b2", 100, 3, 200, 2))	unjoined group (NaN)

例子2

代码语言:txt
复制
$a = series("Y=b2", 100, 1, 200, 1)
$b = series("X=a1,Y=b1", 100, 3, 200, 5)
$c = series("X=a2,Y=b2", 100, 3, 200, 2)


$x = series("X=a2", 100, 2, 200, 1)
$y = series("X=a1,Y=b2", 100, 3, 200, 5)
$z = series("X=a2,Y=b2", 100, 3, 200, 2)

# {X=a1,Y=b1} {X=a2,Y=b2} {Y=b2}
$abc = merge($b, $c, $a)

# {X=a2,Y=b2} {X=a1,Y=b2} {X=a2}, 这里把 {X=a2}  放在最后是因为第一个组合不能计算会报错
$xyz = merge($z, $y, $x)

$abc + $xyz 


-----------------------------------
group	result	computations
{ X=a2, Y=b2 }	
{
  "100": 6,
  "200": 4
}
{ X=a2, Y=b2 }	
{
  "100": 4,
  "200": 3
}
{ X=a1, Y=b2 }	
{
  "100": 4,
  "200": 6
}
{ X=a2, Y=b2 }	
{
  "100": 5,
  "200": 3
}
{ X=a1, Y=b1 }		
merge(series("X=a2,Y=b2", 100, 3, 200, 2), series("X=a1,Y=b2", 100, 3, 200, 5), series("X=a2", 100, 2, 200, 1)) + merge(series("X=a1,Y=b1", 100, 3, 200, 5), series("X=a2,Y=b2", 100, 3, 200, 2), series("Y=b2", 100, 1, 200, 1))

更多例子

代码语言:txt
复制
$aa=series("tagA=a", 0, 2, 60, 2)
$ab=series("tagA=a,tagB=b", 0, 2, 60, 1)
$ac=series("tagA=a,tagC=c", 0, 2, 60, 3)
$bb=series("tagB=b", 0, 2, 60, 2)
$cc=series("tagC=c", 0, 2, 60, 2)

# {tagA=a} {tagB=b} {tagC=c}
$abc=merge($aa,$bb,$cc)
# {tagA=a} {tagA=a,tagB=b}
$aab = merge($aa, $ab)  
# {tagA=a} {tagA=a,tagC=c}
$aac = merge($aa, $ac)

# 这里可以参与计算的组合有 ({tagA=a}, {tagA=a})  ({tagA=a},{tagA=a,tagC=c}) ({tagA=a,tagB=b},{tagA=a}) 
# $aab+$aac


#  {tagA=a} {tagB=b}
$aabb = merge($aa, $bb)  
#  {tagA=a} {tagC=c}
$aacc = merge($aa, $cc)


# $aacc+$aabb

#  {tagA=a} {tagC=c} + {tagA=a} {tagA=a,tagC=c}
# $aacc+$aac


#  {tagA=a} {tagC=c} +  {tagA=a} {tagB=b} {tagC=c}
# $aacc+$abc

技巧

避免 unjoined group

一种常见的做法是使用 group 相关的一些操作函数,比如查询的时候就干脆不生成 group, 使用 filter 语句查询,比如 avg(q("sum:rate:metrics.notexist{}{status=500)}", "1m", "0m")), 或者查询之后使用 addtags, remove 这样的函数来处理 tags,来避免 group 之间的不兼容。这里介绍另一种巧妙的做法,可以忽略掉 unjoined group. 即使用 bandQuery 来查询,

比如一个计算请求错误率的例子:

代码语言:txt
复制
$key_err = "sum:rate{counter}:${service}.rpc.calledby.error.throughput{method=*}"
$key_succ = "sum:rate{counter}:${service}.rpc.calledby.success.throughput{method=*}"

$err_now = avg(q($key_err, "5m", "1m"))
$succ_now = avg(q($key_succ, "5m", "1m"))

$rate_now = $err_now / ($err_now +$succ_now)
$rate_now

使用上面的查询方式会产生大量 unjoined group, 原因是 rpc.calledby.error.throughput 这个指标的 tags 数量比 success 的数量要少很多,但是我又希望返回结果能带上 method 这个分组标签。使用 band 的查询方式如下:

代码语言:txt
复制
$key_err = "sum:rate{counter}:${service}.rpc.calledby.error.throughput{method=*}"
$key_succ = "sum:rate{counter}:${service}.rpc.calledby.success.throughput{method=*}"

$err_now = avg(band($key_err, "4m", "1m", 1))
$succ_now = avg(band($key_succ, "4m", "1m", 1))

$rate_now = $err_now / ($err_now +$succ_now)
$rate_now

使用 band 查询不会产生 unjoined group,unjoined group 的结果会被忽略,即 results 之间的计算中,生成 unjoined group 的步骤会被忽略。

grafana bosun 插件

grafana bosun 插件中会有两个内置的变量

  • $ds: 建议的 downsampling interval, 这个变量很有用,在查询的使用比如 q("avg:$ds-avg:os.disk.fs.space_free{disk=*,host=backup}", "$start", ""), 会在用户选择较大时间范围的时候保持查询效率.
  • $start: 用户选择的起始时间

t 函数的使用

group 的操作函数有几个, 这里介绍一个 t 函数,他可以将多个 group 的 seriesSet join 成一个 group 的,来配合一些计算函数的使用。举个例子,计算 api 的 60 min 加权 latency:

代码语言:txt
复制
$latency=avg(q("avg:${service}.calledby.success.latency.us.pct99{handle_method=*}", "60m", ""))
$count=sum(q("sum:rate{counter,,,diff}:${service}.calledby.success.throughput{handle_method=*}", "60m", ""))
$total=sum(q("sum:rate{counter,,,diff}:${service}.calledby.success.throughput{}", "60m", ""))


sum(t($latency*($count/$total), ""))

其他参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 概念
  • 查询
    • q
      • bandQuery/overQuery
        • window
          • count/change
          • 计算
          • 技巧
            • 避免 unjoined group
              • grafana bosun 插件
                • t 函数的使用
                • 其他参考
                相关产品与服务
                Grafana 服务
                Grafana 服务(TencentCloud Managed Service for Grafana,TCMG)是腾讯云基于社区广受欢迎的开源可视化项目 Grafana ,并与 Grafana Lab 合作开发的托管服务。TCMG 为您提供安全、免运维 Grafana 的能力,内建腾讯云多种数据源插件,如 Prometheus 监控服务、容器服务、日志服务 、Graphite 和 InfluxDB 等,最终实现数据的统一可视化。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档