goprometheusamazon-cloudwatchebnf

How to parse Prometheus data


I have been able to obtain the metrices by sending an HTTP GET as follows:

# TYPE net_conntrack_dialer_conn_attempted_total untyped net_conntrack_dialer_conn_attempted_total{dialer_name="federate",instance="localhost:9090",job="prometheus"} 1 1608520832877

Now I need to parse this data and obtain control over every piece of data so that I can convert tand format like json.

I have been looking into the ebnf package in Go: ebnf package

Can somebody point me the right direction to parse the above data?


Solution

  • There's a nice package already available to do that and it's by the Prometheus's Authors itself.

    They have written a bunch of Go libraries that are shared across Prometheus components and libraries. They are considered internal to Prometheus but you can use them.

    Refer: github.com/prometheus/common doc. There's a package called expfmt that can decode and encode the Prometheus's Exposition Format (Link). Yes, it follows the EBNF syntax so ebnf package could also be used but you're getting expfmt right out of the box.

    Package used: expfmt

    Sample Input:

    # HELP net_conntrack_dialer_conn_attempted_total
    # TYPE net_conntrack_dialer_conn_attempted_total untyped
    net_conntrack_dialer_conn_attempted_total{dialer_name="federate",instance="localhost:9090",job="prometheus"} 1 1608520832877
    

    Sample Program:

    package main
    
    import (
        "flag"
        "fmt"
        "log"
        "os"
    
        dto "github.com/prometheus/client_model/go"
        "github.com/prometheus/common/expfmt"
    )
    
    func fatal(err error) {
        if err != nil {
            log.Fatalln(err)
        }
    }
    
    func parseMF(path string) (map[string]*dto.MetricFamily, error) {
        reader, err := os.Open(path)
        if err != nil {
            return nil, err
        }
    
        var parser expfmt.TextParser
        mf, err := parser.TextToMetricFamilies(reader)
        if err != nil {
            return nil, err
        }
        return mf, nil
    }
    
    func main() {
        f := flag.String("f", "", "set filepath")
        flag.Parse()
    
        mf, err := parseMF(*f)
        fatal(err)
    
        for k, v := range mf {
            fmt.Println("KEY: ", k)
            fmt.Println("VAL: ", v)
        }
    }
    

    Sample Output:

    KEY:  net_conntrack_dialer_conn_attempted_total
    VAL:  name:"net_conntrack_dialer_conn_attempted_total" type:UNTYPED metric:<label:<name:"dialer_name" value:"federate" > label:<name:"instance" value:"localhost:9090" > label:<name:"job" value:"prometheus" > untyped:<value:1 > timestamp_ms:1608520832877 >
    

    So, expfmt is a good choice for your use-case.

    Update: Formatting problem in OP's posted input:

    Refer:

    1. https://github.com/prometheus/pushgateway/issues/147#issuecomment-368215305

    2. https://github.com/prometheus/pushgateway#command-line

    Note that in the text protocol, each line has to end with a line-feed
    character (aka 'LF' or '\n'). Ending a line in other ways, e.g. with 
    'CR' aka '\r', 'CRLF' aka '\r\n', or just the end of the packet, will
    result in a protocol error.
    

    But from the error message, I could see \r char is present in in the put which is not acceptable by design. So use \n for line endings.