root / plugins / chrony / chrony_status @ 357c3586
Historique | Voir | Annoter | Télécharger (4,71 ko)
| 1 |
#!/usr/bin/env python3 |
|---|---|
| 2 |
|
| 3 |
"""Munin plugin to monitor chrony NTP daemon status. |
| 4 |
|
| 5 |
=head1 NAME |
| 6 |
|
| 7 |
chrony_status - monitor chrony NTP daemon status |
| 8 |
|
| 9 |
=head1 APPLICABLE SYSTEMS |
| 10 |
|
| 11 |
Systems with chrony installed. |
| 12 |
|
| 13 |
=head1 CONFIGURATION |
| 14 |
|
| 15 |
Needs to be run as the user running chronyd (or root) in order to access the |
| 16 |
Unix domain socket which chronyc uses to communicate with chronyd. Example |
| 17 |
/etc/munin/plugin-conf.d/chrony_status.conf: |
| 18 |
|
| 19 |
[chrony_status] |
| 20 |
user _chrony |
| 21 |
|
| 22 |
=head1 INTERPRETATION |
| 23 |
|
| 24 |
Monitor Chrony's stratum value (with warning), time offset, network delay, clock |
| 25 |
frequency, packets received, and packets dropped. It would be very easy to |
| 26 |
monitor all of Chrony's values, but IMHO they aren't needed. The most important |
| 27 |
information is stratum, giving "synced" / "not synced" value. |
| 28 |
|
| 29 |
=head1 AUTHOR |
| 30 |
|
| 31 |
Kim B. Heino <b@bbbs.net> |
| 32 |
|
| 33 |
=head1 LICENSE |
| 34 |
|
| 35 |
GPLv2 |
| 36 |
|
| 37 |
=head1 MAGIC MARKERS |
| 38 |
|
| 39 |
#%# family=auto |
| 40 |
#%# capabilities=autoconf |
| 41 |
|
| 42 |
=cut |
| 43 |
""" |
| 44 |
|
| 45 |
import os |
| 46 |
import subprocess |
| 47 |
import sys |
| 48 |
|
| 49 |
|
| 50 |
FIELDS = {
|
| 51 |
'stratum': 'Stratum', |
| 52 |
'systime': 'System time', |
| 53 |
'delay': 'Root delay', |
| 54 |
'frequency': 'Frequency', |
| 55 |
'received': 'NTP packets received', |
| 56 |
'dropped': 'NTP packets dropped', |
| 57 |
'command_received': 'Command packets received', |
| 58 |
'command_dropped': 'Command packets dropped', |
| 59 |
'client_log_records_dropped': 'Client log records dropped', |
| 60 |
} |
| 61 |
|
| 62 |
|
| 63 |
def get_values(): |
| 64 |
"""Run `chronyc tracking` and `chronyc serverstats` and parse their output. |
| 65 |
|
| 66 |
Return: list of (label, value, description) |
| 67 |
""" |
| 68 |
try: |
| 69 |
output = subprocess.run(['chronyc', '-m', 'tracking', 'serverstats'], |
| 70 |
stdout=subprocess.PIPE, check=False, |
| 71 |
encoding='utf-8', errors='ignore') |
| 72 |
except FileNotFoundError: |
| 73 |
return {}
|
| 74 |
lines = output.stdout.splitlines() |
| 75 |
ret = {}
|
| 76 |
for label in FIELDS: |
| 77 |
for line in lines: |
| 78 |
if line.startswith(FIELDS[label]): |
| 79 |
value = float(line.split(':', 1)[1].split()[0])
|
| 80 |
if ' slow' in line: |
| 81 |
value = -value |
| 82 |
ret[label] = value |
| 83 |
return ret |
| 84 |
|
| 85 |
|
| 86 |
def config(): |
| 87 |
"""Print plugin config.""" |
| 88 |
print('multigraph chrony_stratum')
|
| 89 |
print('graph_title Chrony stratum')
|
| 90 |
print('graph_vlabel stratum')
|
| 91 |
print('graph_category time')
|
| 92 |
print('graph_args --base 1000 --lower-limit 0')
|
| 93 |
print('stratum.label Stratum')
|
| 94 |
print('stratum.warning 1:9')
|
| 95 |
# Use long unknown_limit to allow server reboot without Munin warning. |
| 96 |
# Clock doesn't drift fast so there's no hurry with warning. |
| 97 |
print('stratum.unknown_limit 15')
|
| 98 |
|
| 99 |
print('multigraph chrony_systime')
|
| 100 |
print('graph_title Chrony system time offset')
|
| 101 |
print('graph_vlabel seconds')
|
| 102 |
print('graph_category time')
|
| 103 |
print('graph_args --base 1000')
|
| 104 |
print('systime.label System time offset to NTP time')
|
| 105 |
|
| 106 |
print('multigraph chrony_delay')
|
| 107 |
print('graph_title Chrony network delay')
|
| 108 |
print('graph_vlabel seconds')
|
| 109 |
print('graph_category time')
|
| 110 |
print('graph_args --base 1000')
|
| 111 |
print('delay.label Network path delay')
|
| 112 |
|
| 113 |
print('multigraph chrony_frequency')
|
| 114 |
print('graph_title Chrony clock frequency error')
|
| 115 |
print('graph_vlabel ppm')
|
| 116 |
print('graph_category time')
|
| 117 |
print('graph_args --base 1000')
|
| 118 |
print('frequency.label Local clock frequency error')
|
| 119 |
|
| 120 |
print('multigraph chrony_serverstats')
|
| 121 |
print('graph_title Chrony server statistics')
|
| 122 |
print('graph_vlabel Packets/${graph_period}')
|
| 123 |
print('graph_category time')
|
| 124 |
print('graph_args --base 1000')
|
| 125 |
print('received.label Packets received')
|
| 126 |
print('received.type DERIVE')
|
| 127 |
print('received.min 0')
|
| 128 |
print('dropped.label Packets dropped')
|
| 129 |
print('dropped.type DERIVE')
|
| 130 |
print('dropped.min 0')
|
| 131 |
print('command_received.label Command packets received')
|
| 132 |
print('command_received.type DERIVE')
|
| 133 |
print('command_received.min 0')
|
| 134 |
print('command_dropped.label Command packets dropped')
|
| 135 |
print('command_dropped.type DERIVE')
|
| 136 |
print('command_dropped.min 0')
|
| 137 |
print('client_log_records_dropped.label Client log records dropped')
|
| 138 |
print('client_log_records_dropped.type DERIVE')
|
| 139 |
print('client_log_records_dropped.min 0')
|
| 140 |
|
| 141 |
if os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1':
|
| 142 |
fetch() |
| 143 |
|
| 144 |
|
| 145 |
def fetch(): |
| 146 |
"""Print values.""" |
| 147 |
values = get_values() |
| 148 |
for key in FIELDS: |
| 149 |
print('multigraph chrony_{}'.format(key))
|
| 150 |
if key == 'stratum' and values[key] == 0: |
| 151 |
print('{}.value U'.format(key))
|
| 152 |
else: |
| 153 |
print('{}.value {:.8f}'.format(key, values[key]))
|
| 154 |
|
| 155 |
|
| 156 |
if __name__ == '__main__': |
| 157 |
if len(sys.argv) > 1 and sys.argv[1] == 'autoconf': |
| 158 |
print('yes' if get_values() else 'no (chrony is not running)')
|
| 159 |
elif len(sys.argv) > 1 and sys.argv[1] == 'config': |
| 160 |
config() |
| 161 |
else: |
| 162 |
fetch() |
