如何在Grafana中展示Prometheus的Histogram

September 20, 2024

前言

最近在做一些业务上的监控,了解到PrometheusHistogram指标监控,还蛮符合我在业务的监控需求。

之前用PrometheusCounterGuage比较多,对于Histogram的使用还是头一遭,在网上也没找到好的中文文档。

于是想着把自己这次使用Prometheus的经历输出成一篇博客文章,一方面记录自己是如何使用的,另一方面希望能对其他人有所帮助。

先简单介绍一下我的需求,了解一下为什么Histogram是我想要的指标类型。

我业务中需要调用一个外部的数据接口,而这个接口是有每秒钟限定调用次数的限制的,当某次调用达到限流限制后,希望能够过一段时间后进行重试,如果再次失败就再延长间隔时间等待重试,最多重试「MaxRetryTime」次。

我需要监控的内容是,记录「达到多少次后才重试成功;失败多少次」。想要达到的效果如下图所示。

image-20240919164421067

这里设置的「MaxRetryTime」= 5。

在本篇文章中我们会用到Grafana的两个面板类型:Bar guageHeatmap

image-20240920101537156

Add Histogram in Spring

因为后端服务是基于Java的工程,所以在Spring中来编写Histogram数据的收集代码。

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

开启配置文件

# 指标监控
management:
  server:
    port: ${server.port}
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
      # 默认就是 /actuator
      base-path: /actuator
  metrics:
    tags:
      application: ${spring.application.name}
      environment: ${spring.profiles.active}
    # 启用 prometheus 的自动装载
    export:
      prometheus:
        enabled: true

这是一个标准的配置。

收集指标示例

@Service
public class MetricCollectDemo {

    @Resource
    private PrometheusMeterRegistry prometheusMeterRegistry;

    public void retry(int curRetryTime) {
        String retryStatus = "";
        // 查询数据
        try {
            // do rpc call
        } catch (RpcCallLimitException e) {
            retryStatus = "fail";
            // retry ...
            return;
        } finally {
            // 记录这是第几次成功或失败
            DistributionSummary summary = DistributionSummary.builder("my_histogram")
                    .serviceLevelObjectives(1, 2, 3, 4, 5)
                    .tag("status", retryStatus)
                    .register(prometheusMeterRegistry);
            summary.record(curRetryTime);
            }
        }
    }
}

关键的代码就在于声明一个DistributionSummary,配置其serviceLevelObjectives,并注册到prometheusMeterRegistry,然后调用「record」方法记录监测值。

DistributionSummary就是Histogram,而serviceLevelObjectives对应Histogram中的「桶 bucket」概念。

这里声明了5个「桶 bucket」,分别是小于等于1的桶、小于等于2...小于等于5的桶,实际上还会创建有一个小于正无穷+Inf桶

如果此时summary.record(3),那么小于等于3的桶的计数就会+1。

这里不用担心会重复「注册」,如果看底层的代码就知道prometheusMeterRegistry是如何避免重复的指标的registry了。

查看指标

通过暴露的http://{host}/actuator/prometheus接口就能看到所有的prometheus指标了,比如:

# TYPE my_histogram histogram
my_histogram_bucket{status="success",le="1.0",} 19.0
my_histogram_bucket{status="success",le="2.0",} 23.0
my_histogram_bucket{status="success",le="3.0",} 24.0
my_histogram_bucket{status="success",le="4.0",} 24.0
my_histogram_bucket{status="success",le="5.0",} 24.0
my_histogram_bucket{status="success",le="+Inf",} 24.0
my_histogram_count{status="success",} 24.0
my_histogram_sum{status="success",} 30.0
my_histogram_bucket{status="fail",le="1.0",} 5.0
my_histogram_bucket{status="fail",le="2.0",} 6.0
my_histogram_bucket{status="fail",le="3.0",} 6.0
my_histogram_bucket{status="fail",le="4.0",} 6.0
my_histogram_bucket{status="fail",le="5.0",} 6.0
my_histogram_bucket{status="fail",le="+Inf",} 6.0
my_histogram_count{status="fail",} 6.0
my_histogram_sum{status="fail",} 7.0

可以发现生成了对应的桶,在my_histogram后面加了_bucket并携带le标签,le代表的就是less or equal的意思。

所以如果想要计算(3, 4]区间的数量,应该用le="4.0"的桶的数值 - le="3.0"的桶的数值。

关于prometheus server端的指标拉取和Grafana导入数据源这里就不再展开了。

Grafana中展示每个桶的值

如果我们要在Grafana中展示每个桶对应的值(但不看+Inf桶的),首先先创建bar gaugePanel,对应的PromQL语句是:sum by(le) my_histogram_bucket{status="success", le!="+Inf"}

前面我们介绍过桶逐渐增大的,le越大,对应的值越大。每一桶都包含前一桶的值。所以得到的图像是这样的:

image-20240920100806026

但这不是我们想要的结果,我们想看的是每个桶区间的值。

展示每个桶区间的值

Grafana支持直方图的展示,只需要将Prometheus数据格式从Time series更改为Heatmap

image-20240920101131359

我们会得到下面的结果,达到了我们所希望的结果:

image-20240920101235329

但是你会发现这个图中是不包含任何时间序列的,也就是说,他只展示当前bucket中的数值,随着应用重启,数值都会清空。接下来我们使用热点图来查看不同时间点的热点情况。

热力图

Grafana 为此提供了一个热力图面板,观测数据在时间线上的变化。

github上的contributions展示就是一个热力图:

image-20240919193618832

很容易就能发现在哪个时间点最能Coding

接下来就来看看如何设置这个热力图。基于前文的设置,首先将bar gaguePanel样式换成Heatmap

计算每个桶区间在某段时间内,如「[$__interval]」时间间隔内的增加量,值越大的在热点图中就会越明显(颜色很深),PromQLsum(increase(my_histogram_bucket[$__interval])) by (le).

同样的,使用rate函数也能达到相同的目的。最后,「将查询选项 Query options > 最大数据点 Max data points」设置为 25,避免因分辨率过高导致的浏览器性能下降。

image-20240920160757827

最后得到效果如下:

image-20240920102540593

根据热力图我们就能直接观察到,究竟是哪个时间点重试次数很多仍然失败,从而根据日志或别的方式去进一步观察问题。

可惜

可惜,因为PrometheusTiDB 时序型数据库,每次采集的都是某个时间点的Bucket瞬时状态,无法做到计算出某个时间段的具体累计数据,并通过直方图展示出来。

不过bar gauge配合Heatmap能够做到定位到具体的时间点和很长一段时间(应用存活时间)内的数据情况。

Comments