@@ -475,6 +475,9 @@ type HistogramOpts struct {
475
475
476
476
// now is for testing purposes, by default it's time.Now.
477
477
now func () time.Time
478
+
479
+ // afterFunc is for testing purposes, by default it's time.AfterFunc.
480
+ afterFunc func (time.Duration , func ()) * time.Timer
478
481
}
479
482
480
483
// HistogramVecOpts bundles the options to create a HistogramVec metric.
@@ -526,7 +529,9 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
526
529
if opts .now == nil {
527
530
opts .now = time .Now
528
531
}
529
-
532
+ if opts .afterFunc == nil {
533
+ opts .afterFunc = time .AfterFunc
534
+ }
530
535
h := & histogram {
531
536
desc : desc ,
532
537
upperBounds : opts .Buckets ,
@@ -536,6 +541,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
536
541
nativeHistogramMinResetDuration : opts .NativeHistogramMinResetDuration ,
537
542
lastResetTime : opts .now (),
538
543
now : opts .now ,
544
+ afterFunc : opts .afterFunc ,
539
545
}
540
546
if len (h .upperBounds ) == 0 && opts .NativeHistogramBucketFactor <= 1 {
541
547
h .upperBounds = DefBuckets
@@ -716,9 +722,16 @@ type histogram struct {
716
722
nativeHistogramMinResetDuration time.Duration
717
723
// lastResetTime is protected by mtx. It is also used as created timestamp.
718
724
lastResetTime time.Time
725
+ // resetScheduled is protected by mtx. It is true if a reset is
726
+ // scheduled for a later time (when nativeHistogramMinResetDuration has
727
+ // passed).
728
+ resetScheduled bool
719
729
720
730
// now is for testing purposes, by default it's time.Now.
721
731
now func () time.Time
732
+
733
+ // afterFunc is for testing purposes, by default it's time.AfterFunc.
734
+ afterFunc func (time.Duration , func ()) * time.Timer
722
735
}
723
736
724
737
func (h * histogram ) Desc () * Desc {
@@ -874,21 +887,31 @@ func (h *histogram) limitBuckets(counts *histogramCounts, value float64, bucket
874
887
if h .maybeReset (hotCounts , coldCounts , coldIdx , value , bucket ) {
875
888
return
876
889
}
890
+ // One of the other strategies will happen. To undo what they will do as
891
+ // soon as enough time has passed to satisfy
892
+ // h.nativeHistogramMinResetDuration, schedule a reset at the right time
893
+ // if we haven't done so already.
894
+ if h .nativeHistogramMinResetDuration > 0 && ! h .resetScheduled {
895
+ h .resetScheduled = true
896
+ h .afterFunc (h .nativeHistogramMinResetDuration - h .now ().Sub (h .lastResetTime ), h .reset )
897
+ }
898
+
877
899
if h .maybeWidenZeroBucket (hotCounts , coldCounts ) {
878
900
return
879
901
}
880
902
h .doubleBucketWidth (hotCounts , coldCounts )
881
903
}
882
904
883
- // maybeReset resets the whole histogram if at least h.nativeHistogramMinResetDuration
884
- // has been passed. It returns true if the histogram has been reset. The caller
885
- // must have locked h.mtx.
905
+ // maybeReset resets the whole histogram if at least
906
+ // h.nativeHistogramMinResetDuration has been passed. It returns true if the
907
+ // histogram has been reset. The caller must have locked h.mtx.
886
908
func (h * histogram ) maybeReset (
887
909
hot , cold * histogramCounts , coldIdx uint64 , value float64 , bucket int ,
888
910
) bool {
889
911
// We are using the possibly mocked h.now() rather than
890
912
// time.Since(h.lastResetTime) to enable testing.
891
- if h .nativeHistogramMinResetDuration == 0 ||
913
+ if h .nativeHistogramMinResetDuration == 0 || // No reset configured.
914
+ h .resetScheduled || // Do not interefere if a reset is already scheduled.
892
915
h .now ().Sub (h .lastResetTime ) < h .nativeHistogramMinResetDuration {
893
916
return false
894
917
}
@@ -906,6 +929,29 @@ func (h *histogram) maybeReset(
906
929
return true
907
930
}
908
931
932
+ // reset resets the whole histogram. It locks h.mtx itself, i.e. it has to be
933
+ // called without having locked h.mtx.
934
+ func (h * histogram ) reset () {
935
+ h .mtx .Lock ()
936
+ defer h .mtx .Unlock ()
937
+
938
+ n := atomic .LoadUint64 (& h .countAndHotIdx )
939
+ hotIdx := n >> 63
940
+ coldIdx := (^ n ) >> 63
941
+ hot := h .counts [hotIdx ]
942
+ cold := h .counts [coldIdx ]
943
+ // Completely reset coldCounts.
944
+ h .resetCounts (cold )
945
+ // Make coldCounts the new hot counts while resetting countAndHotIdx.
946
+ n = atomic .SwapUint64 (& h .countAndHotIdx , coldIdx << 63 )
947
+ count := n & ((1 << 63 ) - 1 )
948
+ waitForCooldown (count , hot )
949
+ // Finally, reset the formerly hot counts, too.
950
+ h .resetCounts (hot )
951
+ h .lastResetTime = h .now ()
952
+ h .resetScheduled = false
953
+ }
954
+
909
955
// maybeWidenZeroBucket widens the zero bucket until it includes the existing
910
956
// buckets closest to the zero bucket (which could be two, if an equidistant
911
957
// negative and a positive bucket exists, but usually it's only one bucket to be
0 commit comments