SpringBoot 和micrometer 实现接口耗时(RT) 统计

917 阅读1分钟

背景

统计某些接口的rt情况

RTMetrics

import cn.nvriot.platform.geocoding.infra.utils.HistogramSnapshotUtil;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.search.Search;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
public class RTMetrics {

    @Autowired
    MeterRegistry meterRegistry;

    public void geoRt(Long cost) {
        Timer.builder("geoRT")
                .description("geo api req/res time cost")
                .publishPercentileHistogram()
                .publishPercentiles(0.5, 0.90, 0.95, 0.99)
                .serviceLevelObjectives(Duration.ofMillis(50),
                        Duration.ofMillis(100),
                        Duration.ofMillis(200),
                        Duration.ofSeconds(1),
                        Duration.ofSeconds(5))
                .minimumExpectedValue(Duration.ofMillis(10))
                .maximumExpectedValue(Duration.ofSeconds(5))
                .distributionStatisticExpiry(Duration.ofMinutes(5))
                .register(this.meterRegistry)
                .record(cost, TimeUnit.MILLISECONDS);
    }

    public void lbsRt(Long cost) {
        Timer.builder("lbsRT")
                .description("lbs api req/res time cost")
                .publishPercentileHistogram()
                .publishPercentiles(0.5, 0.90, 0.95, 0.99)
                .serviceLevelObjectives(Duration.ofMillis(50),
                        Duration.ofMillis(100),
                        Duration.ofMillis(200),
                        Duration.ofSeconds(1),
                        Duration.ofSeconds(5))
                .minimumExpectedValue(Duration.ofMillis(10))
                .maximumExpectedValue(Duration.ofSeconds(5))
                .distributionStatisticExpiry(Duration.ofMinutes(5))
                .register(this.meterRegistry)
                .record(cost, TimeUnit.MILLISECONDS);
    }

    /**
     * cron 表达式,每隔1分钟执行一次
     */
    @Scheduled(cron = "0 */1 * * *  ?")
    public void print() {
        Search.in(meterRegistry).name(p -> "geoRT".equals(p) || "lbsRT".equals(p)).meters().forEach(each -> {
            StringBuilder builder = new StringBuilder();
            builder.append("name:")
                    .append(each.getId().getName())
                    .append(",type:").append(each.getId().getType())
                    .append(",value:").append(each.measure())
                    .append(",takeSnapshot:").append(HistogramSnapshotUtil.format(((Timer) each).takeSnapshot(), TimeUnit.MILLISECONDS));
            log.info("RT Statistic: [{}] ", builder);
        });
    }
}

HistogramSnapshotUtil


import io.micrometer.core.instrument.distribution.HistogramSnapshot;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author wxl
 */
public class HistogramSnapshotUtil {

    public static String format(HistogramSnapshot snapshot, TimeUnit timeUnit) {
        final StringBuilder buf = new StringBuilder();
        buf.append("HistogramSnapshot{count=");

        buf.append(snapshot.count());
        buf.append(", total=");
        buf.append(snapshot.total(timeUnit));
        buf.append(", mean=");
        buf.append(snapshot.mean(timeUnit));
        buf.append(", max=");
        buf.append(snapshot.max(timeUnit));

        if (snapshot.percentileValues().length > 0) {
            buf.append(", percentileValues=");
            buf.append(Arrays.stream(snapshot.percentileValues()).map(s -> format(s, timeUnit)).collect(Collectors.joining()));
        }

        if (snapshot.histogramCounts().length > 0) {
            buf.append(", histogramCounts=");
            buf.append(Arrays.stream(snapshot.histogramCounts()).map(s -> format(s, timeUnit)).collect(Collectors.joining()));
        }

        buf.append('}');
        return buf.toString();
    }

    public static String format(io.micrometer.core.instrument.distribution.ValueAtPercentile percentile, TimeUnit unit) {

        return "(" + percentile.value(unit) + " at " + percentile.percentile() * 100 + "%)";
    }

    public static String format(io.micrometer.core.instrument.distribution.CountAtBucket percentile, TimeUnit unit) {

         return "(" + percentile.count() + " at " + percentile.bucket(unit) + ')';
    }
}

打印日志

2023-11-21 17:18:00.014  INFO [scheduling-1] xx RT Statistic: [name:lbsRT,type:TIMER,value:[
Measurement{statistic='COUNT', value=12.0}, # 总请求次数
Measurement{statistic='TOTAL_TIME', value=0.408},  # 总消耗时间 单位s
Measurement{statistic='MAX', value=0.324}], # 最大的请求消耗时间 单位s
takeSnapshot:
HistogramSnapshot{count=12, total=408.0, mean=34.0, max=324.0,  # 基本统计 单位毫秒
percentileValues=(6.815744 at 50.0%)(11.79648 at 90.0%)(335.282176 at 95.0%)(335.282176 at 99.0%),  # 单位毫秒,p99等指标
histogramCounts=(11.0 at 50.0)(11.0 at 100.0)(11.0 at 200.0)(12.0 at 1000.0)(12.0 at 5000.0)}] # 单位毫秒 , 耗时分布情况