pylemetry 1.1.4__tar.gz → 1.2.0__tar.gz
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.
- {pylemetry-1.1.4 → pylemetry-1.2.0}/PKG-INFO +62 -13
- {pylemetry-1.1.4 → pylemetry-1.2.0}/README.md +61 -12
- {pylemetry-1.1.4 → pylemetry-1.2.0}/pyproject.toml +1 -1
- pylemetry-1.2.0/src/pylemetry/decorators/count.py +80 -0
- pylemetry-1.2.0/src/pylemetry/decorators/time.py +83 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/meters/counter.py +4 -2
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/meters/gauge.py +4 -2
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/meters/meter.py +13 -1
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/meters/timer.py +14 -6
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/registry.py +39 -24
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/reporting/logging.py +11 -5
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/reporting/reporter.py +18 -7
- pylemetry-1.2.0/src/pylemetry/utils/__init__.py +4 -0
- pylemetry-1.2.0/src/pylemetry/utils/timer_units.py +22 -0
- pylemetry-1.2.0/test/test_pylemetry/decorators/test_count.py +136 -0
- pylemetry-1.2.0/test/test_pylemetry/decorators/test_time.py +145 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/test_pylemetry/meters/test_counter.py +5 -5
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/test_pylemetry/meters/test_gauge.py +7 -7
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/test_pylemetry/meters/test_timer.py +19 -7
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/test_pylemetry/reporting/test_logging.py +24 -24
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/test_pylemetry/reporting/test_reporter.py +45 -22
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/test_pylemetry/test_registry.py +39 -39
- pylemetry-1.2.0/test/test_pylemetry/utils/test_timer_units.py +20 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/uv.lock +1 -1
- pylemetry-1.1.4/src/pylemetry/decorators/count.py +0 -39
- pylemetry-1.1.4/src/pylemetry/decorators/time.py +0 -38
- pylemetry-1.1.4/test/test_pylemetry/decorators/test_count.py +0 -48
- pylemetry-1.1.4/test/test_pylemetry/decorators/test_time.py +0 -51
- {pylemetry-1.1.4 → pylemetry-1.2.0}/.github/actions/configure-uv/action.yaml +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/.github/workflows/ci.yaml +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/.github/workflows/release.yaml +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/.gitignore +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/LICENSE +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/__init__.py +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/__init__.py +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/decorators/__init__.py +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/meters/__init__.py +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/py.typed +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/reporting/__init__.py +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/src/pylemetry/reporting/reporting_type.py +0 -0
- {pylemetry-1.1.4 → pylemetry-1.2.0}/test/conftest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pylemetry
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A Python metrics library
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/pylemetry/
|
|
6
6
|
Project-URL: Repository, https://github.com/amurphy4/pylemetry
|
|
@@ -44,7 +44,7 @@ from pylemetry.meters import Counter
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def some_method() -> None:
|
|
47
|
-
counter = Counter()
|
|
47
|
+
counter = Counter("example")
|
|
48
48
|
|
|
49
49
|
for _ in range(100):
|
|
50
50
|
counter.add() # counter += 1 is also supported
|
|
@@ -93,9 +93,9 @@ from pylemetry.meters import Gauge
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
def some_method() -> None:
|
|
96
|
-
gauge = Gauge()
|
|
96
|
+
gauge = Gauge("sample_gauge")
|
|
97
97
|
|
|
98
|
-
registry.add_gauge(
|
|
98
|
+
registry.add_gauge(gauge)
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The `Gauge` supports incrementing, decrementing, and setting a value directly
|
|
@@ -127,7 +127,7 @@ from pylemetry.meters import Timer
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def some_method() -> None:
|
|
130
|
-
timer = Timer()
|
|
130
|
+
timer = Timer("example")
|
|
131
131
|
|
|
132
132
|
for _ in range(100):
|
|
133
133
|
with timer.time():
|
|
@@ -173,6 +173,54 @@ def main() -> None:
|
|
|
173
173
|
|
|
174
174
|
When using this meter via a decorator, the meter gets added to the global `registry`, with the method name it's decorating as the meter name. Alternatively, you can provide a name for the meter as a parameter to the decorator
|
|
175
175
|
|
|
176
|
+
By default, timer meters will measure time in nanoseconds, this can be changed via the `unit` parameter using the `TimerUnits` enum in the utils module
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import time
|
|
180
|
+
|
|
181
|
+
from pylemetry.meters import Timer
|
|
182
|
+
from pylemetry.utils import TimerUnits
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
timer_s = Timer("example_s", TimerUnits.SECONDS)
|
|
186
|
+
timer_ms = Timer("example_ms", TimerUnits.MILLISECONDS)
|
|
187
|
+
|
|
188
|
+
with timer_s.time():
|
|
189
|
+
time.sleep(1)
|
|
190
|
+
|
|
191
|
+
with timer_ms.time():
|
|
192
|
+
time.sleep(1)
|
|
193
|
+
|
|
194
|
+
timer_s.get_mean_tick_time() # 1
|
|
195
|
+
timer_ms.get_mean_tick_time() # 1000
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Tags
|
|
199
|
+
|
|
200
|
+
When creating a meter you can assign a set of tags to it as key-value pairs. The value must be one of either `str`, `int`, or `float`.
|
|
201
|
+
When using a decorator to create a meter, you can use a custom format for the value to extract values out of the method's args and kwargs
|
|
202
|
+
|
|
203
|
+
In order to allow for multiple meters with the same name and different tags, the name in the registry gets mangled with the tags to produce a unique name,
|
|
204
|
+
as a result when trying to get the meter from the registry you will need to provide both its name and its tags.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from pylemetry import registry
|
|
208
|
+
from pylemetry.decorators import time
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@time("example_timer", tags={"tag_1": "args[0]", "tag_2": "kwargs[param_2]", "tag_3": "some value"})
|
|
212
|
+
def some_method(param_1: int, param_2: int) -> None:
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def main() -> None:
|
|
217
|
+
for _ in range(100):
|
|
218
|
+
some_method(1, param_2=2)
|
|
219
|
+
|
|
220
|
+
timer = registry.get_timer("example_timer", {"tag_1": 1, "tag_2": 2, "tag_3": "some value"})
|
|
221
|
+
timer.get_tags() # {"tag_1": 1, "tag_2": 2, "tag_3": "some value"}
|
|
222
|
+
```
|
|
223
|
+
|
|
176
224
|
## The Registry
|
|
177
225
|
|
|
178
226
|
Pylemetry maintains a global registry of meters, allowing you to share a meter across multiple files, or reference metrics from a central location.
|
|
@@ -183,13 +231,13 @@ from pylemetry import registry
|
|
|
183
231
|
from pylemetry.meters import Counter, Gauge, Timer
|
|
184
232
|
|
|
185
233
|
|
|
186
|
-
counter = Counter()
|
|
187
|
-
gauge = Gauge()
|
|
188
|
-
timer = Timer()
|
|
234
|
+
counter = Counter("example")
|
|
235
|
+
gauge = Gauge("example")
|
|
236
|
+
timer = Timer("example")
|
|
189
237
|
|
|
190
|
-
registry.add_counter(
|
|
191
|
-
registry.add_gauge(
|
|
192
|
-
registry.add_timer(
|
|
238
|
+
registry.add_counter(counter)
|
|
239
|
+
registry.add_gauge(gauge)
|
|
240
|
+
registry.add_timer(timer)
|
|
193
241
|
```
|
|
194
242
|
|
|
195
243
|
Each meter type has an `add_meter`, `get_meter` and `remove_meter` method to manage meters in the `registry`, each requiring a unique meter name.
|
|
@@ -200,9 +248,9 @@ from pylemetry import registry
|
|
|
200
248
|
from pylemetry.meters import Counter, MeterType
|
|
201
249
|
|
|
202
250
|
|
|
203
|
-
counter = Counter()
|
|
251
|
+
counter = Counter("example")
|
|
204
252
|
|
|
205
|
-
registry.add_meter(
|
|
253
|
+
registry.add_meter(counter, MeterType.COUNTER)
|
|
206
254
|
registry.get_meter("example", MeterType.COUNTER)
|
|
207
255
|
registry.remove_meter("example", MeterType.COUNTER)
|
|
208
256
|
registry.get_meter("example", MeterType.COUNTER) # None
|
|
@@ -226,6 +274,7 @@ The message format allows for substitutions for metric values with the following
|
|
|
226
274
|
| max | Maximum value of the meter, equivalent to the `value` substitution for `Counter` and `Gauge` meters, `max_tick_time` for `Timer` meters |
|
|
227
275
|
| avg | Mean average value of the meter, equivalent to the `value` substitution for `Counter` and `Gauge` meters, `mean_tick_time` for `Timer` meters |
|
|
228
276
|
| type | Type of the meter (`counter`, `gauge`, or `timer`) |
|
|
277
|
+
| tags | The tags associated with the meter |
|
|
229
278
|
|
|
230
279
|
As an example, a `Counter` meter named `sample_counter` with a value of 10
|
|
231
280
|
```python
|
|
@@ -21,7 +21,7 @@ from pylemetry.meters import Counter
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def some_method() -> None:
|
|
24
|
-
counter = Counter()
|
|
24
|
+
counter = Counter("example")
|
|
25
25
|
|
|
26
26
|
for _ in range(100):
|
|
27
27
|
counter.add() # counter += 1 is also supported
|
|
@@ -70,9 +70,9 @@ from pylemetry.meters import Gauge
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def some_method() -> None:
|
|
73
|
-
gauge = Gauge()
|
|
73
|
+
gauge = Gauge("sample_gauge")
|
|
74
74
|
|
|
75
|
-
registry.add_gauge(
|
|
75
|
+
registry.add_gauge(gauge)
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
The `Gauge` supports incrementing, decrementing, and setting a value directly
|
|
@@ -104,7 +104,7 @@ from pylemetry.meters import Timer
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
def some_method() -> None:
|
|
107
|
-
timer = Timer()
|
|
107
|
+
timer = Timer("example")
|
|
108
108
|
|
|
109
109
|
for _ in range(100):
|
|
110
110
|
with timer.time():
|
|
@@ -150,6 +150,54 @@ def main() -> None:
|
|
|
150
150
|
|
|
151
151
|
When using this meter via a decorator, the meter gets added to the global `registry`, with the method name it's decorating as the meter name. Alternatively, you can provide a name for the meter as a parameter to the decorator
|
|
152
152
|
|
|
153
|
+
By default, timer meters will measure time in nanoseconds, this can be changed via the `unit` parameter using the `TimerUnits` enum in the utils module
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import time
|
|
157
|
+
|
|
158
|
+
from pylemetry.meters import Timer
|
|
159
|
+
from pylemetry.utils import TimerUnits
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
timer_s = Timer("example_s", TimerUnits.SECONDS)
|
|
163
|
+
timer_ms = Timer("example_ms", TimerUnits.MILLISECONDS)
|
|
164
|
+
|
|
165
|
+
with timer_s.time():
|
|
166
|
+
time.sleep(1)
|
|
167
|
+
|
|
168
|
+
with timer_ms.time():
|
|
169
|
+
time.sleep(1)
|
|
170
|
+
|
|
171
|
+
timer_s.get_mean_tick_time() # 1
|
|
172
|
+
timer_ms.get_mean_tick_time() # 1000
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Tags
|
|
176
|
+
|
|
177
|
+
When creating a meter you can assign a set of tags to it as key-value pairs. The value must be one of either `str`, `int`, or `float`.
|
|
178
|
+
When using a decorator to create a meter, you can use a custom format for the value to extract values out of the method's args and kwargs
|
|
179
|
+
|
|
180
|
+
In order to allow for multiple meters with the same name and different tags, the name in the registry gets mangled with the tags to produce a unique name,
|
|
181
|
+
as a result when trying to get the meter from the registry you will need to provide both its name and its tags.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from pylemetry import registry
|
|
185
|
+
from pylemetry.decorators import time
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@time("example_timer", tags={"tag_1": "args[0]", "tag_2": "kwargs[param_2]", "tag_3": "some value"})
|
|
189
|
+
def some_method(param_1: int, param_2: int) -> None:
|
|
190
|
+
...
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main() -> None:
|
|
194
|
+
for _ in range(100):
|
|
195
|
+
some_method(1, param_2=2)
|
|
196
|
+
|
|
197
|
+
timer = registry.get_timer("example_timer", {"tag_1": 1, "tag_2": 2, "tag_3": "some value"})
|
|
198
|
+
timer.get_tags() # {"tag_1": 1, "tag_2": 2, "tag_3": "some value"}
|
|
199
|
+
```
|
|
200
|
+
|
|
153
201
|
## The Registry
|
|
154
202
|
|
|
155
203
|
Pylemetry maintains a global registry of meters, allowing you to share a meter across multiple files, or reference metrics from a central location.
|
|
@@ -160,13 +208,13 @@ from pylemetry import registry
|
|
|
160
208
|
from pylemetry.meters import Counter, Gauge, Timer
|
|
161
209
|
|
|
162
210
|
|
|
163
|
-
counter = Counter()
|
|
164
|
-
gauge = Gauge()
|
|
165
|
-
timer = Timer()
|
|
211
|
+
counter = Counter("example")
|
|
212
|
+
gauge = Gauge("example")
|
|
213
|
+
timer = Timer("example")
|
|
166
214
|
|
|
167
|
-
registry.add_counter(
|
|
168
|
-
registry.add_gauge(
|
|
169
|
-
registry.add_timer(
|
|
215
|
+
registry.add_counter(counter)
|
|
216
|
+
registry.add_gauge(gauge)
|
|
217
|
+
registry.add_timer(timer)
|
|
170
218
|
```
|
|
171
219
|
|
|
172
220
|
Each meter type has an `add_meter`, `get_meter` and `remove_meter` method to manage meters in the `registry`, each requiring a unique meter name.
|
|
@@ -177,9 +225,9 @@ from pylemetry import registry
|
|
|
177
225
|
from pylemetry.meters import Counter, MeterType
|
|
178
226
|
|
|
179
227
|
|
|
180
|
-
counter = Counter()
|
|
228
|
+
counter = Counter("example")
|
|
181
229
|
|
|
182
|
-
registry.add_meter(
|
|
230
|
+
registry.add_meter(counter, MeterType.COUNTER)
|
|
183
231
|
registry.get_meter("example", MeterType.COUNTER)
|
|
184
232
|
registry.remove_meter("example", MeterType.COUNTER)
|
|
185
233
|
registry.get_meter("example", MeterType.COUNTER) # None
|
|
@@ -203,6 +251,7 @@ The message format allows for substitutions for metric values with the following
|
|
|
203
251
|
| max | Maximum value of the meter, equivalent to the `value` substitution for `Counter` and `Gauge` meters, `max_tick_time` for `Timer` meters |
|
|
204
252
|
| avg | Mean average value of the meter, equivalent to the `value` substitution for `Counter` and `Gauge` meters, `mean_tick_time` for `Timer` meters |
|
|
205
253
|
| type | Type of the meter (`counter`, `gauge`, or `timer`) |
|
|
254
|
+
| tags | The tags associated with the meter |
|
|
206
255
|
|
|
207
256
|
As an example, a `Counter` meter named `sample_counter` with a value of 10
|
|
208
257
|
```python
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Optional, ParamSpec, TypeVar, Union
|
|
4
|
+
|
|
5
|
+
from functools import wraps
|
|
6
|
+
|
|
7
|
+
from pylemetry import registry
|
|
8
|
+
from pylemetry.meters import Counter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
P = ParamSpec("P")
|
|
12
|
+
R = TypeVar("R", covariant=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def count(
|
|
16
|
+
name: Optional[str] = None, tags: Optional[dict[str, Union[str, int, float]]] = None
|
|
17
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
18
|
+
"""
|
|
19
|
+
Decorator to count the number of invocations of a given callable. Creates a Counter meter in the Registry
|
|
20
|
+
with either the provided name or the fully qualified name of the callable object as the metric name.
|
|
21
|
+
|
|
22
|
+
:param name: Name of the meter to create, if None the function name is used
|
|
23
|
+
:param tags: Optional key-value pairs of tags for this meter
|
|
24
|
+
:return: Result of the wrapped function
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def decorator(f: Callable[P, R]) -> Callable[P, R]:
|
|
28
|
+
@wraps(f)
|
|
29
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
30
|
+
counter_name = f.__qualname__ if name is None else name
|
|
31
|
+
|
|
32
|
+
_tags: dict[str, Union[str, int, float]] = {}
|
|
33
|
+
|
|
34
|
+
if tags:
|
|
35
|
+
for key, value in tags.items():
|
|
36
|
+
args_match = re.search(r"^args\[[0-9]+]", str(value))
|
|
37
|
+
kwargs_match = re.search(r"^kwargs\[[a-zA-Z0-9_]+]", str(value))
|
|
38
|
+
|
|
39
|
+
if args_match:
|
|
40
|
+
args_index = int(args_match.group().replace("args[", "").replace("]", ""))
|
|
41
|
+
|
|
42
|
+
if args_index >= len(args):
|
|
43
|
+
raise IndexError(f"args index {args_index} out of range, args has length {len(args)}")
|
|
44
|
+
|
|
45
|
+
if not isinstance(args[args_index], (str, int, float)):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Only args of types (str | int | float) may be added as tags. "
|
|
48
|
+
f"Got {args[args_index].__class__}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
_tags[key] = args[args_index] # type: ignore
|
|
52
|
+
elif kwargs_match:
|
|
53
|
+
kwargs_key = kwargs_match.group().replace("kwargs[", "").replace("]", "")
|
|
54
|
+
|
|
55
|
+
if kwargs_key not in kwargs:
|
|
56
|
+
raise KeyError(f"Key '{kwargs_key}' not present in kwargs")
|
|
57
|
+
|
|
58
|
+
if not isinstance(kwargs[kwargs_key], (str, int, float)):
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Only kwargs of types (str | int | float) may be added as tags. "
|
|
61
|
+
f"Got {kwargs[kwargs_key].__class__}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_tags[key] = kwargs[kwargs_key] # type: ignore
|
|
65
|
+
else:
|
|
66
|
+
_tags[key] = value
|
|
67
|
+
|
|
68
|
+
counter = registry.get_counter(counter_name, tags=_tags)
|
|
69
|
+
|
|
70
|
+
if not counter:
|
|
71
|
+
counter = Counter(name=counter_name, tags=_tags)
|
|
72
|
+
registry.add_counter(counter)
|
|
73
|
+
|
|
74
|
+
counter += 1
|
|
75
|
+
|
|
76
|
+
return f(*args, **kwargs)
|
|
77
|
+
|
|
78
|
+
return wrapper
|
|
79
|
+
|
|
80
|
+
return decorator
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Optional, ParamSpec, TypeVar, Union
|
|
4
|
+
|
|
5
|
+
from functools import wraps
|
|
6
|
+
|
|
7
|
+
from pylemetry import registry
|
|
8
|
+
from pylemetry.meters import Timer
|
|
9
|
+
from pylemetry.utils import TimerUnits
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
P = ParamSpec("P")
|
|
13
|
+
R = TypeVar("R", covariant=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def time(
|
|
17
|
+
name: Optional[str] = None,
|
|
18
|
+
unit: TimerUnits = TimerUnits.NANOSECONDS,
|
|
19
|
+
tags: Optional[dict[str, Union[str, int, float]]] = None,
|
|
20
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
21
|
+
"""
|
|
22
|
+
Decorator to time the invocations of a given callable. Creates a Timer meter in the Registry with either the
|
|
23
|
+
provided name or the fully qualified name of the callable object as the metric name.
|
|
24
|
+
|
|
25
|
+
:param name: Name of the meter to create, if None the function name is used
|
|
26
|
+
:param unit: Unit of time to measure in
|
|
27
|
+
:param tags: Optional key-value pairs of tags for this meter
|
|
28
|
+
:return: Result of the wrapped function
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def decorator(f: Callable[P, R]) -> Callable[P, R]:
|
|
32
|
+
@wraps(f)
|
|
33
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
34
|
+
time_name = f.__qualname__ if name is None else name
|
|
35
|
+
|
|
36
|
+
_tags: dict[str, Union[str, int, float]] = {}
|
|
37
|
+
|
|
38
|
+
if tags:
|
|
39
|
+
for key, value in tags.items():
|
|
40
|
+
args_match = re.search(r"^args\[[0-9]+]", str(value))
|
|
41
|
+
kwargs_match = re.search(r"^kwargs\[[a-zA-Z0-9_]+]", str(value))
|
|
42
|
+
|
|
43
|
+
if args_match:
|
|
44
|
+
args_index = int(args_match.group().replace("args[", "").replace("]", ""))
|
|
45
|
+
|
|
46
|
+
if args_index >= len(args):
|
|
47
|
+
raise IndexError(f"args index {args_index} out of range, args has length {len(args)}")
|
|
48
|
+
|
|
49
|
+
if not isinstance(args[args_index], (str, int, float)):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Only args of types (str | int | float) may be added as tags. "
|
|
52
|
+
f"Got {args[args_index].__class__}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_tags[key] = args[args_index] # type: ignore
|
|
56
|
+
elif kwargs_match:
|
|
57
|
+
kwargs_key = kwargs_match.group().replace("kwargs[", "").replace("]", "")
|
|
58
|
+
|
|
59
|
+
if kwargs_key not in kwargs:
|
|
60
|
+
raise KeyError(f"Key '{kwargs_key}' not present in kwargs")
|
|
61
|
+
|
|
62
|
+
if not isinstance(kwargs[kwargs_key], (str, int, float)):
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Only kwargs of types (str | int | float) may be added as tags. "
|
|
65
|
+
f"Got {kwargs[kwargs_key].__class__}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_tags[key] = kwargs[kwargs_key] # type: ignore
|
|
69
|
+
else:
|
|
70
|
+
_tags[key] = value
|
|
71
|
+
|
|
72
|
+
_timer = registry.get_timer(time_name, tags=_tags)
|
|
73
|
+
|
|
74
|
+
if not _timer:
|
|
75
|
+
_timer = Timer(time_name, unit=unit, tags=_tags)
|
|
76
|
+
registry.add_timer(_timer)
|
|
77
|
+
|
|
78
|
+
with _timer.time():
|
|
79
|
+
return f(*args, **kwargs)
|
|
80
|
+
|
|
81
|
+
return wrapper
|
|
82
|
+
|
|
83
|
+
return decorator
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from typing import Union, Optional
|
|
2
|
+
|
|
1
3
|
from pylemetry.meters.meter import Meter, MeterType
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class Counter(Meter):
|
|
5
|
-
def __init__(self) -> None:
|
|
6
|
-
super().__init__(MeterType.COUNTER)
|
|
7
|
+
def __init__(self, name: str, tags: Optional[dict[str, Union[str, int, float]]] = None) -> None:
|
|
8
|
+
super().__init__(MeterType.COUNTER, name, tags)
|
|
7
9
|
|
|
8
10
|
def add(self, value: int = 1) -> None:
|
|
9
11
|
"""
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from typing import Union, Optional
|
|
2
|
+
|
|
1
3
|
from pylemetry.meters.meter import Meter, MeterType
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class Gauge(Meter):
|
|
5
|
-
def __init__(self) -> None:
|
|
6
|
-
super().__init__(MeterType.GAUGE)
|
|
7
|
+
def __init__(self, name: str, tags: Optional[dict[str, Union[str, int, float]]] = None) -> None:
|
|
8
|
+
super().__init__(MeterType.GAUGE, name, tags)
|
|
7
9
|
|
|
8
10
|
def set_value(self, value: float):
|
|
9
11
|
"""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from threading import Lock
|
|
3
|
+
from typing import Union, Optional
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class MeterType(Enum):
|
|
@@ -9,11 +10,19 @@ class MeterType(Enum):
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class Meter:
|
|
12
|
-
def __init__(
|
|
13
|
+
def __init__(
|
|
14
|
+
self, meter_type: MeterType, name: str, tags: Optional[dict[str, Union[str, int, float]]] = None
|
|
15
|
+
) -> None:
|
|
13
16
|
self.lock = Lock()
|
|
14
17
|
self.value = 0.0
|
|
15
18
|
self.last_interval_value = 0.0
|
|
16
19
|
self.meter_type = meter_type
|
|
20
|
+
self.name = name
|
|
21
|
+
|
|
22
|
+
if not tags:
|
|
23
|
+
tags = {}
|
|
24
|
+
|
|
25
|
+
self.__tags = tags
|
|
17
26
|
|
|
18
27
|
def get_value(self, since_last_interval: bool = False) -> float:
|
|
19
28
|
"""
|
|
@@ -36,3 +45,6 @@ class Meter:
|
|
|
36
45
|
|
|
37
46
|
with self.lock:
|
|
38
47
|
self.last_interval_value = self.value
|
|
48
|
+
|
|
49
|
+
def get_tags(self) -> dict[str, Union[str, int, float]]:
|
|
50
|
+
return self.__tags
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
from typing import Generator
|
|
1
|
+
from typing import Generator, Union, Optional
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
|
|
7
7
|
from pylemetry.meters.meter import Meter, MeterType
|
|
8
|
+
from pylemetry.utils import TimerUnits
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Timer(Meter):
|
|
11
|
-
def __init__(
|
|
12
|
-
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
name: str,
|
|
15
|
+
unit: TimerUnits = TimerUnits.NANOSECONDS,
|
|
16
|
+
tags: Optional[dict[str, Union[str, int, float]]] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(MeterType.TIMER, name, tags)
|
|
19
|
+
|
|
20
|
+
self.unit = unit
|
|
13
21
|
|
|
14
22
|
self.ticks: list[float] = []
|
|
15
23
|
|
|
@@ -29,14 +37,14 @@ class Timer(Meter):
|
|
|
29
37
|
Context manager to time in seconds a code block and add the result to the internal ticks list
|
|
30
38
|
"""
|
|
31
39
|
|
|
32
|
-
start_time = time.
|
|
40
|
+
start_time = time.perf_counter_ns()
|
|
33
41
|
|
|
34
42
|
try:
|
|
35
43
|
yield
|
|
36
44
|
finally:
|
|
37
|
-
end_time = time.
|
|
45
|
+
end_time = time.perf_counter_ns()
|
|
38
46
|
|
|
39
|
-
self.tick(end_time - start_time)
|
|
47
|
+
self.tick((end_time - start_time) / self.unit.conversion_from_ns())
|
|
40
48
|
|
|
41
49
|
def get_value(self, since_last_interval: bool = False) -> int:
|
|
42
50
|
"""
|