Exporter name
Think about name of the exporter, existing go exporter names can be found here
Lets create volume_exporter which basically exports metrics about the volume (Total , Free And Used)
Here is an example of how this can be done
Initialize Project
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-37.png?w=292)
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-38.png?w=786)
Create module
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-39.png?w=288)
E:\practices\Go\volume_exporter>go mod init github.com/mnadeem/volume_exporter
go: creating new go.mod: module github.com/mnadeem/volume_exporter
E:\practices\Go\volume_exporter>
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-40.png?w=718)
get packages
E:\practices\Go\volume_exporter>go get github.com/prometheus/client_golang
go: downloading github.com/prometheus/client_golang v1.9.0
go: github.com/prometheus/client_golang upgrade => v1.9.0
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-41.png?w=650)
Create main file main.go
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-43.png?w=240)
The following would run the http server on port 9888 and expose metrics endpoint /metrics
package main
import (
"flag"
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
listenAddress = flag.String("web.listen-address", ":9888", "Address to listen on for web interface.")
metricPath = flag.String("web.metrics-path", "/metrics", "Path under which to expose metrics.")
)
func main() {
log.Fatal(serverMetrics(*listenAddress, *metricPath))
}
func serverMetrics(listenAddress, metricsPath string) error {
http.Handle(metricsPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<html>
<head><title>Volume Exporter Metrics</title></head>
<body>
<h1>ConfigMap Reload</h1>
<p><a href='` + metricsPath + `'>Metrics</a></p>
</body>
</html>
`))
})
return http.ListenAndServe(listenAddress, nil)
}
go run main.go
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-44.png?w=1024)
Identify the Options for Exporter
Flag | Description |
web.listen-address | Address to listen on for web interface and telemetry. Default is 9888 |
web.telemetry-path | Path under which to expose metrics. Default is /metrics |
volume-dir | volumes to report, the format is volumeName:VolumeDir For example ==> logs:/app/logs you can use this flag multiple times to provide multiple volumes |
Identify the Metrics to Export
metrics | Type | Description |
volume_bytes_total{name=”someName”, path=”/some/path”} | Gauge | Total size of the volume/disk |
volume_bytes_free{name=”someName”, path=”/some/path”} | Gauge | Free size of the volume/disk |
volume_bytes_used{name=”someName”, path=”/some/path”} | Gauge | Used size of volume/disk |
Implementation
Options
We will use flag library to parse command line flags
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-45.png?w=1024)
Exporter
There is two types to data exposed by exporters to prometheus
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-54.png?w=734)
First one is metrics definition (name, definition and type) and the second one is Metric Value
If we analyze Prometheus Collector, this is what is expected as well, when ever prometheus calls the metrics endpoint, the following two methods would be invoked. First one basically describes the metrics while the other one collects the metrics values.
// Collector is the interface implemented by anything that can be used by
// Prometheus to collect metrics. A Collector has to be registered for
// collection. See Registerer.Register.
//
// The stock metrics provided by this package (Gauge, Counter, Summary,
// Histogram, Untyped) are also Collectors (which only ever collect one metric,
// namely itself). An implementer of Collector may, however, collect multiple
// metrics in a coordinated fashion and/or create metrics on the fly. Examples
// for collectors already implemented in this library are the metric vectors
// (i.e. collection of multiple instances of the same Metric but with different
// label values) like GaugeVec or SummaryVec, and the ExpvarCollector.
type Collector interface {
// Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector to the provided channel and returns once
// the last descriptor has been sent. The sent descriptors fulfill the
// consistency and uniqueness requirements described in the Desc
// documentation.
//
// It is valid if one and the same Collector sends duplicate
// descriptors. Those duplicates are simply ignored. However, two
// different Collectors must not send duplicate descriptors.
//
// Sending no descriptor at all marks the Collector as “unchecked”,
// i.e. no checks will be performed at registration time, and the
// Collector may yield any Metric it sees fit in its Collect method.
//
// This method idempotently sends the same descriptors throughout the
// lifetime of the Collector. It may be called concurrently and
// therefore must be implemented in a concurrency safe way.
//
// If a Collector encounters an error while executing this method, it
// must send an invalid descriptor (created with NewInvalidDesc) to
// signal the error to the registry.
Describe(chan<- *Desc)
// Collect is called by the Prometheus registry when collecting
// metrics. The implementation sends each collected metric via the
// provided channel and returns once the last metric has been sent. The
// descriptor of each sent metric is one of those returned by Describe
// (unless the Collector is unchecked, see above). Returned metrics that
// share the same descriptor must differ in their variable label
// values.
//
// This method may be called concurrently and must therefore be
// implemented in a concurrency safe way. Blocking occurs at the expense
// of total performance of rendering all registered metrics. Ideally,
// Collector implementations support concurrent readers.
Collect(chan<- Metric)
}
Lets first create exporter package and volume_exporter.go file
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-46.png?w=308)
If we have more than one metrics to expose, it is always better to group them by Structure.
Lets define our volumeCollector Struct having descriptors
//Define a struct for you collector that contains pointers
//to prometheus descriptors for each metric you wish to expose.
//Note you can also include fields of other types if they provide utility
//but we just won't be exposing them as metrics.
type volumeCollector struct {
volumeBytesTotal *prometheus.Desc
volumeBytesFree *prometheus.Desc
volumeBytesUsed *prometheus.Desc
}
Lets define the factory method that returns the structure
//You must create a constructor for you collector that
//initializes every descriptor and returns a pointer to the collector
func newVolumeCollector() *volumeCollector {
return &volumeCollector{
volumeBytesTotal: prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "bytes_total"),
"Total size of the volume/disk",
[]string{"name", "path"}, nil,
),
volumeBytesFree: prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "bytes_free"),
"Free size of the volume/disk",
[]string{"name", "path"}, nil,
),
volumeBytesUsed: prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "bytes_used"),
"Used size of volume/disk",
[]string{"name", "path"}, nil,
),
}
}
Implement Describe method on Exporter
//Each and every collector must implement the Describe function.
//It essentially writes all descriptors to the prometheus desc channel.
func (collector *volumeCollector) Describe(ch chan<- *prometheus.Desc) {
//Update this section with the each metric you create for a given collector
ch <- collector.volumeBytesTotal
ch <- collector.volumeBytesFree
ch <- collector.volumeBytesUsed
}
Implement Collect method on Exporter
//Collect implements required collect function for all promehteus collectors
func (collector *volumeCollector) Collect(ch chan<- prometheus.Metric) {
//Implement logic here to determine proper metric value to return to prometheus
//for each descriptor or call other functions that do so.
var metricValue float64
if 1 == 1 {
metricValue = 1
}
//Write latest value for each metric in the prometheus metric channel.
//Note that you can pass CounterValue, GaugeValue, or UntypedValue types here.
ch <- prometheus.MustNewConstMetric(collector.volumeBytesTotal, prometheus.GaugeValue, metricValue, "log", "path")
ch <- prometheus.MustNewConstMetric(collector.volumeBytesFree, prometheus.GaugeValue, metricValue, "log", "path")
ch <- prometheus.MustNewConstMetric(collector.volumeBytesUsed, prometheus.GaugeValue, metricValue, "log", "path")
}
Lets define a method so that other packages can talk to exporter
func Register() {
collector := newVolumeCollector()
prometheus.MustRegister(collector)
}
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-49.png?w=884)
Now if you run
go run main.go -volume-dir=abc:/abc -volume-dir=pqr:/pqr
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-50.png?w=1024)
Continue implementing the logic in Collect method to populate the value dynamically.
And finally
go mod tidy
Here is the fully working exporter
Up And Running
Locally
go run main.go --volume-dir=practices:E:\practices
docker run --rm -p 9889:9888 -it mnadeem/volume_exporter --volume-dir=bin:/bin
Add as a side container to existing deployments
- name: volume-exporter
image: mnadeem/volume_exporter
imagePullPolicy: "Always"
args:
- --volume-dir=prometheus:/prometheus
ports:
- name: metrics-volume
containerPort: 9888
volumeMounts:
- mountPath: /prometheus
name: prometheus-data
readOnly: true
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-51.png?w=1024)
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-52.png?w=974)
![](https://reachmnadeem.wordpress.com/wp-content/uploads/2021/01/image-53.png?w=1024)
Good job! Thanks very much!