diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go index 38483d0af70..2f539e21064 100644 --- a/pkg/tsdb/stackdriver/stackdriver.go +++ b/pkg/tsdb/stackdriver/stackdriver.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" @@ -28,7 +29,8 @@ var slog log.Logger // StackdriverExecutor executes queries for the Stackdriver datasource type StackdriverExecutor struct { - HTTPClient *http.Client + httpClient *http.Client + dsInfo *models.DataSource } // NewStackdriverExecutor initializes a http client @@ -39,7 +41,8 @@ func NewStackdriverExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, } return &StackdriverExecutor{ - HTTPClient: httpClient, + httpClient: httpClient, + dsInfo: dsInfo, }, nil } @@ -62,44 +65,7 @@ func (e *StackdriverExecutor) Query(ctx context.Context, dsInfo *models.DataSour } for _, query := range queries { - req, err := e.createRequest(ctx, dsInfo) - if err != nil { - return nil, err - } - - req.URL.RawQuery = query.Params.Encode() - slog.Info("tsdbQuery", "req.URL.RawQuery", req.URL.RawQuery) - - httpClient, err := dsInfo.GetHttpClient() - if err != nil { - return nil, err - } - - span, ctx := opentracing.StartSpanFromContext(ctx, "stackdriver query") - span.SetTag("target", query.Target) - span.SetTag("from", tsdbQuery.TimeRange.From) - span.SetTag("until", tsdbQuery.TimeRange.To) - span.SetTag("datasource_id", dsInfo.Id) - span.SetTag("org_id", dsInfo.OrgId) - - defer span.Finish() - - opentracing.GlobalTracer().Inject( - span.Context(), - opentracing.HTTPHeaders, - opentracing.HTTPHeadersCarrier(req.Header)) - - res, err := ctxhttp.Do(ctx, httpClient, req) - if err != nil { - return nil, err - } - - data, err := e.unmarshalResponse(res) - if err != nil { - return nil, err - } - - queryRes, err := e.parseResponse(data, query.RefID) + queryRes, err := e.executeQuery(ctx, query, tsdbQuery) if err != nil { return nil, err } @@ -153,6 +119,53 @@ func (e *StackdriverExecutor) parseQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd return stackdriverQueries, nil } +func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *StackdriverQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, error) { + queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID} + + req, err := e.createRequest(ctx, e.dsInfo) + if err != nil { + queryResult.Error = err + return queryResult, nil + } + + req.URL.RawQuery = query.Params.Encode() + queryResult.Meta.Set("rawQuery", req.URL.RawQuery) + + span, ctx := opentracing.StartSpanFromContext(ctx, "stackdriver query") + span.SetTag("target", query.Target) + span.SetTag("from", tsdbQuery.TimeRange.From) + span.SetTag("until", tsdbQuery.TimeRange.To) + span.SetTag("datasource_id", e.dsInfo.Id) + span.SetTag("org_id", e.dsInfo.OrgId) + + defer span.Finish() + + opentracing.GlobalTracer().Inject( + span.Context(), + opentracing.HTTPHeaders, + opentracing.HTTPHeadersCarrier(req.Header)) + + res, err := ctxhttp.Do(ctx, e.httpClient, req) + if err != nil { + queryResult.Error = err + return queryResult, nil + } + + data, err := e.unmarshalResponse(res) + if err != nil { + queryResult.Error = err + return queryResult, nil + } + + err = e.parseResponse(queryResult, data) + if err != nil { + queryResult.Error = err + return queryResult, nil + } + + return queryResult, nil +} + func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackDriverResponse, error) { body, err := ioutil.ReadAll(res.Body) defer res.Body.Close() @@ -161,24 +174,21 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (StackDriver } if res.StatusCode/100 != 2 { - slog.Info("Request failed", "status", res.Status, "body", string(body)) - return StackDriverResponse{}, fmt.Errorf("Request failed status: %v", res.Status) + slog.Error("Request failed", "status", res.Status, "body", string(body)) + return StackDriverResponse{}, fmt.Errorf(string(body)) } var data StackDriverResponse err = json.Unmarshal(body, &data) if err != nil { - slog.Info("Failed to unmarshal Stackdriver response", "error", err, "status", res.Status, "body", string(body)) + slog.Error("Failed to unmarshal Stackdriver response", "error", err, "status", res.Status, "body", string(body)) return StackDriverResponse{}, err } return data, nil } -func (e *StackdriverExecutor) parseResponse(data StackDriverResponse, queryRefID string) (*tsdb.QueryResult, error) { - queryRes := tsdb.NewQueryResult() - queryRes.RefId = queryRefID - +func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackDriverResponse) error { for _, series := range data.TimeSeries { points := make([]tsdb.TimePoint, 0) for _, point := range series.Points { @@ -195,7 +205,7 @@ func (e *StackdriverExecutor) parseResponse(data StackDriverResponse, queryRefID }) } - return queryRes, nil + return nil } func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { @@ -227,7 +237,7 @@ func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models. pluginproxy.ApplyRoute(ctx, req, proxyPass, stackdriverRoute, dsInfo) - return req, err + return req, nil } func fixIntervalFormat(target string) string { diff --git a/pkg/tsdb/stackdriver/stackdriver_test.go b/pkg/tsdb/stackdriver/stackdriver_test.go index 91fcfcd8645..228f0a48537 100644 --- a/pkg/tsdb/stackdriver/stackdriver_test.go +++ b/pkg/tsdb/stackdriver/stackdriver_test.go @@ -28,7 +28,7 @@ func TestStackdriver(t *testing.T) { { Model: simplejson.NewFromAny(map[string]interface{}{ "target": "target", - "metricType": "time_series", + "metricType": "a/metric/type", }), RefId: "A", }, @@ -44,52 +44,56 @@ func TestStackdriver(t *testing.T) { So(queries[0].Params["interval.startTime"][0], ShouldEqual, "2018-03-15T13:00:00Z") So(queries[0].Params["interval.endTime"][0], ShouldEqual, "2018-03-15T13:34:00Z") So(queries[0].Params["aggregation.perSeriesAligner"][0], ShouldEqual, "ALIGN_NONE") - So(queries[0].Params["filter"][0], ShouldEqual, "time_series") + So(queries[0].Params["filter"][0], ShouldEqual, "a/metric/type") }) - Convey("Parse stackdriver response for data aggregated to one time series", func() { - var data StackDriverResponse + Convey("Parse stackdriver response in the time series format", func() { + Convey("when data from query aggregated to one time series", func() { + var data StackDriverResponse - jsonBody, err := ioutil.ReadFile("./test-data/1-series-response-agg-one-metric.json") - So(err, ShouldBeNil) - err = json.Unmarshal(jsonBody, &data) - So(err, ShouldBeNil) - So(len(data.TimeSeries), ShouldEqual, 1) + jsonBody, err := ioutil.ReadFile("./test-data/1-series-response-agg-one-metric.json") + So(err, ShouldBeNil) + err = json.Unmarshal(jsonBody, &data) + So(err, ShouldBeNil) + So(len(data.TimeSeries), ShouldEqual, 1) - res, err := executor.parseResponse(data, "A") - So(err, ShouldBeNil) - - So(len(res.Series), ShouldEqual, 1) - So(res.Series[0].Name, ShouldEqual, "serviceruntime.googleapis.com/api/request_count") - So(len(res.Series[0].Points), ShouldEqual, 3) - - So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.0666666666667) - So(res.Series[0].Points[1][0].Float64, ShouldEqual, 1.05) - So(res.Series[0].Points[2][0].Float64, ShouldEqual, 0.05) - }) - - Convey("Parse stackdriver response for data with no aggregation", func() { - var data StackDriverResponse - - jsonBody, err := ioutil.ReadFile("./test-data/2-series-response-no-agg.json") - So(err, ShouldBeNil) - err = json.Unmarshal(jsonBody, &data) - So(err, ShouldBeNil) - So(len(data.TimeSeries), ShouldEqual, 3) - - res, err := executor.parseResponse(data, "A") - So(err, ShouldBeNil) - - Convey("Should add labels to metric name", func() { - So(len(res.Series), ShouldEqual, 3) - So(res.Series[0].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-asia-east-1") - So(res.Series[1].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-europe-west-1") - So(res.Series[2].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-us-east-1") + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + err = executor.parseResponse(res, data) + So(err, ShouldBeNil) + So(len(res.Series), ShouldEqual, 1) + So(res.Series[0].Name, ShouldEqual, "serviceruntime.googleapis.com/api/request_count") So(len(res.Series[0].Points), ShouldEqual, 3) - So(res.Series[0].Points[0][0].Float64, ShouldEqual, 9.7730520330369) - So(res.Series[0].Points[1][0].Float64, ShouldEqual, 9.7323568146676) - So(res.Series[0].Points[2][0].Float64, ShouldEqual, 9.8566497180145) + + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.0666666666667) + So(res.Series[0].Points[1][0].Float64, ShouldEqual, 1.05) + So(res.Series[0].Points[2][0].Float64, ShouldEqual, 0.05) + }) + + Convey("when data from query with no aggregation", func() { + var data StackDriverResponse + + jsonBody, err := ioutil.ReadFile("./test-data/2-series-response-no-agg.json") + So(err, ShouldBeNil) + err = json.Unmarshal(jsonBody, &data) + So(err, ShouldBeNil) + So(len(data.TimeSeries), ShouldEqual, 3) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + err = executor.parseResponse(res, data) + So(err, ShouldBeNil) + + Convey("Should add labels to metric name", func() { + So(len(res.Series), ShouldEqual, 3) + So(res.Series[0].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-asia-east-1") + So(res.Series[1].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-europe-west-1") + So(res.Series[2].Name, ShouldEqual, "compute.googleapis.com/instance/cpu/usage_time collector-us-east-1") + + So(len(res.Series[0].Points), ShouldEqual, 3) + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 9.7730520330369) + So(res.Series[0].Points[1][0].Float64, ShouldEqual, 9.7323568146676) + So(res.Series[0].Points[2][0].Float64, ShouldEqual, 9.8566497180145) + }) }) }) }) diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 7c863ab5697..231e21e8ec5 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -20,26 +20,27 @@ export default class StackdriverDatasource { const result = []; - try { - const { data } = await this.backendSrv.datasourceRequest({ - url: '/api/tsdb/query', - method: 'POST', - data: { - from: options.range.from.valueOf().toString(), - to: options.range.to.valueOf().toString(), - queries, - }, - }); + const { data } = await this.backendSrv.datasourceRequest({ + url: '/api/tsdb/query', + method: 'POST', + data: { + from: options.range.from.valueOf().toString(), + to: options.range.to.valueOf().toString(), + queries, + }, + }); - if (data.results) { - Object['values'](data.results).forEach(queryRes => { - queryRes.series.forEach(series => { - result.push({ target: series.name, datapoints: series.points }); + if (data.results) { + Object['values'](data.results).forEach(queryRes => { + queryRes.series.forEach(series => { + result.push({ + target: series.name, + datapoints: series.points, + refId: queryRes.refId, + meta: queryRes.meta, }); }); - } - } catch (error) { - console.log(error); + }); } return { data: result }; diff --git a/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.png b/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.png new file mode 100644 index 00000000000..2084e85ed2b Binary files /dev/null and b/public/app/plugins/datasource/stackdriver/img/stackdriver_logo.png differ diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index f6df37a1414..021b1f63212 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -2,15 +2,49 @@
{{ctrl.lastQueryMeta.rawQueryString}}+
+Help text for aliasing ++
{{ctrl.lastQueryError}}+