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

Code before:
	}

	var s0, s1, b float64
	// Set initial values.
	s1 = samples.Floats[0].F
	b = samples.Floats[1].F - samples.Floats[0].F

	// Run the smoothing operation.
	var x, y float64
	for i := 1; i < l; i++ {
		// Scale the raw value against the smoothing factor.
		x = sf * samples.Floats[i].F

		// Scale the last smoothed value with the trend at this point.
		b = calcTrendValue(i-1, tf, s0, s1, b)
		y = (1 - sf) * (s1 + b)

		s0, s1 = s1, x+y
	}
	if len(samples.Histograms) > 0 {
		return append(enh.Out, Sample{F: s1}), annotations.New().Add(annotations.NewHistogramIgnoredInMixedRangeInfo(metricName, args[0].PositionRange()))
	}
	return append(enh.Out, Sample{F: s1}), nil
}

func filterFloats(vals []parser.Value) Vector {
	v := vals[0].(Vector)
	floats := make(Vector, 0, len(v))
	for _, s := range v {
		if s.H == nil {
			floats = append(floats, s)
		}
	}
	return floats
}

// === sort(node parser.ValueTypeVector) (Vector, Annotations) ===
func funcSort(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	// NaN should sort to the bottom, so take descending sort with NaN first and
	// reverse it.
	byValueSorter := vectorByReverseValueHeap(filterFloats(vals))
	sort.Sort(sort.Reverse(byValueSorter))
	return Vector(byValueSorter), nil
}

// === sortDesc(node parser.ValueTypeVector) (Vector, Annotations) ===
func funcSortDesc(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	// NaN should sort to the bottom, so take ascending sort with NaN first and
	// reverse it.
	byValueSorter := vectorByValueHeap(filterFloats(vals))
	sort.Sort(sort.Reverse(byValueSorter))

Review comment (performance):
This is very expensive, as you have to copy all the values into a new slice. It could be a lot.

As the original code sorts the vector in place, it should be fine to do also the filtering in place.

So how about going through the vector and remove histograms in place with something like https://go.dev/wiki/SliceTricks#filtering-without-allocating ?

Code after:
	}

	var s0, s1, b float64
	// Set initial values.
	s1 = samples.Floats[0].F
	b = samples.Floats[1].F - samples.Floats[0].F

	// Run the smoothing operation.
	var x, y float64
	for i := 1; i < l; i++ {
		// Scale the raw value against the smoothing factor.
		x = sf * samples.Floats[i].F

		// Scale the last smoothed value with the trend at this point.
		b = calcTrendValue(i-1, tf, s0, s1, b)
		y = (1 - sf) * (s1 + b)

		s0, s1 = s1, x+y
	}
	if len(samples.Histograms) > 0 {
		return append(enh.Out, Sample{F: s1}), annotations.New().Add(annotations.NewHistogramIgnoredInMixedRangeInfo(metricName, args[0].PositionRange()))
	}
	return append(enh.Out, Sample{F: s1}), nil
}

// filterFloats filters out histogram samples from the vector in-place.
func filterFloats(v Vector) Vector {
	floats := v[:0]
	for _, s := range v {
		if s.H == nil {
			floats = append(floats, s)
		}
	}
	return floats
}

// === sort(node parser.ValueTypeVector) (Vector, Annotations) ===
func funcSort(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	// NaN should sort to the bottom, so take descending sort with NaN first and
	// reverse it.
	byValueSorter := vectorByReverseValueHeap(filterFloats(vals[0].(Vector)))
	sort.Sort(sort.Reverse(byValueSorter))
	return Vector(byValueSorter), nil
}

// === sortDesc(node parser.ValueTypeVector) (Vector, Annotations) ===
func funcSortDesc(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	// NaN should sort to the bottom, so take ascending sort with NaN first and
	// reverse it.
	byValueSorter := vectorByValueHeap(filterFloats(vals[0].(Vector)))
	sort.Sort(sort.Reverse(byValueSorter))