简述
PrometheUS有四种数据类型Counter、Gauge、Histogram、Histogram。当我读官方文档的时候,前两种数据类型一读就明白了,可是后两种就让人难受了,怎么读也整不明白了,哎,头皮发麻呀,今天抽空折腾一下
Histogram
在有道翻译中这是柱状图的意思。举个栗子:我想获取北京最近十天内,温度分布情况,我们可以用Histogram这个数据类型,如果用图像表示,也就是柱形图比较合适了,如下图横轴是温度,竖轴是天数
当我们配置了Histogram数据类型后,我们能得到:
- 每个桶中累积值的数量。这里的“桶”就是上面横轴的数值(5,10,15,20),而累计值不是上面的竖轴的值,而是一个累积计数,上图中5的桶对应的累积值是2,10的桶对应的累积值是4,15的桶对应的累积值对应的是8,20的桶对应的是10
- 所有样本值的总和,比如上图中在形成柱状图之前,我们的数据应该是10天的温度(假设温度只在5,10,15,20中取值),然后统计出对应温度的天数而形成柱状图。此处的总和就是10天温度的总和
- 样本数量,对应上图就是10了
Summary
这种类型类似于Histogram,他们都提供了样本值的总和和样本数量。但是Summary没有提供每个桶中的累积值,而是提供了一个 φ-quantiles。
- φ-quantiles
说一下 φ-quantiles吧。 φ-quantiles中的φ的取值范围是0 ≤ φ ≤ 1,总体的意思就是,如果你有N个样本值,首先要从小到大排序,然后取出排在φ *N的值。 - histogram_quantile
这是Histogram类型计算φ-quantiles时用的函数。这个函数的逻辑是,(1)最高的桶的上线如果不是+Inf,那么返回NaN (2)如果quantile 落在最高的桶,那么会返回最高桶的下线值 (3)如果最低桶的上限是大于0的,那么就会使用插值的方式。如果最低同的上限小于等于0,那么就会返回最低桶的上限 - Quantile error
我觉得可以翻译成误差,对于Histogram来说,比如我们有一个200ms 到 300ms的桶,我们的值都接近于220ms,所以0.95-quantiles应该接近于220ms,但是实际上,PrometheUS会使用插值的方法计算φ-quantiles,那么取值就会是295ms
而对于Summary来说,上面的情况不会出现。但是Summary在定义φ-quantiles的时候,会定义一个波动范围,那么实际上φ-quantiles的计算会在φ加减波动范围内波动,如果这个范围内值的波动范围很大,那么取值就会不准确了
Summary和Histogram的不同
Histogram | Summary | |
能获取到的值 | 可以获取到每个桶的累积值 | 能获取到φ-quantiles,但是φ是在客户端写死的,比如φ在客户端时0.5,那么通过API就只能获取0.5-quantiles |
φ-quantiles的计算 | 需要在server端通过histogram_quantile()函数实现 | 客户端直接计算 |
客户端性能 | 优于Summary | 因为要计算φ-quantiles,所以性能差了点 |
Quantile error避免方式 | 需要控制桶之间的间隔 | 需要控制φ的范围,在summary定义φ时会同时定一个波动范围,比如φ为0.95波动范围是0.01那么0.95-quantiles会在0.96-quantiles和0.94-quantiles取值 |
场景 | 如果你的φ-quantiles中φ不能定死,那么就要使用Histogram类型。如果你对要对一部分数据进行计算,那么要使用这种类型 |
实现
我用go写了个小demon:
- 目录结构
- histogram.go
package histogram
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
)
var (
TemperatureHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "beijing_temperature",
Help: "The temperature of the beijing",
Buckets: prometheus.LinearBuckets(0,10,3),
})
)
func InsertTemperature(){
var temperature = [10]float64{1,4,5,10,14,15,20,25,11,30}
for i:=0;i<len(temperature);i++{
TemperatureHistogram.Observe(temperature[i])
fmt.Printf("insert number: %f \n", temperature[i])
}
}
- summary
package summary
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
)
var (
SalarySummary = prometheus.NewSummary(prometheus.SummaryOpts{
Name: "beijing_salary",
Help: "the relationship between salary and population of beijing city",
Objectives: map[float64]float64{0.5:0.05,0.8:0.001,0.9:0.01,0.95:0.01},
})
)
func InsertSummary(){
var salary = [10]float64{8000,7000,8900,10000,9800,17000,15000,14000,11000,12000}
for i:=0;i<len(salary);i++{
SalarySummary.Observe(salary[i])
fmt.Printf("Insert number: %f \n", salary[i])
}
}
- main.go
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"test_histograms/src/histogram"
"test_histograms/src/summary"
"log"
"net/http"
)
func init() {
prometheus.MustRegister(histogram.TemperatureHistogram)
prometheus.MustRegister(summary.SalarySummary)
}
func main() {
histogram.InsertTemperature()
summary.InsertSummary()
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080",nil))
}
运行,访问测试