dt-extensions-sdk 1.1.21__py3-none-any.whl → 1.1.23__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.1.21.dist-info → dt_extensions_sdk-1.1.23.dist-info}/METADATA +2 -2
- dt_extensions_sdk-1.1.23.dist-info/RECORD +33 -0
- {dt_extensions_sdk-1.1.21.dist-info → dt_extensions_sdk-1.1.23.dist-info}/WHEEL +1 -1
- {dt_extensions_sdk-1.1.21.dist-info → dt_extensions_sdk-1.1.23.dist-info}/licenses/LICENSE.txt +9 -9
- dynatrace_extension/__about__.py +5 -5
- dynatrace_extension/__init__.py +27 -27
- dynatrace_extension/cli/__init__.py +5 -5
- dynatrace_extension/cli/create/__init__.py +1 -1
- dynatrace_extension/cli/create/create.py +76 -76
- dynatrace_extension/cli/create/extension_template/.gitignore.template +160 -160
- dynatrace_extension/cli/create/extension_template/README.md.template +33 -33
- dynatrace_extension/cli/create/extension_template/activation.json.template +15 -15
- dynatrace_extension/cli/create/extension_template/extension/activationSchema.json.template +118 -118
- dynatrace_extension/cli/create/extension_template/extension/extension.yaml.template +17 -17
- dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template +43 -43
- dynatrace_extension/cli/create/extension_template/setup.py.template +28 -28
- dynatrace_extension/cli/main.py +428 -428
- dynatrace_extension/cli/schema.py +129 -129
- dynatrace_extension/sdk/__init__.py +3 -3
- dynatrace_extension/sdk/activation.py +43 -43
- dynatrace_extension/sdk/callback.py +134 -134
- dynatrace_extension/sdk/communication.py +482 -480
- dynatrace_extension/sdk/event.py +19 -19
- dynatrace_extension/sdk/extension.py +1045 -1045
- dynatrace_extension/sdk/helper.py +191 -191
- dynatrace_extension/sdk/metric.py +118 -118
- dynatrace_extension/sdk/runtime.py +67 -67
- dynatrace_extension/sdk/vendor/mureq/LICENSE +13 -13
- dynatrace_extension/sdk/vendor/mureq/mureq.py +447 -447
- dt_extensions_sdk-1.1.21.dist-info/RECORD +0 -33
- {dt_extensions_sdk-1.1.21.dist-info → dt_extensions_sdk-1.1.23.dist-info}/entry_points.txt +0 -0
@@ -1,191 +1,191 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2023-present Dynatrace LLC
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: MIT
|
4
|
-
|
5
|
-
from datetime import datetime, timedelta
|
6
|
-
from typing import Callable, Dict, List, Optional, Union
|
7
|
-
|
8
|
-
from .activation import ActivationConfig, ActivationType
|
9
|
-
from .communication import Status
|
10
|
-
from .event import Severity
|
11
|
-
from .extension import DtEventType, Extension
|
12
|
-
from .metric import MetricType, SummaryStat
|
13
|
-
|
14
|
-
|
15
|
-
class _HelperExtension(Extension):
|
16
|
-
@property
|
17
|
-
def is_helper(self) -> bool:
|
18
|
-
return True
|
19
|
-
|
20
|
-
|
21
|
-
def report_metric(
|
22
|
-
key: str,
|
23
|
-
value: Union[float, str, int, SummaryStat],
|
24
|
-
dimensions: Optional[Dict[str, str]] = None,
|
25
|
-
techrule: Optional[str] = None,
|
26
|
-
timestamp: Optional[datetime] = None,
|
27
|
-
metric_type: MetricType = MetricType.GAUGE,
|
28
|
-
) -> None:
|
29
|
-
"""Reports a metric using the MINT protocol
|
30
|
-
By default, it reports a gauge metric
|
31
|
-
|
32
|
-
|
33
|
-
:param key: The metric key, must follow the MINT specification
|
34
|
-
:param value: The metric value, can be a simple value or a SummaryStat
|
35
|
-
:param dimensions: A dictionary of dimensions
|
36
|
-
:param techrule: The techrule of the metric, defaults to None
|
37
|
-
:param timestamp: The timestamp of the metric, defaults to the current time
|
38
|
-
:param metric_type: The type of the metric, defaults to MetricType.GAUGE
|
39
|
-
"""
|
40
|
-
_HelperExtension().report_metric(key, value, dimensions, techrule, timestamp, metric_type)
|
41
|
-
|
42
|
-
|
43
|
-
def report_mint_lines(lines: List[str]) -> None:
|
44
|
-
"""Reports mint lines using the MINT protocol.
|
45
|
-
These lines are not validated before being sent.
|
46
|
-
|
47
|
-
:param lines: A list of mint lines, example: ["my_metric 1", "my_other_metric 2"]
|
48
|
-
"""
|
49
|
-
_HelperExtension().report_mint_lines(lines)
|
50
|
-
|
51
|
-
|
52
|
-
def report_dt_event(
|
53
|
-
event_type: DtEventType,
|
54
|
-
title: str,
|
55
|
-
start_time: Optional[int] = None,
|
56
|
-
end_time: Optional[int] = None,
|
57
|
-
timeout: Optional[int] = None,
|
58
|
-
entity_selector: Optional[str] = None,
|
59
|
-
properties: Optional[dict[str, str]] = None,
|
60
|
-
) -> None:
|
61
|
-
"""
|
62
|
-
Reports a custom event v2 using event ingest
|
63
|
-
|
64
|
-
For reference see: https://www.dynatrace.com/support/help/dynatrace-api/environment-api/events-v2/post-event
|
65
|
-
|
66
|
-
:param event_type: The event type chosen from type Enum (required)
|
67
|
-
:param title: The title of the event (required)
|
68
|
-
:param start_time: The start time of event in UTC ms, if not set, current timestamp (optional)
|
69
|
-
:param end_time: The end time of event in UTC ms, if not set, current timestamp + timeout (optional)
|
70
|
-
:param timeout: The timeout of event in minutes, if not set, 15 (optional)
|
71
|
-
:param entity_selector: The entity selector, if not set, the event is associated with environment entity (optional)
|
72
|
-
:param properties: A map of event properties (optional)
|
73
|
-
"""
|
74
|
-
_HelperExtension().report_dt_event(event_type, title, start_time, end_time, timeout, entity_selector, properties)
|
75
|
-
|
76
|
-
|
77
|
-
def report_dt_event_dict(event: dict):
|
78
|
-
"""
|
79
|
-
Reports a custom event v2 using event ingest using provided dictionary resembling required json
|
80
|
-
|
81
|
-
For reference see: https://www.dynatrace.com/support/help/dynatrace-api/environment-api/events-v2/post-event
|
82
|
-
"""
|
83
|
-
_HelperExtension().report_dt_event_dict(event)
|
84
|
-
|
85
|
-
|
86
|
-
def schedule(
|
87
|
-
callback: Callable,
|
88
|
-
interval: Union[timedelta, int],
|
89
|
-
args: Optional[tuple] = None,
|
90
|
-
activation_type: Optional[ActivationType] = None,
|
91
|
-
) -> None:
|
92
|
-
"""Schedules a callback to be called periodically
|
93
|
-
|
94
|
-
:param callback: The callback to be called
|
95
|
-
:param interval: The time interval between invocations, can be a timedelta object, or an int representing the number of seconds
|
96
|
-
:param args: Arguments to the callback, if any
|
97
|
-
:param activation_type: Optional activation type when this callback should run, can be 'ActivationType.LOCAL' or 'ActivationType.REMOTE'
|
98
|
-
|
99
|
-
"""
|
100
|
-
_HelperExtension().schedule(callback, interval, args, activation_type)
|
101
|
-
|
102
|
-
|
103
|
-
def schedule_function(
|
104
|
-
interval: Union[timedelta, int], args: Optional[tuple] = None, activation_type: Optional[ActivationType] = None
|
105
|
-
):
|
106
|
-
def decorator(function):
|
107
|
-
schedule(function, interval, args=args, activation_type=activation_type)
|
108
|
-
|
109
|
-
return decorator
|
110
|
-
|
111
|
-
|
112
|
-
def schedule_method(
|
113
|
-
interval: Union[timedelta, int], args: Optional[tuple] = None, activation_type: Optional[ActivationType] = None
|
114
|
-
):
|
115
|
-
def decorator(function):
|
116
|
-
Extension.schedule_decorators.append((function, interval, args, activation_type))
|
117
|
-
|
118
|
-
return decorator
|
119
|
-
|
120
|
-
|
121
|
-
def report_event(
|
122
|
-
title: str,
|
123
|
-
description: str,
|
124
|
-
properties: Optional[dict] = None,
|
125
|
-
timestamp: Optional[datetime] = None,
|
126
|
-
severity: Union[Severity, str] = Severity.INFO,
|
127
|
-
) -> None:
|
128
|
-
"""Reports an event using the MINT protocol
|
129
|
-
|
130
|
-
:param title: The title of the event
|
131
|
-
:param description: The description of the event
|
132
|
-
:param properties: A dictionary of properties
|
133
|
-
:param timestamp: The timestamp of the event, defaults to the current time
|
134
|
-
:param severity: The severity of the event, defaults to Severity.INFO
|
135
|
-
"""
|
136
|
-
_HelperExtension().report_event(title, description, properties, timestamp, severity)
|
137
|
-
|
138
|
-
|
139
|
-
def report_log_event(log_event: dict):
|
140
|
-
"""Reports a custom log event using log ingest
|
141
|
-
|
142
|
-
:param log_event: The log event dictionary, reference: https://www.dynatrace.com/support/help/shortlink/log-monitoring-log-data-ingestion
|
143
|
-
"""
|
144
|
-
_HelperExtension().report_log_event(log_event)
|
145
|
-
|
146
|
-
|
147
|
-
def report_log_events(log_events: List[dict]):
|
148
|
-
"""Reports a list of custom log events using log ingest
|
149
|
-
|
150
|
-
:param log_events: The list of log events
|
151
|
-
"""
|
152
|
-
_HelperExtension().report_log_events(log_events)
|
153
|
-
|
154
|
-
|
155
|
-
def report_log_lines(log_lines: List[Union[str, bytes]]):
|
156
|
-
"""Reports a list of log lines using log ingest
|
157
|
-
|
158
|
-
:param log_lines: The list of log lines
|
159
|
-
"""
|
160
|
-
_HelperExtension().report_log_lines(log_lines)
|
161
|
-
|
162
|
-
|
163
|
-
def run_extension():
|
164
|
-
"""Starts the extension loop"""
|
165
|
-
_HelperExtension().run()
|
166
|
-
|
167
|
-
|
168
|
-
def get_activation_config():
|
169
|
-
return _HelperExtension().get_activation_config()
|
170
|
-
|
171
|
-
|
172
|
-
def get_helper_extension():
|
173
|
-
return _HelperExtension()
|
174
|
-
|
175
|
-
|
176
|
-
def dt_fastcheck():
|
177
|
-
def wrapper(fast_check_callback: Callable[[ActivationConfig, str], Status]):
|
178
|
-
_HelperExtension().register_fastcheck(fast_check_callback=fast_check_callback)
|
179
|
-
|
180
|
-
return wrapper
|
181
|
-
|
182
|
-
|
183
|
-
def log(message: str, severity: Severity):
|
184
|
-
"""
|
185
|
-
Logs provided message into the extension's log file.
|
186
|
-
Only two severities are supported: INFO and ERROR
|
187
|
-
"""
|
188
|
-
if severity == Severity.INFO:
|
189
|
-
_HelperExtension().logger.info(message)
|
190
|
-
elif severity == Severity.ERROR:
|
191
|
-
_HelperExtension().logger.error(message)
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Dynatrace LLC
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
from datetime import datetime, timedelta
|
6
|
+
from typing import Callable, Dict, List, Optional, Union
|
7
|
+
|
8
|
+
from .activation import ActivationConfig, ActivationType
|
9
|
+
from .communication import Status
|
10
|
+
from .event import Severity
|
11
|
+
from .extension import DtEventType, Extension
|
12
|
+
from .metric import MetricType, SummaryStat
|
13
|
+
|
14
|
+
|
15
|
+
class _HelperExtension(Extension):
|
16
|
+
@property
|
17
|
+
def is_helper(self) -> bool:
|
18
|
+
return True
|
19
|
+
|
20
|
+
|
21
|
+
def report_metric(
|
22
|
+
key: str,
|
23
|
+
value: Union[float, str, int, SummaryStat],
|
24
|
+
dimensions: Optional[Dict[str, str]] = None,
|
25
|
+
techrule: Optional[str] = None,
|
26
|
+
timestamp: Optional[datetime] = None,
|
27
|
+
metric_type: MetricType = MetricType.GAUGE,
|
28
|
+
) -> None:
|
29
|
+
"""Reports a metric using the MINT protocol
|
30
|
+
By default, it reports a gauge metric
|
31
|
+
|
32
|
+
|
33
|
+
:param key: The metric key, must follow the MINT specification
|
34
|
+
:param value: The metric value, can be a simple value or a SummaryStat
|
35
|
+
:param dimensions: A dictionary of dimensions
|
36
|
+
:param techrule: The techrule of the metric, defaults to None
|
37
|
+
:param timestamp: The timestamp of the metric, defaults to the current time
|
38
|
+
:param metric_type: The type of the metric, defaults to MetricType.GAUGE
|
39
|
+
"""
|
40
|
+
_HelperExtension().report_metric(key, value, dimensions, techrule, timestamp, metric_type)
|
41
|
+
|
42
|
+
|
43
|
+
def report_mint_lines(lines: List[str]) -> None:
|
44
|
+
"""Reports mint lines using the MINT protocol.
|
45
|
+
These lines are not validated before being sent.
|
46
|
+
|
47
|
+
:param lines: A list of mint lines, example: ["my_metric 1", "my_other_metric 2"]
|
48
|
+
"""
|
49
|
+
_HelperExtension().report_mint_lines(lines)
|
50
|
+
|
51
|
+
|
52
|
+
def report_dt_event(
|
53
|
+
event_type: DtEventType,
|
54
|
+
title: str,
|
55
|
+
start_time: Optional[int] = None,
|
56
|
+
end_time: Optional[int] = None,
|
57
|
+
timeout: Optional[int] = None,
|
58
|
+
entity_selector: Optional[str] = None,
|
59
|
+
properties: Optional[dict[str, str]] = None,
|
60
|
+
) -> None:
|
61
|
+
"""
|
62
|
+
Reports a custom event v2 using event ingest
|
63
|
+
|
64
|
+
For reference see: https://www.dynatrace.com/support/help/dynatrace-api/environment-api/events-v2/post-event
|
65
|
+
|
66
|
+
:param event_type: The event type chosen from type Enum (required)
|
67
|
+
:param title: The title of the event (required)
|
68
|
+
:param start_time: The start time of event in UTC ms, if not set, current timestamp (optional)
|
69
|
+
:param end_time: The end time of event in UTC ms, if not set, current timestamp + timeout (optional)
|
70
|
+
:param timeout: The timeout of event in minutes, if not set, 15 (optional)
|
71
|
+
:param entity_selector: The entity selector, if not set, the event is associated with environment entity (optional)
|
72
|
+
:param properties: A map of event properties (optional)
|
73
|
+
"""
|
74
|
+
_HelperExtension().report_dt_event(event_type, title, start_time, end_time, timeout, entity_selector, properties)
|
75
|
+
|
76
|
+
|
77
|
+
def report_dt_event_dict(event: dict):
|
78
|
+
"""
|
79
|
+
Reports a custom event v2 using event ingest using provided dictionary resembling required json
|
80
|
+
|
81
|
+
For reference see: https://www.dynatrace.com/support/help/dynatrace-api/environment-api/events-v2/post-event
|
82
|
+
"""
|
83
|
+
_HelperExtension().report_dt_event_dict(event)
|
84
|
+
|
85
|
+
|
86
|
+
def schedule(
|
87
|
+
callback: Callable,
|
88
|
+
interval: Union[timedelta, int],
|
89
|
+
args: Optional[tuple] = None,
|
90
|
+
activation_type: Optional[ActivationType] = None,
|
91
|
+
) -> None:
|
92
|
+
"""Schedules a callback to be called periodically
|
93
|
+
|
94
|
+
:param callback: The callback to be called
|
95
|
+
:param interval: The time interval between invocations, can be a timedelta object, or an int representing the number of seconds
|
96
|
+
:param args: Arguments to the callback, if any
|
97
|
+
:param activation_type: Optional activation type when this callback should run, can be 'ActivationType.LOCAL' or 'ActivationType.REMOTE'
|
98
|
+
|
99
|
+
"""
|
100
|
+
_HelperExtension().schedule(callback, interval, args, activation_type)
|
101
|
+
|
102
|
+
|
103
|
+
def schedule_function(
|
104
|
+
interval: Union[timedelta, int], args: Optional[tuple] = None, activation_type: Optional[ActivationType] = None
|
105
|
+
):
|
106
|
+
def decorator(function):
|
107
|
+
schedule(function, interval, args=args, activation_type=activation_type)
|
108
|
+
|
109
|
+
return decorator
|
110
|
+
|
111
|
+
|
112
|
+
def schedule_method(
|
113
|
+
interval: Union[timedelta, int], args: Optional[tuple] = None, activation_type: Optional[ActivationType] = None
|
114
|
+
):
|
115
|
+
def decorator(function):
|
116
|
+
Extension.schedule_decorators.append((function, interval, args, activation_type))
|
117
|
+
|
118
|
+
return decorator
|
119
|
+
|
120
|
+
|
121
|
+
def report_event(
|
122
|
+
title: str,
|
123
|
+
description: str,
|
124
|
+
properties: Optional[dict] = None,
|
125
|
+
timestamp: Optional[datetime] = None,
|
126
|
+
severity: Union[Severity, str] = Severity.INFO,
|
127
|
+
) -> None:
|
128
|
+
"""Reports an event using the MINT protocol
|
129
|
+
|
130
|
+
:param title: The title of the event
|
131
|
+
:param description: The description of the event
|
132
|
+
:param properties: A dictionary of properties
|
133
|
+
:param timestamp: The timestamp of the event, defaults to the current time
|
134
|
+
:param severity: The severity of the event, defaults to Severity.INFO
|
135
|
+
"""
|
136
|
+
_HelperExtension().report_event(title, description, properties, timestamp, severity)
|
137
|
+
|
138
|
+
|
139
|
+
def report_log_event(log_event: dict):
|
140
|
+
"""Reports a custom log event using log ingest
|
141
|
+
|
142
|
+
:param log_event: The log event dictionary, reference: https://www.dynatrace.com/support/help/shortlink/log-monitoring-log-data-ingestion
|
143
|
+
"""
|
144
|
+
_HelperExtension().report_log_event(log_event)
|
145
|
+
|
146
|
+
|
147
|
+
def report_log_events(log_events: List[dict]):
|
148
|
+
"""Reports a list of custom log events using log ingest
|
149
|
+
|
150
|
+
:param log_events: The list of log events
|
151
|
+
"""
|
152
|
+
_HelperExtension().report_log_events(log_events)
|
153
|
+
|
154
|
+
|
155
|
+
def report_log_lines(log_lines: List[Union[str, bytes]]):
|
156
|
+
"""Reports a list of log lines using log ingest
|
157
|
+
|
158
|
+
:param log_lines: The list of log lines
|
159
|
+
"""
|
160
|
+
_HelperExtension().report_log_lines(log_lines)
|
161
|
+
|
162
|
+
|
163
|
+
def run_extension():
|
164
|
+
"""Starts the extension loop"""
|
165
|
+
_HelperExtension().run()
|
166
|
+
|
167
|
+
|
168
|
+
def get_activation_config():
|
169
|
+
return _HelperExtension().get_activation_config()
|
170
|
+
|
171
|
+
|
172
|
+
def get_helper_extension():
|
173
|
+
return _HelperExtension()
|
174
|
+
|
175
|
+
|
176
|
+
def dt_fastcheck():
|
177
|
+
def wrapper(fast_check_callback: Callable[[ActivationConfig, str], Status]):
|
178
|
+
_HelperExtension().register_fastcheck(fast_check_callback=fast_check_callback)
|
179
|
+
|
180
|
+
return wrapper
|
181
|
+
|
182
|
+
|
183
|
+
def log(message: str, severity: Severity):
|
184
|
+
"""
|
185
|
+
Logs provided message into the extension's log file.
|
186
|
+
Only two severities are supported: INFO and ERROR
|
187
|
+
"""
|
188
|
+
if severity == Severity.INFO:
|
189
|
+
_HelperExtension().logger.info(message)
|
190
|
+
elif severity == Severity.ERROR:
|
191
|
+
_HelperExtension().logger.error(message)
|
@@ -1,118 +1,118 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2023-present Dynatrace LLC
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: MIT
|
4
|
-
|
5
|
-
from datetime import datetime
|
6
|
-
from enum import Enum
|
7
|
-
from typing import Dict, Optional, Union
|
8
|
-
|
9
|
-
# https://bitbucket.lab.dynatrace.org/projects/ONE/repos/schemaless-metrics-spec/browse/limits.md
|
10
|
-
LIMIT_DIMENSIONS_COUNT = 50
|
11
|
-
LIMIT_LINE_LENGTH = 2000
|
12
|
-
|
13
|
-
CLIENT_FACING_SFM_NAMESPACE = "dsfm"
|
14
|
-
INTERNAL_SFM_NAMESPACE = "isfm"
|
15
|
-
|
16
|
-
|
17
|
-
class SummaryStat:
|
18
|
-
def __init__(
|
19
|
-
self,
|
20
|
-
value_min: float,
|
21
|
-
value_max: float,
|
22
|
-
value_sum: float,
|
23
|
-
value_count: float,
|
24
|
-
):
|
25
|
-
self.value_min = value_min
|
26
|
-
self.value_max = value_max
|
27
|
-
self.value_sum = value_sum
|
28
|
-
self.value_count = value_count
|
29
|
-
|
30
|
-
def __str__(self):
|
31
|
-
return f"min={self.value_min},max={self.value_max},sum={self.value_sum},count={self.value_count}"
|
32
|
-
|
33
|
-
|
34
|
-
class MetricType(Enum):
|
35
|
-
GAUGE = "gauge"
|
36
|
-
COUNT = "count"
|
37
|
-
DELTA = "count,delta"
|
38
|
-
|
39
|
-
|
40
|
-
class Metric:
|
41
|
-
def __init__(
|
42
|
-
self,
|
43
|
-
key: str,
|
44
|
-
value: Union[float, int, str, SummaryStat],
|
45
|
-
dimensions: Optional[Dict[str, str]] = None,
|
46
|
-
metric_type: MetricType = MetricType.GAUGE,
|
47
|
-
timestamp: Optional[datetime] = None,
|
48
|
-
):
|
49
|
-
self.key: str = key
|
50
|
-
self.value: Union[float, int, str, SummaryStat] = value
|
51
|
-
if dimensions is None:
|
52
|
-
dimensions = {}
|
53
|
-
self.dimensions: Dict[str, str] = dimensions
|
54
|
-
self.metric_type: MetricType = metric_type
|
55
|
-
self.timestamp: Optional[datetime] = timestamp
|
56
|
-
|
57
|
-
def __hash__(self):
|
58
|
-
return hash(self._key_and_dimensions())
|
59
|
-
|
60
|
-
def __eq__(self, other):
|
61
|
-
return self._key_and_dimensions() == other._key_and_dimensions()
|
62
|
-
|
63
|
-
def to_mint_line(self) -> str:
|
64
|
-
# Add key and dimensions
|
65
|
-
line = f"{self._key_and_dimensions()}"
|
66
|
-
|
67
|
-
# Add value
|
68
|
-
if self.metric_type == MetricType.DELTA:
|
69
|
-
line = f"{line} {self.metric_type.value}={self.value}"
|
70
|
-
else:
|
71
|
-
line = f"{line} {self.metric_type.value},{self.value}"
|
72
|
-
|
73
|
-
# Add timestamp
|
74
|
-
if self.timestamp is not None:
|
75
|
-
timestamp = int(self.timestamp.timestamp() * 1000)
|
76
|
-
line = f"{line} {timestamp}"
|
77
|
-
|
78
|
-
return line
|
79
|
-
|
80
|
-
def __repr__(self):
|
81
|
-
return self.to_mint_line()
|
82
|
-
|
83
|
-
def _key_and_dimensions(self):
|
84
|
-
if not self.dimensions:
|
85
|
-
return f"{self.key}"
|
86
|
-
|
87
|
-
dimensions_string = ",".join([f'{k}="{v}"' for k, v in self.dimensions.items()])
|
88
|
-
return f"{self.key},{dimensions_string}"
|
89
|
-
|
90
|
-
def validate(self) -> bool:
|
91
|
-
if len(self.dimensions) > LIMIT_DIMENSIONS_COUNT:
|
92
|
-
msg = f"Metric dimension count of {len(self.dimensions)} exceeds limit of {LIMIT_DIMENSIONS_COUNT} for {self.key}"
|
93
|
-
raise ValueError(msg)
|
94
|
-
|
95
|
-
line_length = len(self.to_mint_line())
|
96
|
-
if line_length > LIMIT_LINE_LENGTH:
|
97
|
-
msg = f"Metric line length {line_length} exceeds limit of {LIMIT_LINE_LENGTH} for {self.key}"
|
98
|
-
raise ValueError(msg)
|
99
|
-
return True
|
100
|
-
|
101
|
-
|
102
|
-
class SfmMetric(Metric):
|
103
|
-
def __init__(
|
104
|
-
self,
|
105
|
-
key: str,
|
106
|
-
value: Union[float, int, str, SummaryStat],
|
107
|
-
dimensions: Optional[Dict[str, str]] = None,
|
108
|
-
metric_type: MetricType = MetricType.GAUGE,
|
109
|
-
timestamp: Optional[datetime] = None,
|
110
|
-
client_facing: bool = False,
|
111
|
-
):
|
112
|
-
key = create_sfm_metric_key(key, client_facing)
|
113
|
-
super().__init__(key, value, dimensions, metric_type, timestamp)
|
114
|
-
|
115
|
-
|
116
|
-
def create_sfm_metric_key(key: str, client_facing: bool = False) -> str:
|
117
|
-
namespace = CLIENT_FACING_SFM_NAMESPACE if client_facing else INTERNAL_SFM_NAMESPACE
|
118
|
-
return f"{namespace}:datasource.python.{key}"
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Dynatrace LLC
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
from enum import Enum
|
7
|
+
from typing import Dict, Optional, Union
|
8
|
+
|
9
|
+
# https://bitbucket.lab.dynatrace.org/projects/ONE/repos/schemaless-metrics-spec/browse/limits.md
|
10
|
+
LIMIT_DIMENSIONS_COUNT = 50
|
11
|
+
LIMIT_LINE_LENGTH = 2000
|
12
|
+
|
13
|
+
CLIENT_FACING_SFM_NAMESPACE = "dsfm"
|
14
|
+
INTERNAL_SFM_NAMESPACE = "isfm"
|
15
|
+
|
16
|
+
|
17
|
+
class SummaryStat:
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
value_min: float,
|
21
|
+
value_max: float,
|
22
|
+
value_sum: float,
|
23
|
+
value_count: float,
|
24
|
+
):
|
25
|
+
self.value_min = value_min
|
26
|
+
self.value_max = value_max
|
27
|
+
self.value_sum = value_sum
|
28
|
+
self.value_count = value_count
|
29
|
+
|
30
|
+
def __str__(self):
|
31
|
+
return f"min={self.value_min},max={self.value_max},sum={self.value_sum},count={self.value_count}"
|
32
|
+
|
33
|
+
|
34
|
+
class MetricType(Enum):
|
35
|
+
GAUGE = "gauge"
|
36
|
+
COUNT = "count"
|
37
|
+
DELTA = "count,delta"
|
38
|
+
|
39
|
+
|
40
|
+
class Metric:
|
41
|
+
def __init__(
|
42
|
+
self,
|
43
|
+
key: str,
|
44
|
+
value: Union[float, int, str, SummaryStat],
|
45
|
+
dimensions: Optional[Dict[str, str]] = None,
|
46
|
+
metric_type: MetricType = MetricType.GAUGE,
|
47
|
+
timestamp: Optional[datetime] = None,
|
48
|
+
):
|
49
|
+
self.key: str = key
|
50
|
+
self.value: Union[float, int, str, SummaryStat] = value
|
51
|
+
if dimensions is None:
|
52
|
+
dimensions = {}
|
53
|
+
self.dimensions: Dict[str, str] = dimensions
|
54
|
+
self.metric_type: MetricType = metric_type
|
55
|
+
self.timestamp: Optional[datetime] = timestamp
|
56
|
+
|
57
|
+
def __hash__(self):
|
58
|
+
return hash(self._key_and_dimensions())
|
59
|
+
|
60
|
+
def __eq__(self, other):
|
61
|
+
return self._key_and_dimensions() == other._key_and_dimensions()
|
62
|
+
|
63
|
+
def to_mint_line(self) -> str:
|
64
|
+
# Add key and dimensions
|
65
|
+
line = f"{self._key_and_dimensions()}"
|
66
|
+
|
67
|
+
# Add value
|
68
|
+
if self.metric_type == MetricType.DELTA:
|
69
|
+
line = f"{line} {self.metric_type.value}={self.value}"
|
70
|
+
else:
|
71
|
+
line = f"{line} {self.metric_type.value},{self.value}"
|
72
|
+
|
73
|
+
# Add timestamp
|
74
|
+
if self.timestamp is not None:
|
75
|
+
timestamp = int(self.timestamp.timestamp() * 1000)
|
76
|
+
line = f"{line} {timestamp}"
|
77
|
+
|
78
|
+
return line
|
79
|
+
|
80
|
+
def __repr__(self):
|
81
|
+
return self.to_mint_line()
|
82
|
+
|
83
|
+
def _key_and_dimensions(self):
|
84
|
+
if not self.dimensions:
|
85
|
+
return f"{self.key}"
|
86
|
+
|
87
|
+
dimensions_string = ",".join([f'{k}="{v}"' for k, v in self.dimensions.items()])
|
88
|
+
return f"{self.key},{dimensions_string}"
|
89
|
+
|
90
|
+
def validate(self) -> bool:
|
91
|
+
if len(self.dimensions) > LIMIT_DIMENSIONS_COUNT:
|
92
|
+
msg = f"Metric dimension count of {len(self.dimensions)} exceeds limit of {LIMIT_DIMENSIONS_COUNT} for {self.key}"
|
93
|
+
raise ValueError(msg)
|
94
|
+
|
95
|
+
line_length = len(self.to_mint_line())
|
96
|
+
if line_length > LIMIT_LINE_LENGTH:
|
97
|
+
msg = f"Metric line length {line_length} exceeds limit of {LIMIT_LINE_LENGTH} for {self.key}"
|
98
|
+
raise ValueError(msg)
|
99
|
+
return True
|
100
|
+
|
101
|
+
|
102
|
+
class SfmMetric(Metric):
|
103
|
+
def __init__(
|
104
|
+
self,
|
105
|
+
key: str,
|
106
|
+
value: Union[float, int, str, SummaryStat],
|
107
|
+
dimensions: Optional[Dict[str, str]] = None,
|
108
|
+
metric_type: MetricType = MetricType.GAUGE,
|
109
|
+
timestamp: Optional[datetime] = None,
|
110
|
+
client_facing: bool = False,
|
111
|
+
):
|
112
|
+
key = create_sfm_metric_key(key, client_facing)
|
113
|
+
super().__init__(key, value, dimensions, metric_type, timestamp)
|
114
|
+
|
115
|
+
|
116
|
+
def create_sfm_metric_key(key: str, client_facing: bool = False) -> str:
|
117
|
+
namespace = CLIENT_FACING_SFM_NAMESPACE if client_facing else INTERNAL_SFM_NAMESPACE
|
118
|
+
return f"{namespace}:datasource.python.{key}"
|