Metric collection
Exporting home sensor metrics to Prometheus
I wanted the sensor collector to remain boring. It already received readings from several ESP32 nodes and stored the latest value for each device. Adding Prometheus did not need a second database or a large library; it only needed one HTTP endpoint that renders the current state in the text exposition format.
The collector keeps an in-memory record with a sensor identifier, location, temperature, humidity, and timestamp. The endpoint exposes gauges plus a simple age metric. Age turned out to be more useful than a binary online flag because an alert can decide whether 90 seconds or 15 minutes is acceptable for a particular sensor.
from time import time
from flask import Response
def metric_line(name, labels, value):
label_text = ",".join(
f'{key}="{str(val).replace(chr(34), chr(39))}"'
for key, val in labels.items()
)
return f"{name}{{{label_text}}} {value}\\n"
@app.get("/metrics")
def metrics():
now = time()
body = []
for sensor in sensors.values():
labels = {"sensor": sensor.id, "location": sensor.location}
body.append(metric_line("metrion_temperature_celsius", labels, sensor.temp_c))
body.append(metric_line("metrion_humidity_percent", labels, sensor.rh))
body.append(metric_line("metrion_sample_age_seconds", labels, now - sensor.timestamp))
return Response("".join(body), mimetype="text/plain")
I avoided putting firmware version, network address, or free-form descriptions into labels. Labels create time-series combinations, so they should be stable and limited. For this small setup, sensor and location are enough to distinguish devices without turning every metadata change into a new series.
Scrape settings
The collector runs on the same local machine as Prometheus. A 30-second scrape interval is faster than most sensor update periods, but that is intentional: Prometheus sees the current value repeatedly while metrion_sample_age_seconds continues to grow if the device stops reporting.
scrape_configs:
- job_name: metrion_sensors
scrape_interval: 30s
static_configs:
- targets: ["127.0.0.1:9108"]
An alert is then easy to read: metrion_sample_age_seconds > 300 means a sensor has not produced a fresh value for five minutes. I also keep a separate rule for room humidity above 70% for twenty minutes, because a brief spike after opening a window is not interesting.
The exporter currently exposes fewer than fifty series. That is small enough that clarity matters more than clever optimization. The main rule is that a metric name should include the unit, labels should stay stable, and stale data should be visible as age rather than silently disappearing.