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

Code before:
			samples = append(samples, &Sample{
				H: histograms[j].H,
			})
			j++
		}
	}
	// Append the remaining samples
	for i < len(floats) {
		samples = append(samples, &Sample{
			F: floats[i].F,
		})
		i++
	}
	for j < len(histograms) {
		samples = append(samples, &Sample{
			H: histograms[j].H,
		})
		j++
	}

	return samples
}

// === changes(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcChanges(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	samples := mergeSeries(vals[0].(Matrix)[0])
	changes := 0
	if len(samples) == 0 {
		return enh.Out, nil
	}

	prevSample := samples[0]
	for _, curSample := range samples[1:] {
		switch {
		case prevSample.H == nil && curSample.H == nil:
			{
				if curSample.F != prevSample.F && !(math.IsNaN(curSample.F) && math.IsNaN(prevSample.F)) {
					changes++
				}
			}
		case prevSample.H != nil && curSample.H == nil, prevSample.H == nil && curSample.H != nil:
			{
				changes++
			}
		case prevSample.H != nil && curSample.H != nil:
			{
				if !curSample.H.Equals(prevSample.H) {
					changes++
				}
			}
		}

Review comment (suggestion):
As the series can actually become rather long (users can write things like `changes(foobar[1y])`), I would say we should try really hard to avoid the creation of a new `samples` slice.

At the very least, we could test if the series is really mixed and do the old thing when it is not.

However, I think it should be fairly easy to avoid `mergeSeries` altogether, doing something like

```suggestion
	floats := vals[0].(Matrix)[0].Floats
	histograms := vals[0].(Matrix)[0].Histograms
	for iFloats, iHistograms := 0; iFloats < len(floats) && iHistograms < len(histograms); {
	     // Pick the earlier sample between floats[iFloats] and histograms[iHistograms] (if any).
	     // Store it as `Sample`.
	     // Increment the respective iFloats or  iHistograms.
	     // Compare the stored `Sample` to the previous (if any) and increment `changes` accordingly.
	     // Make the stored `Sample` the previous sample.
	}
```

(Might be a bit fiddlier than sketched out above in detail, but I hope you get what I mean.)

Code after:
				curSample.H = nil
				iFloat++
			}
		case iFloat >= len(floats) || iHistogram < len(histograms) && floats[iFloat].T > histograms[iHistogram].T:
			{
				curSample.H = histograms[iHistogram].H
				iHistogram++
			}
		}
		// Skip the comparison for the first sample, just initialize prevSample.
		if iFloat+iHistogram == 1 {
			prevSample = curSample
			continue
		}
		switch {
		case prevSample.H == nil && curSample.H == nil:
			{
				if curSample.F != prevSample.F && !(math.IsNaN(curSample.F) && math.IsNaN(prevSample.F)) {
					changes++
				}
			}
		case prevSample.H != nil && curSample.H == nil, prevSample.H == nil && curSample.H != nil:
			{
				changes++
			}
		case prevSample.H != nil && curSample.H != nil:
			{
				if !curSample.H.Equals(prevSample.H) {
					changes++
				}
			}
		}
		prevSample = curSample
	}

	return append(enh.Out, Sample{F: float64(changes)}), nil
}

// label_replace function operates only on series; does not look at timestamps or values.
func (ev *evaluator) evalLabelReplace(ctx context.Context, args parser.Expressions) (parser.Value, annotations.Annotations) {
	var (
		dst      = stringFromArg(args[1])
		repl     = stringFromArg(args[2])
		src      = stringFromArg(args[3])
		regexStr = stringFromArg(args[4])
	)

	regex, err := regexp.Compile("^(?s:" + regexStr + ")$")
	if err != nil {
		panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr))
	}