diff --git a/dynatrace_extension/__about__.py b/dynatrace_extension/__about__.py index b04502b..31f13df 100644 --- a/dynatrace_extension/__about__.py +++ b/dynatrace_extension/__about__.py @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT -__version__ = "1.7.2" +__version__ = "1.7.3" diff --git a/dynatrace_extension/sdk/callback.py b/dynatrace_extension/sdk/callback.py index 14d0bad..506c269 100644 --- a/dynatrace_extension/sdk/callback.py +++ b/dynatrace_extension/sdk/callback.py @@ -4,6 +4,7 @@ import logging import random +import time from collections.abc import Callable from datetime import datetime, timedelta from timeit import default_timer as timer @@ -39,6 +40,7 @@ def __init__( self.duration = 0 # global counter self.duration_interval_total = 0 # counter per interval = 1 min by default self.cluster_time_diff = 0 + self.start_timestamp_monotonic = time.monotonic() self.start_timestamp = self.get_current_time_with_cluster_diff() self.running_in_sim = running_in_sim self.activation_type = activation_type @@ -147,8 +149,6 @@ def get_next_execution_timestamp(self) -> float: """ Get the timestamp for the next execution of the callback This is done using execution total, the interval and the start timestamp - :return: datetime + :return: float """ - return ( - self.start_timestamp + timedelta(seconds=self.interval.total_seconds() * (self.iterations or 1)) - ).timestamp() + return self.start_timestamp_monotonic + self.interval.total_seconds() * (self.iterations or 1) diff --git a/dynatrace_extension/sdk/extension.py b/dynatrace_extension/sdk/extension.py index 36fbe86..b06e950 100644 --- a/dynatrace_extension/sdk/extension.py +++ b/dynatrace_extension/sdk/extension.py @@ -28,10 +28,11 @@ from .status import EndpointStatuses, EndpointStatusesMap, IgnoreStatus, Status, StatusValue from .throttled_logger import StrictThrottledHandler, ThrottledHandler -HEARTBEAT_INTERVAL = timedelta(seconds=50) -METRIC_SENDING_INTERVAL = timedelta(seconds=30) -SFM_METRIC_SENDING_INTERVAL = timedelta(seconds=60) -TIME_DIFF_INTERVAL = timedelta(seconds=60) +# Intervals defined in seconds for internal callbacks +HEARTBEAT_INTERVAL = 50 +METRIC_SENDING_INTERVAL = 30 +SFM_METRIC_SENDING_INTERVAL = 60 +TIME_DIFF_INTERVAL = 60 CALLBACKS_THREAD_POOL_SIZE = 100 INTERNAL_THREAD_POOL_SIZE = 20 @@ -240,15 +241,15 @@ def __init__(self, name: str = "") -> None: self._running_callbacks: dict[int, WrappedCallback] = {} self._running_callbacks_lock: Lock = Lock() - self._scheduler = sched.scheduler(time.time, time.sleep) + self._scheduler = sched.scheduler(time.monotonic, time.sleep) # Timestamps for scheduling of internal callbacks - self._next_internal_callbacks_timestamps: dict[str, datetime] = { - "timediff": datetime.now() + TIME_DIFF_INTERVAL, - "heartbeat": datetime.now() + HEARTBEAT_INTERVAL, - "metrics": datetime.now() + METRIC_SENDING_INTERVAL, - "events": datetime.now() + METRIC_SENDING_INTERVAL, - "sfm_metrics": datetime.now() + SFM_METRIC_SENDING_INTERVAL, + self._next_internal_callbacks_timestamps: dict[str, float] = { + "timediff": time.monotonic() + TIME_DIFF_INTERVAL, + "heartbeat": time.monotonic() + HEARTBEAT_INTERVAL, + "metrics": time.monotonic() + METRIC_SENDING_INTERVAL, + "events": time.monotonic() + METRIC_SENDING_INTERVAL, + "sfm_metrics": time.monotonic() + SFM_METRIC_SENDING_INTERVAL, } # Executors for the callbacks and internal methods @@ -777,7 +778,7 @@ def _parse_args(self): parser.add_argument("--secrets", required=False, default="secrets.json") parser.add_argument("--no-print-metrics", required=False, action="store_true") - args, unknown = parser.parse_known_args() + args, _ = parser.parse_known_args() self._is_fastcheck = args.fastcheck if args.dsid is None: # DEV mode @@ -1169,10 +1170,10 @@ def _send_buffered_events(self): def _send_dt_event(self, event: dict[str, str | int | dict[str, str]]): self._client.send_dt_event(event) - def _get_and_set_next_internal_callback_timestamp(self, callback_name: str, interval: timedelta): + def _get_and_set_next_internal_callback_timestamp(self, callback_name: str, interval: float) -> float: next_timestamp = self._next_internal_callbacks_timestamps[callback_name] self._next_internal_callbacks_timestamps[callback_name] += interval - return next_timestamp.timestamp() + return next_timestamp def get_version(self) -> str: """Return the extension version.""" diff --git a/tests/sdk/test_extension.py b/tests/sdk/test_extension.py index 0648479..e9dc395 100644 --- a/tests/sdk/test_extension.py +++ b/tests/sdk/test_extension.py @@ -1,7 +1,7 @@ import threading import time import unittest -from datetime import datetime, timedelta +from datetime import timedelta from unittest.mock import MagicMock, mock_open, patch import pytest @@ -27,7 +27,7 @@ def test_heartbeat_called(self, mock_extension): extension = Extension() extension.logger = MagicMock() extension._running_in_sim = True - extension._next_heartbeat = datetime.now() + extension._next_heartbeat = time.monotonic() extension._heartbeat_iteration() extension._heartbeat.assert_called() @@ -387,15 +387,15 @@ def test_fastcheck_exception(self): extension._client = MagicMock() fastcheck = MagicMock() - fastcheck.side_effect = Exception("SomeException") + fastcheck.side_effect = ImportError("SomeException") extension.register_fastcheck(fastcheck) - self.assertRaises(Exception, extension._run_fastcheck) + self.assertRaises(ImportError, extension._run_fastcheck) fastcheck.assert_called_once() extension._client.send_status.assert_called_once() self.assertEqual(extension._client.send_status.call_args[0][0].status, StatusValue.GENERIC_ERROR) self.assertIn( - "Python datasource fastcheck error: Exception('SomeException')", + "Python datasource fastcheck error: ImportError('SomeException')", extension._client.send_status.call_args[0][0].message, )