Monitoring Golang Application with Prometheus

Published by Aryan on

Instrumenting Golang code for Prometheus

Hello Folks!

I have posted multiple articles about getting started with Prometheus. It talks about installing, configuring and monitoring system through Prometheus.

In this section, we will learn how to instrument our golang code to export interesting metrics to Prometheus. Today’s world is about white box monitoring than old school days of black box monitoring. We should be aware the application performance in detail and notified if it is not upto the expected benchmarks.

Let’s start in instrumenting our Golang application.

Creating Dummy HTTP Service

I will write a dummy HTTP service that will be instrumented. Below is the code for it.

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/speakSlow", speakSlow)
    http.HandleFunc("/", speak)
    http.ListenAndServe(":8080", nil)
}

func speak(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Prom, %s!\n", r.URL.Path[1:])
}


func speakSlow(w http.ResponseWriter, r *http.Request) {
    time.Sleep(time.Second * 7)
    fmt.Fprintf(w, "Prom, %s!\n", r.URL.Path[1:])
}

Above is a very basic Golang HTTP server that speaks to you :).

$ go run simple_http_service.go 

$ curl localhost:8080/
Prom, !

Instrumenting Code for Prometheus

There are two golang packages that will help us to expose metrics for Prometheus. Following are the packages.

github.com/prometheus/client_golang/prometheus
github.com/prometheus/client_golang/prometheus/promhttp

Below is the complete code of simple HTTP service that is modified to expose metrics.

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
        Name:    "speak_seconds",
        Help:    "Time take to speak",
        Buckets: []float64{1, 2, 5, 6, 10},
    }, []string{"code"})
    prometheus.Register(histogram)

    http.Handle("/metrics", promhttp.Handler())
    http.HandleFunc("/speakSlow", speakSlow(histogram))
    http.HandleFunc("/", speak(histogram))
    http.ListenAndServe(":8080", nil)
}

func speak(histogram *prometheus.HistogramVec) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        code := 200
        defer func() {
            duration := time.Since(start)
            histogram.WithLabelValues(fmt.Sprintf("%d", code)).Observe(duration.Seconds())
        }()

        fmt.Fprintf(w, "Prom, %s!\n", r.URL.Path[1:])
    }
}

func speakSlow(histogram *prometheus.HistogramVec) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        code := 200
        time.Sleep(7 * time.Second)
        defer func() {
            duration := time.Since(start)
            histogram.WithLabelValues(fmt.Sprintf("%d", code)).Observe(duration.Seconds())
        }()

        fmt.Fprintf(w, "Prom, %s!\n", r.URL.Path[1:])
    }
}
$ go run http_service.go
$ curl localhost:8080/metrics

# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 0
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0
$ curl localhost:8080

# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 1
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0
# HELP speak_seconds Time take to speak
# TYPE speak_seconds histogram
speak_seconds_bucket{code="200",le="1"} 1
speak_seconds_bucket{code="200",le="2"} 1
speak_seconds_bucket{code="200",le="5"} 1
speak_seconds_bucket{code="200",le="6"} 1
speak_seconds_bucket{code="200",le="10"} 1
speak_seconds_bucket{code="200",le="+Inf"} 1
speak_seconds_sum{code="200"} 5.705e-06
speak_seconds_count{code="200"} 1
$ curl localhost:8080/speakSlow

# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 2
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0
# HELP speak_seconds Time take to speak
# TYPE speak_seconds histogram
speak_seconds_bucket{code="200",le="1"} 1
speak_seconds_bucket{code="200",le="2"} 1
speak_seconds_bucket{code="200",le="5"} 1
speak_seconds_bucket{code="200",le="6"} 1
speak_seconds_bucket{code="200",le="10"} 2
speak_seconds_bucket{code="200",le="+Inf"} 2
speak_seconds_sum{code="200"} 7.000108857000001
speak_seconds_count{code="200"} 2

We have now exposed the metrics. Next step is to tell Prometheus to scrape these metrics at regular intervals.

Scrape Golang Application Metrics

Edit prometheus config with the new target and re-run the prometheus binary.

$ vim prometheus.yml

scrape_configs:
  - job_name: 'golang'
    static_configs:
      - targets: ['localhost:8080']

Start visualizing metrics through expression manager. Below is the graph of rate of requests in last 10 minutes.

Golang HTTP Service Requests Rate

Likewise we can collect various other metrics from golang application. Hope you enjoyed reading the guide.

Enjoy the day.

Categories: Prometheus

Aryan

Hey Friends, Thank you for visiting my blog and spending some of your precious time reading it. Myself Aryan who works with a variety of computer technologies. This blog is a write-up of my understanding of various topics after reading different materials about it. I hope that it helps you in some way. Enjoy reading and feel free to connect with me.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *