Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dynatrace_extension/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# SPDX-License-Identifier: MIT


__version__ = "1.7.2"
__version__ = "1.7.3"
8 changes: 4 additions & 4 deletions dynatrace_extension/sdk/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
29 changes: 15 additions & 14 deletions dynatrace_extension/sdk/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down
10 changes: 5 additions & 5 deletions tests/sdk/test_extension.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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,
)

Expand Down
Loading