The previous chapters covered optimization for individual device classes. This chapter builds a unified monitoring pipeline that collects power data from embedded devices, laptops, and servers into a single view — making it easy to track baselines, detect regressions, and verify that optimizations are holding.
The pipeline uses three tiers:
upowerTopic namespace convention:
power/{location}/{device_name}/{metric}
# Examples:
power/lab/esp32-sensor-01/mW
power/office/laptop-thinkpad/package_W
power/rack/nuc-homeserver/rapl_W
sudo apt install mosquitto mosquitto-clients
sudo systemctl enable --now mosquitto
# Test publish/subscribe
mosquitto_sub -h localhost -t "power/#" &
mosquitto_pub -h localhost -t "power/test/device/mW" -m "245.3"
Running on a Raspberry Pi connected to one or more INA219 sensors:
import smbus2, struct, time
import paho.mqtt.client as mqtt
BUS_NUM, ADDR, SHUNT_OHMS = 1, 0x40, 0.1
BROKER, DEVICE = "localhost", "esp32-sensor-01"
TOPIC = f"power/lab/{DEVICE}/mW"
def read_power_mw(bus):
raw = bus.read_i2c_block_data(ADDR, 0x01, 2)
shunt_mv = struct.unpack('>h', bytes(raw))[0] * 0.01
raw = bus.read_i2c_block_data(ADDR, 0x02, 2)
bus_v = ((struct.unpack('>h', bytes(raw))[0] >> 3) * 4) / 1000
return bus_v * (shunt_mv / SHUNT_OHMS)
client = mqtt.Client()
client.connect(BROKER)
with smbus2.SMBus(BUS_NUM) as bus:
while True:
power_mw = read_power_mw(bus)
client.publish(TOPIC, f"{power_mw:.2f}")
time.sleep(10)
Full version with multiple INA219 addresses is in code/ina219_mqtt.py.
import time, pathlib
import paho.mqtt.client as mqtt
BROKER = "192.168.1.100" # your MQTT broker IP
DEVICE = "nuc-homeserver"
DOMAIN = pathlib.Path("/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj")
TOPIC = f"power/rack/{DEVICE}/rapl_W"
client = mqtt.Client()
client.connect(BROKER)
e0, t0 = int(DOMAIN.read_text()), time.monotonic()
while True:
time.sleep(30)
e1, t1 = int(DOMAIN.read_text()), time.monotonic()
power_w = (e1 - e0) / 1e6 / (t1 - t0)
client.publish(TOPIC, f"{power_w:.3f}")
e0, t0 = e1, t1
Full version with DRAM and uncore subzones is in code/rapl_mqtt.py.
The aggregator subscribes to all power topics and stores them with timestamps:
import sqlite3, time
import paho.mqtt.client as mqtt
DB = "power_log.db"
BROKER = "localhost"
def init_db():
con = sqlite3.connect(DB)
con.execute("""CREATE TABLE IF NOT EXISTS readings
(ts REAL, device TEXT, metric TEXT, value REAL)""")
con.commit()
return con
def on_message(client, con, msg):
parts = msg.topic.split("/") # power/location/device/metric
if len(parts) == 4:
_, _, device, metric = parts
try:
value = float(msg.payload)
con.execute("INSERT INTO readings VALUES (?,?,?,?)",
(time.time(), device, metric, value))
con.commit()
except ValueError:
pass
con = init_db()
client = mqtt.Client(userdata=con)
client.on_message = on_message
client.connect(BROKER)
client.subscribe("power/#")
client.loop_forever()
Full version with alerting and weekly report generation is in code/dashboard.py.
The aggregator can compute a rolling average and alert when a device exceeds its baseline:
import smtplib
from email.mime.text import MIMEText
BASELINES = {
"nuc-homeserver": 8.0, # watts — alert if > 8 W at idle
"esp32-sensor-01": 5.0, # mW — alert if > 5 mW average
}
def check_and_alert(device, current_value, threshold):
if current_value > threshold * 1.5: # 50% above baseline
msg = MIMEText(f"{device} drawing {current_value:.1f} (baseline {threshold})")
msg["Subject"] = f"Power alert: {device}"
msg["From"] = "monitor@home"
msg["To"] = "you@example.com"
with smtplib.SMTP("localhost") as s:
s.send_message(msg)
Alternatively, use ntfy.sh for push notifications without an SMTP server — a single HTTP POST to your ntfy topic.
Software measurements have systematic errors:
energy_uj counter wraps around at max_energy_range_uj. Handle overflow in your polling code by checking for e1 < e0.For a ground-truth baseline, measure wall power with a Kill-a-Watt or calibrated smart plug once, then use RAPL/INA219 for relative comparisons and trend monitoring.
| ← Chapter 11: Service and Workload Scheduling | Table of Contents | Chapter 13: Real-World Case Studies and Design Patterns → |