From 244918fa4659082141ada17f799f708fad71c625 Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Sun, 27 Aug 2017 18:27:52 +0200 Subject: Statistics for precision of local chrony --- python.d/chrony.chart.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 python.d/chrony.chart.py (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py new file mode 100644 index 0000000000..a5afedda28 --- /dev/null +++ b/python.d/chrony.chart.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Description: chrony netdata python.d module +# Author: Dominik Schloesser (domschl) + +from base import ExecutableService + +# default module values (can be overridden per job in `config`) +# update_every = 10 +priority = 60000 +retries = 10 + +# charts order (can be overridden if you want less charts, or different order) +ORDER = ['timediff', 'lastoffset', 'rmsoffset', 'rootdelay', + 'rootdispersion', 'skew', 'frequency', 'residualfreq'] + +CHARTS = { + # id: { + # 'options': [name, title, units, family, context, charttype], + # 'lines': [ + # [unique_dimension_name, name, algorithm, multiplier, divisor] + # ]} + 'timediff': { + 'options': [None, "Difference system time to NTP", "us", 'chrony', 'chrony.timediff', 'line'], + 'lines': [ + ['timediff', None, 'absolute', 1, 1000] + ]}, + 'lastoffset': { + 'options': [None, "Last offset", "us", 'chrony', 'chrony.lastoffset', 'line'], + 'lines': [ + ['lastoffset', None, 'absolute', 1, 1000] + ]}, + 'rmsoffset': { + 'options': [None, "RMS offset", "us", 'chrony', 'chrony.rmsoffset', 'line'], + 'lines': [ + ['rmsoffset', None, 'absolute', 1, 1000] + ]}, + 'rootdelay': { + 'options': [None, "Root delay", "us", 'chrony', 'chrony.rootdelay', 'line'], + 'lines': [ + ['rootdelay', None, 'absolute', 1, 1000] + ]}, + 'rootdispersion': { + 'options': [None, "Root dispersion", "us", 'chrony', 'chrony.rootdispersion', 'line'], + 'lines': [ + ['rootdispersion', None, 'absolute', 1, 1000] + ]}, + 'skew': { + 'options': [None, "Skew, error bound on frequency", "ppm", 'chrony', 'chrony.skew', 'line'], + 'lines': [ + ['skew', None, 'absolute', 1, 1000] + ]}, + 'frequency': { + 'options': [None, "Frequency", "ppm", 'chrony', 'chrony.frequency', 'line'], + 'lines': [ + ['frequency', None, 'absolute', 1, 1000] + ]}, + 'residualfreq': { + 'options': [None, "Residual frequency", "ppm", 'chrony', 'chrony.residualfreq', 'line'], + 'lines': [ + ['residualfreq', None, 'absolute', 1, 1000] + ]} +} + + +class Service(ExecutableService): + def __init__(self, configuration=None, name=None): + ExecutableService.__init__( + self, configuration=configuration, name=name) + self.command = "chronyc -n tracking" + self.order = ORDER + self.definitions = CHARTS + self.error("Chrony netdata started") + + def _get_data(self): + """ + Format data received from shell command + :return: dict + """ + try: + # self.error("Chrony:" + self._get_raw_data()) + chrony_dict = {ln.split(':', 1)[0].strip(): ln.split(':', 1)[1].strip().split(' ')[0] + for ln in self._get_raw_data()[1:]} + return {'timediff': int(float(chrony_dict['System time']) * 1e9), + 'lastoffset': int(float(chrony_dict['Last offset']) * 1e9), + 'rmsoffset': int(float(chrony_dict['RMS offset']) * 1e9), + 'rootdelay': int(float(chrony_dict['Root delay']) * 1e9), + 'rootdispersion': int(float(chrony_dict['Root dispersion']) * 1e9), + 'skew': int(float(chrony_dict['Skew']) * 1e3), + 'frequency': int(float(chrony_dict['Frequency']) * 1e3), + 'residualfreq': int(float(chrony_dict['Residual freq']) * 1e3) + } + except (ValueError, AttributeError): + self.error("Chrony parser exception") + return None -- cgit v1.2.3 From 658e13e292f9ad40863a885b767cfc75599eb51b Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Sun, 27 Aug 2017 18:33:31 +0200 Subject: Debugs removed --- python.d/chrony.chart.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index a5afedda28..35b276a3a8 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -69,7 +69,6 @@ class Service(ExecutableService): self.command = "chronyc -n tracking" self.order = ORDER self.definitions = CHARTS - self.error("Chrony netdata started") def _get_data(self): """ @@ -77,7 +76,6 @@ class Service(ExecutableService): :return: dict """ try: - # self.error("Chrony:" + self._get_raw_data()) chrony_dict = {ln.split(':', 1)[0].strip(): ln.split(':', 1)[1].strip().split(' ')[0] for ln in self._get_raw_data()[1:]} return {'timediff': int(float(chrony_dict['System time']) * 1e9), @@ -90,5 +88,5 @@ class Service(ExecutableService): 'residualfreq': int(float(chrony_dict['Residual freq']) * 1e3) } except (ValueError, AttributeError): - self.error("Chrony parser exception") + self.error("Chrony data parser exception") return None -- cgit v1.2.3 From edb6080edcfa30683f872c52354718a27812e7ea Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Sat, 2 Sep 2017 12:25:45 +0200 Subject: Replace dict comprehension for python 2.6 compatibility --- python.d/chrony.chart.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index 35b276a3a8..6644aa08c4 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -76,17 +76,25 @@ class Service(ExecutableService): :return: dict """ try: - chrony_dict = {ln.split(':', 1)[0].strip(): ln.split(':', 1)[1].strip().split(' ')[0] - for ln in self._get_raw_data()[1:]} - return {'timediff': int(float(chrony_dict['System time']) * 1e9), - 'lastoffset': int(float(chrony_dict['Last offset']) * 1e9), - 'rmsoffset': int(float(chrony_dict['RMS offset']) * 1e9), - 'rootdelay': int(float(chrony_dict['Root delay']) * 1e9), - 'rootdispersion': int(float(chrony_dict['Root dispersion']) * 1e9), - 'skew': int(float(chrony_dict['Skew']) * 1e3), - 'frequency': int(float(chrony_dict['Frequency']) * 1e3), - 'residualfreq': int(float(chrony_dict['Residual freq']) * 1e3) - } + lines = self._get_raw_data() + if lines is not None: + chrony_dict = {} + for line in lines: + lparts = line.split(':', 1) + value = lparts[1].strip().split(' ')[0] + chrony_dict[lparts[0].strip()] = value + return {'timediff': int(float(chrony_dict['System time']) * 1e9), + 'lastoffset': int(float(chrony_dict['Last offset']) * 1e9), + 'rmsoffset': int(float(chrony_dict['RMS offset']) * 1e9), + 'rootdelay': int(float(chrony_dict['Root delay']) * 1e9), + 'rootdispersion': int(float(chrony_dict['Root dispersion']) * 1e9), + 'skew': int(float(chrony_dict['Skew']) * 1e3), + 'frequency': int(float(chrony_dict['Frequency']) * 1e3), + 'residualfreq': int(float(chrony_dict['Residual freq']) * 1e3) + } + else: + self.error("No valid chronyc output") + return None except (ValueError, AttributeError): - self.error("Chrony data parser exception") + self.error("Chronyc data parser exception") return None -- cgit v1.2.3 From 7fbaa2e9fc6ad08d495e005e5da2210f7a12cf25 Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Sat, 2 Sep 2017 12:37:45 +0200 Subject: skip first line of output (empty) --- python.d/chrony.chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index 6644aa08c4..610395f30a 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -79,7 +79,7 @@ class Service(ExecutableService): lines = self._get_raw_data() if lines is not None: chrony_dict = {} - for line in lines: + for line in lines[1:]: lparts = line.split(':', 1) value = lparts[1].strip().split(' ')[0] chrony_dict[lparts[0].strip()] = value -- cgit v1.2.3 From 65ed2942ec3f302adb6520498858f7bea98d88fe Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Sat, 2 Sep 2017 13:14:06 +0200 Subject: More checks against format-changes in chronyc, dict exception handling fixed. --- python.d/chrony.chart.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index 610395f30a..0026c0215f 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -81,8 +81,9 @@ class Service(ExecutableService): chrony_dict = {} for line in lines[1:]: lparts = line.split(':', 1) - value = lparts[1].strip().split(' ')[0] - chrony_dict[lparts[0].strip()] = value + if (len(lparts) > 1): + value = lparts[1].strip().split(' ')[0] + chrony_dict[lparts[0].strip()] = value return {'timediff': int(float(chrony_dict['System time']) * 1e9), 'lastoffset': int(float(chrony_dict['Last offset']) * 1e9), 'rmsoffset': int(float(chrony_dict['RMS offset']) * 1e9), @@ -95,6 +96,6 @@ class Service(ExecutableService): else: self.error("No valid chronyc output") return None - except (ValueError, AttributeError): + except (ValueError, AttributeError, KeyError): self.error("Chronyc data parser exception") return None -- cgit v1.2.3 From 24ba94c9f0b12c1adb523121f89ca865e087bde5 Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Mon, 4 Sep 2017 13:39:46 +0200 Subject: more specific exception handling following l2isbad's suggestions --- python.d/chrony.chart.py | 53 +++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index 0026c0215f..70834bf2cf 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -70,32 +70,39 @@ class Service(ExecutableService): self.order = ORDER self.definitions = CHARTS + CHRONY = [('Frequency', 'frequency', 1e3), + ('Last offset', 'lastoffset', 1e9), + ('RMS offset', 'rmsoffset', 1e9), + ('Residual freq', 'residualfreq', 1e3), + ('Root delay', 'rootdelay', 1e9), + ('Root dispersion', 'rootdispersion', 1e9), + ('Skew', 'skew', 1e3), + ('System time', 'timediff', 1e9)] + def _get_data(self): """ Format data received from shell command :return: dict """ - try: - lines = self._get_raw_data() - if lines is not None: - chrony_dict = {} - for line in lines[1:]: - lparts = line.split(':', 1) - if (len(lparts) > 1): - value = lparts[1].strip().split(' ')[0] - chrony_dict[lparts[0].strip()] = value - return {'timediff': int(float(chrony_dict['System time']) * 1e9), - 'lastoffset': int(float(chrony_dict['Last offset']) * 1e9), - 'rmsoffset': int(float(chrony_dict['RMS offset']) * 1e9), - 'rootdelay': int(float(chrony_dict['Root delay']) * 1e9), - 'rootdispersion': int(float(chrony_dict['Root dispersion']) * 1e9), - 'skew': int(float(chrony_dict['Skew']) * 1e3), - 'frequency': int(float(chrony_dict['Frequency']) * 1e3), - 'residualfreq': int(float(chrony_dict['Residual freq']) * 1e3) - } - else: - self.error("No valid chronyc output") - return None - except (ValueError, AttributeError, KeyError): - self.error("Chronyc data parser exception") + raw_data = self._get_raw_data() + if not raw_data: return None + + raw_data = (line.split(':', 1) for line in raw_data) + parsed, data = dict(), dict() + + for line in raw_data: + try: + key, value = (l.strip() for l in line) + except ValueError: + continue + if len(value) > 0: + parsed[key] = value.split()[0] + + for key, dim_id, multiplier in self.CHRONY: + try: + data[dim_id] = int(float(parsed[key]) * multiplier) + except (KeyError, ValueError): + continue + + return data or None -- cgit v1.2.3 From b5427b5a495dc9f66465d25ebdf1c608bdac563a Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Mon, 4 Sep 2017 13:54:26 +0200 Subject: review: CHRONY array moved out of service class. --- python.d/chrony.chart.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index 70834bf2cf..9db1f8689f 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -61,6 +61,15 @@ CHARTS = { ]} } +CHRONY = [('Frequency', 'frequency', 1e3), + ('Last offset', 'lastoffset', 1e9), + ('RMS offset', 'rmsoffset', 1e9), + ('Residual freq', 'residualfreq', 1e3), + ('Root delay', 'rootdelay', 1e9), + ('Root dispersion', 'rootdispersion', 1e9), + ('Skew', 'skew', 1e3), + ('System time', 'timediff', 1e9)] + class Service(ExecutableService): def __init__(self, configuration=None, name=None): @@ -70,15 +79,6 @@ class Service(ExecutableService): self.order = ORDER self.definitions = CHARTS - CHRONY = [('Frequency', 'frequency', 1e3), - ('Last offset', 'lastoffset', 1e9), - ('RMS offset', 'rmsoffset', 1e9), - ('Residual freq', 'residualfreq', 1e3), - ('Root delay', 'rootdelay', 1e9), - ('Root dispersion', 'rootdispersion', 1e9), - ('Skew', 'skew', 1e3), - ('System time', 'timediff', 1e9)] - def _get_data(self): """ Format data received from shell command @@ -99,7 +99,7 @@ class Service(ExecutableService): if len(value) > 0: parsed[key] = value.split()[0] - for key, dim_id, multiplier in self.CHRONY: + for key, dim_id, multiplier in CHRONY: try: data[dim_id] = int(float(parsed[key]) * multiplier) except (KeyError, ValueError): -- cgit v1.2.3 From 024f87b9eda0606126894d4ec440b6ca9a513557 Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Mon, 4 Sep 2017 14:02:59 +0200 Subject: len() optimized away --- python.d/chrony.chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python.d') diff --git a/python.d/chrony.chart.py b/python.d/chrony.chart.py index 9db1f8689f..580a2e6760 100644 --- a/python.d/chrony.chart.py +++ b/python.d/chrony.chart.py @@ -96,7 +96,7 @@ class Service(ExecutableService): key, value = (l.strip() for l in line) except ValueError: continue - if len(value) > 0: + if value: parsed[key] = value.split()[0] for key, dim_id, multiplier in CHRONY: -- cgit v1.2.3 From 275c3e821930b9dd84a5caac0249aab01b1b258f Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Mon, 4 Sep 2017 15:03:21 +0200 Subject: chrony-updates for Makefiles, README and python.d.conf. --- python.d/Makefile.am | 1 + python.d/README.md | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) (limited to 'python.d') diff --git a/python.d/Makefile.am b/python.d/Makefile.am index 6a43e94c13..84c2aeadd0 100644 --- a/python.d/Makefile.am +++ b/python.d/Makefile.am @@ -16,6 +16,7 @@ dist_python_DATA = \ apache.chart.py \ apache_cache.chart.py \ bind_rndc.chart.py \ + chrony.chart.py \ cpufreq.chart.py \ cpuidle.chart.py \ dns_query_time.chart.py \ diff --git a/python.d/README.md b/python.d/README.md index c4504a7c59..3b3ddb184e 100644 --- a/python.d/README.md +++ b/python.d/README.md @@ -186,6 +186,38 @@ If no configuration is given, module will attempt to read named.stats file at ` --- +# chrony + +This module monitors the precision and statitics of a local chronyd server. + +It produces: + +* frequency +* last offset +* RMS offset +* residual freq +* root delay +* root dispersion +* skew +* system time + +**Requirements:** +Verify that user netdata can execute `chronyc tracking`. If necessary, update `/etc/chrony.conf`, `cmdallow`. + +### Configuration + +Sample: +```yaml +# data collection frequency: +update_every: 1 + +# chrony query command: +local: + command: 'chronyc -n tracking' +``` + +--- + # cpufreq This module shows the current CPU frequency as set by the cpufreq kernel -- cgit v1.2.3 From c6f0748f2b238474d7f7b69110a5df96c9ad2f5a Mon Sep 17 00:00:00 2001 From: Dominik Schloesser Date: Mon, 4 Sep 2017 15:06:04 +0200 Subject: README typo --- python.d/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python.d') diff --git a/python.d/README.md b/python.d/README.md index 3b3ddb184e..1b04ccdf3d 100644 --- a/python.d/README.md +++ b/python.d/README.md @@ -188,7 +188,7 @@ If no configuration is given, module will attempt to read named.stats file at ` # chrony -This module monitors the precision and statitics of a local chronyd server. +This module monitors the precision and statistics of a local chronyd server. It produces: -- cgit v1.2.3