diff --git a/promql/engine.go b/promql/engine.go index a2d6ae8195..b9775d2101 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -314,6 +314,8 @@ func (ng *Engine) exec(ctx context.Context, q *query) (Value, error) { ctx, cancel := context.WithTimeout(ctx, ng.options.Timeout) q.cancel = cancel + execTimer := q.stats.GetTimer(stats.ExecTotalTime).Start() + defer execTimer.Stop() queueTimer := q.stats.GetTimer(stats.ExecQueueTime).Start() if err := ng.gate.Start(ctx); err != nil { diff --git a/util/stats/query_stats.go b/util/stats/query_stats.go index 3d7ad0e835..8c75c6568a 100644 --- a/util/stats/query_stats.go +++ b/util/stats/query_stats.go @@ -25,6 +25,7 @@ const ( InnerEvalTime ResultAppendTime ExecQueueTime + ExecTotalTime ) // Return a string representation of a QueryTiming identifier. @@ -42,7 +43,47 @@ func (s QueryTiming) String() string { return "Result append time" case ExecQueueTime: return "Exec queue wait time" + case ExecTotalTime: + return "Exec total time" default: return "Unknown query timing" } } + +// QueryStats with all query timers mapped to durations. +type QueryStats struct { + TotalEvalTime float64 `json:"totalEvalTime"` + ResultSortTime float64 `json:"resultSortTime"` + QueryPreparationTime float64 `json:"queryPreparationTime"` + InnerEvalTime float64 `json:"innerEvalTime"` + ResultAppendTime float64 `json:"resultAppendTime"` + ExecQueueTime float64 `json:"execQueueTime"` + ExecTotalTime float64 `json:"execTotalTime"` +} + +// MakeQueryStats makes a QueryStats struct with all QueryTimings found in the +// given TimerGroup. +func MakeQueryStats(tg *TimerGroup) *QueryStats { + var qs QueryStats + + for s, timer := range tg.timers { + switch s { + case TotalEvalTime: + qs.TotalEvalTime = timer.Duration() + case ResultSortTime: + qs.ResultSortTime = timer.Duration() + case QueryPreparationTime: + qs.QueryPreparationTime = timer.Duration() + case InnerEvalTime: + qs.InnerEvalTime = timer.Duration() + case ResultAppendTime: + qs.ResultAppendTime = timer.Duration() + case ExecQueueTime: + qs.ExecQueueTime = timer.Duration() + case ExecTotalTime: + qs.ExecTotalTime = timer.Duration() + } + } + + return &qs +} diff --git a/util/stats/timer.go b/util/stats/timer.go index 3d3ee73097..75f5868e6b 100644 --- a/util/stats/timer.go +++ b/util/stats/timer.go @@ -45,6 +45,11 @@ func (t *Timer) ElapsedTime() time.Duration { return time.Since(t.start) } +// Duration returns the duration value of the timer in seconds. +func (t *Timer) Duration() float64 { + return t.duration.Seconds() +} + // Return a string representation of the Timer. func (t *Timer) String() string { return fmt.Sprintf("%s: %s", t.name, t.duration) diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 122b406845..d1b0588477 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -38,6 +38,7 @@ import ( "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage/remote" "github.com/prometheus/prometheus/util/httputil" + "github.com/prometheus/prometheus/util/stats" ) type status string @@ -172,6 +173,7 @@ func (api *API) Register(r *route.Router) { type queryData struct { ResultType promql.ValueType `json:"resultType"` Result promql.Value `json:"result"` + Stats *stats.QueryStats `json:"stats,omitempty"` } func (api *API) options(r *http.Request) (interface{}, *apiError) { @@ -284,9 +286,16 @@ func (api *API) queryRange(r *http.Request) (interface{}, *apiError) { return nil, &apiError{errorExec, res.Err} } + // Optional stats field in response if parameter "stats" is not empty. + var qs *stats.QueryStats + if r.FormValue("stats") != "" { + qs = stats.MakeQueryStats(qry.Stats()) + } + return &queryData{ ResultType: res.Value.Type(), Result: res.Value, + Stats: qs, }, nil }