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)。