Monitoring with Munin#
Why#
Instantaneous numbers (chronyc tracking) confirm the server works
right now. But real validation of a Stratum 1 happens over time: does
frequency drift follow a day/night cycle tied to temperature? Does the
hardware timestamping rate degrade under load? Munin builds historical RRD
graphs (day/week/month) that make these trends visible at a glance,
impossible to get from a single instantaneous command.
Why Munin rather than Prometheus#
Prometheus is today’s most widespread monitoring tool, so the question deserves a reasoned answer. Four reasons tipped the balance toward Munin on this particular server:
- The write profile. Munin stores RRD files: fixed size, written once every 5 minutes, with zero growth. Prometheus writes continuously (a time-series database with a WAL journal): exactly the write profile the Filesystem chapter works to eliminate in order to preserve the SD card.
- No permanent daemon. Here, monitoring only exists once every
5 minutes, confined to core 0 at minimum priority (
taskset -c 0 nice -n 19 ionice -c 3, Public telemetry chapter). Prometheus is a resident process consuming CPU and RAM around the clock, on a machine whose entire purpose is microsecond-level scheduling stability. - The attack surface. The Prometheus model relies on an exporter listening on an HTTP port, one more network service to harden and filter. Munin, configured as done here, produces static PNGs served by the already-hardened nginx: no additional exposed port.
- The actual need. A single host, a handful of graphs, a 5-minute refresh aligned with the public page. No fleet, no required alerting, no ad hoc queries. Prometheus shines precisely where this project has no need.
In exchange, Munin offers neither fine resolution (5 minutes, versus a typical ~15 s scrape), nor a query language (PromQL), nor built-in alerting.
This remains a personal choice, consistent with this server’s constraints, not a general truth: a reader comfortable with Prometheus is entirely welcome to use it. The clean way would then be to host Prometheus on another machine (the administration network) and expose on the Pi only a minimal exporter restricted to that network: storage and computation leave the time server, only metric reads remain.
How#
Metrology plugins#
apt install -y munin munin-node
# Chrony plugin shipped with the package
ln -sf /usr/share/munin/plugins/chrony /etc/munin/plugins/chrony
# Community chrony_status plugin (munin-contrib)
curl -fsSL https://raw.githubusercontent.com/munin-monitoring/contrib/master/plugins/chrony/chrony_status \
-o /usr/share/munin/plugins/chrony_status
chmod +x /usr/share/munin/plugins/chrony_status
ln -sf /usr/share/munin/plugins/chrony_status /etc/munin/plugins/chrony_statusCustom plugin: median-filtered PHC offset#
cat > /usr/share/munin/plugins/ptp_offset <<'EOF'
#!/bin/sh
# Offset between the PTP hardware clock (/dev/ptp0) and CLOCK_REALTIME
if [ "$1" = "config" ]; then
echo "graph_title PHC to System Clock Offset"
echo "graph_vlabel offset (ns)"
echo "graph_category time"
echo "offset.label PHC Offset"
echo "offset.type GAUGE"
echo "offset.draw LINE2"
exit 0
fi
PHC_OFFSET=$(/usr/local/sbin/phc-offset-median /dev/ptp0 5)
[ -z "$PHC_OFFSET" ] && PHC_OFFSET="U"
echo "offset.value $PHC_OFFSET"
EOF
chmod +x /usr/share/munin/plugins/ptp_offset
ln -sf /usr/share/munin/plugins/ptp_offset /etc/munin/plugins/ptp_offset
mkdir -p /etc/munin/plugin-conf.d/
cat > /etc/munin/plugin-conf.d/ptp_offset.conf <<'EOF'
[ptp_offset]
user root
EOFThis plugin delegates to a shared script (phc-offset-median) that
repeats the reading 5 times at real-time priority and keeps the median,
see the PHC chapter for the full justification: a single reading is
vulnerable to one-off scheduling spikes that would produce a misleading
sawtooth graph.
Munin configuration#
cat > /etc/munin/munin.conf <<'EOF'
htmldir /var/time
graph_strategy cron
html_strategy disabled
graph_data_size custom 35d
timeout_fetch_all_nodes 240
[time.example.org]
address 127.0.0.1
use_node_name yes
EOFgraph_strategy cron regenerates PNG graphs on Munin’s cron cadence
rather than on demand (CGI), consistent with the periodic-regeneration
philosophy already seen in the Public telemetry chapter. html_strategy disabled outright disables Munin’s native HTML page generation: this
project serves its own static page (previous chapter) and only consumes
the PNGs, generating unused HTML pages on top would waste CPU and space
on the /var/time tmpfs every cycle. timeout_fetch_all_nodes 240
(4 minutes) leaves a margin under Munin’s 5-minute cron cycle: if metric
collection took longer than that, it would be cut off before overlapping
the next cycle, instead of letting two runs pile up.
Pitfalls to avoid#
A plugin without the executable bit is silently ignored, no error, no log, just a missing graph. After creating or copying any Munin plugin,
chmod +xis not optional. This is the single most common mistake when adding a custom or downloaded plugin.
- Testing a plugin as the wrong user.
munin-noderuns as root by default on Debian and drops privileges to whatever user is specified inplugin-conf.d/(oftenmunin, sometimesrootfor plugins needing elevated privileges likeptp_offset). A manual testsudo -u munin /usr/share/munin/plugins/ptp_offsetcan fail even though the plugin works fine viamunin-node, not a bug, just a different privilege context. For an accurate test, invoke the plugin under plainsudo(root), or explicitly check theuser=set in its config. - Looking for
munin-plugins-extraon a recent distribution. This package has been merged intomunin-nodeon recent Debian releases, check withapt-cache searchbefore assuming it’s missing. - Sizing
graph_data_sizewithout thinking through the actual retention needed. A single RRA (Round Robin Archive) at fixed resolution (here 5 minutes over 35 days) is enough for day/week/month views, but doesn’t cover a fine-grained yearly view, a deliberate trade-off rather than an oversight, consistent with the “volatile logs” philosophy of the rest of this project (Filesystem chapter).
Verify#
# Manual plugin execution, as munin-node would do it
sudo /usr/share/munin/plugins/ptp_offset
# Should return "offset.value <number>", not "offset.value U"
# Test the config the plugin declares
sudo /usr/share/munin/plugins/ptp_offset config
munin-run chrony
# Should return numeric values for every declared metric
# Manually generate graphs (instead of waiting for the cron)
munin-cron --force-root
ls -la /var/time/*.png | head