163 lines
3.7 KiB
Python
Executable File
163 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
|
|
|
|
'''
|
|
=head1 NAME
|
|
|
|
timesync_status - monitor ntp status with systemd-timesyncd
|
|
|
|
=head1 APPLICABLE SYSTEMS
|
|
|
|
All systems using systemd-timesyncd as its NTP-client. However, this
|
|
plugin itself also needs Python 3.5+ to call subprocess.run.
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
This plugin should work out-of-the-box with autoconf. It does expect
|
|
timedatectl to be on $PATH, but that should always be the case in a
|
|
normal system.
|
|
|
|
=head1 INTERPRETATION
|
|
|
|
This plugin shows a graph with one line for every NTP metric it measure.
|
|
Metrics are shown with their usual name, and are explained in their
|
|
respective info fields.
|
|
|
|
This plugin issues no warnings or critical states.
|
|
|
|
=head1 MAGIC MARKERS
|
|
|
|
#%# family=auto
|
|
#%# capabilities=autoconf
|
|
|
|
=head1 VERSION
|
|
|
|
1.0
|
|
|
|
=head1 AUTHOR
|
|
|
|
Bert Peters <bert@bertptrs.nl>
|
|
|
|
=head1 LICENSE
|
|
|
|
GNU General Public License v2.0 only
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-only
|
|
|
|
=cut
|
|
'''
|
|
|
|
|
|
def parse_time(value):
|
|
value = value.strip()
|
|
if ' ' in value:
|
|
return sum(parse_time(x) for x in value.split(' '))
|
|
|
|
# If time is exactly zero (for example Jitter), there is no unit (suffix)
|
|
if value == "0" or value == "-0":
|
|
return 0
|
|
|
|
match = re.match(r'^([+-]?[0-9.]+)([a-z]+)$', value)
|
|
if not match:
|
|
raise ValueError('Invalid time ' + value)
|
|
|
|
value = float(match.group(1))
|
|
suffix = match.group(2)
|
|
|
|
if suffix == 'min':
|
|
value *= 60
|
|
elif suffix == 'ms':
|
|
value /= 1000
|
|
elif suffix == 'us':
|
|
value /= 1e6
|
|
|
|
return value
|
|
|
|
|
|
def parse_response(data):
|
|
values = {}
|
|
for line in data.splitlines():
|
|
k, v = line.split(': ', 1)
|
|
values[k.strip()] = v.strip()
|
|
|
|
return values
|
|
|
|
|
|
def retrieve():
|
|
result = subprocess.run(['timedatectl', 'timesync-status'], capture_output=True)
|
|
if result.returncode != 0:
|
|
sys.exit('timedatectl failed')
|
|
|
|
output = result.stdout.decode('utf-8')
|
|
values = parse_response(output)
|
|
|
|
# If NTP server is not responding, timesync-status will not return all
|
|
# fields, we mark these as "U"
|
|
|
|
if 'Offset' in values:
|
|
print('offset.value', parse_time(values['Offset']))
|
|
else:
|
|
print('offset.value U')
|
|
|
|
if 'Delay' in values:
|
|
print('delay.value', parse_time(values['Delay']))
|
|
print('delay.extinfo', 'Server', values['Server'])
|
|
else:
|
|
print('delay.value U')
|
|
|
|
if 'Jitter' in values:
|
|
print('jitter.value', parse_time(values['Jitter']))
|
|
else:
|
|
print('jitter.value U')
|
|
|
|
if 'Poll interval' in values:
|
|
print('poll.value', parse_time(values['Poll interval'].split('(')[0]))
|
|
else:
|
|
print('poll.value U')
|
|
|
|
|
|
def autoconf():
|
|
result = subprocess.run(['timedatectl', 'status'], capture_output=True)
|
|
if result.returncode != 0:
|
|
print('no (failed to run timedatectl)')
|
|
return
|
|
|
|
values = parse_response(result.stdout.decode('utf-8'))
|
|
if values['NTP service'] == 'active':
|
|
print('yes')
|
|
else:
|
|
print('no (ntp service not running)')
|
|
|
|
|
|
def config():
|
|
print(textwrap.dedent('''\
|
|
graph_title Timesync status
|
|
graph_vlabel s
|
|
graph_category time
|
|
|
|
offset.label Offset
|
|
offset.info Time difference between source and local
|
|
|
|
delay.label Delay
|
|
delay.info Roundtrip time to the NTP-server
|
|
|
|
jitter.label Jitter
|
|
jitter.info Difference in offset between two subsequent samples
|
|
|
|
poll.label Polling time
|
|
poll.info Time between two subsequent NTP-polls
|
|
'''))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) > 1 and sys.argv[1] == 'config':
|
|
config()
|
|
elif len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
|
|
autoconf()
|
|
else:
|
|
retrieve()
|