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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dt-extensions-sdk
3
- Version: 1.7.2
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=qRNIJuMF_5DRmYm6b4aociCQcAdhCKOCXEIN81aEsPQ,110
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=woumpcWID09QHGc_rSrukNslVG8Qo0UQEgv8VTB6m7c,6681
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=8P0ANxF3WVjVeGAA73MXTeSekpL1MarEBB6ZoKGnpZY,49669
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.2.dist-info/METADATA,sha256=FVuIQEkut5SwCh77eZrb92ngUurYD_LxUhFZab6Diw8,2721
35
- dt_extensions_sdk-1.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- dt_extensions_sdk-1.7.2.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
37
- dt_extensions_sdk-1.7.2.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
38
- dt_extensions_sdk-1.7.2.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
5
 
6
- __version__ = "1.7.2"
6
+ __version__ = "1.7.4"
@@ -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
- if not self.running_in_sim:
100
- """
101
- Here we chose a random second between 5 and 55 to start the callback
102
- This is to distribute load for extension running on this host
103
- When running from the simulator, this is not done
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: datetime
158
+ :return: float
151
159
  """
152
160
  return (
153
- self.start_timestamp + timedelta(seconds=self.interval.total_seconds() * (self.iterations or 1))
154
- ).timestamp()
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
- HEARTBEAT_INTERVAL = timedelta(seconds=50)
32
- METRIC_SENDING_INTERVAL = timedelta(seconds=30)
33
- SFM_METRIC_SENDING_INTERVAL = timedelta(seconds=60)
34
- TIME_DIFF_INTERVAL = timedelta(seconds=60)
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.time, time.sleep)
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, datetime] = {
247
- "timediff": datetime.now() + TIME_DIFF_INTERVAL,
248
- "heartbeat": datetime.now() + HEARTBEAT_INTERVAL,
249
- "metrics": datetime.now() + METRIC_SENDING_INTERVAL,
250
- "events": datetime.now() + METRIC_SENDING_INTERVAL,
251
- "sfm_metrics": datetime.now() + SFM_METRIC_SENDING_INTERVAL,
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(interval, callback, api_logger, args, activation_type=activation_type)
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, unknown = parser.parse_known_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: timedelta):
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.timestamp()
1181
+ return next_timestamp
1176
1182
 
1177
1183
  def get_version(self) -> str:
1178
1184
  """Return the extension version."""