距离上一次更新该文章已经过了 379 天,文章所描述的內容可能已经发生变化,请留意。
介绍 Prometheus 是一套成熟且流行的系统和服务监控系统,它几乎满足了监控的所有能力。 Grafana , 它和Prometheus相比更侧重的是图形化展示,有强大、灵活的仪表盘体系,我们会把基于Prometheus收集的数据作为数据源导入到Grafana。
监控模式 目前,监控系统采集指标有两种方式,一种是『推』,另一种就是『拉』:
推的代表有 ElasticSearch,InfluxDB,OpenTSDB 等,需要你从程序中将指标使用 TCP,UDP 等方式推送至相关监控应用,只是使用 TCP 的话,一旦监控应用挂掉或存在瓶颈,容易对应用本身产生影响,而使用 UDP 的话,虽然不用担心监控应用,但是容易丢数据。
拉的代表,主要代表就是 Prometheus,让我们不用担心监控应用本身的状态。而且可以利用 DNS-SRV 或者 Consul 等服务发现功能就可以自动添加监控。
如何监控 Prometheus 监控应用的方式非常简单,只需要进程暴露了一个用于获取当前监控样本数据的 HTTP 访问地址。这样的一个程序称为Exporter ,Exporter 的实例称为一个 Target 。Prometheus 通过轮训的方式定时从这些 Target 中获取监控数据样本,对于应用来讲,只需要暴露一个包含监控数据的 HTTP 访问地址即可,当然提供的数据需要满足一定的格式,这个格式就是 Metrics 格式.
1 metric name>{<label name>=<label value>, ...}
主要分为三个部分 各个部分需符合相关的正则表达式
metric name:指标的名称,主要反映被监控样本的含义 a-zA-Z_:*
_ label name: 标签 反映了当前样本的特征维度 [a-zA-Z0-9_]*
label value: 各个标签的值,不限制格式 需要注意的是,label value 最好使用枚举值,而不要使用无限制的值,比如用户 ID,Email 等,不然会消耗大量内存,也不符合指标采集的意义。
四种指标类型 计数器(Counter) Counter类型指标被用于单调增加的测量结果。 因此它们总是累积的数值,值只能上升。唯一的例外是Counter重启,在这种情况下,它的值会被重置为零。
Counter的实际值通常本身并不十分有用。 一个计数器的值经常被用来计算两个时间戳之间的delta或者随时间变化的速率。
例如,Counter的一个典型用例是记录API调用次数,这是一个总是会增加的测量值。
1 2 3 http_requests_total{api ="add_product" } 4633433
指标名称是http_requests_total,它有一个名为api的标签,值为add_product,Counter的值为4633433。这意味着自从上次服务启动或Counter重置以来,add_product的API已经被调用了4633433次。按照惯例,Counter类型的指标通常以_total为后缀。
这个绝对数字并没有给我们提供多少信息,但当与PromQL的rate函数(或其他监控后端的类似函数)一起使用时,它可以帮助我们了解该API每秒收到的请求数。下面的PromQL查询计算了过去5分钟内每秒的平均请求数。
1 rate (http_requests_total{api="add_product"}[5 m])
为了计算一段时期内的绝对变化,我们将使用delta函数,在PromQL中称为increate():
1 increase (http_requests_total{api="add_product"}[5 m])
这将返回过去5分钟内的总请求数,这相当于用每秒的速率乘以间隔时间的秒数(在我们的例子中是5分钟):
1 rate (http_requests_total{api="add_product"}[5 m]) * 5 * 60
其他你可能会使用Counter类型指标的例子:测量电子商务网站的订单数量,在网络接口上发送和接收的字节数,或者应用程序中的错误数量。如果它是一个会一直上升的指标,那么就使用一个Counter。
下面是一个例子,说明如何使用Prometheus客户端库在Python中创建和增加一个计数器指标:
1 2 3 4 5 6 7 from prometheus_client import Counterapi_requests_counter = Counter( 'http_requests_total' , 'Total number of http api requests' , ['api' ] ) api_requests_counter.labels(api='add_product' ).inc()
需要注意的是,由于Counter可以被重置为零,你要确保你用来存储和查询指标的后端能够支持这种情况,并且在Counter重启的情况下仍然提供准确的结果。Prometheus和兼容PromQL的Prometheus远程存储系统,如Promscale,可以正确处理Counter重启。
仪表(Gauge) Gauge指标用于可以任意增加或减少的测量。 这是你可能更熟悉的指标类型,因为即使没有经过额外处理的实际值也是有意义的,它们经常被使用到。例如,测量温度、CPU和内存使用的指标,或者队列的大小都是Gauge。
例如,为了测量一台主机的内存使用情况,我们可以使用一个Gauge指标,比如:
1 2 3 node_memory_used_bytes{hostname ="host1.domain.com" } 943348382
上面的指标表明,在测量时,节点host1.domain.com使用的内存约为900 MB。该指标的值是有意义的,不需要任何额外的计算,因为它告诉我们该节点上消耗了多少内存。
与使用Counter指标时不同,rate和delta函数对Gauge没有意义。然而,计算特定时间序列的平均数、最大值、最小值或百分比的函数经常与Gauge一起使用。在Prometheus中,这些函数的名称是avg_over_time、max_over_time、min_over_time和quantile_over_time。要计算过去10分钟内在host1.domain.com上使用的平均内存,你可以这样做:
1 avg_over_time (node_memory_used_bytes{hostname="host1.domain.com"}[10 m])
要使用Prometheus客户端库在Python中创建一个Gauge指标,你可以这样做:
1 2 3 4 5 6 7 from prometheus_client import Gaugememory_used = Gauge( 'node_memory_used_bytes' , 'Total memory used in the node in bytes' , ['hostname' ] ) memory_used.labels(hostname='host1.domain.com' ).set (943348382 )
直方图(Histogram) Histogram指标对于表示测量的分布很有用。它们经常被用来测量请求持续时间或响应大小。
直方图将整个测量范围划分为一组区间,称为桶,并计算每个桶中有多少测量值。
一个直方图指标包括几个项目:
一个包含测量次数的Counter。指标名称使用_count后缀。
一个包含所有测量值之和的Counter。指标名称使用_sum后缀。
直方图桶被暴露为一系列的Counter,使用指标名称的后缀_bucket和表示桶的上限的le label。Prometheus中的桶是包含桶的边界的,即一个上限为N的桶(即le label)包括所有数值小于或等于N的数据点。
例如,测量运行在host1.domain.com实例上的add_productAPI端点实例的响应时间的Histogram指标可以表示为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 http_request_duration_seconds_sum{api="add_product" instance="host1.domain.com" } 8953.332 http_request_duration_seconds_count{api="add_product" instance="host1.domain.com" } 27892 http_request_duration_seconds_bucket{api="add_product" instance="host1.domain.com" le="0" } http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="0.01" } 0 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="0.025" } 8 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="0.05" } 1672 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="0.1" } 8954 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="0.25" } 14251 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="0.5" } 24101 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="1" } 26351 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="2.5" } 27534 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="5" } 27814 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="10" } 27881 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="25" } 27890 http_request_duration_seconds_bucket{api="add_product" , instance="host1.domain.com" , le="+Inf" } 27892
上面的例子包括sum、counter和12个桶。sum和counter可以用来计算一个测量值随时间变化的平均值。在PromQL中,过去5分钟的平均请求响应时间可以通过如下方式计算得到。
1 rate (http_request_duration_seconds_sum{api="add_product" , instance="host1.domain.com" }[5 m]) / rate (http_request_duration_seconds_count{api="add_product" , instance="host1.domain.com" }[5 m])
它也可以被用来计算各时间序列的平均数。下面的PromQL查询将计算出所有API和实例在过去5分钟内的平均请求响应时间。
1 sum(rate (http_request_duration_seconds_sum [5 m])) / sum(rate (http_request_duration_seconds_count [5 m]))
利用Histogram,你可以在查询时计算单个时间序列以及多个时间序列的百分位。 在PromQL中,我们将使用histogram_quantile函数。Prometheus使用分位数而不是百分位数。它们本质上是一样的,但是以0到1的比例表示的,而百分位数是以0到100的比例表示的。要计算在host1.domain.com上运行的add_product API响应时间的第99百分位数(0.99四分位数),你可以使用以下查询。
1 histogram_quantile(0.99 , rate(http_request_duration_seconds_bucket {api="add_product" , instance="host1.domain.com" }[5 m]))
Histograms的一大优势是可以进行汇总。下面的查询返回所有API和实例的响应时间的第99个百分点:
1 histogram_quantile(0.99 , sum by (le ) (rate (http_request_duration_seconds_bucket [5 m])))
在云原生环境中,通常有许多相同组件的多个实例在运行,能否跨实例汇总数据是关键。
Histograms有三个主要的缺点:
如果你的桶没有被很好地定义,你可能无法计算出你需要的百分比,或者会消耗不必要的资源。例如,如果你有一个总是需要超过一秒钟的API,那么拥有上限(le label)小于一秒钟的桶将是无用的,只会消耗监控后端服务器的计算和存储资源。另一方面,如果99.9%的API请求耗时少于50毫秒,那么拥有一个上限为100毫秒的初始桶将无法让你准确测量API的性能。
第二,他们提供的是近似的百分位数,而不是精确的百分位数。 这通常没什么问题,只要你的桶被设计为提供具有合理准确性的结果。
第三,由于百分位数需要在服务器端计算,当有大量数据需要处理时,它们的计算成本会非常高。 在Prometheus中减轻这种情况的一个方法是使用录制规则来预先计算所需的百分位数。
下面的例子显示了如何使用Prometheus的Python客户端库创建一个带有自定义桶的直方图指标。
1 2 3 4 5 6 7 8 9 10 11 from prometheus_client import Histogramapi_request_duration = Histogram( name='http_request_duration_seconds' , documentation='Api requests response time in seconds' , labelnames=['api' , 'instance' ], buckets=(0.01 , 0.025 , 0.05 , 0.1 , 0.25 , 0.5 , 1 , 2.5 , 5 , 10 , 25 ) ) api_request_duration.labels( api='add_product' , instance='host1.domain.com' ).observe(0.3672 )
汇总(Summary) 像直方图一样,Summary指标对于测量请求持续时间和响应体大小很有用。
像直方图一样,汇总度量对于测量请求持续时间和响应大小很有用。
一个Summary指标包括这些指标:
例如,测量在host1.domain.com上运行的add_productAPI端点实例的响应时间的Summary指标可以表示为:
1 2 3 4 5 6 7 8 9 10 http_request_duration_seconds_sum{api="add_product" instance="host1.domain.com" } 8953.332 http_request_duration_seconds_count{api="add_product" instance="host1.domain.com" } 27892 http_request_duration_seconds{api="add_product" instance="host1.domain.com" quantile="0" } http_request_duration_seconds{api="add_product" instance="host1.domain.com" quantile="0.5" } 0.232227334 http_request_duration_seconds{api="add_product" instance="host1.domain.com" quantile="0.90" } 0.821139321 http_request_duration_seconds{api="add_product" instance="host1.domain.com" quantile="0.95" } 1.528948804 http_request_duration_seconds{api="add_product" instance="host1.domain.com" quantile="0.99" } 2.829188272 http_request_duration_seconds{api="add_product" instance="host1.domain.com" quantile="1" } 34.283829292
上面这个例子包括总和和计数以及五个分位数。分位数0相当于最小值,分位数1相当于最大值。分位数0.5是中位数,分位数0.90、0.95和0.99相当于在host1.domain.com上运行的add_product API端点响应时间的第90、95和99个百分位。
像直方图一样,Summary指标包括总和和计数,可用于计算随时间的平均值以及不同时间序列的平均值。
Summary提供了比Histogram更精确的百分位计算结果,但这些百分位有三个主要缺点:
首先,客户端计算百分位是很昂贵的。 这是因为客户端库必须保持一个有序的数据点列表,以进行这种计算。在Prometheus SDK中的实现限制了内存中保留和排序的数据点的数量,这降低了准确性以换取效率的提高。注意,并非所有的Prometheus客户端库都支持汇总指标中的量值。例如,Python SDK就不支持。
第二,你要查询的量值必须由客户端预先定义。 只有那些已经提供了指标的量值才能通过查询返回。没有办法在查询时计算其他百分位。增加一个新的百分位指标需要修改代码,该指标才可以被使用。
第三,也是最重要的一点,不可能把多个Summary指标进行聚合计算。 这使得它们对动态现代系统中的大多数用例毫无用处,在这些用例中,通常我们对一个特定的组件感兴趣,这个视角是全局的,它不与特定的实例关联。
因此,想象一下,在我们的例子中,add_product的API端点运行在10个主机上,在这些服务之前有一个负载均衡器。我们没有任何聚合函数可以用来计算add_product API接口在所有请求中响应时间的第99百分位数,无论这些请求被发送到哪个后端实例上。我们只能看到每个主机的第99个百分点。同样地,我们也只能知道某个接口,比如add_productAPI端点的(在某个实例上的)第99百分位数,而不能对不同的接口进行聚合。
下面的代码使用Prometheus的Python客户端库创建了一个Summary指标。
1 2 3 4 5 6 7 from prometheus_client import Summaryapi_request_duration = Summary( 'http_request_duration_seconds' , 'Api requests response time in seconds' , ['api' , 'instance' ] ) api_request_duration.labels(api='add_product' , instance='host1.domain.com' ).observe(0.3672 )
上面的代码没有定义任何量化指标,只会产生总和和计数指标。Prometheus的Python SDK不支持Summary指标中的分位数计算。
Histogram还是Summary?
在大多数情况下,直方图是首选,因为它更灵活,并允许汇总百分位数。
在不需要百分位数而只需要平均数的情况下,或者在需要非常精确的百分位数的情况下,汇总是有用的。例如,在履行关键系统的合约责任的情况下。
下表总结了直方图和汇总表的优点和缺点。
指标信息 以Nodejs + Koa举例, 整合prom-client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import {register, collectDefaultMetrics, Counter , Histogram } from 'prom-client' collectDefaultMetrics ()const counter = new Counter ({ name : 'http_requests_total' , help : 'Counter for total requests received' , labelNames : ['code' , 'path' , 'method' , 'status' ], }) const hrtime2ms = (hrtime ) => (hrtime[0 ] * 1e9 + hrtime[1 ]) / 1e6 const histogram = new Histogram ({ name : `http_request_duration_ms` , help : 'Duration of HTTP requests in ms' , labelNames : ['code' , 'path' , 'method' , 'status' ], buckets : [5 , 10 , 25 , 50 , 100 , 250 , 500 , 1000 ], }) const app = new Koa ();app.use (async (ctx, next) => { if (ctx.path === '/metrics' ) { ctx.set ('Content-Type' , register.contentType ) ctx.body = await register.metrics () return } const start = process.hrtime () try { await next () } catch (ignored) { } let code if (ctx.response .status && ctx.response .status !== 200 ) { code = ctx.response .status } else { code = ctx.response .body ? ctx.response .body .code : 1 } counter.inc ({ code, code : ctx.body .code , path : ctx.path , method : ctx.method , status : ctx.status , }, 1 ) const dur = hrtime2ms (process.hrtime (start)) histogram.observe ({ code : ctx.body .code , path : ctx.path , method : ctx.method , status : ctx.status , }, dur) }) app.listen (port, '0.0.0.0' ) .on ('listening' , () => { });
访问/metrics接口会返回以下信息
1 2 3 4 5 6 7 8 9 10 11 ...... http_requests_total{code="0",path="/api/manager/v2.0/messages",method="GET",status="200"} 107 http_requests_total{code="0",path="/api/manager/v2.0/tenant/brands",method="GET",status="200"} 47 http_request_duration_ms_bucket{le="5",code="0",path="/api/manager/v2.0/messages",method="GET",status="200"} 0 http_request_duration_ms_bucket{le="10",code="0",path="/api/manager/v2.0/messages",method="GET",status="200"} 0 http_request_duration_ms_sum{code="0",path="/api/manager/v2.0/messages",method="GET",status="200"} 8670.540011999998 http_request_duration_ms_count{code="0",path="/api/manager/v2.0/messages",method="GET",status="200"} 107
#HELP
是一个指标的描述文案,可以解释这个指标的功能 #TYPE
是一个指标的类型描述,前面的代表这个指标的名称nodejs_heap_space_size_total_bytes
空格后边的代表这个指标的类型,gauge
不带#
的就是指标的真实值,它的形式是指标名+值
。{}
中的内容代表了这个指标的label,也就是这个指标还可以再分属性,比如这个指标是nodejs 堆内存的大小,里边还可以根据space属性分成read_only
,old
等等。 所有的指标都是这种形式。
PromQL Prometheus 提供了一种称为 PromQL(Prometheus Query Language) 的功能性查询语言,让用户可以实时选择和聚合时间序列数据。
时间序列的理解 我们定义的各种指标是定时被Prometheus抓取的,那么它的存储结构就是以时间为横轴的数据。 我们在prometheus中输入一个指标的时候 获取的是最新一次的值
如果我们加上一个时间范围[1m]代表获取1分钟内的数据。 下图可以看到每一个指标可以获取4个值,因为我们是15秒抓取一次数据。
可以理解成是点数据和时间段数据的区别。
当时点数据的时候我们切换到Graph面板就会展示以时间做为横轴,值作为纵轴的图表。
即时矢量选择器 我们一个指标中可能定义了label,只有指标名称是无法区分label,即时矢量选择器用于对label进行选择。 {}
用于写label的选择器,支持的语法有:
=
:选择与提供的字符串完全相等的标签。!=
:选择不等于提供的字符串的标签。=~
:选择与提供的字符串进行正则表达式匹配的标签。!~
:选择与提供的字符串不匹配的标签。例如:
1 2 3 nodejs_heap_space_size_total_bytes{space ="large_object" } nodejs_heap_space_size_total_bytes{space!="large_object"} nodejs_heap_space_size_total_bytes{space =~"new.*" }
其中正则表达式的匹配可以认为是 /^$/
完全匹配, 例如:
范围矢量选择器 是选择一定范围内的多个样本。持续时间可以写到[]
中,支持的单位包括:
ms
- 毫秒s
- 秒m
- 分钟h
- 小时d
- 天 - 假设一天总是 24 小时w
- 周 - 假设一周总是 7 天y
- 年 - 假设一年总是 365d例如:
1 nodejs_heap_space_size_total_bytes{space =~"new.*" }[1 m]
偏移修改器(不常用) offset
修饰符允许更改查询中各个瞬间和范围向量的时间偏移量。 例如
1 nodejs_heap_space_size_total_bytes[1m]
得到的时间是1656819991.26
而
1 nodejs_heap_space_size_total_bytes[1m] offset 1 m
得到的时间是1656819931.333
向后偏移了一分钟
@修饰符(不常用) 默认指标获取的都是当前时间的指标值,而@修饰符可以指定获取哪一个时间点的值,例如: 1 nodejs_heap_space_size_total_bytes @1656820184
运算 支持的全量运算参考文档 这里说一些常用的运算。 算术运算 Prometheus 中存在以下二元算术运算符:
+
(添加)-
(减法)*
(乘法)/
(分配)%
(模数)^
(幂/幂)比如内存默认单位是byte 我们展示的时候展示MB就可以
1 nodejs_heap_size_total_bytes/1024/1024
常用的聚合运算 因为我们的服务一般都是多实例的,所以需要在统计的时候把所有实例的数据聚合到一起。
sum
(计算维度总和)min
(选择最小尺寸)max
(选择最大尺寸)avg
(计算尺寸的平均值)group
(结果向量中的所有值都是 1)stddev
(计算维度上的总体标准偏差)stdvar
(计算维度上的总体标准方差)count
(计算向量中的元素个数)count_values
(计算具有相同值的元素个数)bottomk
(样本值的最小 k 个元素)topk
(按样本值计算的最大 k 个元素)quantile
(在维度上计算 φ-quantile (0 ≤ φ ≤ 1))histogram_quantile
(可以计算不同分位指标)举例:
1 2 sum (nodejs_heap_space_size_total_bytes) by (space)topk (2 , nodejs_heap_space_size_total_bytes)
常用的函数 全量函数参见文档 , 这里讲解3种计算增长率的方法用到的函数及他们的区别。
increase()
计算区间向量中时间序列的增量,它只计算增量,所以想要计算增长率则需要手动的去除以时间。1 increase (http_requests_total[1 m])/60
rate()
方法用于计算区间向量时间范围内的增长率,秒为单位。1 rate (http_requests_total[1 m])
它计算增长率的方式是时间范围内最后的样本和第一个样本的差值除以时间,所以可能会出现中间某一个时间内增长率高而无法统计到,可能被整个时间范围给平均了,所以一种方法是把时间范围设置的短一些,第二种就是使用irate
。
irate()
也是计算区间向量时间范围内的增长率,但是他是瞬时增长率。基于时间范围内最后两个数据点计算。 所以总结就是irate
更灵敏而rate
侧重时间段内的趋势。基本查询 查询指标最新的值:
1 2 3 4 5 6 7 8 9 10 {__name__ ="http_request_total" , handler ="/home" } http_request_total{handler ="/home" } select * from http_request_total where handler ="/home" AND create_time =《now()》
区间时间段查询 查询过去一分钟内的数据
1 2 3 4 5 6 # promQL http_request_total[1 m] # 等价于 SELECT * from http_requests_total WHERE create_time BETWEEN 《now() - 1 min》 AND 《now()》;
时间偏移查询 PS: promQL 不支持指定时间点进行查询,只能通过 offset 来查询历史某个点的数据
查询一个小时前的数据。
1 2 3 4 5 6 # promQL http_request_total offset 1 h # 等价于 SELECT * from http_requests_total WHERE create_time=《now() - 1 hour》;
promQL 查询函数 根据以上的查询语法,我们可以简单组合出一些指标数据:
例如,查询最近一天内的 /home 页请求数
1 http_request_total{handler ="/home"} - http_request_total{handler ="/home"} offset 1 d
那么实际上面这个写法很明显比较不简洁,我们可使用内置 increase 函数来替换:
1 2 increase (http_request_total{handler="/home" }[1 d])
除了 increase 外,还有很多其他好用的函数,例如, rate 函数计算 QPS
1 2 3 4 5 rate (http_request_total{code="400" }[2 m]) increase (http_request_total{code="400" }[2 m]) / 120
指标聚合查询 除了上述基础查询外,我们可能还需要聚合查询
假如我们有以下数据指标:
1 2 3 4 credit_insight_spl_id_all_pv {url= "/home" ,channel= "none" } credit_insight_spl_id_all_pv {url= "/home" ,channel= "mepage" } credit_insight_spl_id_all_pv {url= "/error" ,channel= "none" } credit_insight_spl_id_all_pv {url= "/error" ,channel= "mepage" }
将所有指标数据以某个维度进行聚合查询时,例如:查询 url="/home" 最近一天的访问量,channel 是 none还是mepage 的 /home 访问量都包括在内。
我们理所当然地会写出:
1 increase (credit_insight_spl_id_all_pv{url="/home" }[1 d])
但实际上我们会得出这样的两条指标结果:
1 2 credit_insight_spl_id_all_pv {url="/home" ,channel="none" } 233 credit_insight_spl_id_all_pv {url="/home" ,channel="mepage" } 666
并非我们预期中的:
1 credit_insight_spl_id_all_pv{url= "/home" } 899
而要是我们想要得到这样的聚合查询结果,就需要用到 sum by
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 聚合 url="/home" 的数据 sum(increase(credit_insight_spl_id_all_pv{url="/home"}[1 d])) by (url) # 得出结果: credit_insight_spl_id_all_pv{url="/home"} 899 # 所有 channel 中 /home 页访问量累加值 # 聚合所有的 url 则可以这样写: sum(increase(credit_insight_spl_id_all_pv{}[1 d])) by (url) # 得出结果: credit_insight_spl_id_all_pv{url="/home"} 899 credit_insight_spl_id_all_pv{url="/error"} 7 # 等价于 mysql SELECT url, COUNT(*) AS total FROM credit_insight_spl_id_all_pv WHERE create_time between <now() - 1 d> and <now()>GROUP BY url;
指标时序曲线 以上的所有例子的查询数值,其实都是最近时间点 的数值,
而我们更关注的是一个时间段 的数值变化。
要实现这个原理也很简单,只需要在历史的每个时间点都执行一次指标查询,
1 2 3 4 5 6 7 8 9 # 假如今天7号 # 6号到7号的一天访问量 sum (increase(credit_insight_spl_id_all_pv{}[1 d] )) by (url) # 5号到6号的一天访问量 offset 1d sum (increase(credit_insight_spl_id_all_pv{}[1 d] offset 1 d)) by (url) # 4号到5号的一天访问量 sum (increase(credit_insight_spl_id_all_pv{}[1 d] offset 2 d)) by (url)
而 Prometheus 已经内置了时间段查询功能,并对此优化处理。
可通过 /api/v1/query_range
接口进行查询,获的 grpah:
Prometheus 查询瓶颈 数据存储:
指标数据有 “Writes are vertical,reads are horizontal” 的(垂直写,水平读)模式: “Writes are vertical,reads are horizontal” 的意思是 tsdb 通常按固定的时间间隔收集指标并写入,会 “垂直” 地写入最近所有时间序列的数据,而读取操作往往面向一定时间范围的一个或多个时间序列,“横向” 地跨越时间进行查询
每个指标(metric)根据指标数量不同,有 labelA labelB labelC * ... 个时序图 每个时序图(time series)的一个点时序是 [timestamp, value], 例如 [1605607257, 233]。[时间戳-值] 可以确定图上的一个点,一个时间区间内的所有点连成一个时序曲线图。 因为 Prometheus 每隔 15s 采集一次数据,所以 时序点的时间间距是 15s,即1分钟有60/15=4个时序点,1小时就有 4 * 60 = 240 个时序点。 而 Prometheus 的默认查询 sample 上限是 5000w
所以,如果指标的时序图数量过大,允许查询的时间区间相对就会较小了 。
一个图表查询时序数量的影响因素有 3 个,分别是:
查询条件的时序数量(n) 查询的时间区间(time) 图表曲线每个时序点之间的间隔(step) 以 credit_insight_spl_id_all_pv
指标为例,该指标总共大约有 n = 163698 种时序,
假如 step = 15s,如果搜索该指标过去 time = 60m 的全部时序图,那么,需要搜索的例子要163698 * 60 * (60/15) = 39287520
,将近 4kw,是可以搜出来的。
但如果搜的是过去 90m 的数据,163698 * 90 * 4 = 58931280
,超过了 5000w,你就发现数据请求异常:Error executing query: query processing would load too many samples into memory in query execution
所以,目测可得一个图的查询时序点数量公式是:total = n * time / step, time 和 step 的时间单位必须一致,total 必须不超过 5000w。
反推一下得出,time < 5000w / n * step 。要扩大搜索时间范围,增大 step ,或者降低 n 即可做到。
step 不变, 降低 n 【指定label值可减少搜索条件的结果数】 : credit_insight_spl_id_all_pv{systemType="Android", systemVersion="10"}
,n = 18955 当然,一般情况下,我们的 n 值只有几百,而 step 基本是大于 60s 的,所以一般情况下都能查询 2 个多月以上的数据图。
Admin Api prom默认从tsdb只能查询,如果要删除数据,需要在启动时添加参数
原生Prometheus Api 参考:https://prometheus.io/docs/prometheus/latest/querying/api
简言 使用RestTemplate作为远程调用工具调用prometheus原生api获取数据 通过访问prometheus原生api,查看原生api返回的数据格式,定义对应的实体类格式 下面所列功能代码,仅为部分调用api结果,仅供参考,如若需要调用其他api,可自行编写对应方法 远程调用类 pom依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <dependency > <groupId > org.apache.httpcomponents.client5</groupId > <artifactId > httpclient5</artifactId > <version > 5.2.1</version > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-lang3</artifactId > <version > 3.12.0</version > </dependency > <dependency > <groupId > com.google.guava</groupId > <artifactId > guava</artifactId > <version > 31.1-jre</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.83</version > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > 5.7.16</version > </dependency >
RestTemplate工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 import com.alibaba.fastjson.JSONObject;import com.google.common.base.Joiner;import com.google.common.base.Throwables;import javax.annotation.Resource;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.apache.hc.client5.http.classic.HttpClient;import org.apache.http.Header;import org.apache.http.client.config.RequestConfig;import org.apache.http.config.Registry;import org.apache.http.config.RegistryBuilder;import org.apache.http.conn.socket.ConnectionSocketFactory;import org.apache.http.conn.socket.PlainConnectionSocketFactory;import org.apache.http.conn.ssl.NoopHostnameVerifier;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;import org.apache.http.ssl.SSLContextBuilder;import org.springframework.http.client.ClientHttpRequestFactory;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.http.client.support.BasicAuthenticationInterceptor;import org.springframework.http.converter.StringHttpMessageConverter;import org.springframework.stereotype.Component;import org.springframework.web.client.DefaultResponseErrorHandler;import org.springframework.web.client.RestTemplate;import org.springframework.web.util.UriComponentsBuilder;import javax.net.ssl.HostnameVerifier;import javax.net.ssl.SSLContext;import java.net.URI;import java.net.URLDecoder;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Set;@Slf4j @Component @SuppressWarnings("all") public class RestTemplateUtils { public String getHttp (String url, JSONObject params) { return getRestConnection(url, params, "http" ); } public String getHttps (String url, JSONObject params) { return getRestConnection(url, params, "https" ); } private String getRestConnection (String url, JSONObject params, String connectionFlag) { String restConnection = null ; if (StringUtils.equals("http" , connectionFlag)) { restConnection = getRestHttpConnection(url, params, 10000 , 60000 , 3 ); } if (StringUtils.equals("https" , connectionFlag)) { restConnection = getRestHttpsConnection(url, params, 10000 , 60000 , 3 ); } return restConnection; } public String getRestHttpConnection (String url, JSONObject params, int connectTimeout, int readTimeout, int retryCount) { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory (); requestFactory.setConnectTimeout(connectTimeout); requestFactory.setReadTimeout(readTimeout); RestTemplate restTemplate = new RestTemplate (requestFactory); restTemplate.getMessageConverters().set(1 , new StringHttpMessageConverter (StandardCharsets.UTF_8)); restTemplate.setErrorHandler(new DefaultResponseErrorHandler ()); URI uri = getUriByUrl(url, params); for (int i = 1 ; i <= retryCount; i++) { try { restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor ("username" , "password" ); return restTemplate.getForEntity(uri, String.class).getBody(); } catch (Exception e) { log.error("[GET/HTTP请求信息]异常, 重试次数:{}, 请求地址:{}, 请求参数:{}, 异常信息:{}" , i, url, params, Throwables.getStackTraceAsString(e)); } } return null ; } public String getRestHttpsConnection (String url, JSONObject params, int connectTimeout, int readTimeout, int retryCount) { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory (); requestFactory.setConnectTimeout(connectTimeout); requestFactory.setReadTimeout(readTimeout); RestTemplate restTemplate = restTemplate(); clientHttpRequestFactory(); restTemplate.getMessageConverters().set(1 , new StringHttpMessageConverter (StandardCharsets.UTF_8)); restTemplate.setErrorHandler(new DefaultResponseErrorHandler ()); restTemplate.setRequestFactory(clientHttpRequestFactory()); URI uri = getUriByUrl(url, params); for (int i = 1 ; i <= retryCount; i++) { try { restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor ("username" , "password" ); return restTemplate.getForEntity(uri, String.class).getBody(); } catch (Exception e) { log.error("[GET/HTTPS请求信息]异常, 重试次数:{}, 请求地址:{}, 请求参数:{}, 异常信息:{}" , i, url, params, Throwables.getStackTraceAsString(e)); } } return null ; } public HttpClient httpClient () { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); try { SSLContext sslContext = new SSLContextBuilder ().loadTrustMaterial(null , (arg0, arg1) -> true ).build(); httpClientBuilder.setSSLContext(sslContext); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory (sslContext, hostnameVerifier); Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register(RestConnectionConstants.HTTP_CONNECTION_FLAG, PlainConnectionSocketFactory.getSocketFactory()) .register(RestConnectionConstants.HTTPS_CONNECTION_FLAG, sslConnectionSocketFactory).build(); PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager (socketFactoryRegistry); poolingHttpClientConnectionManager.setMaxTotal(1000 ); poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100 ); httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler (1 , true )); List<Header> headers = new ArrayList <>(); httpClientBuilder.setDefaultHeaders(headers); RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(10000 ) .setConnectTimeout(10000 ) .setSocketTimeout(60000 ).build(); httpClientBuilder.setDefaultRequestConfig(requestConfig); return (HttpClient) httpClientBuilder.build(); } catch (Exception e) { throw new RestException (RestStatus.SYSTEM_ERROR, Throwables.getStackTraceAsString(e)); } } public RestTemplate restTemplate () { return new RestTemplate (clientHttpRequestFactory()); } private ClientHttpRequestFactory clientHttpRequestFactory () { return new HttpComponentsClientHttpRequestFactory (httpClient()); } private URI getUriByUrl (String url, JSONObject params) { String query = "query" ; if (!params.isEmpty()) { if (params.containsKey(query)) { String replaceQuery = params.getString(query) .replace("=" , "%3D" ).replace(" " , "%20" ) .replace("{" , "%7B" ).replace("}" , "%7D" ) .replace("\"" , "%22" ).replace("/" , "%2F" ) .replace("|" , "%7C" ).replace("+" , "%2B" ) .replace("[" , "%5B" ).replace("]" , "%5D" ) .replace("<" , "%3C" ).replace(">" , "%3E" ) .replace("\n" , "%20" ); params.put(query, replaceQuery); } else { params.keySet().forEach(key -> { String decode = URLDecoder.decode(params.getString(key), StandardCharsets.UTF_8); params.put(key, decode); }); } url = expandUrl(url, params); } UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url); if (params.containsKey(query)) { return builder.build(true ).toUri(); } else { return builder.build().encode().toUri(); } } private String expandUrl (String url, JSONObject jsonObject) { HashMap<String, Object> paramMap = new HashMap <>(16 ); StringBuilder stringBuilder = new StringBuilder (url); stringBuilder.append("?" ); Set<String> keys = jsonObject.keySet(); keys.forEach(key -> paramMap.put(key, jsonObject.getString(key))); String joinStr = Joiner.on("&" ).withKeyValueSeparator("=" ).join(paramMap); return stringBuilder.append(joinStr).toString(); } }
即时查询获取数据 即时查询实体类 Prometheus查询结果实体类:PromQueryResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import lombok.Data;import java.util.List;import java.util.Map;@Data public class PromQueryResult { private Map<String, Object> metric; private List<String> value; }
Prometheus查询数据结果实体类:PromQueryData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import lombok.Data;import java.util.List;@Data public class PromQueryData { private String resultType; private List<PromQueryResult> result; }
Prometheus查询响应实体类:PromQueryResponse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import lombok.Data;@Data public class PromQueryResponse { private String status; private PromQueryData data; }
即时查询接口实现 即时查询接口类:PromQueryService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import xxx.entity.pojo.PromQueryData;public interface PromQueryService { PromQueryData getQueryDataInfo (String query, String time) ; }
即时查询接口实现类:PromQueryServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import cn.hutool.core.date.DateUtil;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.google.common.base.Throwables;import javax.annotation.Resource;import lombok.extern.slf4j.Slf4j;import xxx.entity.pojo.PromQueryData;import xxx.entity.pojo.PromQueryResponse;import xxx.service.PromQueryService;import xxx.util.RestTemplateUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.stereotype.Service;import java.util.Objects;@Slf4j @Service("promQueryService") public class PromQueryServiceImpl implements PromQueryService { @Resource private RestTemplateUtils restTemplateUtils; @Override public PromQueryData getQueryDataInfo (String query, String time) { if (StringUtils.isBlank(time)) { time = String.valueOf(DateUtil.currentSeconds()); } JSONObject param = new JSONObject (); param.put("query" , query); param.put("time" , time); String url = "http://localhost:9090" + "/api/v1/query" ; return (PromQueryData) getDataInfo(url, param); } private Object getDataInfo (String promUrl, JSONObject param) { String http = getHttp(promUrl, param); PromQueryResponse responseInfo = JSON.parseObject(http, PromQueryResponse.class); log.info("即时查询请求地址: {}, 请求参数: {}" , promUrl, param); if (Objects.isNull(responseInfo)) { return null ; } String status = responseInfo.getStatus(); if (StringUtils.isBlank(status) || !StringUtils.equals("success" , status)) { return null ; } return responseInfo.getData(); } private String getHttp (String promUrl, JSONObject param) { String http = null ; try { http = restTemplateUtils.getHttp(promUrl, param); } catch (Exception e) { log.error("请求地址: {}, 请求参数: {}, 异常信息: {}" , promUrl, param, Throwables.getStackTraceAsString(e)); } return http; } }
范围查询获取数据 范围查询实体类 范围查询实体类:PromQueryRange
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import lombok.Data;@Data public class PromQueryRange { private String query; private String start; private String end; private Integer step; }
Prometheus范围区间查询结果实体类:PromQueryRangeResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import lombok.Data;import java.util.List;import java.util.Map;@Data public class PromQueryRangeResult { private Map<String, Object> metric; private List<List<String>> values; }
Prometheus范围区间查询数据结果对象实体类:PromQueryRangeData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import lombok.Data;import java.util.List;@Data public class PromQueryRangeData { private String resultType; private List<PromQueryRangeResult> result; }
Prometheus范围区间查询响应对象实体类:PromQueryRangeResponse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import lombok.Data;@Data public class PromQueryRangeResponse { private String status; private PromQueryRangeData data; }
范围查询接口实现 范围查询接口类:PromQueryService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import xxx.entity.pojo.PromQueryRangeData;import xxx.entity.pojo.PromQueryRange;public interface PromQueryService { PromQueryRangeData getQueryRangeDataInfo (PromQueryRange queryRange) ; }
范围查询接口实现类:PromQueryServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 import cn.hutool.core.date.DateUtil;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.google.common.base.Throwables;import javax.annotation.Resource;import lombok.extern.slf4j.Slf4j;import xxx.entity.pojo.PromQueryRange;import xxx.entity.pojo.PromQueryRangeData;import xxx.entity.pojo.PromQueryRangeResponse;import xxx.service.PromQueryService;import xxx.util.RestTemplateUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.stereotype.Service;import java.util.Objects;@Slf4j @Service("promQueryService") public class PromQueryServiceImpl implements PromQueryService { @Resource private RestTemplateUtils restTemplateUtils; @Override public PromQueryRangeData getQueryRangeDataInfo (PromQueryRange queryRange) { JSONObject param = new JSONObject (); handleQueryRangeParams(param, queryRange); String url = "http://localhost:9090" + "/api/v1/query_range" ; return (PromQueryRangeData) getDataInfo(url, param); } private void handleQueryRangeParams (JSONObject param, PromQueryRange queryRange) { String start = queryRange.getStart(); if (StringUtils.isBlank(start)) { start = String.valueOf(DateUtil.currentSeconds()); } String end = queryRange.getEnd(); if (StringUtils.isBlank(end)) { end = String.valueOf(DateUtil.offsetHour(DateUtil.parse(start), 1 ).getTime()); } param.put("query" , queryRange.getQuery()); param.put("start" , start); param.put("end" , end); param.put("step" , queryRange.getStep()); } private Object getDataInfo (String promUrl, JSONObject param) { String http = getHttp(promUrl, param); PromQueryRangeResponse rangeResponse = JSON.parseObject(http, PromQueryRangeResponse.class); log.info("范围区间查询请求地址: {}, 请求参数: {}" , promUrl, param); if (Objects.isNull(rangeResponse)) { return null ; } String status = rangeResponse.getStatus(); if (StringUtils.isBlank(status) || !StringUtils.equals("success" , status)) { return null ; } return rangeResponse.getData(); } private String getHttp (String promUrl, JSONObject param) { String http = null ; try { http = restTemplateUtils.getHttp(promUrl, param); } catch (Exception e) { log.error("请求地址: {}, 请求参数: {}, 异常信息: {}" , promUrl, param, Throwables.getStackTraceAsString(e)); } return http; } }
根据标签匹配器获取时序数据 时序数据实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import lombok.Data;import java.util.LinkedHashMap;import java.util.List;@Data public class PromSeries { private String status; private List<LinkedHashMap<String, Object>> data; }
时序数据接口实现 根据标签匹配器获取时序数据接口类:PromQueryService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface PromQueryService { List<LinkedHashMap<String, Object>> getSeriesList (String start, String end, String match) ; }
根据标签匹配器获取时序数据接口实现类:PromQueryServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 import cn.hutool.core.date.DateUtil;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.google.common.base.Throwables;import javax.annotation.Resource;import lombok.extern.slf4j.Slf4j;import xxx.entity.pojo.PromQueryData;import xxx.entity.pojo.PromQueryResponse;import xxx.service.PromQueryService;import xxx.util.RestTemplateUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.stereotype.Service;import java.util.Objects;@Slf4j @Service("promQueryService") public class PromQueryServiceImpl implements PromQueryService { @Resource private RestTemplateUtils restTemplateUtils; @Override public List<LinkedHashMap<String, Object>> getSeriesList (String start, String end, String match, Integer datasource) { JSONObject param = new JSONObject (); param.put("start" , start); param.put("end" , end); param.put("match[]" , match); String url = "http://localhost:9090" + "/api/v1/series" ; return getSeriesDataList(url, param); } private List<LinkedHashMap<String, Object>> getSeriesDataList (String promUrl, JSONObject param) { String http = getHttp(promUrl, param); PromSeries promSeries = JSON.parseObject(http, PromSeries.class); if (Objects.nonNull(promSeries)) { String status = promSeries.getStatus(); if (StringUtils.isBlank(status) || !StringUtils.equals(PromConstants.SUCCESS, status)) { return Collections.emptyList(); } } else { return Collections.emptyList(); } return promSeries.getData(); } private String getHttp (String promUrl, JSONObject param) { String http = null ; try { http = restTemplateUtils.getHttp(promUrl, param); } catch (Exception e) { log.error("请求地址: {}, 请求参数: {}, 异常信息: {}" , promUrl, param, Throwables.getStackTraceAsString(e)); } return http; } }
样例 最近 2 分钟平均 QPS, 根据路由分组 1 sum (rate(http_requests_total{job= ~ "koa-app", path= ~ ".*"}[2 m])) by (path)
最近 1 分钟平均响应时间, 根据路由分组 1 avg (increase(http_request_duration_ms_sum{job= ~ "koa-app", path= ~ ".*"}[1 m]) / increase(http_request_duration_ms_count{job= ~ "koa-app", path= ~ ".*"}[1 m]) > 0 ) by (path)
最近 1 分钟 90 分位响应时间, 根据路由分组 1 histogram_quantile(0.90 , sum (irate(http_request_duration_ms_bucket{job= ~ "koa-app", path= ~ ".*"}[1 m])) by (path, le))
最近 5 分钟, 非 200 请求率, 根据路由分组 1 sum (irate(http_requests_total{status! ~ "200",job= ~ "koa-app", path= ~ ".*"}[5 m])) BY (job, path, status)