#!/usr/bin/env python3 """Munin plugin to monitor chrony NTP daemon status. =head1 NAME chrony_status - monitor chrony NTP daemon status =head1 APPLICABLE SYSTEMS Systems with chrony installed. =head1 CONFIGURATION No configuration is required for this plugin. =head1 INTERPRETATION Monitor Chrony's stratum value (with warning), time offset, network delay and clock frequency. It would be very easy to monitor all Chrony's values, but IMHO they aren't needed. The most important information in stratum, giving "synced" / "not synced" value. =head1 AUTHOR Kim B. Heino =head1 LICENSE GPLv2 =head1 MAGIC MARKERS #%# family=auto #%# capabilities=autoconf =cut """ import os import subprocess import sys FIELDS = { 'stratum': 'Stratum', 'systime': 'System time', 'delay': 'Root delay', 'frequency': 'Frequency', } def get_values(): """Run "chronyc tracking" and parse it's output. Return: list of (label, value, description) """ try: output = subprocess.run(['chronyc', 'tracking'], stdout=subprocess.PIPE, check=False, encoding='utf-8', errors='ignore') except FileNotFoundError: return {} lines = output.stdout.splitlines() ret = {} for label in FIELDS: for line in lines: if line.startswith(FIELDS[label]): value = float(line.split(':', 1)[1].split()[0]) if ' slow' in line: value = -value ret[label] = value return ret def config(): """Print plugin config.""" print('multigraph chrony_stratum') print('graph_title Chrony stratum') print('graph_vlabel stratum') print('graph_category time') print('graph_args --base 1000 --lower-limit 0') print('stratum.label Stratum') print('stratum.warning 1:9') # Use long unknown_limit to allow server reboot without Munin warning. # Clock doesn't drift fast so there's no hurry with warning. print('stratum.unknown_limit 15') print('multigraph chrony_systime') print('graph_title Chrony system time offset') print('graph_vlabel seconds') print('graph_category time') print('graph_args --base 1000') print('systime.label System time offset to NTP time') print('multigraph chrony_delay') print('graph_title Chrony network delay') print('graph_vlabel seconds') print('graph_category time') print('graph_args --base 1000') print('delay.label Network path delay') print('multigraph chrony_frequency') print('graph_title Chrony clock frequency error') print('graph_vlabel ppm') print('graph_category time') print('graph_args --base 1000') print('frequency.label Local clock frequency error') if os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1': fetch() def fetch(): """Print values.""" values = get_values() for key in FIELDS: print('multigraph chrony_{}'.format(key)) if key == 'stratum' and values[key] == 0: print('{}.value U'.format(key)) else: print('{}.value {:.8f}'.format(key, values[key])) if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'autoconf': print('yes' if get_values() else 'no (chrony is not running)') elif len(sys.argv) > 1 and sys.argv[1] == 'config': config() else: fetch()