监控样本#
通过 Node Exporter 暴露的 HTTP 服务,Prometheus 可以采集到当前主机所有监控指标的样本数据。比如:
# HELP node_cpu_guest_seconds_total Seconds the CPUs spent in guests (VMs) for each mode.
# TYPE node_cpu_guest_seconds_total counter
node_cpu_guest_seconds_total{cpu="0",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="0",mode="user"} 0
node_cpu_guest_seconds_total{cpu="1",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="1",mode="user"} 0
# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 996.7
node_cpu_seconds_total{cpu="0",mode="iowait"} 0.52其中非 # 开头的每一行表示当前 Node Exporter 采集到的一个监控样本:
node_cpu_seconds_total:表示当前指标名称- 大括号中的标签:表示当前样本的一些特征和维度
- 浮点数:表示该监控样本的具体值
时间序列(time-series)#
Prometheus 会将所有采集到的样本数据以时间序列(time-series)的方式保存在内存数据库中,定时保存到硬盘上。
time-series 是按照时间戳和值的序列顺序存放的,称之为向量(vector)
每条 time-series 通过指标名称(metrics name)和一组标签集(labelset)命名。其中每一个点称为一个样本(sample)。样本由以下三部分组成:
- 指标(metric):metric name 和描述当前样本特征的 labelsets
- 时间戳(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请求总量。指标名称只能由ASCII字符、数字、下划线以及冒号组成并必须符合正则表达式[a-zA-Z_:][a-zA-Z0-9_:]* - 标签(label):反映当前样本的特征维度,通过这些维度 Prometheus 可以对样本数据进行过滤,聚合等。标签的名称只能由ASCII字符、数字以及下划线组成并满足正则表达式
[a-zA-Z_][a-zA-Z0-9_]*
其中以 __ 作为前缀的标签是系统保留的关键字,只能在系统内部使用。标签的值可以包含任何 Unicode 编码的字符。
在 Prometheus 的底层实现中指标名称实际上是以 __name__=<metric name> 的形式保存在数据库中的,因此以下两种方式均表示的同一条 time-series:
api_http_requests_total{method="POST", handler="/messages"}
等同于:
{__name__="api_http_requests_total",method="POST", handler="/messages"}在 Prometheus 源码中也可以指标(Metric)对应的数据结构,如下所示:
type Metric LabelSet type LabelSet map[LabelName]LabelValue type LabelName string type LabelValue string
Metric 类型#
为了能够理解和区分不同监控指标之间的差异,Prometheus 定义了4中不同的指标类型(metric type):
- Counter(计数器)
- Gauge(仪表盘)
- Histogram(直方图)
- Summary(摘要)
Exporter Node 返回的样本数据中,其注释中也包含了该样本的类型。例如:
# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 996.7Counter->只增不减#
Counter 类型的指标工作方式和计数器一样,只增不减(除非系统发生重置)。如 http_requests_total ,node_cpu 都是 Counter 类型的监控指标。 一般在定义 Counter 类型指标的名称时推荐使用 _total 作为后缀。
示例:通过 rate() 函数获取HTTP请求量的增长率:
rate(http_requests_total[5m])查询当前系统中,访问量前10的 HTTP 地址:
topk(10, http_requests_total)Gauge->可增可减#
Gauge 类型的指标侧重于反应系统的当前状态。因此这类指标的样本数据可增可减。如:node_memory_MemFree(主机当前空闲的内容大小)、node_memory_MemAvailable(可用内存大小)都是 Gauge 类型的监控指标。
通过 Gauge 指标,可以直接查看系统的当前状态。对于 Gauge 类型的监控指标,通过 PromQL 内置函数 delta() 可以获取样本在一段时间的变化情况。例如,计算CPU温度在两个小时内的差异:
delta(cpu_temp_celsius{host="zeus"}[2h])还可以使用 deriv() 计算样本的线性回归模型,甚至直接使用 predict_linear() 对数据的变化趋势进行预测。例如:预测系统磁盘空间在4个小时之后的剩余情况:
predict_linear(node_filesystem_free{job="node"}[1h], 4 * 3600)Histogram/Summary#
Histogram 和 Summary 主要用于统计和分析样本的分布情况。
大多数情况下人们都倾向于使用某些量化指标的平均值,例如CPU的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统API调用的平均响应时间为例:如果大多数API请求都维持在100ms 的响应时间范围内,而个别请求的响应时间需要 5s,那么就会导致某些WEB页面的响应时间落到中位数的情况,这种现象被称为长尾问题
为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如:统计延迟在 010ms 之间的请求数有多少而 1020ms 之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram 和Summary 都是为了能够解决这样问题的存在,通过 Histogram 和 Summary 类型的监控指标,可以快速了解监控样本的分布情况。
例如:指标 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
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
prometheus_tsdb_wal_fsync_duration_seconds_count 216从上面的样本中可以得知当前 Prometheus Server 进行 wal_fsync 操作的总次数为216次,耗时 2.888716127000002s。其中中位数(quantile=0.5)的耗时为 0.012352463,9分位数(quantile=0.9)的耗时为 0.014458005s。
在 Prometheus Server 自身返回的样本数据中,还能找到类型为 Histogram 的监控指标prometheus_tsdb_compaction_chunk_range_bucket。
# 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对比 Summary 类型的指标:
- 相似之处在于: Histogram 类型的样本同样会反应当前指标的记录的总数(以
_count作为后缀)以及其值的总量(以_sum作为后缀) - 不同在于: Histogram 指标直接反应了在不同区间内样本的个数,区间通过标签
len进行定义
对于 Histogram 的指标,还可以通过 histogram_quantile() 函数计算出其值的分位数。不同在于:
- Histogram 通过
histogram_quantile函数是在服务器端计算的分位数(会消耗更多的资源) - Sumamry 的分位数则是直接在客户端计算完成(在通过 PromQL 进行查询时有更好的性能表现)
选择这两种方式时应该按照自己的实际场景进行选择
PromQL#
Prometheus 通过指标名称(metrics name)以及对应的一组标签(labelset)来定义一条时间序列。指标名称反映了监控样本的基本标识,而 label 则在这个基本特征上为采集到的数 过滤,聚合,统计从而产生新的计算后的一条时间序列。
PromQL 是 Prometheus 内置的数据查询语言,其提供对时间序列数据丰富的查询,聚合以及逻辑运算能力的支持
查询时间序列#
当 Prometheus 通过 Exporter Node 采集到相应的监控指标样本数据后,可以通过 PromQL 对监控样本数据进行查询。比如:直接使用监控指标名称查询
prometheus_http_requests_total
# 等同于:
prometheus_http_requests_total{}该表达式返回指标名称为 http_requests_total 的所有时间序列:
prometheus_http_requests_total{code="200", handler="/-/ready", instance="localhost:9090", job="prometheus"}
prometheus_http_requests_total{code="200", handler="/api/v1/label/:name/values", instance="localhost:9090", job="prometheus"}标签过滤#
PromQL 支持根据时间序列的标签匹配模式来对时间序列进行过滤,目前主要支持两种匹配模式:
- 完全匹配
=:使用label=value选择标签满足表达式定义的时间序列 - 正则匹配
!=:使用label!=value根据标签匹配排除时间序列
比如:
prometheus_http_requests_total{instance="localhost:9090"}
prometheus_http_requests_total{instance!="localhost:9090"}除了使用完全匹配的方式对时间序列进行过滤以外,PromQL 还可以支持使用正则表达式作为匹配条件,多个表达式之间使用 | 进行分离:PromQL 支持使用 =~ 和 !~ 两种正则匹配模式:
- 使用
label=~regx表示选择那些标签符合正则表达式定义的时间序列 - 反之使用
label!~regx进行排除
比如,如果想查询多个环节下的时间序列序列可以使用如下表达式:
prometheus_http_requests_total{environment=~"staging|testing|development",method!="GET"}范围查询#
直接通过类似于 PromQL 表达式 process_http_request_total 查询时间序列时,返回值中只会包含该时间序列中最新的一个样本值,这样的返回结果为 瞬时向量。相应的表达式称为 瞬时向量表达式。
如果需要过去一段时间范围内的样本数据时,则需要使用 区间向量表达式。区间向量表达式和瞬时向量表达式差异在于:在区间向量表达式中需要定义时间选择的范围,时间范围通过时间范围选择器 [] 进行定义。例如,通过以下表达式可以选择最近5分钟内的所有样本数据:
http_request_total{}[5m]该表达式将会返回查询到的时间序列中最近5分钟的所有样本数据:
prometheus_http_requests_total{code="200", handler="/-/ready", instance="localhost:9090", job="prometheus"}
10 @1657164241.316
10 @1657164256.319
10 @1657164271.319
10 @1657164286.316
prometheus_http_requests_total{code="200", handler="/api/v1/label/:name/values", instance="localhost:9090", job="prometheus"}
10 @1657164241.316
10 @1657164256.319
10 @1657164271.319
10 @1657164286.316
11 @1657164301.316通过区间向量表达式查询到的结果称为 区间向量。
除了使用 m 表示分钟以外,PromQL 的时间范围选择器支持其它时间单位:
s- 秒m- 分钟h- 小时d- 天w- 周y- 年
时间位移#
在瞬时向量表达式或者区间向量表达式中,都是以当前时间为基准:
http_request_total{} # 瞬时向量表达式,选择当前最新的数据
http_request_total{}[5m] # 区间向量表达式,选择以当前时间为基准,5分钟内的数据如果想查询5分钟前的瞬时样本数据,或昨天一天区间内的样本数据可以使用位移操作,位移操作的关键字为 offset。可以使用 offset 执行时间位移操作:
http_request_total{} offset 5m
http_request_total{}[1d] offset 1d聚合操作#
一般如果描述样本特征的标签(label)在并非唯一的情况下,通过 PromQL 查询数据,会返回多条满足这些特征维度的时间序列。PromQL 提供的聚合操作可以用来对这些时间序列进行处理,形成一条新的时间序列:
# 查询系统所有http请求的总量
sum(http_request_total)
# 按照mode计算主机CPU的平均使用时间
avg(node_cpu) by (mode)
# 按照主机查询各个主机的CPU使用率
sum(sum(irate(node_cpu{mode!='idle'}[5m])) / sum(irate(node_cpu[5m]))) by (instance)操作符#
数学运算#
例如:通过指标 node_memory_free_bytes_total 获取到当前主机可用的内存空间大小,样本单位为 Bytes。如果要求使用 MB 作为单位响应数据,只需要将查询到的时间序列的样本值进行单位换算即可:
node_memory_free_bytes_total / (1024 * 1024)瞬时向量与标量之间进行数学运算时:数学运算符会依次作用于瞬时向量中的每一个样本值,从而得到一组新的时间序列
瞬时向量与瞬时向量之间进行数学运算时,过程会相对复杂一点。 例如,如果想根据 node_disk_bytes_written 和 node_disk_bytes_read 获取主机磁盘IO的总量,可以使用如下表达式:
node_disk_bytes_written + node_disk_bytes_read工作原理是:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。同时新的时间序列将不会包含指标名称。 该表达式返回结果的示例如下所示:
{device="sda",instance="localhost:9100",job="node_exporter"}=>1634967552@1518146427.807 + 864551424@1518146427.807
{device="sdb",instance="localhost:9100",job="node_exporter"}=>0@1518146427.807 + 1744384@1518146427.807PromQL 支持的数学运算符如下所示:
+(加法)-(减法)*(乘法)/(除法)%(求余)^(幂运算)
布尔运算#
在 PromQL 通过标签匹配模式,可以根据时间序列的特征维度对其进行查询。而布尔运算则支持根据时间序列中样本的值,对时间序列进行过滤。
例如:通过数学运算符可以计算出当前所有主机节点的内存使用率:
(node_memory_bytes_total - node_memory_free_bytes_total) / node_memory_bytes_total而如果只想知道当前内存使用率超过95%的主机通过使用布尔运算符可以方便的获取到该结果:
(node_memory_bytes_total - node_memory_free_bytes_total) / node_memory_bytes_total > 0.95瞬时向量与标量进行布尔运算时:PromQL 依次比较向量中的所有时间序列样本的值,如果比较结果为 true 则保留,反之丢弃。
瞬时向量与瞬时向量直接进行布尔运算时:同样遵循默认的匹配模式:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行相应的操作,如果没找到匹配元素,则直接丢弃。
目前,Prometheus支持以下布尔运算符如下:
==(相等)!=(不相等)>(大于)<(小于)>=(大于等于)<=(小于等于)
bool 修饰符#
布尔运算符的默认行为是对时序数据进行过滤。而在其它的情况下可能需要的是真正的布尔结果。例如,只需要知道当前模块的 HTTP 请求量是否 >=1000,如果大于等于1000则返回1(true)否则返回0(false)。这时可以使用 bool 修饰符改变布尔运算的默认行为。 例如:
http_requests_total > bool 1000使用 bool 修改符后,布尔运算不会对时间序列进行过滤,而是直接依次瞬时向量中的各个样本数据与标量的比较结果0或者1,从而形成一条新的时间序列。
http_requests_total{code="200",handler="query",instance="localhost:9090",job="prometheus",method="get"} 1
http_requests_total{code="200",handler="query_range",instance="localhost:9090",job="prometheus",method="get"} 0同时需要注意的是,如果是在两个标量之间使用布尔运算,则必须使用 bool 修饰符
2 == bool 2 # 结果为1集合运算符#
使用瞬时向量表达式能够获取到一个包含多个时间序列的集合,称为瞬时向量。 通过集合运算,可以在两个瞬时向量之间进行相应的集合操作。目前 Prometheus 支持以下集合运算符:
and(并且)or(或者)unless(排除)
vector1 and vector2 产生一个由 vector1 的元素组成的新的向量。该向量包含 vector1 中完全匹配 vector2 中的元素组成
vector1 or vector2 产生一个新的向量,该向量包含 vector1 中所有的样本数据,以及 vector2 中没有与 vector1 匹配到的样本数据
vector1 unless vector2 产生一个新的向量,新向量中的元素由 vector1 中没有与 vector2 匹配的元素组成
操作符优先级#
对于复杂类型的表达式,需要了解运算操作的运行优先级,PromQL 操作符中优先级由高到低依次为:
^*, /, %+, -==, !=, <=, <, >=, >and, unlessor
匹配模式#
向量与向量之间进行运算操作时会基于默认的匹配规则:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。
在 PromQL 中有两种典型的匹配模式:一对一(one-to-one),多对一(many-to-one)或一对多(one-to-many)。
一对一匹配#
一对一匹配模式会从操作符两边表达式获取的瞬时向量依次比较并找到唯一匹配(标签完全一致)的样本值。默认情况下,使用表达式:
vector1 <operator> vector2在操作符两边表达式标签不一致的情况下,可以使用 on(label list) 或者 ignoring(label list)修改便签的匹配行为。使用 ignoreing 可以在匹配时忽略某些标签。而 on 则用于将匹配行为限定在某些标签之内。
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>例如当存在样本:
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使用 PromQL 表达式:
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m该表达式会返回在过去5分钟内,HTTP请求状态码为500的在所有请求中的比例。如果没有使用 ignoring(code),操作符两边表达式返回的瞬时向量中将找不到任何一个标签完全相同的匹配项。
因此结果如下:
{method="get"} 0.04 // 24 / 600
{method="post"} 0.05 // 6 / 120同时由于method为put和del的样本找不到匹配项,因此不会出现在结果当中。
多对一和一对多#
多对一和一对多两种匹配模式指的是“一”侧的每一个向量元素可以与"多"侧的多个元素匹配的情况。在这种情况下,必须使用 group 修饰符:group_left 或者 group_right 来确定哪一个向量具有更高的基数(充当“多”的角色)。
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况。因此需要使用 ignoring 和 on 修饰符来排除或者限定匹配的标签列表。
例如表达式:
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m该表达式中,左向量 method_code:http_errors:rate5m 包含两个标签method和code。而右向量method:http_requests:rate5m 中只包含一个标签 method,因此匹配时需要使用 ignoring 限定匹配的标签为code。 在限定匹配标签后,右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一,需要使用 group 修饰符 group_left 指定左向量具有更好的基数。
最终运算结果如下:
{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注意:group 修饰符只能在比较和数学运算符中使用。在逻辑运算
and,unless和or才注意操作中默认与右向量中的所有元素进行匹配。
聚合操作#
Prometheus 还提供了下列内置的聚合操作符,这些操作符作用域瞬时向量。用于将瞬时表达式返回的样本数据进行聚合,形成一个新的时间序列。
sum(求和)min(最小值)max(最大值)avg(平均值)stddev(标准差)stdvar(标准差异)count(计数)count_values(对value进行计数)bottomk(后n条时序)topk(前n条时序)quantile(分布统计)
使用聚合操作的语法如下:
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]其中只有 count_values、quantile、topk、bottomk 支持参数(parameter)。
without/by#
without:用于从计算结果中移除列举的标签而保留其它标签by:正好相反,结果向量中只保留列出的标签,其余标签则移除
通过 without 和 by 可以按照样本的问题对数据进行聚合。
例如:
sum(http_requests_total) without (instance)
等价于:
sum(http_requests_total) by (code,handler,job,method)如果只需要计算整个应用的HTTP请求总量,可以直接使用表达式:
sum(http_requests_total)count_values#
count_values 用于时间序列中每一个样本值出现的次数。count_values 会为每一个唯一的样本值输出一个时间序列,并且每一个时间序列包含一个额外的标签。
例如:
count_values("count", http_requests_total)topk/boottomk#
topk 和 bottomk 则用于对样本值进行排序,返回当前样本值前n位,或者后n位的时间序列。
例如:获取HTTP请求数前5位的时序样本数据,可以使用表达式:
topk(5, http_requests_total)quantile#
quantile 用于计算当前样本数据值的分布情况 quantile(φ, express) 其中 0 ≤ φ ≤ 1。
例如:当φ为0.5时,即表示找到当前样本数据中的中位数:
quantile(0.5, http_requests_total)