10
10
RESERVED_METRIC_LABEL_NAME_RE ,
11
11
)
12
12
from .registry import REGISTRY
13
+ from .samples import Exemplar
13
14
from .utils import floatToGoString , INF
14
15
15
16
if sys .version_info > (3 ,):
@@ -36,18 +37,32 @@ def _build_full_name(metric_type, name, namespace, subsystem, unit):
36
37
return full_name
37
38
38
39
40
+ def _validate_labelname (l ):
41
+ if not METRIC_LABEL_NAME_RE .match (l ):
42
+ raise ValueError ('Invalid label metric name: ' + l )
43
+ if RESERVED_METRIC_LABEL_NAME_RE .match (l ):
44
+ raise ValueError ('Reserved label metric name: ' + l )
45
+
46
+
39
47
def _validate_labelnames (cls , labelnames ):
40
48
labelnames = tuple (labelnames )
41
49
for l in labelnames :
42
- if not METRIC_LABEL_NAME_RE .match (l ):
43
- raise ValueError ('Invalid label metric name: ' + l )
44
- if RESERVED_METRIC_LABEL_NAME_RE .match (l ):
45
- raise ValueError ('Reserved label metric name: ' + l )
50
+ _validate_labelname (l )
46
51
if l in cls ._reserved_labelnames :
47
52
raise ValueError ('Reserved label metric name: ' + l )
48
53
return labelnames
49
54
50
55
56
+ def _validate_exemplar (exemplar ):
57
+ runes = 0
58
+ for k , v in exemplar .items ():
59
+ _validate_labelname (k )
60
+ runes += len (k )
61
+ runes += len (v )
62
+ if runes > 128 :
63
+ raise ValueError ('Exemplar labels have %d UTF-8 characters, exceeding the limit of 128' )
64
+
65
+
51
66
class MetricWrapperBase (object ):
52
67
_type = None
53
68
_reserved_labelnames = ()
@@ -76,8 +91,8 @@ def describe(self):
76
91
77
92
def collect (self ):
78
93
metric = self ._get_metric ()
79
- for suffix , labels , value in self ._samples ():
80
- metric .add_sample (self ._name + suffix , labels , value )
94
+ for suffix , labels , value , timestamp , exemplar in self ._samples ():
95
+ metric .add_sample (self ._name + suffix , labels , value , timestamp , exemplar )
81
96
return [metric ]
82
97
83
98
def __str__ (self ):
@@ -202,8 +217,8 @@ def _multi_samples(self):
202
217
metrics = self ._metrics .copy ()
203
218
for labels , metric in metrics .items ():
204
219
series_labels = list (zip (self ._labelnames , labels ))
205
- for suffix , sample_labels , value in metric ._samples ():
206
- yield (suffix , dict (series_labels + list (sample_labels .items ())), value )
220
+ for suffix , sample_labels , value , timestamp , exemplar in metric ._samples ():
221
+ yield (suffix , dict (series_labels + list (sample_labels .items ())), value , timestamp , exemplar )
207
222
208
223
def _child_samples (self ): # pragma: no cover
209
224
raise NotImplementedError ('_child_samples() must be implemented by %r' % self )
@@ -256,12 +271,15 @@ def _metric_init(self):
256
271
self ._labelvalues )
257
272
self ._created = time .time ()
258
273
259
- def inc (self , amount = 1 ):
274
+ def inc (self , amount = 1 , exemplar = None ):
260
275
"""Increment counter by the given amount."""
261
276
self ._raise_if_not_observable ()
262
277
if amount < 0 :
263
278
raise ValueError ('Counters can only be incremented by non-negative amounts.' )
264
279
self ._value .inc (amount )
280
+ if exemplar :
281
+ _validate_exemplar (exemplar )
282
+ self ._value .set_exemplar (Exemplar (exemplar , amount , time .time ()))
265
283
266
284
def count_exceptions (self , exception = Exception ):
267
285
"""Count exceptions in a block of code or function.
@@ -275,8 +293,8 @@ def count_exceptions(self, exception=Exception):
275
293
276
294
def _child_samples (self ):
277
295
return (
278
- ('_total' , {}, self ._value .get ()),
279
- ('_created' , {}, self ._created ),
296
+ ('_total' , {}, self ._value .get (), None , self . _value . get_exemplar () ),
297
+ ('_created' , {}, self ._created , None , None ),
280
298
)
281
299
282
300
@@ -399,12 +417,12 @@ def set_function(self, f):
399
417
self ._raise_if_not_observable ()
400
418
401
419
def samples (self ):
402
- return (('' , {}, float (f ())),)
420
+ return (('' , {}, float (f ()), None , None ),)
403
421
404
422
self ._child_samples = create_bound_method (samples , self )
405
423
406
424
def _child_samples (self ):
407
- return (('' , {}, self ._value .get ()),)
425
+ return (('' , {}, self ._value .get (), None , None ),)
408
426
409
427
410
428
class Summary (MetricWrapperBase ):
@@ -470,9 +488,10 @@ def time(self):
470
488
471
489
def _child_samples (self ):
472
490
return (
473
- ('_count' , {}, self ._count .get ()),
474
- ('_sum' , {}, self ._sum .get ()),
475
- ('_created' , {}, self ._created ))
491
+ ('_count' , {}, self ._count .get (), None , None ),
492
+ ('_sum' , {}, self ._sum .get (), None , None ),
493
+ ('_created' , {}, self ._created , None , None ),
494
+ )
476
495
477
496
478
497
class Histogram (MetricWrapperBase ):
@@ -564,7 +583,7 @@ def _metric_init(self):
564
583
self ._labelvalues + (floatToGoString (b ),))
565
584
)
566
585
567
- def observe (self , amount ):
586
+ def observe (self , amount , exemplar = None ):
568
587
"""Observe the given amount.
569
588
570
589
The amount is usually positive or zero. Negative values are
@@ -579,6 +598,9 @@ def observe(self, amount):
579
598
for i , bound in enumerate (self ._upper_bounds ):
580
599
if amount <= bound :
581
600
self ._buckets [i ].inc (1 )
601
+ if exemplar :
602
+ _validate_exemplar (exemplar )
603
+ self ._buckets [i ].set_exemplar (Exemplar (exemplar , amount , time .time ()))
582
604
break
583
605
584
606
def time (self ):
@@ -593,11 +615,11 @@ def _child_samples(self):
593
615
acc = 0
594
616
for i , bound in enumerate (self ._upper_bounds ):
595
617
acc += self ._buckets [i ].get ()
596
- samples .append (('_bucket' , {'le' : floatToGoString (bound )}, acc ))
597
- samples .append (('_count' , {}, acc ))
618
+ samples .append (('_bucket' , {'le' : floatToGoString (bound )}, acc , None , self . _buckets [ i ]. get_exemplar () ))
619
+ samples .append (('_count' , {}, acc , None , None ))
598
620
if self ._upper_bounds [0 ] >= 0 :
599
- samples .append (('_sum' , {}, self ._sum .get ()))
600
- samples .append (('_created' , {}, self ._created ))
621
+ samples .append (('_sum' , {}, self ._sum .get (), None , None ))
622
+ samples .append (('_created' , {}, self ._created , None , None ))
601
623
return tuple (samples )
602
624
603
625
@@ -634,7 +656,7 @@ def info(self, val):
634
656
635
657
def _child_samples (self ):
636
658
with self ._lock :
637
- return (('_info' , self ._value , 1.0 ,),)
659
+ return (('_info' , self ._value , 1.0 , None , None ),)
638
660
639
661
640
662
class Enum (MetricWrapperBase ):
@@ -692,7 +714,7 @@ def state(self, state):
692
714
def _child_samples (self ):
693
715
with self ._lock :
694
716
return [
695
- ('' , {self ._name : s }, 1 if i == self ._value else 0 ,)
717
+ ('' , {self ._name : s }, 1 if i == self ._value else 0 , None , None )
696
718
for i , s
697
719
in enumerate (self ._states )
698
720
]
0 commit comments