|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-only |
| 2 | +// Provenance-includes-location: https://github.com/prometheus/prometheus/blob/main/promql/engine.go |
| 3 | +// Provenance-includes-license: Apache-2.0 |
| 4 | +// Provenance-includes-copyright: The Prometheus Authors |
| 5 | + |
| 6 | +package aggregations |
| 7 | + |
| 8 | +import ( |
| 9 | + "math" |
| 10 | + |
| 11 | + "github.com/prometheus/prometheus/model/histogram" |
| 12 | + "github.com/prometheus/prometheus/promql" |
| 13 | + |
| 14 | + "github.com/grafana/mimir/pkg/streamingpromql/floats" |
| 15 | + "github.com/grafana/mimir/pkg/streamingpromql/functions" |
| 16 | + "github.com/grafana/mimir/pkg/streamingpromql/limiting" |
| 17 | + "github.com/grafana/mimir/pkg/streamingpromql/types" |
| 18 | +) |
| 19 | + |
| 20 | +type AvgAggregationGroup struct { |
| 21 | + floats []float64 |
| 22 | + floatMeans []float64 |
| 23 | + floatCompensatingMeans []float64 // Mean, or "compensating value" for Kahan summation. |
| 24 | + incrementalMeans []bool // True after reverting to incremental calculation of the mean value. |
| 25 | + floatPresent []bool |
| 26 | + histograms []*histogram.FloatHistogram |
| 27 | + histogramPointCount int |
| 28 | + |
| 29 | + // Keeps track of how many series we have encountered thus far for the group at this point |
| 30 | + // This is necessary to do per point (instead of just counting the groups) as a series may have |
| 31 | + // stale or non-existent values that are not added towards the count. |
| 32 | + groupSeriesCounts []float64 |
| 33 | +} |
| 34 | + |
| 35 | +func (g *AvgAggregationGroup) AccumulateSeries(data types.InstantVectorSeriesData, steps int, start int64, interval int64, memoryConsumptionTracker *limiting.MemoryConsumptionTracker, emitAnnotationFunc functions.EmitAnnotationFunc) error { |
| 36 | + defer types.PutInstantVectorSeriesData(data, memoryConsumptionTracker) |
| 37 | + if len(data.Floats) == 0 && len(data.Histograms) == 0 { |
| 38 | + // Nothing to do |
| 39 | + return nil |
| 40 | + } |
| 41 | + |
| 42 | + var err error |
| 43 | + |
| 44 | + if g.groupSeriesCounts == nil { |
| 45 | + g.groupSeriesCounts, err = types.Float64SlicePool.Get(steps, memoryConsumptionTracker) |
| 46 | + if err != nil { |
| 47 | + return err |
| 48 | + } |
| 49 | + g.groupSeriesCounts = g.groupSeriesCounts[:steps] |
| 50 | + } |
| 51 | + |
| 52 | + err = g.accumulateFloats(data, steps, start, interval, memoryConsumptionTracker) |
| 53 | + if err != nil { |
| 54 | + return err |
| 55 | + } |
| 56 | + err = g.accumulateHistograms(data, steps, start, interval, memoryConsumptionTracker, emitAnnotationFunc) |
| 57 | + if err != nil { |
| 58 | + return err |
| 59 | + } |
| 60 | + |
| 61 | + return nil |
| 62 | +} |
| 63 | + |
| 64 | +func (g *AvgAggregationGroup) accumulateFloats(data types.InstantVectorSeriesData, steps int, start int64, interval int64, memoryConsumptionTracker *limiting.MemoryConsumptionTracker) error { |
| 65 | + var err error |
| 66 | + if len(data.Floats) > 0 && g.floats == nil { |
| 67 | + // First series with float values for this group, populate it. |
| 68 | + g.floats, err = types.Float64SlicePool.Get(steps, memoryConsumptionTracker) |
| 69 | + if err != nil { |
| 70 | + return err |
| 71 | + } |
| 72 | + |
| 73 | + g.floatCompensatingMeans, err = types.Float64SlicePool.Get(steps, memoryConsumptionTracker) |
| 74 | + if err != nil { |
| 75 | + return err |
| 76 | + } |
| 77 | + |
| 78 | + g.floatPresent, err = types.BoolSlicePool.Get(steps, memoryConsumptionTracker) |
| 79 | + if err != nil { |
| 80 | + return err |
| 81 | + } |
| 82 | + |
| 83 | + g.floats = g.floats[:steps] |
| 84 | + g.floatCompensatingMeans = g.floatCompensatingMeans[:steps] |
| 85 | + g.floatPresent = g.floatPresent[:steps] |
| 86 | + } |
| 87 | + |
| 88 | + for _, p := range data.Floats { |
| 89 | + idx := (p.T - start) / interval |
| 90 | + g.groupSeriesCounts[idx]++ |
| 91 | + if !g.floatPresent[idx] { |
| 92 | + // The first point is just taken as the value |
| 93 | + g.floats[idx] = p.F |
| 94 | + g.floatPresent[idx] = true |
| 95 | + continue |
| 96 | + } |
| 97 | + |
| 98 | + if g.incrementalMeans == nil || !g.incrementalMeans[idx] { |
| 99 | + newV, newC := floats.KahanSumInc(p.F, g.floats[idx], g.floatCompensatingMeans[idx]) |
| 100 | + if !math.IsInf(newV, 0) { |
| 101 | + // The sum doesn't overflow, so we propagate it to the |
| 102 | + // group struct and continue with the regular |
| 103 | + // calculation of the mean value. |
| 104 | + g.floats[idx], g.floatCompensatingMeans[idx] = newV, newC |
| 105 | + continue |
| 106 | + } |
| 107 | + // If we are here, we know that the sum _would_ overflow. So |
| 108 | + // instead of continuing to sum up, we revert to incremental |
| 109 | + // calculation of the mean value from here on. |
| 110 | + if g.floatMeans == nil { |
| 111 | + g.floatMeans, err = types.Float64SlicePool.Get(steps, memoryConsumptionTracker) |
| 112 | + if err != nil { |
| 113 | + return err |
| 114 | + } |
| 115 | + g.floatMeans = g.floatMeans[:steps] |
| 116 | + } |
| 117 | + if g.incrementalMeans == nil { |
| 118 | + // First time we are using an incremental mean. Track which samples will be incremental. |
| 119 | + g.incrementalMeans, err = types.BoolSlicePool.Get(steps, memoryConsumptionTracker) |
| 120 | + if err != nil { |
| 121 | + return err |
| 122 | + } |
| 123 | + g.incrementalMeans = g.incrementalMeans[:steps] |
| 124 | + } |
| 125 | + g.incrementalMeans[idx] = true |
| 126 | + g.floatMeans[idx] = g.floats[idx] / (g.groupSeriesCounts[idx] - 1) |
| 127 | + g.floatCompensatingMeans[idx] /= g.groupSeriesCounts[idx] - 1 |
| 128 | + } |
| 129 | + if math.IsInf(g.floatMeans[idx], 0) { |
| 130 | + if math.IsInf(p.F, 0) && (g.floatMeans[idx] > 0) == (p.F > 0) { |
| 131 | + // The `floatMean` and `s.F` values are `Inf` of the same sign. They |
| 132 | + // can't be subtracted, but the value of `floatMean` is correct |
| 133 | + // already. |
| 134 | + continue |
| 135 | + } |
| 136 | + if !math.IsInf(p.F, 0) && !math.IsNaN(p.F) { |
| 137 | + // At this stage, the mean is an infinite. If the added |
| 138 | + // value is neither an Inf or a Nan, we can keep that mean |
| 139 | + // value. |
| 140 | + // This is required because our calculation below removes |
| 141 | + // the mean value, which would look like Inf += x - Inf and |
| 142 | + // end up as a NaN. |
| 143 | + continue |
| 144 | + } |
| 145 | + } |
| 146 | + currentMean := g.floatMeans[idx] + g.floatCompensatingMeans[idx] |
| 147 | + g.floatMeans[idx], g.floatCompensatingMeans[idx] = floats.KahanSumInc( |
| 148 | + p.F/g.groupSeriesCounts[idx]-currentMean/g.groupSeriesCounts[idx], |
| 149 | + g.floatMeans[idx], |
| 150 | + g.floatCompensatingMeans[idx], |
| 151 | + ) |
| 152 | + } |
| 153 | + return nil |
| 154 | +} |
| 155 | + |
| 156 | +func (g *AvgAggregationGroup) accumulateHistograms(data types.InstantVectorSeriesData, steps int, start int64, interval int64, memoryConsumptionTracker *limiting.MemoryConsumptionTracker, emitAnnotationFunc functions.EmitAnnotationFunc) error { |
| 157 | + var err error |
| 158 | + if len(data.Histograms) > 0 && g.histograms == nil { |
| 159 | + // First series with histogram values for this group, populate it. |
| 160 | + g.histograms, err = types.HistogramSlicePool.Get(steps, memoryConsumptionTracker) |
| 161 | + if err != nil { |
| 162 | + return err |
| 163 | + } |
| 164 | + g.histograms = g.histograms[:steps] |
| 165 | + } |
| 166 | + |
| 167 | + var lastUncopiedHistogram *histogram.FloatHistogram |
| 168 | + |
| 169 | + for i, p := range data.Histograms { |
| 170 | + idx := (p.T - start) / interval |
| 171 | + g.groupSeriesCounts[idx]++ |
| 172 | + |
| 173 | + if g.histograms[idx] == invalidCombinationOfHistograms { |
| 174 | + // We've already seen an invalid combination of histograms at this timestamp. Ignore this point. |
| 175 | + continue |
| 176 | + } |
| 177 | + |
| 178 | + if g.histograms[idx] == nil { |
| 179 | + if lastUncopiedHistogram == p.H { |
| 180 | + // We've already used this histogram for a previous point due to lookback. |
| 181 | + // Make a copy of it so we don't modify the other point. |
| 182 | + g.histograms[idx] = p.H.Copy() |
| 183 | + g.histogramPointCount++ |
| 184 | + continue |
| 185 | + } |
| 186 | + // This is the first time we have seen this histogram. |
| 187 | + // It is safe to store it and modify it later without copying, as we'll make copies above if the same histogram is used for subsequent points. |
| 188 | + g.histograms[idx] = p.H |
| 189 | + g.histogramPointCount++ |
| 190 | + lastUncopiedHistogram = p.H |
| 191 | + continue |
| 192 | + } |
| 193 | + |
| 194 | + // Check if the next point in data.Histograms is the same as the current point (due to lookback) |
| 195 | + // If it is, create a copy before modifying it. |
| 196 | + toAdd := p.H |
| 197 | + if i+1 < len(data.Histograms) && data.Histograms[i+1].H == p.H { |
| 198 | + toAdd = p.H.Copy() |
| 199 | + } |
| 200 | + |
| 201 | + _, err = toAdd.Sub(g.histograms[idx]) |
| 202 | + if err != nil { |
| 203 | + // Unable to subtract histograms (likely due to invalid combination of histograms). Make sure we don't emit a sample at this timestamp. |
| 204 | + g.histograms[idx] = invalidCombinationOfHistograms |
| 205 | + g.histogramPointCount-- |
| 206 | + |
| 207 | + if err := functions.NativeHistogramErrorToAnnotation(err, emitAnnotationFunc); err != nil { |
| 208 | + // Unknown error: we couldn't convert the error to an annotation. Give up. |
| 209 | + return err |
| 210 | + } |
| 211 | + continue |
| 212 | + } |
| 213 | + |
| 214 | + toAdd.Div(g.groupSeriesCounts[idx]) |
| 215 | + _, err = g.histograms[idx].Add(toAdd) |
| 216 | + if err != nil { |
| 217 | + // Unable to add histograms together (likely due to invalid combination of histograms). Make sure we don't emit a sample at this timestamp. |
| 218 | + g.histograms[idx] = invalidCombinationOfHistograms |
| 219 | + g.histogramPointCount-- |
| 220 | + |
| 221 | + if err := functions.NativeHistogramErrorToAnnotation(err, emitAnnotationFunc); err != nil { |
| 222 | + // Unknown error: we couldn't convert the error to an annotation. Give up. |
| 223 | + return err |
| 224 | + } |
| 225 | + continue |
| 226 | + } |
| 227 | + } |
| 228 | + return nil |
| 229 | +} |
| 230 | + |
| 231 | +// reconcileAndCountFloatPoints will return the number of points with a float present. |
| 232 | +// It also takes the opportunity whilst looping through the floats to check if there |
| 233 | +// is a conflicting Histogram present. If both are present, an empty vector should |
| 234 | +// be returned. So this method removes the float+histogram where they conflict. |
| 235 | +func (g *AvgAggregationGroup) reconcileAndCountFloatPoints() (int, bool) { |
| 236 | + // It would be possible to calculate the number of points when constructing |
| 237 | + // the series groups. However, it requires checking each point at each input |
| 238 | + // series which is more costly than looping again here and just checking each |
| 239 | + // point of the already grouped series. |
| 240 | + // See: https://github.com/grafana/mimir/pull/8442 |
| 241 | + // We also take two different approaches here: One with extra checks if we |
| 242 | + // have both Floats and Histograms present, and one without these checks |
| 243 | + // so we don't have to do it at every point. |
| 244 | + floatPointCount := 0 |
| 245 | + haveMixedFloatsAndHistograms := false |
| 246 | + if len(g.floatPresent) > 0 && len(g.histograms) > 0 { |
| 247 | + for idx, present := range g.floatPresent { |
| 248 | + if present { |
| 249 | + if g.histograms[idx] != nil { |
| 250 | + // If a mix of histogram samples and float samples, the corresponding vector element is removed from the output vector entirely |
| 251 | + // and a warning annotation is emitted. |
| 252 | + g.floatPresent[idx] = false |
| 253 | + g.histograms[idx] = nil |
| 254 | + g.histogramPointCount-- |
| 255 | + |
| 256 | + haveMixedFloatsAndHistograms = true |
| 257 | + } else { |
| 258 | + floatPointCount++ |
| 259 | + } |
| 260 | + } |
| 261 | + } |
| 262 | + } else { |
| 263 | + for _, p := range g.floatPresent { |
| 264 | + if p { |
| 265 | + floatPointCount++ |
| 266 | + } |
| 267 | + } |
| 268 | + } |
| 269 | + return floatPointCount, haveMixedFloatsAndHistograms |
| 270 | +} |
| 271 | + |
| 272 | +func (g *AvgAggregationGroup) ComputeOutputSeries(start int64, interval int64, memoryConsumptionTracker *limiting.MemoryConsumptionTracker) (types.InstantVectorSeriesData, bool, error) { |
| 273 | + floatPointCount, hasMixedData := g.reconcileAndCountFloatPoints() |
| 274 | + var floatPoints []promql.FPoint |
| 275 | + var err error |
| 276 | + |
| 277 | + if floatPointCount > 0 { |
| 278 | + floatPoints, err = types.FPointSlicePool.Get(floatPointCount, memoryConsumptionTracker) |
| 279 | + if err != nil { |
| 280 | + return types.InstantVectorSeriesData{}, hasMixedData, err |
| 281 | + } |
| 282 | + |
| 283 | + for i, havePoint := range g.floatPresent { |
| 284 | + if havePoint { |
| 285 | + t := start + int64(i)*interval |
| 286 | + var f float64 |
| 287 | + if g.incrementalMeans != nil && g.incrementalMeans[i] { |
| 288 | + f = g.floatMeans[i] + g.floatCompensatingMeans[i] |
| 289 | + } else { |
| 290 | + f = (g.floats[i] + g.floatCompensatingMeans[i]) / g.groupSeriesCounts[i] |
| 291 | + } |
| 292 | + floatPoints = append(floatPoints, promql.FPoint{T: t, F: f}) |
| 293 | + } |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + var histogramPoints []promql.HPoint |
| 298 | + if g.histogramPointCount > 0 { |
| 299 | + histogramPoints, err = types.HPointSlicePool.Get(g.histogramPointCount, memoryConsumptionTracker) |
| 300 | + if err != nil { |
| 301 | + return types.InstantVectorSeriesData{}, hasMixedData, err |
| 302 | + } |
| 303 | + |
| 304 | + for i, h := range g.histograms { |
| 305 | + if h != nil && h != invalidCombinationOfHistograms { |
| 306 | + t := start + int64(i)*interval |
| 307 | + histogramPoints = append(histogramPoints, promql.HPoint{T: t, H: h.Compact(0)}) |
| 308 | + } |
| 309 | + } |
| 310 | + } |
| 311 | + |
| 312 | + types.Float64SlicePool.Put(g.floats, memoryConsumptionTracker) |
| 313 | + types.Float64SlicePool.Put(g.floatMeans, memoryConsumptionTracker) |
| 314 | + types.Float64SlicePool.Put(g.floatCompensatingMeans, memoryConsumptionTracker) |
| 315 | + types.BoolSlicePool.Put(g.floatPresent, memoryConsumptionTracker) |
| 316 | + types.HistogramSlicePool.Put(g.histograms, memoryConsumptionTracker) |
| 317 | + types.BoolSlicePool.Put(g.incrementalMeans, memoryConsumptionTracker) |
| 318 | + types.Float64SlicePool.Put(g.groupSeriesCounts, memoryConsumptionTracker) |
| 319 | + |
| 320 | + return types.InstantVectorSeriesData{Floats: floatPoints, Histograms: histogramPoints}, hasMixedData, nil |
| 321 | +} |
0 commit comments