1+ import math
12import os
23from threading import Lock
34import time
@@ -557,10 +558,16 @@ def create_response(request):
557558
558559 The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
559560 They can be overridden by passing `buckets` keyword argument to `Histogram`.
561+
562+ In addition, native histograms are experimentally supported, but may change at any time. In order
563+ to use native histograms, one must set `native_histogram_bucket_factor` to a value greater than 1.0.
564+ When native histograms are enabled the classic histogram buckets are only collected if they are
565+ explicitly set.
560566 """
561567 _type = 'histogram'
562568 _reserved_labelnames = ['le' ]
563569 DEFAULT_BUCKETS = (.005 , .01 , .025 , .05 , .075 , .1 , .25 , .5 , .75 , 1.0 , 2.5 , 5.0 , 7.5 , 10.0 , INF )
570+ DEFAULT_NATIVE_HISTOGRAM_ZERO_THRESHOLD = 2.938735877055719e-39
564571
565572 def __init__ (self ,
566573 name : str ,
@@ -571,9 +578,26 @@ def __init__(self,
571578 unit : str = '' ,
572579 registry : Optional [CollectorRegistry ] = REGISTRY ,
573580 _labelvalues : Optional [Sequence [str ]] = None ,
574- buckets : Sequence [Union [float , str ]] = DEFAULT_BUCKETS ,
581+ buckets : Optional [Sequence [Union [float , str ]]] = None ,
582+ native_histogram_initial_schema : Optional [int ] = None ,
583+ native_histogram_max_buckets : int = 160 ,
584+ native_histogram_zero_threshold : float = DEFAULT_NATIVE_HISTOGRAM_ZERO_THRESHOLD ,
585+ native_histogram_max_exemplars : int = 10 ,
575586 ):
587+ if native_histogram_initial_schema and (native_histogram_initial_schema > 8 or native_histogram_initial_schema < - 4 ):
588+ raise ValueError ("native_histogram_initial_schema must be between -4 and 8 inclusive" )
589+
590+ # Use the default buckets iff we are not using a native histogram.
591+ if buckets is None and native_histogram_initial_schema is None :
592+ buckets = self .DEFAULT_BUCKETS
593+
576594 self ._prepare_buckets (buckets )
595+
596+ self ._schema = native_histogram_initial_schema
597+ self ._max_nh_buckets = native_histogram_max_buckets
598+ self ._zero_threshold = native_histogram_zero_threshold
599+ self ._max_nh_exemplars = native_histogram_max_exemplars ,
600+
577601 super ().__init__ (
578602 name = name ,
579603 documentation = documentation ,
@@ -586,7 +610,12 @@ def __init__(self,
586610 )
587611 self ._kwargs ['buckets' ] = buckets
588612
589- def _prepare_buckets (self , source_buckets : Sequence [Union [float , str ]]) -> None :
613+ def _prepare_buckets (self , source_buckets : Optional [Sequence [Union [float , str ]]]) -> None :
614+ # Only native histograms are supported for this case.
615+ if source_buckets is None :
616+ self ._upper_bounds = None
617+ return
618+
590619 buckets = [float (b ) for b in source_buckets ]
591620 if buckets != sorted (buckets ):
592621 # This is probably an error on the part of the user,
@@ -601,18 +630,36 @@ def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
601630 def _metric_init (self ) -> None :
602631 self ._buckets : List [values .ValueClass ] = []
603632 self ._created = time .time ()
604- bucket_labelnames = self ._labelnames + ('le' ,)
605- self ._sum = values .ValueClass (self ._type , self ._name , self ._name + '_sum' , self ._labelnames , self ._labelvalues , self ._documentation )
606- for b in self ._upper_bounds :
607- self ._buckets .append (values .ValueClass (
633+
634+ if self ._schema is not None :
635+ self ._native_histogram = values .NativeHistogramMutexValue (
608636 self ._type ,
609637 self ._name ,
610- self ._name + '_bucket' ,
611- bucket_labelnames ,
612- self ._labelvalues + (floatToGoString (b ),),
613- self ._documentation )
638+ self ._name ,
639+ self ._labelnames ,
640+ self ._labelvalues ,
641+ self ._documentation ,
642+ self ._schema ,
643+ self ._zero_threshold ,
644+ self ._max_nh_buckets ,
645+ self ._max_nh_exemplars ,
614646 )
615647
648+ if self ._upper_bounds is not None :
649+ bucket_labelnames = self ._labelnames + ('le' ,)
650+ self ._sum = values .ValueClass (self ._type , self ._name , self ._name + '_sum' , self ._labelnames , self ._labelvalues , self ._documentation )
651+ for b in self ._upper_bounds :
652+ self ._buckets .append (values .ValueClass (
653+ self ._type ,
654+ self ._name ,
655+ self ._name + '_bucket' ,
656+ bucket_labelnames ,
657+ self ._labelvalues + (floatToGoString (b ),),
658+ self ._documentation )
659+ )
660+
661+
662+
616663 def observe (self , amount : float , exemplar : Optional [Dict [str , str ]] = None ) -> None :
617664 """Observe the given amount.
618665
@@ -624,14 +671,18 @@ def observe(self, amount: float, exemplar: Optional[Dict[str, str]] = None) -> N
624671 for details.
625672 """
626673 self ._raise_if_not_observable ()
627- self ._sum .inc (amount )
628- for i , bound in enumerate (self ._upper_bounds ):
629- if amount <= bound :
630- self ._buckets [i ].inc (1 )
631- if exemplar :
632- _validate_exemplar (exemplar )
633- self ._buckets [i ].set_exemplar (Exemplar (exemplar , amount , time .time ()))
634- break
674+ if self ._upper_bounds is not None :
675+ self ._sum .inc (amount )
676+ for i , bound in enumerate (self ._upper_bounds ):
677+ if amount <= bound :
678+ self ._buckets [i ].inc (1 )
679+ if exemplar :
680+ _validate_exemplar (exemplar )
681+ self ._buckets [i ].set_exemplar (Exemplar (exemplar , amount , time .time ()))
682+ break
683+
684+ if self ._schema and not math .isnan (amount ):
685+ self ._native_histogram .observe (amount )
635686
636687 def time (self ) -> Timer :
637688 """Time a block of code or function, and observe the duration in seconds.
@@ -642,15 +693,19 @@ def time(self) -> Timer:
642693
643694 def _child_samples (self ) -> Iterable [Sample ]:
644695 samples = []
645- acc = 0.0
646- for i , bound in enumerate (self ._upper_bounds ):
647- acc += self ._buckets [i ].get ()
648- samples .append (Sample ('_bucket' , {'le' : floatToGoString (bound )}, acc , None , self ._buckets [i ].get_exemplar ()))
649- samples .append (Sample ('_count' , {}, acc , None , None ))
650- if self ._upper_bounds [0 ] >= 0 :
651- samples .append (Sample ('_sum' , {}, self ._sum .get (), None , None ))
652- if _use_created :
653- samples .append (Sample ('_created' , {}, self ._created , None , None ))
696+ if self ._upper_bounds is not None :
697+ acc = 0.0
698+ for i , bound in enumerate (self ._upper_bounds ):
699+ acc += self ._buckets [i ].get ()
700+ samples .append (Sample ('_bucket' , {'le' : floatToGoString (bound )}, acc , None , self ._buckets [i ].get_exemplar ()))
701+ samples .append (Sample ('_count' , {}, acc , None , None ))
702+ if self ._upper_bounds [0 ] >= 0 :
703+ samples .append (Sample ('_sum' , {}, self ._sum .get (), None , None ))
704+ if _use_created :
705+ samples .append (Sample ('_created' , {}, self ._created , None , None ))
706+
707+ if self ._schema :
708+ samples .append (Sample ('' , {}, 0.0 , None , None , self ._native_histogram .get ()))
654709 return tuple (samples )
655710
656711
0 commit comments