@@ -577,28 +577,37 @@ func (hc *histogramCounts) observe(v float64, bucket int, doSparse bool) {
577
577
atomic .AddUint64 (& hc .buckets [bucket ], 1 )
578
578
}
579
579
atomicAddFloat (& hc .sumBits , v )
580
- if doSparse {
580
+ if doSparse && ! math . IsNaN ( v ) {
581
581
var (
582
- sparseKey int
583
- sparseSchema = atomic .LoadInt32 (& hc .sparseSchema )
584
- sparseZeroThreshold = math .Float64frombits (atomic .LoadUint64 (& hc .sparseZeroThresholdBits ))
585
- frac , exp = math .Frexp (math .Abs (v ))
586
- bucketCreated bool
582
+ sparseKey int
583
+ sparseSchema = atomic .LoadInt32 (& hc .sparseSchema )
584
+ sparseZeroThreshold = math .Float64frombits (atomic .LoadUint64 (& hc .sparseZeroThresholdBits ))
585
+ bucketCreated , isInf bool
587
586
)
588
- switch {
589
- case math .IsInf (v , 0 ):
590
- sparseKey = math .MaxInt32 // Largest possible sparseKey.
591
- case sparseSchema > 0 :
587
+ if math .IsInf (v , 0 ) {
588
+ // Pretend v is MaxFloat64 but later increment sparseKey by one.
589
+ if math .IsInf (v , + 1 ) {
590
+ v = math .MaxFloat64
591
+ } else {
592
+ v = - math .MaxFloat64
593
+ }
594
+ isInf = true
595
+ }
596
+ frac , exp := math .Frexp (math .Abs (v ))
597
+ if sparseSchema > 0 {
592
598
bounds := sparseBounds [sparseSchema ]
593
599
sparseKey = sort .SearchFloat64s (bounds , frac ) + (exp - 1 )* len (bounds )
594
- default :
600
+ } else {
595
601
sparseKey = exp
596
602
if frac == 0.5 {
597
603
sparseKey --
598
604
}
599
605
div := 1 << - sparseSchema
600
606
sparseKey = (sparseKey + div - 1 ) / div
601
607
}
608
+ if isInf {
609
+ sparseKey ++
610
+ }
602
611
switch {
603
612
case v > sparseZeroThreshold :
604
613
bucketCreated = addToSparseBucket (& hc .sparseBucketsPositive , sparseKey , 1 )
@@ -1062,7 +1071,8 @@ func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
1062
1071
// WithLabelValues works as GetMetricWithLabelValues, but panics where
1063
1072
// GetMetricWithLabelValues would have returned an error. Not returning an
1064
1073
// error allows shortcuts like
1065
- // myVec.WithLabelValues("404", "GET").Observe(42.21)
1074
+ //
1075
+ // myVec.WithLabelValues("404", "GET").Observe(42.21)
1066
1076
func (v * HistogramVec ) WithLabelValues (lvs ... string ) Observer {
1067
1077
h , err := v .GetMetricWithLabelValues (lvs ... )
1068
1078
if err != nil {
@@ -1073,7 +1083,8 @@ func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
1073
1083
1074
1084
// With works as GetMetricWith but panics where GetMetricWithLabels would have
1075
1085
// returned an error. Not returning an error allows shortcuts like
1076
- // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
1086
+ //
1087
+ // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
1077
1088
func (v * HistogramVec ) With (labels Labels ) Observer {
1078
1089
h , err := v .GetMetricWith (labels )
1079
1090
if err != nil {
@@ -1219,8 +1230,8 @@ func (s buckSort) Less(i, j int) bool {
1219
1230
// 2^(2^-n) is less or equal the provided bucketFactor.
1220
1231
//
1221
1232
// Special cases:
1222
- // - bucketFactor <= 1: panics.
1223
- // - bucketFactor < 2^(2^-8) (but > 1): still returns 8.
1233
+ // - bucketFactor <= 1: panics.
1234
+ // - bucketFactor < 2^(2^-8) (but > 1): still returns 8.
1224
1235
func pickSparseSchema (bucketFactor float64 ) int32 {
1225
1236
if bucketFactor <= 1 {
1226
1237
panic (fmt .Errorf ("bucketFactor %f is <=1" , bucketFactor ))
@@ -1346,13 +1357,55 @@ func findSmallestKey(m *sync.Map) int {
1346
1357
}
1347
1358
1348
1359
func getLe (key int , schema int32 ) float64 {
1360
+ // Here a bit of context about the behavior for the last bucket counting
1361
+ // regular numbers (called simply "last bucket" below) and the bucket
1362
+ // counting observations of ±Inf (called "inf bucket" below, with a key
1363
+ // one higher than that of the "last bucket"):
1364
+ //
1365
+ // If we apply the usual formula to the last bucket, its upper bound
1366
+ // would be calculated as +Inf. The reason is that the max possible
1367
+ // regular float64 number (math.MaxFloat64) doesn't coincide with one of
1368
+ // the calculated bucket boundaries. So the calculated boundary has to
1369
+ // be larger than math.MaxFloat64, and the only float64 larger than
1370
+ // math.MaxFloat64 is +Inf. However, we want to count actual
1371
+ // observations of ±Inf in the inf bucket. Therefore, we have to treat
1372
+ // the upper bound of the last bucket specially and set it to
1373
+ // math.MaxFloat64. (The upper bound of the inf bucket, with its key
1374
+ // being one higher than that of the last bucket, naturally comes out as
1375
+ // +Inf by the usual formula. So that's fine.)
1376
+ //
1377
+ // math.MaxFloat64 has a frac of 0.9999999999999999 and an exp of
1378
+ // 1024. If there were a float64 number following math.MaxFloat64, it
1379
+ // would have a frac of 1.0 and an exp of 1024, or equivalently a frac
1380
+ // of 0.5 and an exp of 1025. However, since frac must be smaller than
1381
+ // 1, and exp must be smaller than 1025, either representation overflows
1382
+ // a float64. (Which, in turn, is the reason that math.MaxFloat64 is the
1383
+ // largest possible float64. Q.E.D.) However, the formula for
1384
+ // calculating the upper bound from the idx and schema of the last
1385
+ // bucket results in precisely that. It is either frac=1.0 & exp=1024
1386
+ // (for schema < 0) or frac=0.5 & exp=1025 (for schema >=0). (This is,
1387
+ // by the way, a power of two where the exponent itself is a power of
1388
+ // two, 2¹⁰ in fact, which coinicides with a bucket boundary in all
1389
+ // schemas.) So these are the special cases we have to catch below.
1349
1390
if schema < 0 {
1350
- return math .Ldexp (1 , key << (- schema ))
1391
+ exp := key << - schema
1392
+ if exp == 1024 {
1393
+ // This is the last bucket before the overflow bucket
1394
+ // (for ±Inf observations). Return math.MaxFloat64 as
1395
+ // explained above.
1396
+ return math .MaxFloat64
1397
+ }
1398
+ return math .Ldexp (1 , exp )
1351
1399
}
1352
1400
1353
1401
fracIdx := key & ((1 << schema ) - 1 )
1354
1402
frac := sparseBounds [schema ][fracIdx ]
1355
1403
exp := (key >> schema ) + 1
1404
+ if frac == 0.5 && exp == 1025 {
1405
+ // This is the last bucket before the overflow bucket (for ±Inf
1406
+ // observations). Return math.MaxFloat64 as explained above.
1407
+ return math .MaxFloat64
1408
+ }
1356
1409
return math .Ldexp (frac , exp )
1357
1410
}
1358
1411
0 commit comments