Skip to content

Commit c02b12a

Browse files
pandafynemesifier
andauthored
[feature] Log execution time of monitoring checks #658
Closes #658 --------- Co-authored-by: Federico Capoano <f.capoano@openwisp.io>
1 parent 4a93b39 commit c02b12a

File tree

8 files changed

+92
-15
lines changed

8 files changed

+92
-15
lines changed

openwisp_monitoring/check/base/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def perform_check(self, store=True):
102102
and self.content_object.organization.is_active is False
103103
):
104104
return
105-
return self.check_instance.check(store=True)
105+
return self.check_instance.timed_check(store=True)
106106

107107
def perform_check_delayed(self, duration=0):
108108
from ..tasks import perform_check

openwisp_monitoring/check/classes/base.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import logging
2+
import time
3+
14
from django.contrib.contenttypes.models import ContentType
25
from django.core.exceptions import ValidationError
36
from swapper import load_model
47

8+
logger = logging.getLogger(__name__)
9+
510
Check = load_model('check', 'Check')
611
Metric = load_model('monitoring', 'Metric')
712
Device = load_model('config', 'Device')
@@ -55,6 +60,57 @@ def may_execute(cls):
5560
def check(self, store=True):
5661
raise NotImplementedError
5762

63+
def store(self, *args, **kwargs):
64+
raise NotImplementedError
65+
66+
def timed_check(self, store=True):
67+
"""
68+
Executes the check method and measures its execution time.
69+
70+
Optionally stores the result and logs the time taken for the check execution
71+
and the time spent storing the result in the database(if available).
72+
73+
Args:
74+
store (bool, optional): Whether to store the result of the check. Defaults to True.
75+
76+
Returns:
77+
The result of the check method.
78+
79+
Logs:
80+
The time taken to execute the check and store the result.
81+
"""
82+
start_time = time.time()
83+
result = self.check(store=store)
84+
elapsed_time = time.time() - start_time
85+
if hasattr(self, '_store_result_elapsed_time'):
86+
elapsed_time -= self._store_result_elapsed_time
87+
logger.info(
88+
'Check "%s" executed in %.2fs, writing took %.2fs'
89+
% (
90+
self.check_instance,
91+
elapsed_time,
92+
getattr(self, '_store_result_elapsed_time', 0.0),
93+
),
94+
)
95+
return result
96+
97+
def timed_store(self, *args, **kwargs):
98+
"""
99+
Calls the `store` method with the provided arguments and measures the time taken to execute it.
100+
101+
The elapsed time (in seconds) is stored in the `timed_store` attribute of the instance.
102+
103+
Args:
104+
*args: Variable length argument list to pass to the `store` method.
105+
**kwargs: Arbitrary keyword arguments to pass to the `store` method.
106+
107+
Side Effects:
108+
Sets the `timed_store` attribute to the duration (in seconds) of the `store` method execution.
109+
"""
110+
start_time = time.time()
111+
self.store(*args, **kwargs)
112+
self._store_result_elapsed_time = time.time() - start_time
113+
58114
def _get_or_create_metric(self, configuration=None):
59115
"""Gets or creates metric."""
60116
check = self.check_instance

openwisp_monitoring/check/classes/config_applied.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,14 @@ def check(self, store=True):
3333
# but we need to ensure health status will be changed
3434
send_alert = self.related_object.config.status != 'error'
3535
if store:
36-
self._get_metric().write(
37-
result, retention_policy=SHORT_RP, send_alert=send_alert
38-
)
36+
self.timed_store(result, send_alert)
3937
return result
4038

39+
def store(self, result, send_alert, **kwargs):
40+
self._get_metric().write(
41+
result, retention_policy=SHORT_RP, send_alert=send_alert
42+
)
43+
4144
def _get_metric(self):
4245
metric, created = self._get_or_create_metric(configuration='config_applied')
4346
if created:

openwisp_monitoring/check/classes/data_collected.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ def check(self, store=True):
4747
result = 0
4848
send_alert = device_monitoring.status != 'critical'
4949
if store:
50-
self._get_metric().write(
51-
result, retention_policy=SHORT_RP, send_alert=send_alert
52-
)
50+
self.timed_store(result, send_alert)
5351
return {'data_collected': result}
5452

