From 9448eaeaf755c24aabb19f48e90e1a1834d96a5c Mon Sep 17 00:00:00 2001 From: Lukasz Mierzwa Date: Fri, 28 Oct 2022 10:54:25 +0100 Subject: [PATCH] Fix range merges --- internal/checks/alerts_count.go | 1 + internal/checks/alerts_count_test.go | 7 ++ internal/checks/base_test.go | 5 +- internal/promapi/range.go | 2 +- internal/promapi/range_normalize.go | 10 ++- internal/promapi/range_normalize_test.go | 98 ++++++++++++++++++++++-- 6 files changed, 111 insertions(+), 12 deletions(-) diff --git a/internal/checks/alerts_count.go b/internal/checks/alerts_count.go index c4a8d379..2639f27f 100644 --- a/internal/checks/alerts_count.go +++ b/internal/checks/alerts_count.go @@ -73,6 +73,7 @@ func (c AlertsCheck) Check(ctx context.Context, rule parser.Rule, entries []disc if err != nil { log.Warn().Err(err).Str("name", c.prom.Name()).Msg("Cannot detect Prometheus uptime gaps") } else { + // FIXME: gaps are not used qr.Series.FindGaps(promUptime.Series, qr.Series.From, qr.Series.Until) } } diff --git a/internal/checks/alerts_count_test.go b/internal/checks/alerts_count_test.go index f639e63f..5e5e447b 100644 --- a/internal/checks/alerts_count_test.go +++ b/internal/checks/alerts_count_test.go @@ -132,42 +132,49 @@ func TestAlertsCountCheck(t *testing.T) { }, resp: matrixResponse{ samples: []*model.SampleStream{ + // 7m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-24), time.Now().Add(time.Hour*-24).Add(time.Minute*6), time.Minute, ), + // 7m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-23), time.Now().Add(time.Hour*-23).Add(time.Minute*6), time.Minute, ), + // 2m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-22), time.Now().Add(time.Hour*-22).Add(time.Minute), time.Minute, ), + // 17m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-21), time.Now().Add(time.Hour*-21).Add(time.Minute*16), time.Minute, ), + // 37m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-20), time.Now().Add(time.Hour*-20).Add(time.Minute*36), time.Minute, ), + // 37m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-19), time.Now().Add(time.Hour*-19).Add(time.Minute*36), time.Minute, ), + // 2h1m generateSampleStream( map[string]string{"job": "foo"}, time.Now().Add(time.Hour*-10), diff --git a/internal/checks/base_test.go b/internal/checks/base_test.go index 3d2852db..0d13ab54 100644 --- a/internal/checks/base_test.go +++ b/internal/checks/base_test.go @@ -454,7 +454,7 @@ var ( samples: []*model.SampleStream{ generateSampleStream( map[string]string{}, - time.Now().Add(time.Hour*24), + time.Now().Add(time.Hour*-24), time.Now(), time.Minute*5, ), @@ -500,6 +500,9 @@ func generateSampleWithValue(labels map[string]string, val float64) *model.Sampl } func generateSampleStream(labels map[string]string, from, until time.Time, step time.Duration) (s *model.SampleStream) { + if from.After(until) { + panic(fmt.Sprintf("generateSampleStream() got from > until: %s ~ %s", from.UTC().Format(time.RFC3339), until.UTC().Format(time.RFC3339))) + } metric := model.Metric{} for k, v := range labels { metric[model.LabelName(k)] = model.LabelValue(v) diff --git a/internal/promapi/range.go b/internal/promapi/range.go index a498be26..49907396 100644 --- a/internal/promapi/range.go +++ b/internal/promapi/range.go @@ -186,7 +186,7 @@ func (p *Prometheus) RangeQuery(ctx context.Context, expr string, params RangeQu wg.Done() } if len(merged.Series.Ranges) > 1 { - merged.Series.Ranges = MergeRanges(merged.Series.Ranges) + merged.Series.Ranges = MergeRanges(merged.Series.Ranges, step) } if lastErr != nil { diff --git a/internal/promapi/range_normalize.go b/internal/promapi/range_normalize.go index 2aeeae9c..33c3c90e 100644 --- a/internal/promapi/range_normalize.go +++ b/internal/promapi/range_normalize.go @@ -123,7 +123,7 @@ func (str *SeriesTimeRanges) FindGaps(baseline SeriesTimeRanges, from, until tim } // merge [t1:t2] [t2:t3] together -func MergeRanges(dst MetricTimeRanges) MetricTimeRanges { +func MergeRanges(dst MetricTimeRanges, step time.Duration) MetricTimeRanges { sort.Stable(dst) toPurge := map[int]struct{}{} @@ -139,7 +139,13 @@ func MergeRanges(dst MetricTimeRanges) MetricTimeRanges { if _, ok = toPurge[j]; ok { continue } - if dst[i].Start.Before(dst[j].Start) && !dst[i].End.Before(dst[j].Start) && !dst[i].End.After(dst[j].End) { + // 1. Merge two ranges next to each other + // [s1 ~ e1] + // [s2 - e2] + // 2. Merge two overlapping ranges + // [s1 ~ e1] + // [s2 ~ e2] + if dst[i].Start.Before(dst[j].Start) && !dst[i].End.Before(dst[j].Start.Add(step*-1)) { dst[i].End = dst[j].End toPurge[j] = struct{}{} } diff --git a/internal/promapi/range_normalize_test.go b/internal/promapi/range_normalize_test.go index 36f3dbb5..d66ecae7 100644 --- a/internal/promapi/range_normalize_test.go +++ b/internal/promapi/range_normalize_test.go @@ -225,6 +225,84 @@ func TestAppendSampleToRanges(t *testing.T) { }, }, }, + { + in: nil, + samples: []model.SampleStream{ + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T09:14:59Z"), timeParse("2022-10-27T09:20:59Z"), time.Minute), + }, + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T10:14:59Z"), timeParse("2022-10-27T10:20:59Z"), time.Minute), + }, + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T11:14:59Z"), timeParse("2022-10-27T11:15:59Z"), time.Minute), + }, + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T12:14:59Z"), timeParse("2022-10-27T12:30:59Z"), time.Minute), + }, + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T13:14:59Z"), timeParse("2022-10-27T13:50:59Z"), time.Minute), + }, + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T14:14:59Z"), timeParse("2022-10-27T14:50:59Z"), time.Minute), + }, + { + Metric: model.Metric{"instance": "1"}, + Values: generateSamples(timeParse("2022-10-27T23:14:59Z"), timeParse("2022-10-28T01:14:59Z"), time.Minute), + }, + }, + step: time.Minute, + out: []promapi.MetricTimeRange{ + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T09:14:59Z"), + End: timeParse("2022-10-27T09:21:59Z"), + }, + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T10:14:59Z"), + End: timeParse("2022-10-27T10:21:59Z"), + }, + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T11:14:59Z"), + End: timeParse("2022-10-27T11:16:59Z"), + }, + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T12:14:59Z"), + End: timeParse("2022-10-27T12:31:59Z"), + }, + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T13:14:59Z"), + End: timeParse("2022-10-27T13:51:59Z"), + }, + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T14:14:59Z"), + End: timeParse("2022-10-27T14:51:59Z"), + }, + { + Fingerprint: labels.FromStrings("instance", "1").Hash(), + Labels: labels.FromStrings("instance", "1"), + Start: timeParse("2022-10-27T23:14:59Z"), + End: timeParse("2022-10-28T01:15:59Z"), + }, + }, + }, } for i, tc := range testCases { @@ -233,7 +311,7 @@ func TestAppendSampleToRanges(t *testing.T) { lset := promapi.MetricToLabels(s.Metric) tc.in = promapi.AppendSampleToRanges(tc.in, lset, s.Values, tc.step) } - tc.in = promapi.MergeRanges(tc.in) + tc.in = promapi.MergeRanges(tc.in, tc.step) sort.Stable(tc.in) require.Equal(t, printRange(tc.out), printRange(tc.in)) }) @@ -242,8 +320,9 @@ func TestAppendSampleToRanges(t *testing.T) { func TestMergeRanges(t *testing.T) { type testCaseT struct { - in promapi.MetricTimeRanges - out promapi.MetricTimeRanges + in promapi.MetricTimeRanges + out promapi.MetricTimeRanges + step time.Duration } timeParse := func(s string) time.Time { @@ -264,12 +343,14 @@ func TestMergeRanges(t *testing.T) { testCases := []testCaseT{ { - in: nil, - out: nil, + in: nil, + out: nil, + step: time.Minute, }, { - in: promapi.MetricTimeRanges{}, - out: promapi.MetricTimeRanges{}, + in: promapi.MetricTimeRanges{}, + out: promapi.MetricTimeRanges{}, + step: time.Minute, }, { in: promapi.MetricTimeRanges{ @@ -299,12 +380,13 @@ func TestMergeRanges(t *testing.T) { out: promapi.MetricTimeRanges{ {Fingerprint: labels.EmptyLabels().Hash(), Labels: labels.EmptyLabels(), Start: timeParse("2022-10-19T10:50:44Z"), End: timeParse("2022-10-26T10:55:44Z")}, }, + step: time.Minute, }, } for i, tc := range testCases { t.Run(strconv.Itoa(i), func(t *testing.T) { - out := promapi.MergeRanges(tc.in) + out := promapi.MergeRanges(tc.in, tc.step) sort.Stable(out) require.Equal(t, printRange(tc.out), printRange(out)) })