From e5bbb7f5177e8f3ea729ac47dcf0d9fc2a27adcc Mon Sep 17 00:00:00 2001 From: Dominik Westner Date: Thu, 27 Oct 2022 10:38:52 +0200 Subject: [PATCH] implemented prometheus metrics --- include/WebApi.h | 2 + include/WebApi_prometheus.h | 17 ++++++ platformio.ini | 2 +- src/WebApi.cpp | 1 + src/WebApi_prometheus.cpp | 101 ++++++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 include/WebApi_prometheus.h create mode 100644 src/WebApi_prometheus.cpp diff --git a/include/WebApi.h b/include/WebApi.h index 699b9cd..e831f0b 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -12,6 +12,7 @@ #include "WebApi_network.h" #include "WebApi_ntp.h" #include "WebApi_power.h" +#include "WebApi_prometheus.h" #include "WebApi_security.h" #include "WebApi_sysstatus.h" #include "WebApi_webapp.h" @@ -41,6 +42,7 @@ private: WebApiNetworkClass _webApiNetwork; WebApiNtpClass _webApiNtp; WebApiPowerClass _webApiPower; + WebApiPrometheusClass _webApiPrometheus; WebApiSecurityClass _webApiSecurity; WebApiSysstatusClass _webApiSysstatus; WebApiWebappClass _webApiWebapp; diff --git a/include/WebApi_prometheus.h b/include/WebApi_prometheus.h new file mode 100644 index 0000000..3323ea1 --- /dev/null +++ b/include/WebApi_prometheus.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include "Hoymiles.h" + +class WebApiPrometheusClass { +public: + void init(AsyncWebServer* server); + void loop(); + +private: + void onPrometheusMetrics(AsyncWebServerRequest* request); + + void addField(AsyncResponseStream *stream, const char* serial, uint8_t idx, std::shared_ptr inv, uint8_t channel, uint8_t fieldId, const char* channelName = NULL); + + AsyncWebServer* _server; +}; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 6417bff..addad77 100644 --- a/platformio.ini +++ b/platformio.ini @@ -92,4 +92,4 @@ build_flags = ${env.build_flags} -DHOYMILES_PIN_IRQ=33 -DHOYMILES_PIN_CE=14 -DHOYMILES_PIN_CS=15 - -DOPENDTU_ETHERNET \ No newline at end of file + -DOPENDTU_ETHERNET diff --git a/src/WebApi.cpp b/src/WebApi.cpp index f6a6355..d522e7c 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -29,6 +29,7 @@ void WebApiClass::init() _webApiNetwork.init(&_server); _webApiNtp.init(&_server); _webApiPower.init(&_server); + _webApiPrometheus.init(&_server); _webApiSecurity.init(&_server); _webApiSysstatus.init(&_server); _webApiWebapp.init(&_server); diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp new file mode 100644 index 0000000..f99c984 --- /dev/null +++ b/src/WebApi_prometheus.cpp @@ -0,0 +1,101 @@ +#include "WebApi_prometheus.h" + +#include "Configuration.h" +#include "NetworkSettings.h" +#include "Hoymiles.h" + +void WebApiPrometheusClass::init(AsyncWebServer* server) +{ + using std::placeholders::_1; + + _server = server; + + _server->on("/metrics", HTTP_GET, std::bind(&WebApiPrometheusClass::onPrometheusMetrics, this, _1)); +} + +void WebApiPrometheusClass::loop() +{ +} + +void WebApiPrometheusClass::onPrometheusMetrics(AsyncWebServerRequest* request) +{ + auto stream = request->beginResponseStream("text/plain; charset=utf-8", 8192); + + stream->print(F("# HELP opendtu_build Build info\n")); + stream->print(F("# TYPE opendtu_build gauge\n")); + stream->printf("opendtu_build{name=\"%s\",id=\"%s\",version=\"%d.%d.%d\"} 1\n", + NetworkSettings.getHostname().c_str(), AUTO_GIT_HASH, CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff); + + stream->print(F("# HELP opendtu_platform Platform info\n")); + stream->print(F("# TYPE opendtu_platform gauge\n")); + stream->printf("opendtu_platform{arch=\"%s\",mac=\"%s\"} 1\n", ESP.getChipModel(), WiFi.macAddress().c_str()); + + stream->print(F("# HELP opendtu_uptime Uptime in seconds\n")); + stream->print(F("# TYPE opendtu_uptime counter\n")); + stream->printf("opendtu_uptime %lld\n", esp_timer_get_time() / 1000000); + + stream->print(F("# HELP opendtu_heap_size System memory size\n")); + stream->print(F("# TYPE opendtu_heap_size gauge\n")); + stream->printf("opendtu_heap_size %zu\n", ESP.getHeapSize()); + + stream->print(F("# HELP opendtu_free_heap_size System free memory\n")); + stream->print(F("# TYPE opendtu_free_heap_size gauge\n")); + stream->printf("opendtu_free_heap_size %zu\n", ESP.getFreeHeap()); + + stream->print(F("# HELP wifi_rssi WiFi RSSI\n")); + stream->print(F("# TYPE wifi_rssi gauge\n")); + stream->printf("wifi_rssi %d\n", WiFi.RSSI()); + + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); + + char serial[sizeof(uint64_t) * 8 + 1]; + snprintf(serial, sizeof(serial), "%0x%08x", + ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), + ((uint32_t)(inv->serial() & 0xFFFFFFFF))); + const char* name = inv->name(); + if(i==0) { + stream->print(F("# HELP opendtu_last_update last update from inverter in s\n")); + stream->print(F("# TYPE opendtu_last_update gauge\n")); + } + stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n", + serial, i, name, inv->Statistics()->getLastUpdate() / 1000); + + // Loop all channels + for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) { + addField(stream, serial, i, inv, c, FLD_PAC); + addField(stream, serial, i, inv, c, FLD_UAC); + addField(stream, serial, i, inv, c, FLD_IAC); + if (c == 0) { + addField(stream, serial, i, inv, c, FLD_PDC, "PowerDC"); + } else { + addField(stream, serial, i, inv, c, FLD_PDC); + } + addField(stream, serial, i, inv, c, FLD_UDC); + addField(stream, serial, i, inv, c, FLD_IDC); + addField(stream, serial, i, inv, c, FLD_YD); + addField(stream, serial, i, inv, c, FLD_YT); + addField(stream, serial, i, inv, c, FLD_F); + addField(stream, serial, i, inv, c, FLD_T); + addField(stream, serial, i, inv, c, FLD_PF); + addField(stream, serial, i, inv, c, FLD_PRA); + addField(stream, serial, i, inv, c, FLD_EFF); + addField(stream, serial, i, inv, c, FLD_IRR); + } + } + stream->addHeader(F("Cache-Control"), F("no-cache")); + stream->addHeader(F("Cache-Control"), F("no-cache")); + request->send(stream); +} + +void WebApiPrometheusClass::addField(AsyncResponseStream *stream, const char* serial, uint8_t idx, std::shared_ptr inv, uint8_t channel, uint8_t fieldId, const char* channelName) +{ + if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) { + const char* chanName = (channelName == NULL) ? inv->Statistics()->getChannelFieldName(channel, fieldId) : channelName; + if(idx==0 && channel==0) { + stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(channel, fieldId)); + stream->printf("# TYPE opendtu_%s gauge\n", chanName); + } + stream->printf("opendtu_%s{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %f\n", chanName, serial, idx, inv->name(), channel, inv->Statistics()->getChannelFieldValue(channel, fieldId)); + } +} \ No newline at end of file