Writing Munin Plugins
Protocol
A plugin is called with no arguments to fetch values, and with config to describe the graph. Optional: autoconf (print yes/no), suggest (print modes for wildcard plugins).
Config output
Global attributes describe the graph:
| Attribute | Purpose | Example |
|---|---|---|
graph_title |
Title above graph | graph_title CPU usage |
graph_vlabel |
Y-axis label | graph_vlabel percent |
graph_category |
Grouping in web UI | graph_category system |
graph_args |
Passed to rrdgraph | graph_args --base 1000 -l 0 |
graph_scale |
Enable SI scaling | graph_scale no |
graph_info |
Description below graph | graph_info CPU usage by type |
Per-field attributes:
| Attribute | Purpose | Example |
|---|---|---|
field.label |
Legend label (required) | cpu.label CPU |
field.type |
GAUGE, COUNTER, DERIVE | cpu.type DERIVE |
field.draw |
LINE1, LINE2, AREA, AREASTACK | mem.draw AREASTACK |
field.min |
Minimum valid value | cpu.min 0 |
field.max |
Maximum valid value | cpu.max 100 |
field.warning |
Warning threshold | cpu.warning 80 |
field.critical |
Critical threshold | cpu.critical 95 |
field.info |
Field description | cpu.info Percent CPU used |
field.cdef |
RPN expression transform | bytes.cdef bytes,8,* |
field.negative |
Mirror another field below axis | up.negative down |
Value output
fieldname.value 42
otherfield.value 3.14
Use U for unknown: fieldname.value U
Wildcard plugins
A single plugin script handles multiple instances via its symlink name. The plugin parses basename $0 to determine what to monitor.
Example: if_ plugin symlinked as if_eth0, if_wlan0. The script strips its prefix to get the interface name.
Wildcard plugins should implement suggest to list valid instances:
if [ "${1:-}" = "suggest" ]; then
ls /sys/class/net/ | grep -v '^lo$'
exit 0
fi
Multigraph plugins
Emit multigraph <graph_id> before each graph's output:
#!/bin/sh
if [ "${1:-}" = "config" ]; then
echo "multigraph service_users"
echo "graph_title Users"
echo "graph_category myservice"
echo "graph_vlabel count"
echo "total.label Total users"
echo "multigraph service_uptime"
echo "graph_title Uptime"
echo "graph_category myservice"
echo "graph_vlabel days"
echo "uptime.label Uptime"
exit 0
fi
echo "multigraph service_users"
echo "total.value $(get_user_count)"
echo "multigraph service_uptime"
echo "uptime.value $(get_uptime_days)"
The master must negotiate cap multigraph before list will show these plugins. This happens automatically during normal polling.
Graph args reference
Common graph_args values:
--base 1000: decimal SI units (1k = 1000)--base 1024: binary units (1Ki = 1024), use for bytes-l 0: lower limit 0 (graph won't go below)--upper-limit 100: upper limit (for percentages)
Magic markers
Add these comments for munin-node-configure auto-detection:
#%# family=auto # or contrib, manual
#%# capabilities=autoconf suggest
Testing during development
# Set MUNIN_LIBDIR if plugin uses plugin.sh helpers
export MUNIN_LIBDIR=/usr/share/munin # or /usr/lib/munin on Arch
# Test directly
chmod +x ./myplugin
./myplugin config
./myplugin
# Test through munin-run (sets up full environment)
cp myplugin /etc/munin/plugins/
munin-run myplugin config
munin-run myplugin
Common patterns
Monitoring a CLI tool's output
#!/bin/sh
case ${1:-} in
config)
echo "graph_title My Service Stats"
echo "graph_category myservice"
echo "graph_vlabel count"
echo "connections.label Active connections"
exit 0 ;;
esac
echo "connections.value $(myservice-ctl status | awk '/connections:/ {print $2}')"
Monitoring an API endpoint
#!/bin/sh
case ${1:-} in
config)
echo "graph_title API Response Time"
echo "graph_category network"
echo "graph_vlabel ms"
echo "response.label Response time"
exit 0 ;;
esac
ms=$(curl -s -o /dev/null -w '%{time_total}' http://localhost:8080/health | awk '{printf "%.0f", $1 * 1000}')
echo "response.value $ms"
Monitoring container resource usage (Incus/LXD)
Query the Incus API via incus query for per-container stats. Run as user root in plugin-conf.d. Use DERIVE for CPU (cumulative nanoseconds → rate), AREASTACK for memory.