Browse Source

initial commit for prometheus exporter

pull/383/head
Oliver Simon 3 years ago
parent
commit
8d805067e6
  1. 52
      tools/rpi/README.md
  2. 5
      tools/rpi/ahoy.yml.example
  3. 38
      tools/rpi/docker-supplementals/docker-compose.yml
  4. 30
      tools/rpi/docker-supplementals/prometheus/prometheus.yml
  5. 16
      tools/rpi/hoymiles/__main__.py
  6. 75
      tools/rpi/hoymiles/outputs.py
  7. 1
      tools/rpi/optional-requirements.txt
  8. 14
      tools/rpi/systemd/hoymiles.service

52
tools/rpi/README.md

@ -171,7 +171,59 @@ Use basic command line tools to get an idea what you recorded. For example:
A brief example log is supplied in the `example-logs` folder.
Prometheus Exporter
-------------------
Python exporter for https://prometheus.io/
install requirements:
```
pip install -r optional-requirements.txt
```
Systemd service:
----------------
in systemd you find a service file to (fe) run the exporter as a systemd service at boot time.
Adjust the user in it and copy it to /lib/systemd/system
Activate it:
```
sudo systemctl enable hoymiles.service
```
Start it
```
sudo service hoymiles start
```
After that, you should be able to open a website to http://<YOUR PI IP>/ and see some stats
```
# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 304.0
python_gc_objects_collected_total{generation="1"} 124.0
python_gc_objects_collected_total{generation="2"} 0.0
```
and on the bottom :
```
string_voltage{panel="panel_3"} 39.4
# HELP string_current DC/Panel current
# TYPE string_current gauge
string_current{panel="panel_3"} 5.73
# HELP string_power DC/Panel power
# TYPE string_power gauge
string_power{panel="panel_3"} 225.4
# HELP string_energy_daily DC/Panel energy_daily
# TYPE string_energy_daily gauge
string_energy_daily{panel="panel_3"} 487.0
```
+ more stats
Todo

5
tools/rpi/ahoy.yml.example

@ -19,6 +19,11 @@ ahoy:
useTLS: False
insecureTLS: False #set True for e.g. self signed certificates.
# adding prometheus exporter
prometheus:
disabled: false
port: 9233
# Influx2 output
influxdb:
disabled: true

38
tools/rpi/docker-supplementals/docker-compose.yml

@ -0,0 +1,38 @@
version: '3.3'
services:
prometheus:
container_name: prometheus
image: prom/prometheus
volumes:
- ./prometheus:/etc/prometheus
networks:
hoymiles_net:
ipv4_address: 172.18.0.3
ports:
- 9090:9090
restart: always
grafana:
container_name: grafana
image: grafana/grafana
ports:
- 3000:3000
restart: always
volumes:
- grafana-storage:/var/lib/grafana
networks:
hoymiles_net:
ipv4_address: 172.18.0.2
volumes:
data:
grafana-storage:
networks:
hoymiles_net:
ipam:
driver: default
config:
- subnet: "172.18.0.0/24"

30
tools/rpi/docker-supplementals/prometheus/prometheus.yml

@ -0,0 +1,30 @@
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: []
scheme: http
timeout: 10s
api_version: v1
scrape_configs:
- job_name: prometheus
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- localhost:9090
- job_name: hoymiles
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /
scheme: http
static_configs:
- targets:
- 172.18.0.1:9233

16
tools/rpi/hoymiles/__main__.py

