pylemetry 1.1.3__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.
Files changed (41) hide show
  1. {pylemetry-1.1.3 → pylemetry-1.2.0}/PKG-INFO +63 -13
  2. {pylemetry-1.1.3 → pylemetry-1.2.0}/README.md +61 -12
  3. {pylemetry-1.1.3 → pylemetry-1.2.0}/pyproject.toml +4 -2
  4. pylemetry-1.2.0/src/pylemetry/decorators/count.py +80 -0
  5. pylemetry-1.2.0/src/pylemetry/decorators/time.py +83 -0
  6. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/meters/counter.py +4 -2
  7. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/meters/gauge.py +4 -2
  8. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/meters/meter.py +13 -1
  9. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/meters/timer.py +14 -6
  10. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/registry.py +39 -24
  11. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/reporting/logging.py +11 -5
  12. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/reporting/reporter.py +18 -7
  13. pylemetry-1.2.0/src/pylemetry/utils/__init__.py +4 -0
  14. pylemetry-1.2.0/src/pylemetry/utils/timer_units.py +22 -0
  15. pylemetry-1.2.0/test/test_pylemetry/decorators/test_count.py +136 -0
  16. pylemetry-1.2.0/test/test_pylemetry/decorators/test_time.py +145 -0
  17. {pylemetry-1.1.3 → pylemetry-1.2.0}/test/test_pylemetry/meters/test_counter.py +5 -5
  18. {pylemetry-1.1.3 → pylemetry-1.2.0}/test/test_pylemetry/meters/test_gauge.py +7 -7
  19. {pylemetry-1.1.3 → pylemetry-1.2.0}/test/test_pylemetry/meters/test_timer.py +19 -7
  20. {pylemetry-1.1.3 → pylemetry-1.2.0}/test/test_pylemetry/reporting/test_logging.py +24 -24
  21. {pylemetry-1.1.3 → pylemetry-1.2.0}/test/test_pylemetry/reporting/test_reporter.py +45 -22
  22. {pylemetry-1.1.3 → pylemetry-1.2.0}/test/test_pylemetry/test_registry.py +39 -39
  23. pylemetry-1.2.0/test/test_pylemetry/utils/test_timer_units.py +20 -0
  24. {pylemetry-1.1.3 → pylemetry-1.2.0}/uv.lock +5 -1
  25. pylemetry-1.1.3/src/pylemetry/decorators/count.py +0 -39
  26. pylemetry-1.1.3/src/pylemetry/decorators/time.py +0 -38
  27. pylemetry-1.1.3/test/test_pylemetry/decorators/test_count.py +0 -48
  28. pylemetry-1.1.3/test/test_pylemetry/decorators/test_time.py +0 -51
  29. {pylemetry-1.1.3 → pylemetry-1.2.0}/.github/actions/configure-uv/action.yaml +0 -0
  30. {pylemetry-1.1.3 → pylemetry-1.2.0}/.github/workflows/ci.yaml +0 -0
  31. {pylemetry-1.1.3 → pylemetry-1.2.0}/.github/workflows/release.yaml +0 -0
  32. {pylemetry-1.1.3 → pylemetry-1.2.0}/.gitignore +0 -0
  33. {pylemetry-1.1.3 → pylemetry-1.2.0}/LICENSE +0 -0
  34. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/__init__.py +0 -0
  35. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/__init__.py +0 -0
  36. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/decorators/__init__.py +0 -0
  37. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/meters/__init__.py +0 -0
  38. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/py.typed +0 -0
  39. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/reporting/__init__.py +0 -0
  40. {pylemetry-1.1.3 → pylemetry-1.2.0}/src/pylemetry/reporting/reporting_type.py +0 -0
  41. {pylemetry-1.1.3 → 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.1.3
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
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
20
  Requires-Python: >=3.10
21
+ Requires-Dist: typing-extensions>=4.15.0
21
22
  Description-Content-Type: text/markdown
22
23
 
23
24
  # Pylemetry
@@ -43,7 +44,7 @@ from pylemetry.meters import Counter
43
44
 
44
45
 
45
46
  def some_method() -> None:
46
- counter = Counter()
47
+ counter = Counter("example")
47
48
 
48
49
  for _ in range(100):
49
50
  counter.add() # counter += 1 is also supported
@@ -92,9 +93,9 @@ from pylemetry.meters import Gauge
92
93
 
93
94
 
94
95
  def some_method() -> None:
95
- gauge = Gauge()
96
+ gauge = Gauge("sample_gauge")
96
97
 
97
- registry.add_gauge("sample_gauge", gauge)
98
+ registry.add_gauge(gauge)
98
99
  ```
99
100
 
100
101
  The `Gauge` supports incrementing, decrementing, and setting a value directly
@@ -126,7 +127,7 @@ from pylemetry.meters import Timer
126
127
 
127
128
 
128
129
  def some_method() -> None:
129
- timer = Timer()
130
+ timer = Timer("example")
130
131
 
131
132
  for _ in range(100):
132
133
  with timer.time():
@@ -172,6 +173,54 @@ def main() -> None:
172
173
 
173
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
174
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
+
175
224
  ## The Registry
176
225
 
177
226
  Pylemetry maintains a global registry of meters, allowing you to share a meter across multiple files, or reference metrics from a central location.
@@ -182,13 +231,13 @@ from pylemetry import registry
182
231
  from pylemetry.meters import Counter, Gauge, Timer
183
232
 
184
233
 
185
- counter = Counter()
186
- gauge = Gauge()
187
- timer = Timer()
234
+ counter = Counter("example")
235
+ gauge = Gauge("example")
236
+ timer = Timer("example")
188
237
 
189
- registry.add_counter("example", counter)
190
- registry.add_gauge("example", gauge)
191
- registry.add_timer("example", timer)
238
+ registry.add_counter(counter)
239
+ registry.add_gauge(gauge)
240
+ registry.add_timer(timer)
192
241
  ```
193
242
 
194
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.
@@ -199,9 +248,9 @@ from pylemetry import registry
199
248
  from pylemetry.meters import Counter, MeterType
200
249
 
201
250
 
202
- counter = Counter()
251
+ counter = Counter("example")
203
252
 
204
- registry.add_meter("example", counter, MeterType.COUNTER)
253
+ registry.add_meter(counter, MeterType.COUNTER)
205
254
  registry.get_meter("example", MeterType.COUNTER)
206
255
  registry.remove_meter("example", MeterType.COUNTER)
207
256
  registry.get_meter("example", MeterType.COUNTER) # None
@@ -225,6 +274,7 @@ The message format allows for substitutions for metric values with the following
225
274
  | max | Maximum value of the meter, equivalent to the `value` substitution for `Counter` and `Gauge` meters, `max_tick_time` for `Timer` meters |
226
275
  | avg | Mean average value of the meter, equivalent to the `value` substitution for `Counter` and `Gauge` meters, `mean_tick_time` for `Timer` meters |
227
276
  | type | Type of the meter (`counter`, `gauge`, or `timer`) |
277
+ | tags | The tags associated with the meter |
228
278
 
229
279
  As an example, a `Counter` meter named `sample_counter` with a value of 10
230
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("sample_gauge", 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("example", counter)
168
- registry.add_gauge("example", gauge)
169
- registry.add_timer("example", 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("example", counter, MeterType.COUNTER)
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pylemetry"
3
- version = "1.1.3"
3
+ version = "1.2.0"
4
4
  description = "A Python metrics library"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -25,7 +25,9 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.14",
26
26
  ]
27
27
 
28
- dependencies = []
28
+ dependencies = [
29
+ "typing-extensions>=4.15.0",
30
+ ]
29
31
 
30
32
  [project.urls]
31
33
  Homepage = "https://pypi.org/project/pylemetry/"
@@ -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__(self, meter_type: MeterType) -> None:
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__(self) -> None:
12
- super().__init__(MeterType.TIMER)
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.perf_counter()
40
+ start_time = time.perf_counter_ns()
33
41
 
34
42
  try:
35
43
  yield
36
44
  finally:
37
- end_time = time.perf_counter()
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
  """