Tangible Bytes

A Web Developer’s Blog

Golang Prometheus Exporter Raspberry Pi

I have solar panels that are over 10 years old and I wanted to check if performance is degrading

I used an Open Source tool (SBFSpot) to grab some data from the inverter over bluetooth and so it has to be physically near the inverter and runs on a Raspberry Pi

So far so good - I have nice graphs at https://pvoutput.org

But I really wanted grafana graphs it is such a powerful dataviz tool

For that I needed to get the data to Prometheus

SBFSpot is written in C and deals with some very arcane bluetooth data - so I can’t easily instrument this.

But it stores the data in and SQLite database so I can use a Prometheus exporter that reads this data source.

The sensible option would likely have been to use this https://github.com/burningalchemist/sql_exporter

But I wanted to understand a bit more how a Prometheus exporter works so chose to write one.

Prometheus

Prometheus works by polling configured data feeds - which display a simple text format

An “Exporter” is just a service that reads whatever data is needed and exposes it using this format via http

Exporter

There are client libraries available for many programming languages, these can be used to instrument a service or to expose external data.

Note that

If no client library is available for your language, or you want to avoid dependencies, you may also implement one of the supported exposition formats yourself to expose metrics.

I think another sensible approach for some simple types of data export would be to read the data and use a template to expose it according to the format.

But it also wasn’t complicated to write the code using the prometheus library.

What I ended up with is here https://gitlab.com/tangiblebytes/sbfspot-prom

It’s very simple: each time the service is scraped it makes a query via SQLite, and returns that data via the prometheus format.

Each metric has a Name, a description and optional labels.

The library is designed for highly concurrent systems and potentially slow running processes so it is a little over-engineered for my needs but nice to see how this would work equally well for a much more complex system.

Cross Compiling

I wanted to build this on my Linux desktop but run it on the Raspberry Pi which is within range of the inverter I want the data from.

I thought cross compiling Go was easy - but it turns out to be a lot trickier when the Go code links to a C library (which this does for the SQLite database driver)

There is a good post here on Cross-Compiling Golang (CGO) Projects

I found I just needed to install a cross compiler and find the right incantation

env CC=arm-linux-gnueabi-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=5 go build -tags netgo -ldflags '-extldflags "-static"'  -o sbfspot-prom-arm64 

What this does is

  • uses the ARM GCC compiler
  • enables CGO (for the C library)
  • targets Linux on ARM
  • builds a static binary

I did get some warning messages but it worked well enough for my purposes.

I also experimented and found that compiling directly on the PI worked well (it was just a bit slow and less convenient)

Prometheus Config

With the code working and data being exported I next needed to tell Prometheus to get the data

  - job_name: sbfspot
    scrape_interval: 1m
    static_configs:
    - targets:
      - octopi:19100

Where octopi is the name of my Raspbery Pi host, the exporter is exposed on port 19100 and the sbfspot name is used to mark the data stored in Prometheus.

I set it to scrape at 1 minute intervals - the SBFSpot code runs on cron every 5 minutes so the data won’t change every time but I wil pickup updates fairly soon.

From here I can use the Prometheus UI to verify the data is present.

Grafana

My Grafana instance was already setup to read from Prometheus so it was just a case of configuring a new dashboard

Screenshot

Source Code

The full (if scrappy) code and documentation is here

This includes the Grafana dashboard and systemd configuration.

https://gitlab.com/tangiblebytes/sbfspot-prom

Conclusion

This was a fun learning exercise and I now have a better understanding of the process

Writing another exporter or instrumenting some code wil be easier.

The resulting code does what I want and was very easy to run via systemd

Whether this was a sensible way to do it - or whether it would make more sense to use and configure an existing exporter - I’m not sure.

As for the panels - output looks pretty good but I get more days with zero output. I think this is caused by lichen growth which creates shaded patches. Shade on panels not only stops power production in the affected areas but also - these areas have resistance to current flow. As far as I can tell it isn’t bad enough to have a big impact overall - but does mean higher light levels are needed before any power is generated. These patches can also get hot (current through a resistor) and that can damage the panels.

Time to call a cleaner.