-
Notifications
You must be signed in to change notification settings - Fork 0
/
ntpd_metrics.py
executable file
·120 lines (102 loc) · 3.55 KB
/
ntpd_metrics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python3
#
# Description: Extract NTPd metrics from ntpq -np.
# Author: Ben Kochie <[email protected]>
import re
import subprocess
import sys
from prometheus_client import CollectorRegistry, Gauge, generate_latest
# NTP peers status, with no DNS lookups.
ntpq_cmd = ['ntpq', '-np']
ntpq_rv_cmd = ['ntpq', '-c', 'rv 0 offset,sys_jitter,rootdisp,rootdelay']
# Regex to match all of the fields in the output of ntpq -np
metrics_fields = [
r'^(?P<status>.)(?P<remote>[\w\.]+)',
r'(?P<refid>[\w\.]+)',
r'(?P<stratum>\d+)',
r'(?P<type>\w)',
r'(?P<when>\d+)',
r'(?P<poll>\d+)',
r'(?P<reach>\d+)',
r'(?P<delay>\d+\.\d+)',
r'(?P<offset>-?\d+\.\d+)',
r'(?P<jitter>\d+\.\d+)',
]
metrics_re = r'\s+'.join(metrics_fields)
# Remote types
# http://support.ntp.org/bin/view/Support/TroubleshootingNTP
remote_types = {
'l': 'local',
'u': 'unicast',
'm': 'multicast',
'b': 'broadcast',
'-': 'netaddr',
}
# Status codes:
# http://www.eecis.udel.edu/~mills/ntp/html/decode.html#peer
status_types = {
' ': 0,
'x': 1,
'.': 2,
'-': 3,
'+': 4,
'#': 5,
'*': 6,
'o': 7,
}
# Run the ntpq command.
def get_output(command):
try:
output = subprocess.check_output(command, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
return None
return output.decode()
# Parse raw ntpq lines.
def parse_line(line):
if re.match(r'\s+remote\s+refid', line):
return None
if re.match(r'=+', line):
return None
if re.match(r'.+\.(LOCL|POOL)\.', line):
return None
if re.match(r'^$', line):
return None
return re.match(metrics_re, line)
# Main function
def main(argv):
ntpq = get_output(ntpq_cmd)
namespace = 'ntpd'
registry = CollectorRegistry()
peer_status = Gauge('peer_status', 'NTPd metric for peer_status',
['remote', 'reference', 'stratum', 'type'],
namespace=namespace, registry=registry)
delay_ms = Gauge('delay_milliseconds', 'NTPd metric for delay_milliseconds',
['remote', 'reference'], namespace=namespace, registry=registry)
offset_ms = Gauge('offset_milliseconds', 'NTPd metric for offset_milliseconds',
['remote', 'reference'], namespace=namespace, registry=registry)
jitter_ms = Gauge('jitter_milliseconds', 'NTPd metric for jitter_milliseconds',
['remote', 'reference'], namespace=namespace, registry=registry)
for line in ntpq.split('\n'):
metric_match = parse_line(line)
if metric_match is None:
continue
remote = metric_match.group('remote')
refid = metric_match.group('refid')
stratum = metric_match.group('stratum')
remote_type = remote_types[metric_match.group('type')]
peer_status.labels(remote, refid, stratum, remote_type).set(
status_types[metric_match.group('status')]
)
delay_ms.labels(remote, refid).set(metric_match.group('delay'))
offset_ms.labels(remote, refid).set(metric_match.group('offset'))
jitter_ms.labels(remote, refid).set(metric_match.group('jitter'))
ntpq_rv = get_output(ntpq_rv_cmd)
for metric in ntpq_rv.split(','):
metric_name, metric_value = metric.strip().split('=')
g = Gauge(metric_name, 'NTPd metric for {}'.format(metric_name), [],
namespace=namespace, registry=registry)
g.set(metric_value)
print(generate_latest(registry).decode(), end='')
# Go go go!
if __name__ == "__main__":
main(sys.argv[1:])