@@ -22,7 +22,7 @@ class MetricRegistry:
22
22
Provides methods for retrieving and collecting metrics.
23
23
"""
24
24
25
- _instance = None
25
+ _instance : "MetricRegistry" = None
26
26
_mutex : threading .Lock = threading .Lock ()
27
27
28
28
def __new__ (cls ):
@@ -107,34 +107,22 @@ def collect(self) -> List[Dict[str, Union[str, int]]]:
107
107
pass
108
108
109
109
110
- class _SimpleCounter ( Metric ) :
110
+ class _Counter :
111
111
"""
112
- A thread-safe counter for tracking occurrences of an event without labels .
112
+ A thread-safe counter for any kind of tracking .
113
113
"""
114
114
115
115
_mutex : threading .Lock
116
- _namespace : Optional [str ]
117
- _name : str
118
- _type : str
119
116
_count : int
120
117
121
- @property
122
- def mutex (self ) -> threading .Lock :
123
- """
124
- Provides thread-safe access to the internal lock.
125
- """
126
- return self ._mutex
127
-
128
- def __init__ (self , name : str , namespace : Optional [str ] = "" ):
129
- if not name :
130
- raise ValueError ("Name is required and cannot be empty." )
131
-
118
+ def __init__ (self ):
119
+ super (_Counter , self ).__init__ ()
132
120
self ._mutex = threading .Lock ()
133
- self ._name = name .strip ()
134
- self ._namespace = namespace .strip () if namespace else ""
135
- self ._type = "counter"
136
121
self ._count = 0
137
- MetricRegistry ().register (self )
122
+
123
+ @property
124
+ def count (self ) -> int :
125
+ return self ._count
138
126
139
127
def increment (self , value : int = 1 ) -> None :
140
128
"""Increments the counter unless events are disabled."""
@@ -155,38 +143,56 @@ def reset(self) -> None:
155
143
with self ._mutex :
156
144
self ._count = 0
157
145
146
+
147
+ class _CounterMetric (Metric , _Counter ):
148
+ """
149
+ A thread-safe counter for tracking occurrences of an event without labels.
150
+ """
151
+
152
+ _namespace : Optional [str ]
153
+ _name : str
154
+ _type : str
155
+
156
+ def __init__ (self , name : str , namespace : Optional [str ] = "" ):
157
+ super (_CounterMetric , self ).__init__ ()
158
+ if not name :
159
+ raise ValueError ("Name is required and cannot be empty." )
160
+
161
+ self ._name = name .strip ()
162
+ self ._namespace = namespace .strip () if namespace else ""
163
+ self ._type = "counter"
164
+ MetricRegistry ().register (self )
165
+
158
166
def collect (self ) -> List [Dict [str , Union [str , int ]]]:
159
167
"""Collects the metric unless events are disabled."""
160
168
if config .DISABLE_EVENTS :
161
169
return list ()
162
170
163
- with self ._mutex :
164
- if self ._count == 0 :
165
- # Return an empty list if the count is 0, as there are no metrics to send to the analytics backend.
166
- return list ()
167
- return [
168
- {
169
- "namespace" : self ._namespace ,
170
- "name" : self ._name ,
171
- "value" : self ._count ,
172
- "type" : self ._type ,
173
- }
174
- ]
171
+ if self ._count == 0 :
172
+ # Return an empty list if the count is 0, as there are no metrics to send to the analytics backend.
173
+ return list ()
174
+ return [
175
+ {
176
+ "namespace" : self ._namespace ,
177
+ "name" : self ._name ,
178
+ "value" : self ._count ,
179
+ "type" : self ._type ,
180
+ }
181
+ ]
175
182
176
183
177
- class _LabeledCounter (Metric ):
184
+ class _LabeledCounterMetric (Metric ):
178
185
"""
179
186
A labeled counter that tracks occurrences of an event across different label combinations.
180
187
"""
181
188
182
- _mutex : threading .Lock
183
189
_namespace : Optional [str ]
184
190
_name : str
185
191
_type : str
186
192
_unit : str
187
- _labels : List [str ]
193
+ _labels : list [str ]
188
194
_label_values : tuple [Optional [str ], ...]
189
- _count_by_labels : defaultdict [Tuple [str , ...], int ]
195
+ _counters_by_label_values : defaultdict [Tuple [str , ...], _Counter ]
190
196
191
197
def __init__ (self , name : str , labels : List [str ] = list , namespace : Optional [str ] = "" ):
192
198
if not name :
@@ -198,32 +204,19 @@ def __init__(self, name: str, labels: List[str] = list, namespace: Optional[str]
198
204
if len (labels ) > 8 :
199
205
raise ValueError ("A maximum of 8 labels are allowed." )
200
206
201
- self ._mutex = threading .Lock ()
202
207
self ._name = name .strip ()
203
208
self ._namespace = namespace .strip () if namespace else ""
204
209
self ._type = "counter"
205
210
self ._labels = labels
206
- self ._label_values = tuple ()
207
- self ._count_by_labels = defaultdict (int )
211
+ self ._counters_by_label_values = defaultdict (_Counter )
208
212
MetricRegistry ().register (self )
209
213
210
- @property
211
- def mutex (self ) -> threading .Lock :
212
- """
213
- Provides thread-safe access to the internal lock.
214
- """
215
- return self ._mutex
216
-
217
- @property
218
- def count_by_labels (self ) -> defaultdict [Tuple [str , ...], int ]:
219
- return self ._count_by_labels
220
-
221
- def labels (self , ** kwargs : str ) -> _LabeledCounterProxy :
214
+ def labels (self , ** kwargs : str ) -> _Counter :
222
215
"""
223
216
Create a scoped counter instance with specific label values.
224
217
225
218
This method assigns values to the predefined labels of a labeled counter and returns
226
- a proxy object (`_LabeledCounterProxy `) that allows tracking metrics for that specific
219
+ a _Counter object (``) that allows tracking metrics for that specific
227
220
combination of label values.
228
221
229
222
The proxy ensures that increments and resets are scoped to the given label values,
@@ -233,15 +226,15 @@ def labels(self, **kwargs: str) -> _LabeledCounterProxy:
233
226
- If the number of provided labels does not match the expected count.
234
227
- If any of the provided labels are empty strings.
235
228
"""
236
- self ._label_values = tuple (label_value for label_value in kwargs .values ())
229
+ if set (self ._labels ) != set (kwargs .keys ()):
230
+ raise ValueError (f"Expected labels { self ._labels } , got { list (kwargs .keys ())} " )
237
231
238
- if len (kwargs ) != len (self ._label_values ):
239
- raise ValueError (f"Expected labels { self ._label_values } , got { list (kwargs .values ())} " )
232
+ _label_values = tuple (kwargs [label ] for label in self ._labels )
240
233
241
- if any (not label for label in self . _label_values ):
234
+ if any (not label for label in _label_values ):
242
235
raise ValueError ("Label values must be non-empty strings." )
243
236
244
- return _LabeledCounterProxy ( counter = self , label_values = self . _label_values )
237
+ return self . _counters_by_label_values [ _label_values ]
245
238
246
239
def _as_list (self ) -> List [Dict [str , Union [str , int ]]]:
247
240
num_labels = len (self ._labels )
@@ -251,8 +244,8 @@ def _as_list(self) -> List[Dict[str, Union[str, int]]]:
251
244
252
245
collected_metrics = []
253
246
254
- for label_values , count in self ._count_by_labels .items ():
255
- if count == 0 :
247
+ for label_values , counter in self ._counters_by_label_values .items ():
248
+ if counter . count == 0 :
256
249
continue # Skip items with a count of 0, as they should not be sent to the analytics backend.
257
250
258
251
if len (label_values ) != num_labels :
@@ -265,7 +258,7 @@ def _as_list(self) -> List[Dict[str, Union[str, int]]]:
265
258
{
266
259
"namespace" : self ._namespace ,
267
260
"name" : self ._name ,
268
- "value" : count ,
261
+ "value" : counter . count ,
269
262
"type" : self ._type ,
270
263
** dict (zip (static_key_label_value , label_values )),
271
264
** dict (zip (static_key_label , self ._labels )),
@@ -277,36 +270,7 @@ def _as_list(self) -> List[Dict[str, Union[str, int]]]:
277
270
def collect (self ) -> List [Dict [str , Union [str , int ]]]:
278
271
if config .DISABLE_EVENTS :
279
272
return list ()
280
-
281
- with self ._mutex :
282
- return self ._as_list ()
283
-
284
-
285
- class _LabeledCounterProxy :
286
- """A proxy for a labeled counter, enforcing scoped label values."""
287
-
288
- def __init__ (self , counter : _LabeledCounter , label_values : Tuple [str , ...]):
289
- self ._counter = counter
290
- self ._label_values = label_values
291
-
292
- def increment (self , value : int = 1 ) -> None :
293
- """Increments the counter for the assigned labels unless events are disabled."""
294
- if config .DISABLE_EVENTS :
295
- return
296
-
297
- if value <= 0 :
298
- raise ValueError ("Increment value must be positive." )
299
-
300
- with self ._counter .mutex :
301
- self ._counter .count_by_labels [self ._label_values ] += value
302
-
303
- def reset (self ) -> None :
304
- """Resets the counter to zero for the assigned labels unless events are disabled."""
305
- if config .DISABLE_EVENTS :
306
- return
307
-
308
- with self ._counter .mutex :
309
- self ._counter .count_by_labels [self ._label_values ] = 0
273
+ return self ._as_list ()
310
274
311
275
312
276
class Counter :
@@ -319,21 +283,21 @@ class Counter:
319
283
"""
320
284
321
285
@overload
322
- def __new__ (cls , name : str , namespace : Optional [str ] = "" ) -> _SimpleCounter :
323
- return _SimpleCounter (namespace = namespace , name = name )
286
+ def __new__ (cls , name : str , namespace : Optional [str ] = "" ) -> _CounterMetric :
287
+ return _CounterMetric (namespace = namespace , name = name )
324
288
325
289
@overload
326
290
def __new__ (
327
291
cls , name : str , labels : List [str ], namespace : Optional [str ] = ""
328
- ) -> _LabeledCounter :
329
- return _LabeledCounter (namespace = namespace , name = name , labels = labels )
292
+ ) -> _LabeledCounterMetric :
293
+ return _LabeledCounterMetric (namespace = namespace , name = name , labels = labels )
330
294
331
295
def __new__ (
332
296
cls , name : str , namespace : Optional [str ] = "" , labels : Optional [List [str ]] = None
333
- ) -> Union [_SimpleCounter , _LabeledCounter ]:
297
+ ) -> Union [_CounterMetric , _LabeledCounterMetric ]:
334
298
if labels :
335
- return _LabeledCounter (namespace = namespace , name = name , labels = labels )
336
- return _SimpleCounter (namespace = namespace , name = name )
299
+ return _LabeledCounterMetric (namespace = namespace , name = name , labels = labels )
300
+ return _CounterMetric (namespace = namespace , name = name )
337
301
338
302
339
303
@hooks .on_infra_shutdown ()
0 commit comments