Improve timestamp/duration parsing in query API.

Don't handle `0` as a special timestamp value for "now" anymore, except
in the `QueryRange()` case, where existing API consumers still expect
`0` to mean "now".

Also, properly return errors now for malformed timestamp/duration
float values.
This commit is contained in:
Julius Volz 2015-03-21 16:58:45 +01:00
parent 8a4acefd66
commit cb816ea14a

View file

@ -40,6 +40,26 @@ func setAccessControlHeaders(w http.ResponseWriter) {
w.Header().Set("Access-Control-Expose-Headers", "Date") w.Header().Set("Access-Control-Expose-Headers", "Date")
} }
func parseTimestampOrNow(t string) (clientmodel.Timestamp, error) {
if t == "" {
return clientmodel.Now(), nil
} else {
tFloat, err := strconv.ParseFloat(t, 64)
if err != nil {
return 0, err
}
return clientmodel.TimestampFromUnixNano(int64(tFloat) * int64(time.Second/time.Nanosecond)), nil
}
}
func parseDuration(d string) (time.Duration, error) {
dFloat, err := strconv.ParseFloat(d, 64)
if err != nil {
return 0, err
}
return time.Duration(dFloat) * (time.Second / time.Nanosecond), nil
}
// Query handles the /api/query endpoint. // Query handles the /api/query endpoint.
func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) { func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) {
setAccessControlHeaders(w) setAccessControlHeaders(w)
@ -47,13 +67,11 @@ func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) {
params := httputils.GetQueryParams(r) params := httputils.GetQueryParams(r)
expr := params.Get("expr") expr := params.Get("expr")
asText := params.Get("asText") asText := params.Get("asText")
tsFloat, _ := strconv.ParseFloat(params.Get("timestamp"), 64)
var timestamp clientmodel.Timestamp timestamp, err := parseTimestampOrNow(params.Get("timestamp"))
if tsFloat == 0 { if err != nil {
timestamp = clientmodel.Now() http.Error(w, fmt.Sprintf("invalid query timestamp %s", err), http.StatusBadRequest)
} else { return
timestamp = clientmodel.TimestampFromUnixNano(int64(tsFloat) * int64(time.Second/time.Nanosecond))
} }
var format ast.OutputFormat var format ast.OutputFormat
@ -86,14 +104,30 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
params := httputils.GetQueryParams(r) params := httputils.GetQueryParams(r)
expr := params.Get("expr") expr := params.Get("expr")
// Input times and durations are in seconds and get converted to nanoseconds. duration, err := parseDuration(params.Get("range"))
endFloat, _ := strconv.ParseFloat(params.Get("end"), 64) if err != nil {
durationFloat, _ := strconv.ParseFloat(params.Get("range"), 64) http.Error(w, fmt.Sprintf("invalid query range: %s", err), http.StatusBadRequest)
stepFloat, _ := strconv.ParseFloat(params.Get("step"), 64) return
nanosPerSecond := int64(time.Second / time.Nanosecond) }
end := int64(endFloat) * nanosPerSecond
duration := int64(durationFloat) * nanosPerSecond step, err := parseDuration(params.Get("step"))
step := int64(stepFloat) * nanosPerSecond if err != nil {
http.Error(w, fmt.Sprintf("invalid query resolution: %s", err), http.StatusBadRequest)
return
}
end, err := parseTimestampOrNow(params.Get("end"))
if err != nil {
http.Error(w, fmt.Sprintf("invalid query timestamp: %s", err), http.StatusBadRequest)
return
}
// TODO(julius): Remove this special-case handling a while after PromDash and
// other API consumers have been changed to no longer set "end=0" for setting
// the current time as the end time. Instead, the "end" parameter should
// simply be omitted or set to an empty string for that case.
if end == 0 {
end = clientmodel.Now()
}
exprNode, err := rules.LoadExprFromString(expr) exprNode, err := rules.LoadExprFromString(expr)
if err != nil { if err != nil {
@ -105,18 +139,6 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
return return
} }
if end == 0 {
end = clientmodel.Now().UnixNano()
}
if step <= 0 {
step = nanosPerSecond
}
if end-duration < 0 {
duration = end
}
// For safety, limit the number of returned points per timeseries. // For safety, limit the number of returned points per timeseries.
// This is sufficient for 60s resolution for a week or 1h resolution for a year. // This is sufficient for 60s resolution for a week or 1h resolution for a year.
if duration/step > 11000 { if duration/step > 11000 {
@ -125,15 +147,15 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
} }
// Align the start to step "tick" boundary. // Align the start to step "tick" boundary.
end -= end % step end = end.Add(-time.Duration(end.UnixNano() % int64(step)))
queryStats := stats.NewTimerGroup() queryStats := stats.NewTimerGroup()
matrix, err := ast.EvalVectorRange( matrix, err := ast.EvalVectorRange(
exprNode.(ast.VectorNode), exprNode.(ast.VectorNode),
clientmodel.TimestampFromUnixNano(end-duration), end.Add(-duration),
clientmodel.TimestampFromUnixNano(end), end,
time.Duration(step), step,
serv.Storage, serv.Storage,
queryStats) queryStats)
if err != nil { if err != nil {