数据模型

Prometheus采集到的数据会以time-series(时间序列)的方式保存在内存中,定时刷到硬盘上,time-series是按照时间戳和值的序列顺序排放的,这个叫做vector(向量),每条time-series通过metrics-name(指标名称)和一组labelset(标签)来命名

  ^
  │     . . . . . . . . . . . . . . . .   . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . . . . . . . . . . .   node_load1{}
  │     . . . . . . . . . . . . . . . . . . .  
  v
    <------------------ 时间 ---------------->

在time-series中的每一个点叫做一个sample(样本),每个sample有三部分 - metric(指标): metric-name以及labelset - timestamp(时间戳): 精确到毫秒的时间戳 - value(样本值): 一个float64的浮点型数据表示当前样本值

<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
http_request_total{status="200", method="GET"}@1434417561287 => 94334

http_request_total{status="404", method="GET"}@1434417560938 => 38473
http_request_total{status="404", method="GET"}@1434417561287 => 38544

http_request_total{status="200", method="POST"}@1434417560938 => 4748
http_request_total{status="200", method="POST"}@1434417561287 => 4785

Metric

形式上,所有的metric都是以下格式:

<metric name>{<label name>=<label value>,...}

metric name可以反映出被监控样本的含义,比如说http_request_total表示接收到的http请求的总数

label反应了当前样本的特征维度,通过这些维度prometheus可以对样本数据进行过滤,聚合等

其中,以__(双下划线)开头的label是系统保留关键字,只能在系统内部使用,在prometheus内部,实际上metric name是以__name__的label来保存的

http_requests_total{method="POST", handler="/messages"}

实际上是这样的

{__name__="http_requests_total",method="POST", handler="/messages"}

源码中是在这里定义的

metric name

metric label

Metric类型

从存储上来讲,所有的metric都是相同的,但是为了方便理解不同metric之间的差异,prometheus定义了4种不同的metric type(指标类型):

  • Counter (计数器)
  • Gauge (仪表盘)
  • Histogram (直方图)
  • Summary (摘要)

访问采集接口也可以看到定义的类型

metric type

Counter: 只增不减的计数器

Counter类型的metric都是只增不减的,类似于计数器的工作方式,常见的例如http_request_total,node_cpu_total

推荐所有的counter类型全部以_total结尾,以此来标识

Gauge: 可增可减的仪表盘

与Counter类型不同,Gauge一般用来反映系统当前的状态,例如go_goroutines(表示当前启动的协程数)

Histogram & Summary

除了上面两种类型,prometheus还定义了两种类型用来统计和分析sample的分布情况

大多数情况下,可能人们会倾向于使用量化指标的平均值,例如CPU的平均使用率,页面的平均响应时间,但是这种方式的问题是很明显的,如果大多数响应在100ms左右,但是个别响应需要5s,就会导致结果落在中位数,这显然不是想要的结果.针对这种情况,prometheus对sample进行了分组,例如把0~10ms的分为一组,10~20ms的分为一组,以此来解决刚才的问题

例如prometheus自己的监控中prometheus_tsdb_wal_fsync_duration_seconds的指标类型为Summary. 它记录了Prometheus Server中wal_fsync处理的处理时间,通过访问Prometheus Server的/metrics地址,可以获取到以下监控样本数据

# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463      # 50%的sample低于这个数值
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005      # 90%的sample低于这个数值
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173     # 99%的sample低于这个数值
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002            # 总耗时
prometheus_tsdb_wal_fsync_duration_seconds_count 216                        # 总次数

从上面的样本中可以得知当前Promtheus Server进行wal_fsync操作的总次数为216次,耗时2.888716127000002s.其中中位数(quantile=0.5)的耗时为0.012352463,9分位数(quantile=0.9)的耗时为0.014458005s.这个数据是在客户端计算好的,对于服务端来说减少了资源消耗,但是也增加了客户端的压力

下面的是Histogram类型的一个metric

# HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction
# TYPE prometheus_tsdb_compaction_chunk_range histogram
prometheus_tsdb_compaction_chunk_range_bucket{le="100"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="6400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="25600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="102400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="409600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1.6384e+06"} 260
prometheus_tsdb_compaction_chunk_range_bucket{le="6.5536e+06"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="2.62144e+07"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="+Inf"} 780
prometheus_tsdb_compaction_chunk_range_sum 1.1540798e+09            $ 总和
prometheus_tsdb_compaction_chunk_range_count 780                    # 总次数

Histogram和Summary一样,也有_sum_count来记录总体数据,不同的是,Histogram直接展示了在落不同区间的sample数量,每个区间通过le标签来定义

PromQL

PromQL是prometheus内置的查询语言,用来查询prometheus中的数据

注意: Only Instant Vectors can be graphed.(只有瞬时向量可以用来画图)

直接查询

以一个counter为例

traefik_backend_requests_total这个表示traefik接收的所有请求

promql-1

因为是counter,所以这个图是单调递增的,而且会把所有metric名字相同的时间序列都展示出来

通过label来过滤

traefik_backend_requests_total{code="200",dc="M6V",instance="192.168.100.22:8080"}

promql-filter-by-label

还可以通过正则表达式来过滤

traefik_backend_requests_total{code=~"2.*",dc="M6V",instance="192.168.100.22:8080"}

Range Vector (范围向量)

注意: Only Instant Vectors can be graphed.(只有瞬时向量可以用来画图)

由于范围向量无法画图,所以要去prometheus直接查询,这里对比一下瞬时向量和范围向量查询的结果

这个是瞬时向量

traefik_backend_requests_total

promql-instant-vector

下面是范围向量

traefik_backend_requests_total[1m]

这个表达式的意思是从当前时间开始获取过去1分钟的sample

[1m] 这里的m也可以替换为 s,m,h,d,w,y

promql-range-vector

这样的话,每条序列就会返回一组sample,所以没办法画图

函数

对于Range Vectors常用的函数

由于只能用瞬时向量画图,所以需要使用函数将范围向量转化为瞬时向量,然后就可以画图了

rate

是用来计算,这一组范围向量的平均增长速率,举个例子:

rate(traefik_backend_requests_total[5m])

这个表达式计算的就是过去5分钟内请求的平均增长速率,计算方法就是获取范围向量中最后一个sample的value减去第一个sample,然后除以时间

(RangeVectors[last] - RangeVectors[first] )/ 5*60

大概就是这个意思

promql-rate

irate

rate很像,但是irate计算时是获取范围向量中最新的两个sample计算,所以结果比较即时

promql-irate

一般在配置报警时,建议使用rate函数,因为它计算出的结果比较平滑,更能体现整体趋势,而irate由于即时性较高,可能会导致反复触发阈值

increate

用来计算增长量,它的结果应该就是rate * 时间

increase(traefik_backend_requests_total[1h])

表示过去1小时的增长量

promql-increase

对于Instant Vector常用的函数

sum

用来将不同时间序列的数据聚合

sum(rate(traefik_backend_requests_total[5m]))

promql-sum

而且计算后的结果会失去所有的label

promql-sum-value

还可以根据label来聚合

sum by (code) (rate(traefik_backend_requests_total[5m]))

这样计算会把相同code的序列相加

promql-sum-by-code

计算后的时间序列会带上聚合条件

promql-sum-by-code-value

聚合函数还有min,max,avg,count,quantilesum的用法一样

除了by以外还有without,可以过滤掉某种label,类似于mysql的group by

topk / bottomk

对sample进行排序,选择出前几,或者后几条数据

topk by (method) (1,traefik_backend_requests_total)

这个表达式就是得到每种method的第一

promql-topk

Offset

可以使用offset来改变瞬时向量的时间

sum(rate(traefik_backend_requests_total[5m] offset [5m]))

这样就会变成以5分钟前为起始点,查询再向前5分钟的数据了

统计Histogram指标的分位数

Histogram和Summary都可以同于统计和分析数据的分布情况.区别在于Summary是直接在客户端计算了数据分布的分位数情况.而Histogram的分位数计算需要通过histogram_quantile(φ float, b instant-vector)函数进行计算。其中φ(0<φ<1)表示需要计算的分位数,如果需要计算中位数φ取值为0.5,以此类推即可。

举个例子:

histogram_quantile(0.9,traefik_backend_request_duration_seconds_bucket)

promql-histogram

Operators (操作符)

操作符可以在两个标量,两个向量,或者一个向量一个标量之间计算,向量间计算时,期望找到与它匹配的元素,也就是一对一匹配规则

操作符分为:

  • 算数运算: +, -, *, /, %, ^
  • 比较运算: ==, !=, >, <, >=, <=
  • 逻辑运算: and,or,unless

如果是向量与标量进行计算时,会将向量中的所有value与标量进行相应的计算

Vector Matching 向量匹配原则

One To One 一对一

如果两个向量的label是相等的,那么他们俩就是相同可以匹配的,举个例子:

 rate(traefik_backend_requests_total{code=~"5.*"}[5m])> 0.1 * rate(traefik_backend_requests_total[5m])

这个表达式的意思是计算出过去5分钟内状态码为500的响应大于总请求数量10% 相当于做了一次减法,然后把大于的sample挑出来组成了序列

promql-one-to-one

Many To One

指的是one的那一侧可以匹配到many那一侧的多个label

可以使用group_left(或者group_right)以及ignoring(或者on)来指定prometheus来如何处理label 就是下面这个图的意思

promql-many-to-one

举个例子:

method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m
method_code:http_errors:rate5m{method="get", code="500"}  24
method_code:http_errors:rate5m{method="get", code="404"}  30
method_code:http_errors:rate5m{method="put", code="501"}  3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21

method:http_requests:rate5m{method="get"}  600
method:http_requests:rate5m{method="del"}  34
method:http_requests:rate5m{method="post"} 120

左边的这个labelset是 method,code两个 右边的labelset只有一个method 所以应该使用group_left,然后ignoring忽略掉code,这样的话就只剩下method这一个label,就可以计算了

{method="get", code="500"}  0.04            //  24 / 600
{method="get", code="404"}  0.05            //  30 / 600
{method="post", code="500"} 0.05            //   6 / 120
{method="post", code="404"} 0.175           //  21 / 120