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

Code before:
	}
	if len(samples.Histograms) > 0 {
		metricName := samples.Metric.Get(labels.MetricName)
		annos.Add(annotations.NewHistogramIgnoredInMixedRangeInfo(metricName, args[0].PositionRange()))
	}
	return aggrOverTime(vals, enh, func(s Series) float64 {
		values := make(vectorByValueHeap, 0, len(s.Floats))
		for _, f := range s.Floats {
			values = append(values, Sample{F: f.F})
		}
		median := quantile(0.5, values)
		values = make(vectorByValueHeap, 0, len(s.Floats))
		for _, f := range s.Floats {
			values = append(values, Sample{F: math.Abs(f.F - median)})
		}
		return quantile(0.5, values)
	}), annos
}

// === ts_of_last_over_time(Matrix parser.ValueTypeMatrix) (Vector, Notes)  ===
func funcTsOfLastOverTime(vals []parser.Value, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	el := vals[0].(Matrix)[0]

	var tf float64
	if len(el.Floats) > 0 {
		tf = float64(el.Floats[len(el.Floats)-1].T / 1000)
	}

	var th float64
	if len(el.Histograms) > 0 {
		th = float64(el.Floats[len(el.Floats)-1].T / 1000)
	}

	return append(enh.Out, Sample{
		Metric: el.Metric,
		F:      max(tf, th),
	}), nil
}

// === ts_of_max_over_time(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcTsOfMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	return compareOverTime(vals, args, enh, func(cur, maxVal float64) bool {
		return (cur >= maxVal) || math.IsNaN(maxVal)
	}, true)
}

// === ts_of_min_over_time(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcTsOfMinOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	return compareOverTime(vals, args, enh, func(cur, maxVal float64) bool {
		return (cur <= maxVal) || math.IsNaN(maxVal)
	}, true)

Review comment (suggestion):
```suggestion
		tf = float64(el.Floats[len(el.Floats)-1].T) / 1000
```

Otherwise everything will be rounded down to full seconds. 

It would be good to cover this case in the tests.

Code after:
	}
	if len(samples.Histograms) > 0 {
		metricName := samples.Metric.Get(labels.MetricName)
		annos.Add(annotations.NewHistogramIgnoredInMixedRangeInfo(metricName, args[0].PositionRange()))
	}
	return aggrOverTime(vals, enh, func(s Series) float64 {
		values := make(vectorByValueHeap, 0, len(s.Floats))
		for _, f := range s.Floats {
			values = append(values, Sample{F: f.F})
		}
		median := quantile(0.5, values)
		values = make(vectorByValueHeap, 0, len(s.Floats))
		for _, f := range s.Floats {
			values = append(values, Sample{F: math.Abs(f.F - median)})
		}
		return quantile(0.5, values)
	}), annos
}

// === ts_of_last_over_time(Matrix parser.ValueTypeMatrix) (Vector, Notes)  ===
func funcTsOfLastOverTime(vals []parser.Value, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	el := vals[0].(Matrix)[0]

	var tf int64
	if len(el.Floats) > 0 {
		tf = el.Floats[len(el.Floats)-1].T
	}

	var th int64
	if len(el.Histograms) > 0 {
		th = el.Floats[len(el.Floats)-1].T
	}

	return append(enh.Out, Sample{
		Metric: el.Metric,
		F:      float64(max(tf, th)) / 1000,
	}), nil
}

// === ts_of_max_over_time(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcTsOfMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	return compareOverTime(vals, args, enh, func(cur, maxVal float64) bool {
		return (cur >= maxVal) || math.IsNaN(maxVal)
	}, true)
}

// === ts_of_min_over_time(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcTsOfMinOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
	return compareOverTime(vals, args, enh, func(cur, maxVal float64) bool {
		return (cur <= maxVal) || math.IsNaN(maxVal)
	}, true)