Forráskód Böngészése

Merge pull request #1473 from Pokom/perf/align-step-range-queries

perf(query): Align start/end times with step
Ajay Tripathy 3 éve
szülő
commit
f7c7366822
2 módosított fájl, 182 hozzáadás és 1 törlés
  1. 23 0
      pkg/prom/query.go
  2. 159 1
      pkg/prom/query_test.go

+ 23 - 0
pkg/prom/query.go

@@ -258,9 +258,21 @@ func (ctx *Context) query(query string, t time.Time) (interface{}, v1.Warnings,
 	return toReturn, warnings, nil
 }
 
+// isRequestStepAligned will check if the start and end times are aligned with the step
+func (ctx *Context) isRequestStepAligned(start, end time.Time, step time.Duration) bool {
+	startInUnix := start.Unix()
+	endInUnix := end.Unix()
+	stepInSeconds := step.Milliseconds() / 1e3
+	return startInUnix%stepInSeconds == 0 && endInUnix%stepInSeconds == 0
+}
+
 func (ctx *Context) QueryRange(query string, start, end time.Time, step time.Duration) QueryResultsChan {
 	resCh := make(QueryResultsChan)
 
+	if !ctx.isRequestStepAligned(start, end, step) {
+		start, end = ctx.alignWindow(start, end, step)
+	}
+
 	go runQueryRange(query, start, end, step, ctx, resCh, "")
 
 	return resCh
@@ -357,6 +369,7 @@ func (ctx *Context) RawQueryRange(query string, start, end time.Time, step time.
 
 func (ctx *Context) queryRange(query string, start, end time.Time, step time.Duration) (interface{}, v1.Warnings, error) {
 	body, err := ctx.RawQueryRange(query, start, end, step)
+
 	if err != nil {
 		return nil, nil, err
 	}
@@ -382,6 +395,16 @@ func (ctx *Context) queryRange(query string, start, end time.Time, step time.Dur
 	return toReturn, warnings, nil
 }
 
+// alignWindow will update the start and end times to be aligned with the step duration.
+// Current implementation will always floor the start/end times
+func (ctx *Context) alignWindow(start time.Time, end time.Time, step time.Duration) (time.Time, time.Time) {
+	// Convert the step duration from Milliseconds to Seconds to match the Unix timestamp, which is in seconds
+	stepInSeconds := step.Milliseconds() / 1e3
+	alignedStart := (start.Unix() / stepInSeconds) * stepInSeconds
+	alignedEnd := (end.Unix() / stepInSeconds) * stepInSeconds
+	return time.Unix(alignedStart, 0).UTC(), time.Unix(alignedEnd, 0).UTC()
+}
+
 // Extracts the warnings from the resulting json if they exist (part of the prometheus response api).
 func warningsFrom(result interface{}) v1.Warnings {
 	var warnings v1.Warnings

+ 159 - 1
pkg/prom/query_test.go

@@ -1,6 +1,11 @@
 package prom
 
-import "testing"
+import (
+	"github.com/prometheus/client_golang/api"
+	"reflect"
+	"testing"
+	"time"
+)
 
 func TestWarningsFrom(t *testing.T) {
 	var results interface{}
@@ -25,3 +30,156 @@ func TestWarningsFrom(t *testing.T) {
 		t.Errorf("Unexpected second warning: %s", warnings[1])
 	}
 }
+
+func TestContext_isRequestStepAligned(t *testing.T) {
+	type fields struct {
+		Client         api.Client
+		name           string
+		errorCollector *QueryErrorCollector
+	}
+	type args struct {
+		start time.Time
+		end   time.Time
+		step  time.Duration
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		args   args
+		want   bool
+	}{
+		{
+			name:   "Test with times that are not step aligned to the hour",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 59, 30, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 59, 30, 0, time.UTC),
+				step:  time.Hour,
+			},
+			want: false,
+		},
+		{
+			name:   "Test with times that are step aligned to the hour",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			want: true,
+		},
+		{
+			name:   "Test with times where start is aligned to the hour but end is not",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 59, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			want: false,
+		},
+		{
+			name:   "Test with times where end is aligned to the hour but start is not",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 59, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := &Context{
+				Client:         tt.fields.Client,
+				name:           tt.fields.name,
+				errorCollector: tt.fields.errorCollector,
+			}
+			if got := ctx.isRequestStepAligned(tt.args.start, tt.args.end, tt.args.step); got != tt.want {
+				t.Errorf("isRequestStepAligned() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestContext_alignWindow(t *testing.T) {
+	type fields struct {
+		Client         api.Client
+		name           string
+		errorCollector *QueryErrorCollector
+	}
+	type args struct {
+		start time.Time
+		end   time.Time
+		step  time.Duration
+	}
+	tests := []struct {
+		name      string
+		fields    fields
+		args      args
+		wantStart time.Time
+		wantEnd   time.Time
+	}{
+		{
+			name:   "Do not update the start and end when step-aligned",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			wantStart: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+			wantEnd:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+		},
+		{
+			name:   "Update start to be step-aligned and leave end the same",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 59, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			wantStart: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+			wantEnd:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+		},
+		{
+			name:   "Update end to be step-aligned and leave start the same",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 59, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			wantStart: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+			wantEnd:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+		},
+		{
+			name:   "Update start and end to be step-aligned",
+			fields: fields{},
+			args: args{
+				start: time.Date(2022, 11, 7, 4, 59, 0, 0, time.UTC),
+				end:   time.Date(2022, 11, 8, 4, 59, 0, 0, time.UTC),
+				step:  time.Hour,
+			},
+			wantStart: time.Date(2022, 11, 7, 4, 0, 0, 0, time.UTC),
+			wantEnd:   time.Date(2022, 11, 8, 4, 0, 0, 0, time.UTC),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := &Context{
+				Client:         tt.fields.Client,
+				name:           tt.fields.name,
+				errorCollector: tt.fields.errorCollector,
+			}
+			got, got1 := ctx.alignWindow(tt.args.start, tt.args.end, tt.args.step)
+			if !reflect.DeepEqual(got, tt.wantStart) {
+				t.Errorf("alignWindow() got = %v, want %v", got, tt.wantStart)
+			}
+			if !reflect.DeepEqual(got1, tt.wantEnd) {
+				t.Errorf("alignWindow() got1 = %v, want %v", got1, tt.wantEnd)
+			}
+		})
+	}
+}