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
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.