1
+ import math
1
2
import os
2
3
from threading import Lock
3
4
import time
@@ -557,10 +558,16 @@ def create_response(request):
557
558
558
559
The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds.
559
560
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.
560
566
"""
561
567
_type = 'histogram'
562
568
_reserved_labelnames = ['le' ]
563
569
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
564
571
565
572
def __init__ (self ,
566
573
name : str ,
@@ -571,9 +578,26 @@ def __init__(self,
571
578
unit : str = '' ,
572
579
registry : Optional [CollectorRegistry ] = REGISTRY ,
573
580
_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 ,
575
586
):
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
+
576
594
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
+
577
601
super ().__init__ (
578
602
name = name ,
579
603
documentation = documentation ,
@@ -586,7 +610,12 @@ def __init__(self,
586
610
)
587
611
self ._kwargs ['buckets' ] = buckets
588
612
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
+
590
619
buckets = [float (b ) for b in source_buckets ]
591
620
if buckets != sorted (buckets ):
592
621
# This is probably an error on the part of the user,
@@ -601,17 +630,35 @@ def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
601
630
def _metric_init (self ) -> None :
602
631
self ._buckets : List [values .ValueClass ] = []
603
632
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 (
608
- self ._type ,
609
- self ._name ,
610
- self ._name + '_bucket' ,
611
- bucket_labelnames ,
612
- self ._labelvalues + (floatToGoString (b ),),
613
- self ._documentation )
614
- )
633
+
634
+ if self ._schema is not None :
635
+ self ._native_histogram = values .NativeHistogramMutexValue (
636
+ self ._type ,
637
+ self ._name ,
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 ,
646
+ )
647
+
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
+
615
662
616
663
def observe (self , amount : float , exemplar : Optional [Dict [str , str ]] = None ) -> None :
617
664
"""Observe the given amount.
@@ -624,14 +671,18 @@ def observe(self, amount: float, exemplar: Optional[Dict[str, str]] = None) -> N
624
671
for details.
625
672
"""
626
673
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 )
635
686
636
687
def time (self ) -> Timer :
637
688
"""Time a block of code or function, and observe the duration in seconds.
@@ -642,15 +693,19 @@ def time(self) -> Timer:
642
693
643
694
def _child_samples (self ) -> Iterable [Sample ]:
644
695
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 ()))
654
709
return tuple (samples )
655
710
656
711
0 commit comments