@ -135,6 +135,9 @@ def poll_inverter(inverter, do_init, retries=4):
if volkszaehler_client:
volkszaehler_client.store_status(result)
if prometheus_client:
prometheus_client.store_status(result)
def mqtt_send_status(broker, inverter_ser, data, topic=None):
"""
Publish StatusResponse object
@ -285,6 +288,13 @@ if __name__ == '__main__':
bucket=influx_config.get('bucket', None),
measurement=influx_config.get('measurement', 'hoymiles'))
prometheus_client = None
prometheus_config = ahoy_config.get('prometheus', {})
if prometheus_config and not prometheus_config.get('disabled', False):
from .outputs import PrometheusOutputPlugin
prometheus_client = PrometheusOutputPlugin(
prometheus_config)
volkszaehler_client = None
volkszaehler_config = ahoy_config.get('volkszaehler', {})
if volkszaehler_config and not volkszaehler_config.get('disabled', False):
@ -322,10 +332,8 @@ if __name__ == '__main__':
print('', end='', flush=True)
time_to_sleep = loop_interval - (time.time() - t_loop_start)
if loop_interval > 0 and time_to_sleep > 0:
time.sleep(time_to_sleep)
if loop_interval > 0 and (time.time() - t_loop_start) < loop_interval:
time.sleep(loop_interval - (time.time() - t_loop_start))
except KeyboardInterrupt:
sys.exit()

75
tools/rpi/hoymiles/outputs.py

@ -8,6 +8,7 @@ Hoymiles output plugin library
import socket
from datetime import datetime, timezone
from hoymiles.decoders import StatusResponse
from os import path
try:
from influxdb_client import InfluxDBClient
@ -36,6 +37,78 @@ class OutputPluginFactory:
"""
raise NotImplementedError('The current output plugin does not implement store_status')
try:
from os import path
import yaml
from prometheus_client.core import GaugeMetricFamily, REGISTRY, CounterMetricFamily
from prometheus_client import CollectorRegistry, Gauge
from prometheus_client import Info
from prometheus_client import start_http_server
except ModuleNotFoundError:
pass
class PrometheusOutputPlugin(OutputPluginFactory):
current_data = None
def __init__(self, config, **params):
super().__init__(**params)
start_http_server(config.get('port'))
REGISTRY.register(self)
def collect(self):
if self.current_data:
yield CounterMetricFamily('energy_total', 'Energy Total', value=self.current_data['energy_total'])
yield GaugeMetricFamily('temperature', 'Device Temperature', value=self.current_data['temperature'])
yield GaugeMetricFamily('pf', 'Power Factor', value=self.current_data['powerfactor'])
yield GaugeMetricFamily('frequency', 'Frequency', value=self.current_data['frequency'])
# AC Data
phase_id = 0
phase_gauge = {}
phase_types = [ 'power', 'voltage', 'current' ]
for phase_type in phase_types:
for phase in self.current_data['phases']:
phase_gauge[phase_type] = GaugeMetricFamily(f'phase_{phase_type}', f'AC/Phase {phase_id} Power', labels=['phase'])
phase_gauge[phase_type].add_metric(f'phase_{phase_id}', phase[phase_type])
yield phase_gauge[phase_type]
phase_id = phase_id + 1
# DC Data
string_id = 0
types = [ "voltage", "current", "power", "energy_daily" ]
gauge={}
for string in self.current_data['strings']:
gauge['energy_total'] = CounterMetricFamily(f'string_energy_total', f'DC/Panel energy_total', labels=['panel'])
gauge['energy_total'].add_metric([f'panel_{string_id}'], string['energy_total'])
yield gauge['energy_total']
for type in types:
gauge[type] = GaugeMetricFamily(f'string_{type}', f'DC/Panel {type}', labels=['panel'])
gauge[type].add_metric([f'panel_{string_id}'], string[type])
yield gauge[type]
string_id = string_id + 1
def store_status(self, response, **params):
"""
Publish StatusResponse object
:param hoymiles.decoders.StatusResponse response: StatusResponse object
:param topic: custom mqtt topic prefix (default: hoymiles/{inverter_ser})
:type topic: str
:raises ValueError: when response is not instance of StatusResponse
"""
if not isinstance(response, StatusResponse):
raise ValueError('Data needs to be instance of StatusResponse')
data = response.__dict__()
self.current_data = response.__dict__()
serial = data["inverter_ser"]
self.collect()
class InfluxOutputPlugin(OutputPluginFactory):
""" Influx2 output plugin """
api = None
@ -311,3 +384,5 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
output.store_status(data, self.session)
except ValueError as e:
print('Could not send data to volkszaehler instance: %s' % e)

1
tools/rpi/optional-requirements.txt

@ -1 +1,2 @@
influxdb-client>=1.28.0
prometheus_client

14
tools/rpi/systemd/hoymiles.service

@ -0,0 +1,14 @@
[Unit]
Description=Hoymiles Exporter
After=network.target
[Service]
WorkingDirectory=/home/rpi/hoymiles_exporter
ExecStart=/usr/bin/bash -c 'cd /home/rpi/hoymiles_exporter ; /usr/bin/python3 -um hoymiles --config ahoy.yml'
User=rpi
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
Alias=hoymiles.service
Loading…
Cancel
Save