dt-extensions-sdk 1.7.2__py3-none-any.whl → 1.7.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dt_extensions_sdk-1.7.2.dist-info → dt_extensions_sdk-1.7.4.dist-info}/METADATA +1 -1
- {dt_extensions_sdk-1.7.2.dist-info → dt_extensions_sdk-1.7.4.dist-info}/RECORD +8 -8
- {dt_extensions_sdk-1.7.2.dist-info → dt_extensions_sdk-1.7.4.dist-info}/WHEEL +1 -1
- dynatrace_extension/__about__.py +1 -1
- dynatrace_extension/sdk/callback.py +30 -20
- dynatrace_extension/sdk/extension.py +22 -16
- {dt_extensions_sdk-1.7.2.dist-info → dt_extensions_sdk-1.7.4.dist-info}/entry_points.txt +0 -0
- {dt_extensions_sdk-1.7.2.dist-info → dt_extensions_sdk-1.7.4.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dt-extensions-sdk
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.4
|
|
4
4
|
Project-URL: Documentation, https://github.com/dynatrace-extensions/dt-extensions-python-sdk#readme
|
|
5
5
|
Project-URL: Issues, https://github.com/dynatrace-extensions/dt-extensions-python-sdk/issues
|
|
6
6
|
Project-URL: Source, https://github.com/dynatrace-extensions/dt-extensions-python-sdk
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
dynatrace_extension/__about__.py,sha256=
|
|
1
|
+
dynatrace_extension/__about__.py,sha256=mYwZ34U1NdFWsIoFPXD2wSIjr7MDYun_CILrVcKXdDU,110
|
|
2
2
|
dynatrace_extension/__init__.py,sha256=MJNJYCFWLEwPmBLoETWFZddyUCMDgZfKkRycmmGM_w4,806
|
|
3
3
|
dynatrace_extension/cli/__init__.py,sha256=HCboY_eJPoqjFmoPDsBL8Jk6aNvank8K7JpkVrgwzUM,123
|
|
4
4
|
dynatrace_extension/cli/main.py,sha256=OTjJ4XHJvvYXj10a7WFFHVNnkyECPg1ClW6Os8piN8k,20168
|
|
@@ -17,10 +17,10 @@ dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.tem
|
|
|
17
17
|
dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template,sha256=cS79GVxJB-V-gocu4ZOjmZ54HXJNg89eXdLf89zDHJQ,1249
|
|
18
18
|
dynatrace_extension/sdk/__init__.py,sha256=RsqQ1heGyCmSK3fhuEKAcxQIRCg4gEK0-eSkIehL5Nc,86
|
|
19
19
|
dynatrace_extension/sdk/activation.py,sha256=KIoPWMZs3tKiMG8XhCfeNgRlz2vxDKcAASgSACcEfIQ,1456
|
|
20
|
-
dynatrace_extension/sdk/callback.py,sha256=
|
|
20
|
+
dynatrace_extension/sdk/callback.py,sha256=S_ELy2D9Y2YhUsL2lBry3UZfmy3BrZgZM2hvKgQIQss,6949
|
|
21
21
|
dynatrace_extension/sdk/communication.py,sha256=uTSURmgSHit2N1hHUc3-yKmEBVMHi6hDBrdb1EaCAsE,18419
|
|
22
22
|
dynatrace_extension/sdk/event.py,sha256=J261imbFKpxfuAQ6Nfu3RRcsIQKKivy6fme1nww2g-8,388
|
|
23
|
-
dynatrace_extension/sdk/extension.py,sha256=
|
|
23
|
+
dynatrace_extension/sdk/extension.py,sha256=ZbuoHQ-sbj4jUSz0w1Iz6IePrnC9a0BEwoXIdLKJsU8,49957
|
|
24
24
|
dynatrace_extension/sdk/helper.py,sha256=m4gGHtIKYkfANC2MOGdxKUZlmH5tnZO6WTNqll27lyY,6476
|
|
25
25
|
dynatrace_extension/sdk/metric.py,sha256=-kq7JWpk7UGvcjqafTt-o6k4urwhsGVXmnuQg7Sf9PQ,3622
|
|
26
26
|
dynatrace_extension/sdk/runtime.py,sha256=7bC4gUJsVSHuL_E7r2EWrne95nm1BjZiMGkyNqA7ZCU,2796
|
|
@@ -31,8 +31,8 @@ dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
31
31
|
dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=8AVcgZgiT_mvK1fOofXtRRr2f1dRXS_K21NuxQgP4VM,671
|
|
32
32
|
dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=znF4mvzk5L03CLNozRz8UpK-fMijmSkObDFwlbhwLUg,14656
|
|
34
|
-
dt_extensions_sdk-1.7.
|
|
35
|
-
dt_extensions_sdk-1.7.
|
|
36
|
-
dt_extensions_sdk-1.7.
|
|
37
|
-
dt_extensions_sdk-1.7.
|
|
38
|
-
dt_extensions_sdk-1.7.
|
|
34
|
+
dt_extensions_sdk-1.7.4.dist-info/METADATA,sha256=L-E8TbTHsP72vnEmdYbBYHPJRTRsrOc81QUWsiunyLk,2721
|
|
35
|
+
dt_extensions_sdk-1.7.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
dt_extensions_sdk-1.7.4.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
|
|
37
|
+
dt_extensions_sdk-1.7.4.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
|
|
38
|
+
dt_extensions_sdk-1.7.4.dist-info/RECORD,,
|
dynatrace_extension/__about__.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import random
|
|
7
|
+
import time
|
|
7
8
|
from collections.abc import Callable
|
|
8
9
|
from datetime import datetime, timedelta
|
|
9
10
|
from timeit import default_timer as timer
|
|
@@ -22,6 +23,7 @@ class WrappedCallback:
|
|
|
22
23
|
kwargs: dict | None = None,
|
|
23
24
|
running_in_sim=False,
|
|
24
25
|
activation_type: ActivationType | None = None,
|
|
26
|
+
offset_seconds: float | None = None,
|
|
25
27
|
):
|
|
26
28
|
self.callback: Callable = callback
|
|
27
29
|
if args is None:
|
|
@@ -39,6 +41,7 @@ class WrappedCallback:
|
|
|
39
41
|
self.duration = 0 # global counter
|
|
40
42
|
self.duration_interval_total = 0 # counter per interval = 1 min by default
|
|
41
43
|
self.cluster_time_diff = 0
|
|
44
|
+
self.start_timestamp_monotonic = time.monotonic()
|
|
42
45
|
self.start_timestamp = self.get_current_time_with_cluster_diff()
|
|
43
46
|
self.running_in_sim = running_in_sim
|
|
44
47
|
self.activation_type = activation_type
|
|
@@ -46,6 +49,7 @@ class WrappedCallback:
|
|
|
46
49
|
self.timeouts_count = 0 # counter per interval = 1 min by default
|
|
47
50
|
self.exception_count = 0 # counter per interval = 1 min by default
|
|
48
51
|
self.iterations = 0 # how many times we ran the callback iterator for this callback
|
|
52
|
+
self.offset_seconds = offset_seconds or self.calculate_initial_wait_time()
|
|
49
53
|
|
|
50
54
|
def get_current_time_with_cluster_diff(self):
|
|
51
55
|
return datetime.now() + timedelta(milliseconds=self.cluster_time_diff)
|
|
@@ -95,24 +99,28 @@ class WrappedCallback:
|
|
|
95
99
|
def name(self):
|
|
96
100
|
return self.callback.__name__
|
|
97
101
|
|
|
102
|
+
def calculate_initial_wait_time(self) -> float:
|
|
103
|
+
"""
|
|
104
|
+
Here we chose a random second between 5 and 55 to start the callback
|
|
105
|
+
This is to distribute load for extension running on this host
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
now = self.get_current_time_with_cluster_diff()
|
|
109
|
+
random_second = random.randint(5, 55) # noqa: S311
|
|
110
|
+
next_execution = datetime.now().replace(second=random_second, microsecond=0)
|
|
111
|
+
if next_execution <= now:
|
|
112
|
+
# The random chosen second already passed this minute
|
|
113
|
+
next_execution += timedelta(minutes=1)
|
|
114
|
+
wait_time = (next_execution - now).total_seconds()
|
|
115
|
+
self.logger.debug(f"Randomly choosing next execution time for callback {self} to be {next_execution}")
|
|
116
|
+
return wait_time
|
|
117
|
+
|
|
98
118
|
def initial_wait_time(self) -> float:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
now = self.get_current_time_with_cluster_diff()
|
|
107
|
-
random_second = random.randint(5, 55) # noqa: S311
|
|
108
|
-
next_execution = datetime.now().replace(second=random_second, microsecond=0)
|
|
109
|
-
if next_execution <= now:
|
|
110
|
-
# The random chosen second already passed this minute
|
|
111
|
-
next_execution += timedelta(minutes=1)
|
|
112
|
-
wait_time = (next_execution - now).total_seconds()
|
|
113
|
-
self.logger.debug(f"Randomly choosing next execution time for callback {self} to be {next_execution}")
|
|
114
|
-
return wait_time
|
|
115
|
-
return 0
|
|
119
|
+
# When running from the simulator, we don't want any offset
|
|
120
|
+
if self.running_in_sim:
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
return self.offset_seconds
|
|
116
124
|
|
|
117
125
|
def get_adjusted_metric_timestamp(self) -> datetime:
|
|
118
126
|
"""
|
|
@@ -147,8 +155,10 @@ class WrappedCallback:
|
|
|
147
155
|
"""
|
|
148
156
|
Get the timestamp for the next execution of the callback
|
|
149
157
|
This is done using execution total, the interval and the start timestamp
|
|
150
|
-
:return:
|
|
158
|
+
:return: float
|
|
151
159
|
"""
|
|
152
160
|
return (
|
|
153
|
-
self.
|
|
154
|
-
|
|
161
|
+
self.initial_wait_time()
|
|
162
|
+
+ self.start_timestamp_monotonic
|
|
163
|
+
+ self.interval.total_seconds() * (self.iterations or 1)
|
|
164
|
+
)
|
|
@@ -28,10 +28,11 @@ from .snapshot import Snapshot
|
|
|
28
28
|
from .status import EndpointStatuses, EndpointStatusesMap, IgnoreStatus, Status, StatusValue
|
|
29
29
|
from .throttled_logger import StrictThrottledHandler, ThrottledHandler
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
# Intervals defined in seconds for internal callbacks
|
|
32
|
+
HEARTBEAT_INTERVAL = 50
|
|
33
|
+
METRIC_SENDING_INTERVAL = 30
|
|
34
|
+
SFM_METRIC_SENDING_INTERVAL = 60
|
|
35
|
+
TIME_DIFF_INTERVAL = 60
|
|
35
36
|
|
|
36
37
|
CALLBACKS_THREAD_POOL_SIZE = 100
|
|
37
38
|
INTERNAL_THREAD_POOL_SIZE = 20
|
|
@@ -180,7 +181,7 @@ class Extension:
|
|
|
180
181
|
"""
|
|
181
182
|
|
|
182
183
|
_instance: ClassVar = None
|
|
183
|
-
schedule_decorators: ClassVar = []
|
|
184
|
+
schedule_decorators: ClassVar[list[tuple[Callable, timedelta | int, tuple | None, ActivationType | None]]] = []
|
|
184
185
|
|
|
185
186
|
def __new__(cls, *args, **kwargs): # noqa: ARG004
|
|
186
187
|
if Extension._instance is None:
|
|
@@ -240,15 +241,15 @@ class Extension:
|
|
|
240
241
|
self._running_callbacks: dict[int, WrappedCallback] = {}
|
|
241
242
|
self._running_callbacks_lock: Lock = Lock()
|
|
242
243
|
|
|
243
|
-
self._scheduler = sched.scheduler(time.
|
|
244
|
+
self._scheduler = sched.scheduler(time.monotonic, time.sleep)
|
|
244
245
|
|
|
245
246
|
# Timestamps for scheduling of internal callbacks
|
|
246
|
-
self._next_internal_callbacks_timestamps: dict[str,
|
|
247
|
-
"timediff":
|
|
248
|
-
"heartbeat":
|
|
249
|
-
"metrics":
|
|
250
|
-
"events":
|
|
251
|
-
"sfm_metrics":
|
|
247
|
+
self._next_internal_callbacks_timestamps: dict[str, float] = {
|
|
248
|
+
"timediff": time.monotonic() + TIME_DIFF_INTERVAL,
|
|
249
|
+
"heartbeat": time.monotonic() + HEARTBEAT_INTERVAL,
|
|
250
|
+
"metrics": time.monotonic() + METRIC_SENDING_INTERVAL,
|
|
251
|
+
"events": time.monotonic() + METRIC_SENDING_INTERVAL,
|
|
252
|
+
"sfm_metrics": time.monotonic() + SFM_METRIC_SENDING_INTERVAL,
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
# Executors for the callbacks and internal methods
|
|
@@ -384,6 +385,7 @@ class Extension:
|
|
|
384
385
|
interval: timedelta | int,
|
|
385
386
|
args: tuple | None = None,
|
|
386
387
|
activation_type: ActivationType | None = None,
|
|
388
|
+
offset_seconds: float | None = None,
|
|
387
389
|
) -> None:
|
|
388
390
|
"""Schedule a method to be executed periodically.
|
|
389
391
|
|
|
@@ -397,6 +399,7 @@ class Extension:
|
|
|
397
399
|
args: Arguments to the callback, if any
|
|
398
400
|
activation_type: Optional activation type when this callback should run,
|
|
399
401
|
can be 'ActivationType.LOCAL' or 'ActivationType.REMOTE'
|
|
402
|
+
offset_seconds: Optional offset of first execution represented in seconds. Offset is random if `offset_seconds` is `None`.
|
|
400
403
|
"""
|
|
401
404
|
|
|
402
405
|
if isinstance(interval, int):
|
|
@@ -406,7 +409,10 @@ class Extension:
|
|
|
406
409
|
msg = f"Interval must be at least 1 second, got {interval.total_seconds()} seconds"
|
|
407
410
|
raise ValueError(msg)
|
|
408
411
|
|
|
409
|
-
callback = WrappedCallback(
|
|
412
|
+
callback = WrappedCallback(
|
|
413
|
+
interval, callback, api_logger, args, activation_type=activation_type, offset_seconds=offset_seconds
|
|
414
|
+
)
|
|
415
|
+
|
|
410
416
|
if self._is_fastcheck:
|
|
411
417
|
self._scheduled_callbacks_before_run.append(callback)
|
|
412
418
|
else:
|
|
@@ -777,7 +783,7 @@ class Extension:
|
|
|
777
783
|
parser.add_argument("--secrets", required=False, default="secrets.json")
|
|
778
784
|
parser.add_argument("--no-print-metrics", required=False, action="store_true")
|
|
779
785
|
|
|
780
|
-
args,
|
|
786
|
+
args, _ = parser.parse_known_args()
|
|
781
787
|
self._is_fastcheck = args.fastcheck
|
|
782
788
|
if args.dsid is None:
|
|
783
789
|
# DEV mode
|
|
@@ -1169,10 +1175,10 @@ class Extension:
|
|
|
1169
1175
|
def _send_dt_event(self, event: dict[str, str | int | dict[str, str]]):
|
|
1170
1176
|
self._client.send_dt_event(event)
|
|
1171
1177
|
|
|
1172
|
-
def _get_and_set_next_internal_callback_timestamp(self, callback_name: str, interval:
|
|
1178
|
+
def _get_and_set_next_internal_callback_timestamp(self, callback_name: str, interval: float) -> float:
|
|
1173
1179
|
next_timestamp = self._next_internal_callbacks_timestamps[callback_name]
|
|
1174
1180
|
self._next_internal_callbacks_timestamps[callback_name] += interval
|
|
1175
|
-
return next_timestamp
|
|
1181
|
+
return next_timestamp
|
|
1176
1182
|
|
|
1177
1183
|
def get_version(self) -> str:
|
|
1178
1184
|
"""Return the extension version."""
|
|
File without changes
|
{dt_extensions_sdk-1.7.2.dist-info → dt_extensions_sdk-1.7.4.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|