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