Browse Source

Declare metric type only once. Add grouping of metrics. Send realtime data for each inverter in a separate chunk.

pull/958/head
Frank 2 years ago
parent
commit
5ae1f4b86f
  1. 222
      src/web/web.h

222
src/web/web.h

@ -627,14 +627,22 @@ class Web {
#ifdef ENABLE_PROMETHEUS_EP #ifdef ENABLE_PROMETHEUS_EP
// Note
// Prometheus exposition format is defined here: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
// TODO: Check packetsize for MAX_NUM_INVERTERS. Successfull Tested with 4 Inverters (each with 4 channels)
enum { enum {
metricsStateStart, metricsStateInverter, metricStateRealtimeData,metricsStateAlarmData,metricsStateEnd metricsStateStart,
metricsStateInverter1, metricsStateInverter2, metricsStateInverter3, metricsStateInverter4,
metricStateRealtimeFieldId, metricStateRealtimeInverterId,
metricsStateAlarmData,
metricsStateEnd
} metricsStep; } metricsStep;
int metricsInverterId,metricsChannelId; int metricsInverterId;
uint8_t metricsFieldId;
bool metricDeclared;
void showMetrics(AsyncWebServerRequest *request) { void showMetrics(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); DPRINTLN(DBG_VERBOSE, F("web::showMetrics"));
metricsStep = metricsStateStart; metricsStep = metricsStateStart;
AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"), AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"),
[this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t [this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t
@ -647,7 +655,11 @@ class Web {
char type[60], topic[100], val[25]; char type[60], topic[100], val[25];
size_t len = 0; size_t len = 0;
int alarmChannelId; int alarmChannelId;
int metricsChannelId;
// Perform grouping on metrics according to format specification
// Each step must return at least one character. Otherwise the processing of AsyncWebServerResponse stops.
// So several "Info:" blocks are used to keep the transmission going
switch (metricsStep) { switch (metricsStep) {
case metricsStateStart: // System Info & NRF Statistics : fit to one packet case metricsStateStart: // System Info & NRF Statistics : fit to one packet
snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n");
@ -676,93 +688,138 @@ class Web {
metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt);
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
// Start Inverter loop // Next is Inverter information
metricsInverterId = 0; metricsInverterId = 0;
metricsStep = metricsStateInverter; metricsStep = metricsStateInverter1;
break; break;
case metricsStateInverter: // Inverter loop case metricsStateInverter1: // Information about all inverters configured : fit to one packet
if (metricsInverterId < mSys->getNumInverters()) { metrics = "# TYPE ahoy_solar_inverter_info gauge\n";
iv = mSys->getInverterByPos(metricsInverterId); metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n",
if(NULL != iv) { [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->serial.u64;});
// Inverter info : fit to one packet len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_info gauge\n"); metricsStep = metricsStateInverter2;
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", break;
iv->config->name, iv->config->serial.u64);
metrics = String(type) + String(topic); case metricsStateInverter2: // Information about all inverters configured : fit to one packet
metrics += "# TYPE ahoy_solar_inverter_is_enabled gauge\n";
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n"); metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",iv->config->name,iv->config->enabled); [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->enabled;});
metrics += String(type) + String(topic);
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_available gauge\n"); metricsStep = metricsStateInverter3;
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",iv->config->name,iv->isAvailable(mApp->getTimestamp())); break;
metrics += String(type) + String(topic);
case metricsStateInverter3: // Information about all inverters configured : fit to one packet
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n"); metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n";
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",iv->config->name,iv->isProducing(mApp->getTimestamp())); metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",
metrics += String(type) + String(topic); [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable(mApp->getTimestamp());});
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); metricsStep = metricsStateInverter4;
break;
// Start Realtime Data Channel loop for this inverter
metricsChannelId = 0; case metricsStateInverter4: // Information about all inverters configured : fit to one packet
metricsStep = metricStateRealtimeData; metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n";
} metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing(mApp->getTimestamp());});
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
// Start Realtime Field loop
metricsFieldId = FLD_UDC;
metricsStep = metricStateRealtimeFieldId;
break;
case metricStateRealtimeFieldId: // Iterate over all defined fields
if (metricsFieldId < FLD_LAST_ALARM_CODE) {
metrics = "# Info: processing realtime field #"+String(metricsFieldId)+"\n";
metricDeclared = false;
metricsInverterId = 0;
metricsStep = metricStateRealtimeInverterId;
} else { } else {
metricsStep = metricsStateEnd; metrics = "# Info: all realtime fields processed\n";
metricsStep = metricsStateAlarmData;
} }
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
break; break;
case metricStateRealtimeData: // Realtime Data Channel loop case metricStateRealtimeInverterId: // Iterate over all inverters for this field
iv = mSys->getInverterByPos(metricsInverterId); metrics = "";
rec = iv->getRecordStruct(RealTimeRunData_Debug); if (metricsInverterId < mSys->getNumInverters()) {
if (metricsChannelId < rec->length) { // process all channels of this inverter
uint8_t channel = rec->assign[metricsChannelId].ch;
// Skip entry if maxPwr is 0 and it's not the inverter channel (channel 0) iv = mSys->getInverterByPos(metricsInverterId);
if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) { if (NULL != iv) {
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); rec = iv->getRecordStruct(RealTimeRunData_Debug);
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); for (metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) {
if (0 == channel) { uint8_t channel = rec->assign[metricsChannelId].ch;
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name);
} else { // Try inverter channel (channel 0) or any channel with maxPwr > 0
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) {
if (metricsFieldId == iv->getByteAssign(metricsChannelId, rec)->fieldId) {
// This is the correct field to report
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec));
// Declare metric only once
if (!metricDeclared) {
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str());
metrics += type;
metricDeclared = true;
}
// report value
if (0 == channel) {
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name);
} else {
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]);
}
snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec));
metrics += topic;
metrics += val;
}
}
}
if (metrics.length() < 1) {
metrics = "# Info: Field #"+String(metricsFieldId)+" not available for inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n";
metricsFieldId++; // Process next field Id
metricsStep = metricStateRealtimeFieldId;
} }
snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec));
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
} else { } else {
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. metrics = "# Info: No data for field #"+String(metricsFieldId)+" of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n";
metricsFieldId++; // Process next field Id
metricsStep = metricStateRealtimeFieldId;
} }
// Stay in this state and try next inverter
metricsChannelId++; metricsInverterId++;
} else { } else {
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. metrics = "# Info: All inverters for field #"+String(metricsFieldId)+" processed.\n";
metricsFieldId++; // Process next field Id
// All realtime data channels processed --> try alarm data metricsStep = metricStateRealtimeFieldId;
metricsStep = metricsStateAlarmData;
} }
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
break; break;
case metricsStateAlarmData: // Alarm Info loop case metricsStateAlarmData: // Alarm Info loop : fit to one packet
iv = mSys->getInverterByPos(metricsInverterId); // Perform grouping on metrics according to Prometheus exposition format specification
rec = iv->getRecordStruct(AlarmData); snprintf(type, sizeof(type),"# TYPE ahoy_solar_%s gauge\n",fields[FLD_LAST_ALARM_CODE]);
// simple hack : there is only one channel with alarm data metrics = type;
// TODO: find the right one channel with the alarm id
alarmChannelId = 0; for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) {
// printf("AlarmData Length %d\n",rec->length); iv = mSys->getInverterByPos(metricsInverterId);
if (alarmChannelId < rec->length) { if (NULL != iv) {
//uint8_t channel = rec->assign[alarmChannelId].ch; rec = iv->getRecordStruct(AlarmData);
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); // simple hack : there is only one channel with alarm data
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str()); // TODO: find the right one channel with the alarm id
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); alarmChannelId = 0;
snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec)); if (alarmChannelId < rec->length) {
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
} else { snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name);
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec));
metrics += topic;
metrics += val;
}
}
} }
// alarm channel processed --> try next inverter len = snprintf((char*)buffer,maxLen,"%s",metrics.c_str());
metricsInverterId++; metricsStep = metricsStateEnd;
metricsStep = metricsStateInverter;
break; break;
case metricsStateEnd: case metricsStateEnd:
@ -775,6 +832,21 @@ class Web {
request->send(response); request->send(response);
} }
// Traverse all inverters and collect the metric via valueFunc
String inverterMetric(char *buffer, size_t len, const char *format, std::function<uint64_t(Inverter<> *iv, IApp *mApp)> valueFunc) {
Inverter<> *iv;
String metric = "";
for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) {
iv = mSys->getInverterByPos(metricsInverterId);
if (NULL != iv) {
snprintf(buffer,len,format,iv->config->name, valueFunc(iv,mApp));
metric += String(buffer);
}
}
return metric;
}
String radioStatistic(String statistic, uint32_t value) { String radioStatistic(String statistic, uint32_t value) {
char type[60], topic[80], val[25]; char type[60], topic[80], val[25];
snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str()); snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str());

Loading…
Cancel
Save