Prometheus export 开发系列:2 Prometheus client 开发

Prometheus client 开发

如何在应用里面去暴露我的采集指标给 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.浏览器访问

http://10.0.0.3:9090/metrics/

通过过滤找到我们编写的 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 的更新方法,因为不常用,因为一般这两项都是通过事件触发,所以一般都监听指标,然后通过指标进行更新一个值

注意:

  1. historgram,summary 指标常用事件更新或时间更新

  2. couter,gauage 指标时间、事件、metrics api 更新

  3. 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                # 请求时输出

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