Repository: prometheus/prometheus
File: promql/functions.go (Go)

Code before:
		}
		if !enh.enableDelayedNameRemoval {
			sample.Metric = sample.Metric.DropMetricName()
		}
		enh.Out = append(enh.Out, Sample{
			Metric:   sample.Metric,
			F:        sample.H.Sum / sample.H.Count,
			DropName: true,
		})
	}
	return enh.Out, nil
}

// === histogram_stddev(Vector parser.ValueTypeVector) (Vector, Annotations)  ===
func funcHistogramStdDev(vals []parser.Value, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	inVec := vals[0].(Vector)

	for _, sample := range inVec {
		// Skip non-histogram samples.
		if sample.H == nil {
			continue
		}
		mean := sample.H.Sum / sample.H.Count
		var variance, cVariance float64
		it := sample.H.AllBucketIterator()
		for it.Next() {
			bucket := it.At()
			if bucket.Count == 0 {
				continue
			}
			var val float64
			switch {
			case bucket.Lower <= 0 && bucket.Upper >= 0:
				val = 0
			case sample.H.Schema == -53:
				val = (bucket.Upper + bucket.Lower) / 2.0
			default:
				val = math.Sqrt(bucket.Upper * bucket.Lower)
				if bucket.Upper < 0 {
					val = -val
				}
			}
			delta := val - mean
			variance, cVariance = kahanSumInc(bucket.Count*delta*delta, variance, cVariance)
		}
		variance += cVariance
		variance /= sample.H.Count
		if !enh.enableDelayedNameRemoval {
			sample.Metric = sample.Metric.DropMetricName()
		}
		enh.Out = append(enh.Out, Sample{

Review comment (suggestion):
For NHCB, `val` should be the arithmetic mean, while for other cases, it should be the geometric mean. Therefore, only the calculation of `val` needs to be adjusted, and the rest of the logic can remain unchanged.

Code after:
func histogramVariance(vals []parser.Value, enh *EvalNodeHelper, varianceToResult func(float64) float64) (Vector, annotations.Annotations) {
	vec := vals[0].(Vector)
	for _, sample := range vec {
		// Skip non-histogram samples.
		if sample.H == nil {
			continue
		}
		mean := sample.H.Sum / sample.H.Count
		var variance, cVariance float64
		it := sample.H.AllBucketIterator()
		for it.Next() {
			bucket := it.At()
			if bucket.Count == 0 {
				continue
			}
			var val float64
			switch {
			case sample.H.UsesCustomBuckets():
				// Use arithmetic mean in case of custom buckets.
				val = (bucket.Upper + bucket.Lower) / 2.0
			case bucket.Lower <= 0 && bucket.Upper >= 0:
				// Use zero (effectively the arithmetic mean) in the zero bucket of a standard exponential histogram.
				val = 0
			default:
				// Use geometric mean in case of standard exponential buckets.
				val = math.Sqrt(bucket.Upper * bucket.Lower)
				if bucket.Upper < 0 {
					val = -val
				}
			}
			delta := val - mean
			variance, cVariance = kahanSumInc(bucket.Count*delta*delta, variance, cVariance)
		}
		variance += cVariance
		variance /= sample.H.Count
		if !enh.enableDelayedNameRemoval {
			sample.Metric = sample.Metric.DropMetricName()
		}
		if varianceToResult != nil {
			variance = varianceToResult(variance)
		}
		enh.Out = append(enh.Out, Sample{
			Metric:   sample.Metric,
			F:        variance,
			DropName: true,
		})
	}
	return enh.Out, nil
}

// === histogram_stddev(Vector parser.ValueTypeVector) (Vector, Annotations)  ===