使用程序向Prometheus Pushgateway推送指标(Protocol buffer版本)

概述

以下是介绍如何通过程序向Pushgateway推送指标的方法。

在推送可能度量的格式中,有两种选项:Text和Protocol buffer。
本文将介绍如何使用Protocol buffer格式进行推送。

请参考以下文章了解关于使用 Text 格式进行推送的内容。
通过程序向 Prometheus Pushgateway 推送指标 [Text format 篇]。

针对不同语言的库

由于「Prometheus」的客户端库已经悄悄地实现了向「Pushgateway」推送指标的方法,所以这可能是一个小小的遗漏,但是对于每种语言都有相应的库可用。
通常情况下,我认为以下库是最好的选择。

    Pushing metrics | Prometheus

此外,我并没有检查所有的库,但是我没有找到使用Protocol buffer格式进行推送的处理。

另外,在互联网上查阅了一下,我没有找到任何代码示例,即使用POST方法将Protocol buffer的字节流发送到Pushgateway,并附加上后续提到的分隔符信息,以便推送指标。如果有看到相关实现的朋友,请务必在评论区告诉我。

所以这一次我决定自己动手做。首先,我会介绍一下我参考的文件。

普罗米修斯客户端数据规范

以下文件中提供了Prometheus Client Data的格式说明。

    Prometheus Client Data Exposition Format – Google ドキュメント

如果您打算在协议缓冲区格式中进行推送,请注意以下内容。

32-Bit Varint-Encoded Record Length-Delimited をほどこす必要がある

Protocol buffer のバイト列全体のLengthをバイト列先頭に付与すると良さそうです。

protoファイル(metrics.proto)は以下リポジトリで公開されている

prometheus/client_model: Data model artifacts for Prometheus.
ちなみにGo言語用の構造体定義(metrics.pb.go)も用意されているのでGo言語で実装する場合はgo getして入手するだけで良いです。
念の為お使いのバージョンのPushGatewayの依存モジュールのclient_modelと同じバージョンのものを使うと良いかと思います。

Content-Typeヘッダの値はapplication/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimitedとする

下面是实施的示例。

实施示例

package main

import (
    "bytes"
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"

    "github.com/golang/protobuf/proto"
    "github.com/matttproud/golang_protobuf_extensions/pbutil"

    dto "github.com/prometheus/client_model/go"
)

func main() {

    // metrics
    m := &dto.MetricFamily{
        Name: proto.String("some_metric"),
        Help: proto.String("Just an example."),
        Type: dto.MetricType_COUNTER.Enum(),
        Metric: []*dto.Metric{
            {
                Label: []*dto.LabelPair{
                    {
                        Name:  proto.String("label1"),
                        Value: proto.String("val1"),
                    },
                    {
                        Name:  proto.String("label2"),
                        Value: proto.String("val2"),
                    },
                },
                Counter: &dto.Counter{
                    Value: proto.Float64(123.456),
                },
            },
        },
    }
    fmt.Println("metric:", m)

    // byte列を見てみよう
    dump(m)

    // push
    push(m)

}

func dump(m proto.Message) {
    // byte列に変換
    marshaled, _ := proto.Marshal(m)
    fmt.Println("metric marshaled: ", marshaled)

    // 逆変換の例
    unmarshaled := &dto.MetricFamily{}
    _ = proto.Unmarshal(marshaled, unmarshaled)
    fmt.Println("metric unmarshaled: ", unmarshaled)
}

func push(m proto.Message) {

    // lenghをデリミタとしてbyte列の先頭に付与する
    buf := &bytes.Buffer{}
    _, err := pbutil.WriteDelimited(buf, m)
    fmt.Println("metric delimited: ", buf.Bytes())

    // post
    err = post(buf.Bytes())
    if err != nil {
        fmt.Println("err: ", err)
    }
}

func post(in []byte) error {

    endpoint := "http://localhost:9091/metrics/job/some_job/instance/some_instance"
    contentType := "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited"

    res, err := http.Post(endpoint, contentType, bytes.NewBuffer(in))
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.StatusCode >= 400 {
        b, err := ioutil.ReadAll(res.Body)
        if err != nil {
            return errors.New(res.Status)
        }
        return fmt.Errorf("%s: %s", res.Status, b)
    }
    return err
}

dump(m)是一段不需要用于指标推送处理的代码,而是用于字节序列比较的。

执行结果

运行后将输出如下日志。

metric: name:"some_metric" help:"Just an example." type:COUNTER metric:<label:<name:"label1" value:"val1" > label:<name:"label2" value:"val2" > counter:<value:123.456 > > 
metric marshaled:  [10 11 115 111 109 101 95 109 101 116 114 105 99 18 16 74 117 115 116 32 97 110 32 101 120 97 109 112 108 101 46 24 0 34 43 10 14 10 6 108 97 98 101 108 49 18 4 118 97 108 49 10 14 10 6 108 97 98 101 108 50 18 4 118 97 108 50 26 9 9 119 190 159 26 47 221 94 64]
metric unmarshaled:  name:"some_metric" help:"Just an example." type:COUNTER metric:<label:<name:"label1" value:"val1" > label:<name:"label2" value:"val2" > counter:<value:123.456 > > 
metric delimited:  [78 10 11 115 111 109 101 95 109 101 116 114 105 99 18 16 74 117 115 116 32 97 110 32 101 120 97 109 112 108 101 46 24 0 34 43 10 14 10 6 108 97 98 101 108 49 18 4 118 97 108 49 10 14 10 6 108 97 98 101 108 50 18 4 118 97 108 50 26 9 9 119 190 159 26 47 221 94 64]

比较”metric marshaled”和”metric delimited”的字节序列时,我们可以发现”delimited”的字节序列开头附加了表示长度的值。
如果没有这个长度值,Pushgateway将无法进行反序列化,会返回400错误码的响应。

顺便说一下,当推送成功时,Pushgateway的WebUI将显示如下内容。

push結果
广告
将在 10 秒后关闭
bannerAds