github.com/GuanceCloud/cliutils@v1.1.21/metrics/README.md (about) 1 # Prometheuse 指标体系构建 2 3 任何一个服务需要有完整有效的指标体系来做自观测,本文说明如何使用 Prometheuse 方式来构建服务的指标体系。以下以 Datakit 为例来做说明。 4 5 ## 如何给模块增加自身指标 6 7 对 Datakit 而言,自身的指标有几大类: 8 9 - 计数:某些操作的执行次数(如日志文件 rotate 次数)、某些物理量的计数(如字节数) 10 - 耗时:某些操作的耗时 11 - 报错信息:当某个模块报错时,也能通过 Prometheuse 方式来暴露,这种有点像日志。可以直接使用全局 metrics 模块提供的接口 12 13 ## 计数(counter) 14 15 对于计数性质的指标,一般通过如下方式构建: 16 17 ```golang 18 dwAPIVec = prometheus.NewCounterVec( 19 prometheus.CounterOpts{ 20 Namespace : "datakit", // 所有 Datakit 指标,都统一用这个 Namespace 21 Subsystem : "dataway", // 视具体模块而定,比如这里的 dataway 指 io 中的 dataway 模块 22 Name : "api_total", // 具体的指标名,比如这里指 HTTP API 的发送次数(当前是一个 counter) 23 24 // 这样之后,就能看到一个完整的指标名:datakit_dataway_api_total 25 26 Help: "The dataway API request count", // 该指标的说明文档 27 }, 28 []string{"api", "status"}, // 可能的 label(tag) 名称 29 ) 30 31 // 将该指标注册到 datakit 全局指标体系中 32 // NOTE: 注意,不要注册同样的指标名,不然这里会崩溃 33 metrics.MustRegister(dwAPIVec) 34 ``` 35 36 注意这里的 label 列表,对 dataway HTTP 请求而言,除了区分不同的 API 调用,每个 API 请求有不同的结果状态,故需要将状态这个维度添加进去,以区分不同结果状态下的指标。 37 38 注册完成后,在该模块代码中,就能无脑使用了: 39 40 ```golang 41 // 请求 /v1/write/metric 成功 42 dwAPIVec.WithLabelValues("/v1/write/metric", "Status OK").Inc() 43 ``` 44 45 注意: 46 47 - `WithLabelValues` 的时候,label 值的顺序,必须跟注册该指标时指定的 label 列表顺序一致,不然会导致数据错乱 48 - `WithLabelValues` 中的 label 个数不能少于注册时的个数,否则会崩溃 49 50 在最终的 /metrics 接口返回中,能看到类似如下的返回: 51 52 ``` 53 # HELP datakit_dataway_api_total The dataway API request count 54 # TYPE datakit_dataway_api_total counter 55 datakit_dataway_api_total{api="/v1/write/metric",status="Status OK"} 169 56 ``` 57 58 ## 计量(counter) 59 60 计量用于表示一些忽高忽低的指标,比如温度、CPU 使用率等,它不是单调递增的, 61 62 ## 概要(summary) 63 64 概要用来表示一种自启动以来的总次数和总量之间的关系,比如网络请求总耗时和总次数,API 调用的总 Body 大小和次数,它自带两个字段: 65 66 - Sample Count:表示总次数 67 - Sample Sum:表示总量 68 69 比如,以 HTTP 请求为例,一个指标维度就是总的请求次数和总的请求耗时,即可组成一个 summary 指标: 70 71 ```golang 72 httpLatencyVec = prometheuse.NewSummaryVec( 73 prometheus.SummaryOpts{ 74 Namespace : "datakit", 75 Subsystem : "http", 76 77 Name : "api_cost_seconds", // 具体的指标名 78 Help : "API request cost", 79 }, 80 []string{"api", "status"}, // 这里的维度跟上面的基本一致 81 ) 82 ``` 83 84 summary 可以这么用: 85 86 ```golang 87 httpLatencyVec.WithLabelValues("/v1/write/metric", "Status OK").Observe(float64(time.Since(start))/float64(time.Second)) 88 ``` 89 90 在最终的 /metrics 接口返回中,能看到类似如下的返回: 91 92 ``` 93 # HELP datakit_dataway_api_latency Dataway API request latency(ms) 94 # TYPE datakit_dataway_api_latency summary 95 datakit_http_api_cost_seconds_sum{api="/v1/write/metric",status="Status OK"}"} 3.1415926 96 datakit_http_api_cost_seconds_count{api="/v1/write/metric",status="Status OK"}"} 42 97 ``` 98 99 它表示「在 API `/v1/write/metric` 上总共有 42 次请求,总的请求耗时为 3.1415926 秒」,通过简单的除法,我们即可知道该 API 上的平均耗时。 100 101 ### 概要的百分位 102 103 上面的方式只能计算平均值,但是我们可以在 summary 中设置一定的百分位,来获取最近一段时间的数据: 104 105 ```golang 106 httpLatencyVec = prometheuse.NewSummaryVec( 107 prometheus.SummaryOpts{ 108 Namespace : "datakit", 109 Subsystem : "http", 110 111 Name : "api_cost_seconds", // 具体的指标名 112 Help : "API request cost", 113 114 Objectives: map[float64][float64] { 115 0.5: 0.05, 116 0.75: 0.0075, 117 0.95: 0.005, 118 }, 119 MaxAge: 10 * time.Minute, 120 AgeBuckets: 5, 121 }, 122 []string{"api", "status"}, // 这里的维度跟上面的基本一致 123 ) 124 ``` 125 126 这样,我们就能获取最近 10min 每个 API 上几个百分位(P50/P75/P95)的响应情况: 127 128 ``` 129 # HELP datakit_dataway_api_latency Dataway API request latency(ms) 130 # TYPE datakit_dataway_api_latency summary 131 datakit_http_api_cost_seconds{api="/v1/write/metric",status="Status OK",quantile="0.5"} 1.002858834 132 datakit_http_api_cost_seconds{api="/v1/write/metric",status="Status OK",quantile="0.75"} 1.002858834 133 datakit_http_api_cost_seconds{api="/v1/write/metric",status="Status OK",quantile="0.95"} 1.002858834 134 datakit_http_api_cost_seconds_sum{api="/v1/write/metric",status="Status OK"}"} 3.1415926 135 datakit_http_api_cost_seconds_count{api="/v1/write/metric",status="Status OK"}"} 42 136 ``` 137 138 ## 指标命名规范 139 140 Prometheus 指标有自身的命名规范,参见[官方文档](https://prometheus.io/docs/practices/naming/)。 141 142 ## 报错 143 144 > 报错信息的处理,还需进一步考虑一下,暂时不建议使用。 145 146 ```golang 147 AddLastErr("my-module", "I got some error message") 148 ``` 149 150 在最终的 /metrics 接口返回中,能看到类似如下的返回: 151 152 ``` 153 # HELP datakit_lasterr Datakit internal errors(with error occurred unix timestamp) 154 # TYPE datakit_lasterr gauge 155 datakit_lasterr{message="I got some error message",source="my-module"} 1.678531768e+09 156 ``` 157 158 ## 测试指标效果 159 160 如果如下代码,可以直接在自己的代码中获取 /metrics 接口返回的数据效果: 161 162 ```golang 163 164 import ( 165 "github.com/prometheus/common/expfmt" 166 "github.com/prometheus/client_golang/prometheus" 167 "github.com/GuanceCloud/cliutils/metrics" 168 ) 169 170 apiLatencyVec = prometheuse.NewSummaryVec( 171 prometheus.SummaryOpts{ 172 Namespace : "datakit", 173 Subsystem : "dataway", 174 175 Name : "api_cost_seconds", 176 Help : "Dataway API request latency", 177 }, 178 []string{"api", "status"}, 179 ) 180 181 // 构建一个 registry 182 reg := prometheus.NewRegistry() 183 reg.MustRegister(apiLatencyVec) 184 185 // 塞进去一个指标 186 apiLatencyVec.WithLabelValues("/v1/write/metric", "Status OK").Observe(float64(time.Since(start))/float64(time.Second)) 187 188 // 获取 reg 上所有指标 189 mfs, err := reg.Gather() 190 191 // 此处即可看到 /metrics 接口返回的效果 192 fmt.Println(metrics.MetricFamily2Text(mfs)) 193 ``` 194 195 ## 使用用例 196 197 参见 [diskcache 的指标暴露实现](https://github.com/GuanceCloud/cliutils/blob/main/diskcache/metric.go)。