diff --git a/docs/api.md b/docs/api.md index ba5c824a64267..401746d8585ae 100644 --- a/docs/api.md +++ b/docs/api.md @@ -204,7 +204,7 @@ accepts the following query parameters in the URL: - `limit`: The max number of entries to return - `start`: The start time for the query as a nanosecond Unix epoch. Defaults to one hour ago. - `end`: The start time for the query as a nanosecond Unix epoch. Defaults to now. -- `step`: Query resolution step width in seconds. Defaults to a dynamic value based on `start` and `end`. +- `step`: Query resolution step width in `duration` format or float number of seconds. `duration` refers to Prometheus duration strings of the form `[0-9]+[smhdwy]`. For example, 5m refers to a duration of 5 minutes. Defaults to a dynamic value based on `start` and `end`. - `direction`: Determines the sort order of logs. Supported values are `forward` or `backward`. Defaults to `backward.` Requests against this endpoint require Loki to query the index store in order to diff --git a/pkg/loghttp/params.go b/pkg/loghttp/params.go index 419a73f79f0c6..948b0b7a65c28 100644 --- a/pkg/loghttp/params.go +++ b/pkg/loghttp/params.go @@ -8,6 +8,9 @@ import ( "strings" "time" + "github.com/pkg/errors" + "github.com/prometheus/common/model" + "github.com/grafana/loki/pkg/logproto" ) @@ -50,11 +53,22 @@ func bounds(r *http.Request) (time.Time, time.Time, error) { } func step(r *http.Request, start, end time.Time) (time.Duration, error) { - s, err := parseInt(r.URL.Query().Get("step"), defaultQueryRangeStep(start, end)) - if err != nil { - return 0, err + value := r.URL.Query().Get("step") + if value == "" { + return time.Duration(defaultQueryRangeStep(start, end)) * time.Second, nil + } + + if d, err := strconv.ParseFloat(value, 64); err == nil { + ts := d * float64(time.Second) + if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) { + return 0, errors.Errorf("cannot parse %q to a valid duration. It overflows int64", value) + } + return time.Duration(ts), nil + } + if d, err := model.ParseDuration(value); err == nil { + return time.Duration(d), nil } - return time.Duration(s) * time.Second, nil + return 0, errors.Errorf("cannot parse %q to a valid duration", value) } // defaultQueryRangeStep returns the default step used in the query range API, diff --git a/pkg/loghttp/params_test.go b/pkg/loghttp/params_test.go index b6559bd53ba98..7146a92e55dc7 100644 --- a/pkg/loghttp/params_test.go +++ b/pkg/loghttp/params_test.go @@ -63,7 +63,7 @@ func TestHttp_ParseRangeQuery_Step(t *testing.T) { Direction: logproto.BACKWARD, }, }, - "should use the input step parameter if provided": { + "should use the input step parameter if provided as an integer": { reqPath: "/loki/api/v1/query_range?query={}&start=0&end=3600000000000&step=5", expected: &RangeQuery{ Query: "{}", @@ -74,6 +74,50 @@ func TestHttp_ParseRangeQuery_Step(t *testing.T) { Direction: logproto.BACKWARD, }, }, + "should use the input step parameter if provided as a float without decimals": { + reqPath: "/loki/api/v1/query_range?query={}&start=0&end=3600000000000&step=5.000", + expected: &RangeQuery{ + Query: "{}", + Start: time.Unix(0, 0), + End: time.Unix(3600, 0), + Step: 5 * time.Second, + Limit: 100, + Direction: logproto.BACKWARD, + }, + }, + "should use the input step parameter if provided as a float with decimals": { + reqPath: "/loki/api/v1/query_range?query={}&start=0&end=3600000000000&step=5.500", + expected: &RangeQuery{ + Query: "{}", + Start: time.Unix(0, 0), + End: time.Unix(3600, 0), + Step: 5.5 * 1e9, + Limit: 100, + Direction: logproto.BACKWARD, + }, + }, + "should use the input step parameter if provided as a duration in seconds": { + reqPath: "/loki/api/v1/query_range?query={}&start=0&end=3600000000000&step=5s", + expected: &RangeQuery{ + Query: "{}", + Start: time.Unix(0, 0), + End: time.Unix(3600, 0), + Step: 5 * time.Second, + Limit: 100, + Direction: logproto.BACKWARD, + }, + }, + "should use the input step parameter if provided as a duration in days": { + reqPath: "/loki/api/v1/query_range?query={}&start=0&end=3600000000000&step=5d", + expected: &RangeQuery{ + Query: "{}", + Start: time.Unix(0, 0), + End: time.Unix(3600, 0), + Step: 5 * 24 * 3600 * time.Second, + Limit: 100, + Direction: logproto.BACKWARD, + }, + }, } for testName, testData := range tests {