53+
def store(self, result, send_alert, *args, **kwargs):
54+
self._get_metric().write(
55+
result, retention_policy=SHORT_RP, send_alert=send_alert
56+
)
57+
5558
def _get_metric(self):
5659
metric, created = self._get_or_create_metric(configuration='data_collected')
5760
if created:

openwisp_monitoring/check/classes/iperf3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def _run_iperf3_check(self, store, server, time):
279279
# Store iperf3_result field 1 if any mode passes, store 0 when both fails
280280
iperf3_result = result_tcp['iperf3_result'] | result_udp['iperf3_result']
281281
result.update({**result_tcp, **result_udp, 'iperf3_result': iperf3_result})
282-
self.store_result(result)
282+
self.timed_store(result)
283283
device_connection.disconnect()
284284
return result
285285

@@ -489,7 +489,7 @@ def _get_iperf3_result(self, result, exit_code, mode):
489489
'lost_percent': float(result['end']['sum']['lost_percent']),
490490
}
491491

492-
def store_result(self, result):
492+
def store(self, result, *args, **kwargs):
493493
"""Store result in the DB."""
494494
metric = self._get_metric()
495495
copied = result.copy()

openwisp_monitoring/check/classes/ping.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def check(self, store=True):
8383
# device is known, simulate down
8484
result = {'reachable': 0, 'loss': 100.0}
8585
if store:
86-
self.store_result(result)
86+
self.timed_store(result)
8787
return result
8888
command = [
8989
'fping',
@@ -116,10 +116,10 @@ def check(self, store=True):
116116
{'rtt_min': float(min), 'rtt_avg': float(avg), 'rtt_max': float(max)}
117117
)
118118
if store:
119-
self.store_result(result)
119+
self.timed_store(result)
120120
return result
121121

122-
def store_result(self, result):
122+
def store(self, result):
123123
"""Stores result in the DB."""
124124
metric = self._get_metric()
125125
copied = result.copy()

openwisp_monitoring/check/tasks.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
import time
34

45
from celery import shared_task
56
from django.conf import settings
@@ -65,6 +66,8 @@ def perform_check(uuid):
6566
Retrieves check according to the passed UUID and calls the
6667
``perform_check()`` method.
6768
"""
69+
start_time = time.time()
70+
6871
try:
6972
check = get_check_model().objects.get(pk=uuid)
7073
except ObjectDoesNotExist:
@@ -74,6 +77,9 @@ def perform_check(uuid):
7477
if settings.DEBUG: # pragma: nocover
7578
print(json.dumps(result, indent=4, sort_keys=True))
7679

80+
elapsed_time = time.time() - start_time
81+
logger.info(f'Check "{check}" took {elapsed_time:.2f}s to complete.')
82+
7783

7884
@shared_task(base=OpenwispCeleryTask)
7985
def auto_create_check(

openwisp_monitoring/check/tests/test_ping.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,18 +251,27 @@ def test_schema_violation(self):
251251
self.fail('ValidationError not raised')
252252

253253
@patch.object(Ping, '_command', return_value=_FPING_REACHABLE)
254-
def test_store_result(self, mocked_method):
254+
@patch('openwisp_monitoring.check.classes.base.logger.info')
255+
def test_store_result(self, mocked_logger, mocked_method):
255256
self.assertEqual(Check.objects.count(), 0)
256257
device = self._create_device(organization=self._create_org())
257258
device.management_ip = '10.40.0.1'
258259
device.save()
259260
# check created automatically by autoping
260261
self.assertEqual(Check.objects.count(), 4)
261-
self.assertEqual(Metric.objects.count(), 0)
262-
self.assertEqual(Chart.objects.count(), 0)
262+
# self.assertEqual(Metric.objects.count(), 0)
263+
# self.assertEqual(Chart.objects.count(), 0)
263264
self.assertEqual(AlertSettings.objects.count(), 0)
264265
check = Check.objects.filter(check_type=self._PING).first()
265266
result = check.perform_check()
267+
self.assertIn(
268+
f'Check "{check}" executed in',
269+
mocked_logger.call_args_list[0][0][0],
270+
)
271+
self.assertIn(
272+
'writing took ',
273+
mocked_logger.call_args_list[0][0][0],
274+
)
266275
self.assertEqual(Metric.objects.count(), 1)
267276
self.assertEqual(Chart.objects.count(), 3)
268277
self.assertEqual(AlertSettings.objects.count(), 1)

0 commit comments

Comments
 (0)