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

5
tools/rpi/ahoy.yml.example

@ -19,6 +19,11 @@ ahoy:
useTLS: False useTLS: False
insecureTLS: False #set True for e.g. self signed certificates. insecureTLS: False #set True for e.g. self signed certificates.
# adding prometheus exporter
prometheus:
disabled: false
port: 9233
# Influx2 output # Influx2 output
influxdb: influxdb:
disabled: true 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: if volkszaehler_client:
volkszaehler_client.store_status(result) volkszaehler_client.store_status(result)
if prometheus_client:
prometheus_client.store_status(result)
def mqtt_send_status(broker, inverter_ser, data, topic=None): def mqtt_send_status(broker, inverter_ser, data, topic=None):
""" """
Publish StatusResponse object Publish StatusResponse object
@ -285,6 +288,13 @@ if __name__ == '__main__':
bucket=influx_config.get('bucket', None), bucket=influx_config.get('bucket', None),
measurement=influx_config.get('measurement', 'hoymiles')) 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_client = None
volkszaehler_config = ahoy_config.get('volkszaehler', {}) volkszaehler_config = ahoy_config.get('volkszaehler', {})
if volkszaehler_config and not volkszaehler_config.get('disabled', False): if volkszaehler_config and not volkszaehler_config.get('disabled', False):
@ -322,10 +332,8 @@ if __name__ == '__main__':
print('', end='', flush=True) print('', end='', flush=True)
time_to_sleep = loop_interval - (time.time() - t_loop_start) if loop_interval > 0 and (time.time() - t_loop_start) < loop_interval:
time.sleep(loop_interval - (time.time() - t_loop_start))
if loop_interval > 0 and time_to_sleep > 0:
time.sleep(time_to_sleep)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit() sys.exit()

75
tools/rpi/hoymiles/outputs.py

@ -8,6 +8,7 @@ Hoymiles output plugin library
import socket import socket
from datetime import datetime, timezone from datetime import datetime, timezone
from hoymiles.decoders import StatusResponse from hoymiles.decoders import StatusResponse
from os import path
try: try:
from influxdb_client import InfluxDBClient from influxdb_client import InfluxDBClient
@ -36,6 +37,78 @@ class OutputPluginFactory:
""" """
raise NotImplementedError('The current output plugin does not implement store_status') 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): class InfluxOutputPlugin(OutputPluginFactory):
""" Influx2 output plugin """ """ Influx2 output plugin """
api = None api = None
@ -311,3 +384,5 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
output.store_status(data, self.session) output.store_status(data, self.session)
except ValueError as e: except ValueError as e:
print('Could not send data to volkszaehler instance: %s' % 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 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