1010 RESERVED_METRIC_LABEL_NAME_RE ,
1111)
1212from .registry import REGISTRY
13+ from .samples import Exemplar
1314from .utils import floatToGoString , INF
1415
1516if sys .version_info > (3 ,):
@@ -36,18 +37,32 @@ def _build_full_name(metric_type, name, namespace, subsystem, unit):
3637 return full_name
3738
3839
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+
3947def _validate_labelnames (cls , labelnames ):
4048 labelnames = tuple (labelnames )
4149 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 )
4651 if l in cls ._reserved_labelnames :
4752 raise ValueError ('Reserved label metric name: ' + l )
4853 return labelnames
4954
5055
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+
5166class MetricWrapperBase (object ):
5267 _type = None
5368 _reserved_labelnames = ()
@@ -76,8 +91,8 @@ def describe(self):
7691
7792 def collect (self ):
7893 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 )
8196 return [metric ]
8297
8398 def __str__ (self ):
@@ -202,8 +217,8 @@ def _multi_samples(self):
202217 metrics = self ._metrics .copy ()
203218 for labels , metric in metrics .items ():
204219 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 )
207222
208223 def _child_samples (self ): # pragma: no cover
209224 raise NotImplementedError ('_child_samples() must be implemented by %r' % self )
@@ -256,12 +271,15 @@ def _metric_init(self):
256271 self ._labelvalues )
257272 self ._created = time .time ()
258273
259- def inc (self , amount = 1 ):
274+ def inc (self , amount = 1 , exemplar = None ):
260275 """Increment counter by the given amount."""
261276 self ._raise_if_not_observable ()
262277 if amount < 0 :
263278 raise ValueError ('Counters can only be incremented by non-negative amounts.' )
264279 self ._value .inc (amount )
280+ if exemplar :
281+ _validate_exemplar (exemplar )
282+ self ._value .set_exemplar (Exemplar (exemplar , amount , time .time ()))
265283
266284 def count_exceptions (self , exception = Exception ):
267285 """Count exceptions in a block of code or function.
@@ -275,8 +293,8 @@ def count_exceptions(self, exception=Exception):
275293
276294 def _child_samples (self ):
277295 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 ),
280298 )
281299
282300
@@ -399,12 +417,12 @@ def set_function(self, f):
399417 self ._raise_if_not_observable ()
400418
401419 def samples (self ):
402- return (('' , {}, float (f ())),)
420+ return (('' , {}, float (f ()), None , None ),)
403421
404422 self ._child_samples = create_bound_method (samples , self )
405423
406424 def _child_samples (self ):
407- return (('' , {}, self ._value .get ()),)
425+ return (('' , {}, self ._value .get (), None , None ),)
408426
409427
410428class Summary (MetricWrapperBase ):
@@ -470,9 +488,10 @@ def time(self):
470488
471489 def _child_samples (self ):
472490 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+ )
476495
477496
478497class Histogram (MetricWrapperBase ):
@@ -564,7 +583,7 @@ def _metric_init(self):
564583 self ._labelvalues + (floatToGoString (b ),))
565584 )
566585
567- def observe (self , amount ):
586+ def observe (self , amount , exemplar = None ):
568587 """Observe the given amount.
569588
570589 The amount is usually positive or zero. Negative values are
@@ -579,6 +598,9 @@ def observe(self, amount):
579598 for i , bound in enumerate (self ._upper_bounds ):
580599 if amount <= bound :
581600 self ._buckets [i ].inc (1 )
601+ if exemplar :
602+ _validate_exemplar (exemplar )
603+ self ._buckets [i ].set_exemplar (Exemplar (exemplar , amount , time .time ()))
582604 break
583605
584606 def time (self ):
@@ -593,11 +615,11 @@ def _child_samples(self):
593615 acc = 0
594616 for i , bound in enumerate (self ._upper_bounds ):
595617 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 ))
598620 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 ))
601623 return tuple (samples )
602624
603625
@@ -634,7 +656,7 @@ def info(self, val):
634656
635657 def _child_samples (self ):
636658 with self ._lock :
637- return (('_info' , self ._value , 1.0 ,),)
659+ return (('_info' , self ._value , 1.0 , None , None ),)
638660
639661
640662class Enum (MetricWrapperBase ):
@@ -692,7 +714,7 @@ def state(self, state):
692714 def _child_samples (self ):
693715 with self ._lock :
694716 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 )
696718 for i , s
697719 in enumerate (self ._states )
698720 ]
0 commit comments