From 36636c1f6f06028c8a57680638d9855c728e7a2b Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Sun, 7 Jul 2019 19:44:26 +0300 Subject: [PATCH] app/vmselect/prometheus: handle `minTime` and `maxTime` values that may be set by Promxy or Prometheus client Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/88 --- app/vmselect/prometheus/prometheus.go | 15 +++++ app/vmselect/prometheus/prometheus_test.go | 78 ++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 app/vmselect/prometheus/prometheus_test.go diff --git a/app/vmselect/prometheus/prometheus.go b/app/vmselect/prometheus/prometheus.go index e1b4548422..e97c887235 100644 --- a/app/vmselect/prometheus/prometheus.go +++ b/app/vmselect/prometheus/prometheus.go @@ -627,6 +627,14 @@ func getTime(r *http.Request, argKey string, defaultValue int64) (int64, error) // Try parsing string format t, err := time.Parse(time.RFC3339, argValue) if err != nil { + // Handle Prometheus'-provided minTime and maxTime. + // See https://github.com/prometheus/client_golang/issues/614 + switch argValue { + case prometheusMinTimeFormatted: + return minTimeMsecs, nil + case prometheusMaxTimeFormatted: + return maxTimeMsecs, nil + } return 0, fmt.Errorf("cannot parse %q=%q: %s", argKey, argValue, err) } secs = float64(t.UnixNano()) / 1e9 @@ -638,6 +646,13 @@ func getTime(r *http.Request, argKey string, defaultValue int64) (int64, error) return msecs, nil } +var ( + // These constants were obtained from https://github.com/prometheus/prometheus/blob/91d7175eaac18b00e370965f3a8186cc40bf9f55/web/api/v1/api.go#L442 + // See https://github.com/prometheus/client_golang/issues/614 for details. + prometheusMinTimeFormatted = time.Unix(math.MinInt64/1000+62135596801, 0).UTC().Format(time.RFC3339Nano) + prometheusMaxTimeFormatted = time.Unix(math.MaxInt64/1000-62135596801, 999999999).UTC().Format(time.RFC3339Nano) +) + const ( // These values prevent from overflow when storing msec-precision time in int64. minTimeMsecs = int64(-1<<63) / 1e6 diff --git a/app/vmselect/prometheus/prometheus_test.go b/app/vmselect/prometheus/prometheus_test.go new file mode 100644 index 0000000000..a4a5cb517d --- /dev/null +++ b/app/vmselect/prometheus/prometheus_test.go @@ -0,0 +1,78 @@ +package prometheus + +import ( + "fmt" + "testing" + "net/http" + "net/url" +) + +func TestGetTimeSuccess(t *testing.T) { + f := func(s string, timestampExpected int64) { + t.Helper() + urlStr := fmt.Sprintf("http://foo.bar/baz?s=%s", url.QueryEscape(s)) + r, err := http.NewRequest("GET", urlStr, nil) + if err != nil { + t.Fatalf("unexpected error in NewRequest: %s", err) + } + + // Verify defaultValue + ts, err := getTime(r, "foo", 123) + if err != nil { + t.Fatalf("unexpected error when obtaining default time from getTime(%q): %s", s, err) + } + if ts != 123 { + t.Fatalf("unexpected default value for getTime(%q); got %d; want %d", s, ts, 123) + } + + // Verify timestampExpected + ts, err = getTime(r, "s", 123) + if err != nil { + t.Fatalf("unexpected error in getTime(%q): %s", s, err) + } + if ts != timestampExpected { + t.Fatalf("unexpected timestamp for getTime(%q); got %d; want %d", s, ts, timestampExpected) + } + } + + f("2019-07-07T20:01:02Z", 1562529662000) + f("2019-07-07T20:47:40+03:00", 1562521660000) + f("-292273086-05-16T16:47:06Z", -9223372036854) + f("292277025-08-18T07:12:54.999999999Z", 9223372036854) + f("1562529662.324", 1562529662324) + f("-9223372036.854", -9223372036854) +} + +func TestGetTimeError(t *testing.T) { + f := func(s string) { + t.Helper() + urlStr := fmt.Sprintf("http://foo.bar/baz?s=%s", url.QueryEscape(s)) + r, err := http.NewRequest("GET", urlStr, nil) + if err != nil { + t.Fatalf("unexpected error in NewRequest: %s", err) + } + + // Verify defaultValue + ts, err := getTime(r, "foo", 123) + if err != nil { + t.Fatalf("unexpected error when obtaining default time from getTime(%q): %s", s, err) + } + if ts != 123 { + t.Fatalf("unexpected default value for getTime(%q); got %d; want %d", s, ts, 123) + } + + // Verify timestampExpected + ts, err = getTime(r, "s", 123) + if err == nil { + t.Fatalf("expecting non-nil error in getTime(%q)", s) + } + } + + f("foo") + f("2019-07-07T20:01:02Zisdf") + f("2019-07-07T20:47:40+03:00123") + f("-292273086-05-16T16:47:07Z") + f("292277025-08-18T07:12:54.999999998Z") + f("-9223372036.855") + f("9223372036.855") +}