如何在应用里面去暴露我的采集指标给 Prometheus 呢,这个就需要使用到暴露端的开发。
暴露端的开发肯定需要启动一个 http 服务,因为 Prometheus 本身是通过 http 服务来采集数据的,启动服务最简单的方式就是通过 go 的 http 包
其实对于 http 的服务器我们最关心的是采集的数据,以及给 http 响应的文本格式,这个文本格式 Prometheus 已经给我们提供了好多工具,采集 Prometheus 也提供了很多工具
Prometheus client 开发 golang 使用的库:https://pkg.go.dev/github.com/prometheus/client_golang/prometheus
1.开发思路
/*
A.监控开发流程
1.定义指标:类型,有标签或无标签的指标
2.注册指标
3.暴露指标采样值
4.暴露 http api
5.启动 web 服务
B.采样值什么时候更新的三种方式:
以当前 cpu 使用率来说
(1).定时更新:比如每分钟更新一次,这样不是很准确因为有个时间差
(2).metrics api 请求时候暴露,但是如果采集数据时间会影响 API 请求时间
(3).通过事件触发,事件触发
C.四种指标类型应用场景:
(用于统计区间范围的指标采集,指标常用事件更新或时间更新)
1.historgram:固定值计算
2.summary:百分比计算
(对指标时间、事件、metrics api 更新以下两种指标)
3.couter:对递增、递减的指标进行采集
4.gauage:对有变化的指标进行采集
couter,gauage 调用 metrics api 更新的时候会有一个问题, label 是固定的不能变,但是我们想采集 cpu 和 内存 使用率的时候,可能需要在请求触发的时候去设置这个 label 值,所以在 Prometheus 里面还有一个最基础的结构体 collector ,collector 其实是一个接口我们在使用的时候一定要实现两个方法 Describe、Collect,Describe 用来告诉我们有哪些指标,Collect 用来告诉我们有哪些采集数据。
*/
1.1 客户端 Counter 指标开发范例
1.1.1 NewCounter 固定 label 范例
1.编写程序
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
// 1.定义 counture 指标类型,并且该指标类型有固定 label
totalV1 := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: "",
Subsystem: "",
Name: "test_total_v1", // 指标类型的名称
Help: "Test Total v1 Counter", // 帮助
ConstLabels: map[string]string{"name": "1"}, // 定义标签
},
)
// 2.注册指标
prometheus.MustRegister(totalV1)
// 3.更新指标采样值,这里程序起来采集样本就是 10
totalV1.Add(10)
// 暴露 http api
http.Handle("/metrics/", promhttp.Handler())
// 启动 web 服务
http.ListenAndServe(addr, nil)
}
2.执行程序
[16:48:57 root@go metrics_exporter]#go run mian.go
3.浏览器访问
通过过滤找到我们编写的 test_total_v1{name="1"} 10
,就会看到我们在程序中定义的类型已经被写加载了,并且指定的采集样本为 10
1.1.2 NewCounterVec 可变 label 范例
type Labels map[string]string
可有指定多个可以变 label
1.编写程序
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
// 1.指定指标类型
totalV2 := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "V2",
Namespace: "testV2",
Subsystem: "total",
Help: "test total v2",
// prometheus.Labels 方式下面再加 string 切片可指定多个label
ConstLabels: prometheus.Labels{"name": "v2"},
},
// 定义多个标签
[]string{"device", "path"},
)
// 2.注册指标
prometheus.MustRegister(totalV2)
// 因为 WithLabelValues 传入的是可变参数
args := []string{"/root/", "/test/"}
// 3.更新指标采样值,必须要设置 WithLabelValues 值,然后 root 和 test Inc 默认为 1
totalV2.WithLabelValues(args...).Inc()
// login 的值为 10
totalV2.WithLabelValues("/login/", "logintest").Add(10)
// 4.暴露 API
http.Handle("/metrics/", promhttp.Handler())
// 5.启动 http
http.ListenAndServe(addr, nil)
}
2.启动程序
[17:40:26 root@go metrics_exporter]#go run main.go
3.浏览器访问
就有了我们程序中定义的信息
1.1.3 NewCounterFunc 范例
NewCounterFunc 函数,可以自动生产 counter 指标
1.编写程序
package main
import (
"fmt"
"math/rand"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
// 1.指定指标类型
totalV3 := prometheus.NewCounterFunc(
prometheus.CounterOpts{
Namespace: "total_v3",
Name: "test_total_v3",
Subsystem: "total_v3",
Help: "Test totalv3",
ConstLabels: prometheus.Labels{"name": "v3"},
}, func() float64 {
fmt.Println("func total_v3")
// 返回随机数 float64
return rand.Float64()
})
// 2.注册指标
prometheus.MustRegister(totalV3)
// 3.暴露 api
http.Handle("/metrics/", promhttp.Handler())
// 4.启动
http.ListenAndServe(addr, nil)
}
2.启动程序
[17:40:26 root@go metrics_exporter]#go run main.go
3.浏览器访问
4.而且我们每次请求这个 url 的时候都会输出func total_v3
[18:20:30 root@go metrics_exporter]#go run main.go
func total_v3
func total_v3
func total_v3
func total_v3
1.1.4 定时获取监控指标
1.编写
package main
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
total_v1 := prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "total_v1",
Name: "test_total_v1",
Subsystem: "total_v1",
Help: "test totalv1",
ConstLabels: prometheus.Labels{"name": "v1"},
})
total_v2 := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "v2",
Name: "test_v2",
Subsystem: "v2",
Help: "test v2",
ConstLabels: prometheus.Labels{"name": "v2"},
}, []string{"test", "v2"})
prometheus.MustRegister(total_v1)
prometheus.MustRegister(total_v2)
// 通过开启协程每隔 10 秒获取一次监控指标
go func() {
for range time.Tick(10 * time.Second) {
total_v1.Add(10)
total_v1.Inc()
total_v2.WithLabelValues("/login/", "logintest").Add(10)
}
}()
http.Handle("/metrics/", promhttp.Handler())
http.ListenAndServe(addr, nil)
}
2.启动程序
[15:22:34 root@go metrics_exporter]#go run main.go
3.浏览器
第一次 10s
然后等待 10 s ,我们可以看到定期获取的指标信息
1.2 客户端 Gauge 开发范例
下面是 Gauge 的源码
type Gauge // 固定 label 类型
func NewGauge(opts GaugeOpts) Gauge
type GaugeFunc // 在 API 请求的时候触发
func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc
type GaugeOpts
type GaugeVec // 可变 label
func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec
func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error)
func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error)
func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error)
func (v *GaugeVec) MustCurryWith(labels Labels) *GaugeVec
func (v *GaugeVec) With(labels Labels) Gauge
func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge
// Gauge 接口中的方法
type Gauge interface {
Metric
Collector
Set(float64)
Inc() // 加1
Dec() // 减1
Add(float64) // 加上某个值
Sub(float64) // 减去某个值
SetToCurrentTime() // 把值设置成当前时间
}
这里我就值演示 vec 的方法可变 label ,当然如果想使用固定 Label 则可以通过 NewGauge
1.2.1 GaugeVec 演示
假如我们这里需要对 cpu 进行测量,我们就写一个 cpu 的实例
1.编写程序
package main
import (
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
// 定义 Gauge
cpuPercent := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "cpu",
Name: "cpuPercent",
Help: "Test cpu percent guage",
ConstLabels: prometheus.Labels{"cpuji": "percent"},
},
[]string{"cpu"},
)
// 开启监控指标
go func() {
for range time.Tick(10 * time.Second) {
cpuPercent.WithLabelValues("0").Set(rand.Float64())
cpuPercent.WithLabelValues("1").Set(rand.Float64())
}
}()
// 注册
prometheus.MustRegister(cpuPercent)
// 暴露 APi
http.Handle("/cpu/", promhttp.Handler())
http.ListenAndServe(addr, nil)
}
2.启动程序
[15:26:19 root@go testprometheus]#go run main.go
3.浏览器
1.3 客户端 Histogram 开发范例
我们需要设置他的区间也就是桶,然后在设置他的采样的值,在 histogra 中就没有 func 的那个方法,所以在 histogram 中只有 Histogram、HistogramVec
type Histogram // 固定 label
func NewHistogram(opts HistogramOpts) Histogram
type HistogramOpts
type HistogramVec // 支持可变 label
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec
func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error)
func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error)
func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error)
func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec
func (v *HistogramVec) With(labels Labels) Observer
func (v *HistogramVec) WithLabelValues(lvs ...string) Observer
histogram 结构体
type HistogramOpts struct {
Namespace string
Subsystem string
Name string
Help string
ConstLabels Labels
Buckets []float64 // 区间范围,每个区间的范围
}
1.3.1 HistogramVec 演示
1.编写程序
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
requestTime := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "request_Time",
Name: "Time",
Help: "Request time histogram",
// 定义桶的一个区间范围
Buckets: prometheus.LinearBuckets(0, 3, 3),
},
[]string{"path"},
)
prometheus.MustRegister(requestTime)
go func() {
// 通过开启协程每隔 10 秒获取一次监控指标
for range time.Tick(10 * time.Second) {
fmt.Println("v2")
// 每隔 10 获取一次 /root/ /login/ 数据,然后再生成一个随机数乘以 20 , 在每个区间可能会有
requestTime.WithLabelValues("/root/").Observe(rand.Float64() * 20)
requestTime.WithLabelValues("/login/").Observe(rand.Float64() * 20)
}
}()
http.Handle("/metrics/", promhttp.Handler())
http.ListenAndServe(addr, nil)
}
2.执行程序
[16:07:53 root@go testprometheus]#go run main.go
v2
v2
v2
v2
3.浏览器
1.4 客户端 summary 开发范例
summary 和 histogram 类似,只不过 summary 有一个计算百分位,所以 summary 是基于 histogram 进行计算的,所以 summary 在处理的时候相对来说比较耗费采集端资源
// summary 其实和 histogram 是类似的,只不过 summary 需要设置一个百分位
type Summary
func NewSummary(opts SummaryOpts) Summary
type SummaryOpts
type SummaryVec
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec
func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error)
func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error)
func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error)
func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec
func (v *SummaryVec) With(labels Labels) Observer
func (v *SummaryVec) WithLabelValues(lvs ...string) Observer
SummaryOpts 结构体
type SummaryOpts struct {
Namespace string
Subsystem string
Name string
Help string
ConstLabels Labels
Objectives map[float64]float64 // key 百分位点数,value 就是这个百分位他有一个偏差范围
MaxAge time.Duration
AgeBuckets uint32
BufCap uint32
}
1.4.1 SummaryVec 演示
这里我也是通过 summaryVec 进行演示,其他写法同 counter 指标开发类似
1.编写程序
package main
import (
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := ":9090"
requestTime := prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Namespace: "Request_Time",
Name: "Time",
Help: "Request time summary",
// 设置百分位,如下我设置 3 个
// 第一个为 90% 的人满足,然后偏差的话再 1% 左右
// 第二个为 80% 的人满足,然后偏差的话再 1% 左右
// 第三个为 70% 的人满足,然后偏差的话再 3% 左右
Objectives: map[float64]float64{0.9: 0.01, 0.8: 0.01, 0.7: 0.03},
},
[]string{"path"},
)
// 注册
prometheus.MustRegister(requestTime)
// 通过协程监控指标项
go func() {
for range time.Tick(10 * time.Second) {
requestTime.WithLabelValues("/root/").Observe(rand.Float64() * 20)
requestTime.WithLabelValues("/login/").Observe(rand.Float64() * 20)
}
}()
http.Handle("/metrics/", promhttp.Handler())
http.ListenAndServe(addr, nil)
}
2.执行程序
[16:11:57 root@go testprometheus]#go run main.go
3.浏览器
总结:
在 histogram 和 summary 中没有提供 metrics api 的更新方法,因为不常用,因为一般这两项都是通过事件触发,所以一般都监听指标,然后通过指标进行更新一个值
注意:
-
historgram,summary 指标常用事件更新或时间更新
-
couter,gauage 指标时间、事件、metrics api 更新
-
couter,gauage 调用 metrics api 更新的时候会有一个问题, label 是固定的不能变,但是我们想采集 cpu 和 内存 使用率的时候,可能需要在请求触发的时候去设置这个 label 值,所以在 Prometheus 里面还有一个最基础的结构体 collector ,collector 其实是一个接口我们在使用的时候要实现两个方法 Describe、Collect,Describe 用来告诉我们有哪些指标,Collect 用来告诉我们有哪些采集数据。
1.5 collector 接口范例
我们可以自定义结构体来实现 collector 接口的使用范例
查看 Collector 接口
type Collector interface {
// 用来告诉我们有哪些指标,并且 Desc 是一个结构体
Describe(chan<- *Desc)
// 用来告诉我们有哪些采集数据。
Collect(chan<- Metric)
}
查看 Desc 结构体,我们主要通过 NewDesc 方法进行创建
type Desc struct {
fqName string
help string
constLabelPairs []*dto.LabelPair
variableLabels []string
id uint64
dimHash uint64
err error
}
/* 参数:
1.名字
2.help
3.可变 Label 的值
4.固定 label 的值
*/
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc
查看 Metric 接口
/* 他的方法有两种,一种是有 Must,第二种是没有 Must
有 must 打头会报 panic ,没有 must 就不会报 panic
*/
type Metric
func MustNewConstHistogram(desc *Desc, count uint64, sum float64, buckets map[float64]uint64, ...) Metric
func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) Metric // 常用
func MustNewConstSummary(desc *Desc, count uint64, sum float64, quantiles map[float64]float64, ...) Metric
func NewConstHistogram(desc *Desc, count uint64, sum float64, buckets map[float64]uint64, ...) (Metric, error)
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error)
func NewConstSummary(desc *Desc, count uint64, sum float64, quantiles map[float64]float64, ...) (Metric, error)
func NewInvalidMetric(desc *Desc, err error) Metric
func NewMetricWithTimestamp(t time.Time, m Metric) Metric
1.编写程序
package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type CpuCollector struct {
CpuDesc *prometheus.Desc
}
// 声明 desc 定义指标模板信息
func NewCpuCollector() *CpuCollector {
return &CpuCollector{
// 初始化 CpuDesc
CpuDesc: prometheus.NewDesc(
"test_cpu_percent", // name
"help cpu", // help
[]string{"cpu"}, // 可变 Labe
nil, // 固定 labe 为 nil
),
}
}
// 用来告诉我们有哪些指标
func (c *CpuCollector) Describe(descs chan<- *prometheus.Desc) {
fmt.Println("cpu Describe")
// 将 CpuDesc 属性中的监控指标写入到 descs 中
descs <- c.CpuDesc
}
// 采集数据
func (c *CpuCollector) Collect(metrics chan<- prometheus.Metric) {
// 通过 fmt 来观察是否是在请求的时候触发
fmt.Println("cpu Collect")
// 通过 for 循环采集两个 cpu
for i := 0; i < 2; i++ {
// 这里使用 MustNewConstMetric 方法进行数据的采集,并写入 metrics 管道中
metrics <- prometheus.MustNewConstMetric(
c.CpuDesc, // desc 需要采集的指标
prometheus.GaugeValue, // 指标类型
rand.Float64(), // 这里定义一个随机采集的值
strconv.Itoa(i), // 定义 cpu , 因为有两个 cpu 所以通过 itoa 方法将 int 转为 string
)
}
}
func main() {
addr := ":9090"
// 注册 CpuCollector
prometheus.MustRegister(NewCpuCollector())
http.Handle("/metrics/", promhttp.Handler())
http.ListenAndServe(addr, nil)
}
2.运行程序
[18:13:36 root@go testexec]#go run cmd.go
cpu Describe # 程序运行前就会输出 Describe
3.浏览器访问
4.并且请求之后会触发 collect 的输出
[18:13:36 root@go testexec]#go run cmd.go
cpu Describe
cpu Collect # 请求时输出