diff --git a/openwisp_monitoring/check/classes/iperf.py b/openwisp_monitoring/check/classes/iperf.py index d56280174..a8a4b34b0 100644 --- a/openwisp_monitoring/check/classes/iperf.py +++ b/openwisp_monitoring/check/classes/iperf.py @@ -11,7 +11,7 @@ Chart = load_model('monitoring', 'Chart') Metric = load_model('monitoring', 'Metric') Device = load_model('config', 'Device') -DeviceData = load_model('device_monitoring', 'DeviceData') +DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring') Credentials = load_model('connection', 'Credentials') AlertSettings = load_model('monitoring', 'AlertSettings') DeviceConnection = load_model('connection', 'DeviceConnection') @@ -30,29 +30,33 @@ def check(self, store=True): res, exit_code = device_connection.connector_instance.exec_command( command, raise_unexpected_exit=False ) - if exit_code != 0: - print('---- Command Failed ----') - if store: - self.store_result( - { - 'iperf_result': 0, - 'sent_bps': 0.0, - 'received_bps': 0.0, - 'sent_bytes': 0.0, - 'received_bytes': 0.0, - 'retransmits': 0, - } - ) + if store and exit_code != 0: + print('---- Command Failed (TCP)----') + self.store_result_fail() device_connection.disconnect() return else: - result_dict = self._get_iperf_result(res) - print('---- Command Output ----') - print(result_dict) - if store: - self.store_result(result_dict) + result_dict_tcp = self._get_iperf_result(res, mode='TCP') + print('---- Command Output (TCP) ----') + print(result_dict_tcp) + # UDP + command = f'iperf3 -c {servers[0]} -u -J' + res, exit_code = device_connection.connector_instance.exec_command( + command, raise_unexpected_exit=False + ) + if store and exit_code != 0: + print('---- Command Failed (UDP) ----') + self.store_result_fail() + device_connection.disconnect() + return + else: + result_dict_udp = self._get_iperf_result(res, mode='UDP') + print('---- Command Output (UDP) ----') + print(result_dict_udp) + if store: + self.store_result({**result_dict_tcp, **result_dict_udp}) device_connection.disconnect() - return result_dict + return {**result_dict_tcp, **result_dict_udp} else: print( f'{self.related_object}: connection not properly set, Iperf skipped!' @@ -69,8 +73,13 @@ def _check_device_connection(self, device): """ openwrt_ssh = UPDATE_STRATEGIES[0][0] device_connection = DeviceConnection.objects.get(device_id=device.id) + device_monitoring = DeviceMonitoring.objects.get(device_id=device.id) if device_connection.update_strategy == openwrt_ssh: - if device_connection.enabled and device_connection.is_working: + if ( + device_connection.enabled + and device_connection.is_working + and device_monitoring.status in ['ok', 'problem'] + ): return device_connection else: return False @@ -84,12 +93,12 @@ def _get_iperf_servers(self, organization): org_servers = app_settings.IPERF_SERVERS.get(str(organization)) return org_servers - def _get_iperf_result(self, res, mode=None): + def _get_iperf_result(self, res, mode): """ Get iperf test result """ res_dict = json.loads(res) - if mode is None: + if mode == 'TCP': # Gbps = Gigabits per second # GB = GigaBytes sent_json = res_dict['end']['sum_sent'] @@ -114,8 +123,18 @@ def _get_iperf_result(self, res, mode=None): } return result # For UDP - else: - pass + elif mode == 'UDP': + jitter_ms = res_dict['end']['sum']['jitter_ms'] + packets = res_dict['end']['sum']['packets'] + lost_packets = res_dict['end']['sum']['lost_packets'] + lost_percent = res_dict['end']['sum']['lost_percent'] + result = { + 'jitter': round(jitter_ms, 2), + 'packets': packets, + 'lost_packets': lost_packets, + 'lost_percent': lost_percent, + } + return result def store_result(self, result): """ @@ -126,6 +145,25 @@ def store_result(self, result): iperf_result = copied.pop('iperf_result') metric.write(iperf_result, extra_values=copied) + def store_result_fail(self): + """ + store fail result in the DB + """ + self.store_result( + { + 'iperf_result': 0, + 'sent_bps': 0.0, + 'received_bps': 0.0, + 'sent_bytes': 0.0, + 'received_bytes': 0.0, + 'retransmits': 0, + 'jitter': 0.0, + 'packets': 0, + 'lost_packets': 0, + 'lost_percent': 0, + } + ) + def _get_metric(self): """ Gets or creates metric @@ -139,7 +177,14 @@ def _create_charts(self, metric): """ Creates iperf related charts (Bandwith/Jitter) """ - charts = ['bitrate', 'transfer', 'retransmits'] + charts = [ + 'bitrate', + 'transfer', + 'retransmits', + 'jitter', + 'datagram', + 'datagram_loss', + ] for chart in charts: chart = Chart(metric=metric, configuration=chart) chart.full_clean() diff --git a/openwisp_monitoring/db/backends/influxdb/queries.py b/openwisp_monitoring/db/backends/influxdb/queries.py index a540a45d5..7352cf4c3 100644 --- a/openwisp_monitoring/db/backends/influxdb/queries.py +++ b/openwisp_monitoring/db/backends/influxdb/queries.py @@ -123,6 +123,28 @@ "AND object_id = '{object_id}' GROUP BY time(1d)" ) }, + 'jitter': { + 'influxdb': ( + "SELECT MEAN(jitter) AS jitter FROM {key} " + "WHERE time >= '{time}' AND content_type = '{content_type}' " + "AND object_id = '{object_id}' GROUP BY time(1d)" + ) + }, + 'datagram': { + 'influxdb': ( + "SELECT MEAN(lost_packets) AS lost_datagram," + "MEAN(packets) AS total_datagram FROM {key} WHERE " + "time >= '{time}' AND content_type = '{content_type}' " + "AND object_id = '{object_id}' GROUP BY time(1d)" + ) + }, + 'datagram_loss': { + 'influxdb': ( + "SELECT MEAN(lost_percent) AS datagram_loss FROM {key} " + "WHERE time >= '{time}' AND content_type = '{content_type}' " + "AND object_id = '{object_id}' GROUP BY time(1d)" + ) + }, } default_chart_query = [ diff --git a/openwisp_monitoring/monitoring/configuration.py b/openwisp_monitoring/monitoring/configuration.py index 2cad24970..b6756fb84 100644 --- a/openwisp_monitoring/monitoring/configuration.py +++ b/openwisp_monitoring/monitoring/configuration.py @@ -554,12 +554,16 @@ def _get_access_tech(): 'sent_bytes', 'received_bytes', 'retransmits', + 'jitter', + 'packets', + 'lost_packets', + 'lost_percent', ], 'charts': { 'bitrate': { 'type': 'stackedbar', 'title': _('Bandwidth'), - 'description': _('Iperf3 bitrate in TCP mode.'), + 'description': _('Bitrate during Iperf3 test in TCP mode.'), 'summary_labels': [ _('Sent bitrate'), _('Received bitrate'), @@ -572,7 +576,7 @@ def _get_access_tech(): 'transfer': { 'type': 'stackedbar', 'title': _('Transfer'), - 'description': _('Iperf3 transfer in TCP mode.'), + 'description': _('Total transfer during Iperf3 test in TCP mode.'), 'summary_labels': [ _('Sent bytes'), _('Received bytes'), @@ -585,12 +589,54 @@ def _get_access_tech(): 'retransmits': { 'type': 'bar', 'title': _('Retransmits'), - 'colors': (DEFAULT_COLORS[4]), + 'colors': [DEFAULT_COLORS[-3]], 'description': _('No. of retransmits during Iperf3 test in TCP mode.'), - 'unit': '', 'order': 300, 'query': chart_query['retransmits'], }, + 'jitter': { + 'type': 'scatter', + 'title': _('Jitter'), + 'description': _( + 'Jitter is a variance in latency measured using Iperf3 utility in UDP mode' + ), + 'summary_labels': [ + _('Jitter'), + ], + 'unit': _(' ms'), + 'order': 310, + 'query': chart_query['jitter'], + 'colors': [DEFAULT_COLORS[4]], + }, + 'datagram': { + 'type': 'stackedbar', + 'title': _('Datagram'), + 'description': _( + 'Lost/total datagram ratio during Iperf3 test in UDP mode' + ), + 'summary_labels': [ + _('Lost datagram'), + _('Total datagram'), + ], + 'unit': _(''), + 'order': 320, + 'query': chart_query['datagram'], + 'colors': [DEFAULT_COLORS[3], DEFAULT_COLORS[2]], + }, + 'datagram_loss': { + 'type': 'scatter', + 'title': _('Datagram Loss'), + 'description': _( + 'Indicates datagram loss during Iperf3 test in UDP mode.' + ), + 'summary_labels': [ + _('Datagram loss'), + ], + 'unit': '%', + 'order': 330, + 'query': chart_query['datagram_loss'], + 'colors': [DEFAULT_COLORS[8]], + }, }, }, }