From 0e2f1fe3f5a1ea0a79bedc59101270bd5cf6ffc5 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 13 Jan 2021 15:20:25 +0100 Subject: [PATCH] Metrics API: Use jsoniter for JSON encoding (#30250) * QueryMetricsV2: Stream response Signed-off-by: Arve Knudsen * API: Use jsoniter instead of standard JSON package Signed-off-by: Arve Knudsen --- go.mod | 1 + pkg/api/common.go | 90 ++++++---------------------------- pkg/api/metrics.go | 14 +++--- pkg/api/response.go | 116 ++++++++++++++++++++++++++++++++++++++++++++ pkg/tsdb/models.go | 3 +- 5 files changed, 140 insertions(+), 84 deletions(-) create mode 100644 pkg/api/response.go diff --git a/go.mod b/go.mod index 9a44e6a1fb1..f4d26b8055d 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.2.0 github.com/jmespath/go-jmespath v0.4.0 github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/json-iterator/go v1.1.10 github.com/jung-kurt/gofpdf v1.10.1 github.com/lib/pq v1.9.0 github.com/linkedin/goavro/v2 v2.9.7 diff --git a/pkg/api/common.go b/pkg/api/common.go index bb9f7a1f5e1..36faa1f917b 100644 --- a/pkg/api/common.go +++ b/pkg/api/common.go @@ -15,22 +15,6 @@ var ( } ) -type Response interface { - WriteTo(ctx *models.ReqContext) - // Status gets the response's status. - Status() int - // Body gets the response's body. - Body() []byte -} - -type NormalResponse struct { - status int - body []byte - header http.Header - errMessage string - err error -} - func Wrap(action interface{}) macaron.Handler { return func(c *models.ReqContext) { var res Response @@ -45,46 +29,22 @@ func Wrap(action interface{}) macaron.Handler { } } -// Status gets the response's status. -func (r *NormalResponse) Status() int { - return r.status -} - -// Body gets the response's body. -func (r *NormalResponse) Body() []byte { - return r.body -} - -func (r *NormalResponse) WriteTo(ctx *models.ReqContext) { - if r.err != nil { - ctx.Logger.Error(r.errMessage, "error", r.err, "remote_addr", ctx.RemoteAddr()) - } - - header := ctx.Resp.Header() - for k, v := range r.header { - header[k] = v - } - ctx.Resp.WriteHeader(r.status) - if _, err := ctx.Resp.Write(r.body); err != nil { - ctx.Logger.Error("Error writing to response", "err", err) - } -} - -func (r *NormalResponse) Header(key, value string) *NormalResponse { - r.header.Set(key, value) - return r -} - -// Empty creates an empty response. -func Empty(status int) *NormalResponse { - return Respond(status, nil) -} - -// JSON create a JSON response +// JSON creates a JSON response. func JSON(status int, body interface{}) *NormalResponse { return Respond(status, body).Header("Content-Type", "application/json") } +// jsonStreaming creates a streaming JSON response. +func jsonStreaming(status int, body interface{}) streamingResponse { + header := make(http.Header) + header.Set("Content-Type", "application/json") + return streamingResponse{ + status: status, + body: body, + header: header, + } +} + // Success create a successful response func Success(message string) *NormalResponse { resp := make(map[string]interface{}) @@ -123,16 +83,16 @@ func Error(status int, message string, err error) *NormalResponse { return resp } -// Respond create a response +// Respond creates a response. func Respond(status int, body interface{}) *NormalResponse { var b []byte - var err error switch t := body.(type) { case []byte: b = t case string: b = []byte(t) default: + var err error if b, err = json.Marshal(body); err != nil { return Error(500, "body json marshal", err) } @@ -144,28 +104,6 @@ func Respond(status int, body interface{}) *NormalResponse { } } -// RedirectResponse represents a redirect response. -type RedirectResponse struct { - location string -} - -// WriteTo writes to a response. -func (r *RedirectResponse) WriteTo(ctx *models.ReqContext) { - ctx.Redirect(r.location) -} - -// Status gets the response's status. -// Required to implement api.Response. -func (*RedirectResponse) Status() int { - return http.StatusFound -} - -// Body gets the response's body. -// Required to implement api.Response. -func (r *RedirectResponse) Body() []byte { - return nil -} - func Redirect(location string) *RedirectResponse { return &RedirectResponse{location: location} } diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 4fbf80115a5..8ef0839b48b 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -16,22 +16,22 @@ import ( "github.com/grafana/grafana/pkg/util" ) -// QueryMetricsV2 returns query metrics +// QueryMetricsV2 returns query metrics. // POST /api/ds/query DataSource query w/ expressions -func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDto dtos.MetricRequest) Response { - if len(reqDto.Queries) == 0 { +func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricRequest) Response { + if len(reqDTO.Queries) == 0 { return Error(400, "No queries found in query", nil) } request := &tsdb.TsdbQuery{ - TimeRange: tsdb.NewTimeRange(reqDto.From, reqDto.To), - Debug: reqDto.Debug, + TimeRange: tsdb.NewTimeRange(reqDTO.From, reqDTO.To), + Debug: reqDTO.Debug, User: c.SignedInUser, } hasExpr := false var ds *models.DataSource - for i, query := range reqDto.Queries { + for i, query := range reqDTO.Queries { hs.log.Debug("Processing metrics query", "query", query) name := query.Get("datasource").MustString("") if name == expr.DatasourceName { @@ -95,7 +95,7 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDto dtos.MetricReq } } - return JSON(statusCode, &resp) + return jsonStreaming(statusCode, resp) } // QueryMetrics returns query metrics diff --git a/pkg/api/response.go b/pkg/api/response.go new file mode 100644 index 00000000000..bae77f64dbe --- /dev/null +++ b/pkg/api/response.go @@ -0,0 +1,116 @@ +package api + +import ( + "net/http" + + "github.com/grafana/grafana/pkg/models" + jsoniter "github.com/json-iterator/go" +) + +// Response is an HTTP response interface. +type Response interface { + // WriteTo writes to a context. + WriteTo(ctx *models.ReqContext) + // Body gets the response's body. + Body() []byte + // Status gets the response's status. + Status() int +} + +type NormalResponse struct { + status int + body []byte + header http.Header + errMessage string + err error +} + +// Status gets the response's status. +func (r *NormalResponse) Status() int { + return r.status +} + +// Body gets the response's body. +func (r *NormalResponse) Body() []byte { + return r.body +} + +func (r *NormalResponse) WriteTo(ctx *models.ReqContext) { + if r.err != nil { + ctx.Logger.Error(r.errMessage, "error", r.err, "remote_addr", ctx.RemoteAddr()) + } + + header := ctx.Resp.Header() + for k, v := range r.header { + header[k] = v + } + ctx.Resp.WriteHeader(r.status) + if _, err := ctx.Resp.Write(r.body); err != nil { + ctx.Logger.Error("Error writing to response", "err", err) + } +} + +func (r *NormalResponse) Header(key, value string) *NormalResponse { + r.header.Set(key, value) + return r +} + +// Empty creates an empty NormalResponse. +func Empty(status int) *NormalResponse { + return Respond(status, nil) +} + +// streamingResponse is a response that streams itself back to the client. +type streamingResponse struct { + body interface{} + status int + header http.Header +} + +// Status gets the response's status. +// Required to implement api.Response. +func (r streamingResponse) Status() int { + return r.status +} + +// Body gets the response's body. +// Required to implement api.Response. +func (r streamingResponse) Body() []byte { + return nil +} + +// WriteTo writes the response to the provided context. +// Required to implement api.Response. +func (r streamingResponse) WriteTo(ctx *models.ReqContext) { + header := ctx.Resp.Header() + for k, v := range r.header { + header[k] = v + } + ctx.Resp.WriteHeader(r.status) + enc := jsoniter.NewEncoder(ctx.Resp) + if err := enc.Encode(r.body); err != nil { + ctx.Logger.Error("Error writing to response", "err", err) + } +} + +// RedirectResponse represents a redirect response. +type RedirectResponse struct { + location string +} + +// WriteTo writes to a response. +func (r *RedirectResponse) WriteTo(ctx *models.ReqContext) { + ctx.Redirect(r.location) +} + +// Status gets the response's status. +// Required to implement api.Response. +func (*RedirectResponse) Status() int { + return http.StatusFound +} + +// Body gets the response's body. +// Required to implement api.Response. +func (r *RedirectResponse) Body() []byte { + return nil +} diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go index 2f56c44962b..9953619400c 100644 --- a/pkg/tsdb/models.go +++ b/pkg/tsdb/models.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + jsoniter "github.com/json-iterator/go" ) // TsdbQuery contains all information about a query request. @@ -263,5 +264,5 @@ func (df *dataFrames) MarshalJSON() ([]byte, error) { return nil, err } - return json.Marshal(encoded) + return jsoniter.Marshal(encoded) }