Introduce TSDB service (#31520)

* Introduce TSDB service

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
Co-authored-by: Will Browne <will.browne@grafana.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
Arve Knudsen
2021-03-08 07:02:49 +01:00
committed by GitHub
co-authored by Erik Sundell Will Browne Torkel Ödegaard Will Browne Zoltán Bedi
parent c899bf3592
commit b79e61656a
203 changed files with 5269 additions and 4776 deletions
@@ -18,8 +18,8 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
@@ -49,20 +49,22 @@ type ApplicationInsightsQuery struct {
aggregation string
}
func (e *ApplicationInsightsDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
func (e *ApplicationInsightsDatasource) executeTimeSeriesQuery(ctx context.Context,
originalQueries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{},
}
queries, err := e.buildQueries(originalQueries, timeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
for _, query := range queries {
queryRes, err := e.executeQuery(ctx, query)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
result.Results[query.RefID] = queryRes
}
@@ -70,7 +72,8 @@ func (e *ApplicationInsightsDatasource) executeTimeSeriesQuery(ctx context.Conte
return result, nil
}
func (e *ApplicationInsightsDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*ApplicationInsightsQuery, error) {
func (e *ApplicationInsightsDatasource) buildQueries(queries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) ([]*ApplicationInsightsQuery, error) {
applicationInsightsQueries := []*ApplicationInsightsQuery{}
startTime, err := timeRange.ParseFrom()
if err != nil {
@@ -100,7 +103,7 @@ func (e *ApplicationInsightsDatasource) buildQueries(queries []*tsdb.Query, time
timeGrain := insightsJSONModel.TimeGrain
timeGrains := insightsJSONModel.AllowedTimeGrainsMs
if timeGrain == "auto" {
timeGrain, err = setAutoTimeGrain(query.IntervalMs, timeGrains)
timeGrain, err = setAutoTimeGrain(query.IntervalMS, timeGrains)
if err != nil {
return nil, err
}
@@ -122,7 +125,7 @@ func (e *ApplicationInsightsDatasource) buildQueries(queries []*tsdb.Query, time
params.Add("segment", strings.Join(insightsJSONModel.Dimensions, ","))
}
applicationInsightsQueries = append(applicationInsightsQueries, &ApplicationInsightsQuery{
RefID: query.RefId,
RefID: query.RefID,
ApiURL: azureURL,
Params: params,
Alias: insightsJSONModel.Alias,
@@ -136,8 +139,9 @@ func (e *ApplicationInsightsDatasource) buildQueries(queries []*tsdb.Query, time
return applicationInsightsQueries, nil
}
func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query *ApplicationInsightsQuery) (*tsdb.QueryResult, error) {
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}
func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query *ApplicationInsightsQuery) (
plugins.DataQueryResult, error) {
queryResult := plugins.DataQueryResult{Meta: simplejson.New(), RefID: query.RefID}
req, err := e.createRequest(ctx, e.dsInfo)
if err != nil {
@@ -178,18 +182,18 @@ func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query
}
}()
if err != nil {
return nil, err
return plugins.DataQueryResult{}, err
}
if res.StatusCode/100 != 2 {
azlog.Debug("Request failed", "status", res.Status, "body", string(body))
return nil, fmt.Errorf("request failed, status: %s", res.Status)
return plugins.DataQueryResult{}, fmt.Errorf("request failed, status: %s", res.Status)
}
mr := MetricsResult{}
err = json.Unmarshal(body, &mr)
if err != nil {
return nil, err
return plugins.DataQueryResult{}, err
}
frame, err := InsightsMetricsResultToFrame(mr, query.metricName, query.aggregation, query.dimensions)
@@ -200,13 +204,13 @@ func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query
applyInsightsMetricAlias(frame, query.Alias)
queryResult.Dataframes = tsdb.NewDecodedDataFrames(data.Frames{frame})
queryResult.Dataframes = plugins.NewDecodedDataFrames(data.Frames{frame})
return queryResult, nil
}
func (e *ApplicationInsightsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
// find plugin
plugin, ok := plugins.DataSources[dsInfo.Type]
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
return nil, errors.New("unable to find datasource plugin Azure Application Insights")
}
@@ -239,7 +243,8 @@ func (e *ApplicationInsightsDatasource) createRequest(ctx context.Context, dsInf
return req, nil
}
func (e *ApplicationInsightsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (*plugins.AppPluginRoute, string, error) {
func (e *ApplicationInsightsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (
*plugins.AppPluginRoute, string, error) {
pluginRouteName := "appinsights"
if cloudName == "chinaazuremonitor" {
@@ -247,7 +252,6 @@ func (e *ApplicationInsightsDatasource) getPluginRoute(plugin *plugins.DataSourc
}
var pluginRoute *plugins.AppPluginRoute
for _, route := range plugin.Routes {
if route.Path == pluginRouteName {
pluginRoute = route
@@ -11,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/stretchr/testify/require"
. "github.com/smartystreets/goconvey/convey"
@@ -23,12 +22,12 @@ func TestApplicationInsightsDatasource(t *testing.T) {
Convey("Parse queries from frontend and build AzureMonitor API queries", func() {
fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
tsdbQuery := &tsdb.TsdbQuery{
TimeRange: &tsdb.TimeRange{
tsdbQuery := plugins.DataQuery{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
Queries: []*tsdb.Query{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{}),
@@ -43,13 +42,13 @@ func TestApplicationInsightsDatasource(t *testing.T) {
"queryType": "Application Insights",
},
}),
RefId: "A",
IntervalMs: 1234,
RefID: "A",
IntervalMS: 1234,
},
},
}
Convey("and is a normal query", func() {
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
queries, err := datasource.buildQueries(tsdbQuery.Queries, *tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(len(queries), ShouldEqual, 1)
@@ -74,9 +73,9 @@ func TestApplicationInsightsDatasource(t *testing.T) {
"queryType": "Application Insights",
},
})
tsdbQuery.Queries[0].IntervalMs = 400000
tsdbQuery.Queries[0].IntervalMS = 400000
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
queries, err := datasource.buildQueries(tsdbQuery.Queries, *tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Params["interval"][0], ShouldEqual, "PT15M")
@@ -94,9 +93,9 @@ func TestApplicationInsightsDatasource(t *testing.T) {
"allowedTimeGrainsMs": []int64{60000, 300000},
},
})
tsdbQuery.Queries[0].IntervalMs = 400000
tsdbQuery.Queries[0].IntervalMS = 400000
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
queries, err := datasource.buildQueries(tsdbQuery.Queries, *tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Params["interval"][0], ShouldEqual, "PT5M")
@@ -116,7 +115,7 @@ func TestApplicationInsightsDatasource(t *testing.T) {
},
})
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
queries, err := datasource.buildQueries(tsdbQuery.Queries, *tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Target, ShouldEqual, "aggregation=Average&filter=blob+eq+%27%2A%27&interval=PT1M&segment=blob&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
@@ -136,7 +135,7 @@ func TestApplicationInsightsDatasource(t *testing.T) {
},
})
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
queries, err := datasource.buildQueries(tsdbQuery.Queries, *tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Target, ShouldEqual, "aggregation=Average&interval=PT1M&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
@@ -17,8 +17,8 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
@@ -45,14 +45,15 @@ type AzureLogAnalyticsQuery struct {
// 1. build the AzureMonitor url and querystring for each query
// 2. executes each query by calling the Azure Monitor API
// 3. parses the responses for each query into the timeseries format
func (e *AzureLogAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
func (e *AzureLogAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{},
}
queries, err := e.buildQueries(originalQueries, timeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
for _, query := range queries {
@@ -62,7 +63,8 @@ func (e *AzureLogAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context
return result, nil
}
func (e *AzureLogAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*AzureLogAnalyticsQuery, error) {
func (e *AzureLogAnalyticsDatasource) buildQueries(queries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) ([]*AzureLogAnalyticsQuery, error) {
azureLogAnalyticsQueries := []*AzureLogAnalyticsQuery{}
for _, query := range queries {
@@ -97,7 +99,7 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRa
params.Add("query", rawQuery)
azureLogAnalyticsQueries = append(azureLogAnalyticsQueries, &AzureLogAnalyticsQuery{
RefID: query.RefId,
RefID: query.RefID,
ResultFormat: resultFormat,
URL: apiURL,
Model: query.Model,
@@ -109,10 +111,11 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRa
return azureLogAnalyticsQueries, nil
}
func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *AzureLogAnalyticsQuery, queries []*tsdb.Query, timeRange *tsdb.TimeRange) *tsdb.QueryResult {
queryResult := &tsdb.QueryResult{RefId: query.RefID}
func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *AzureLogAnalyticsQuery,
queries []plugins.DataSubQuery, timeRange plugins.DataTimeRange) plugins.DataQueryResult {
queryResult := plugins.DataQueryResult{RefID: query.RefID}
queryResultErrorWithExecuted := func(err error) *tsdb.QueryResult {
queryResultErrorWithExecuted := func(err error) plugins.DataQueryResult {
queryResult.Error = err
frames := data.Frames{
&data.Frame{
@@ -122,7 +125,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
},
},
}
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryResult.Dataframes = plugins.NewDecodedDataFrames(frames)
return queryResult
}
@@ -193,7 +196,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
}
}
frames := data.Frames{frame}
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryResult.Dataframes = plugins.NewDecodedDataFrames(frames)
return queryResult
}
@@ -214,7 +217,7 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, dsInfo
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
// find plugin
plugin, ok := plugins.DataSources[dsInfo.Type]
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
return nil, errors.New("unable to find datasource plugin Azure Monitor")
}
@@ -229,7 +232,8 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, dsInfo
return req, nil
}
func (e *AzureLogAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (*plugins.AppPluginRoute, string, error) {
func (e *AzureLogAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (
*plugins.AppPluginRoute, string, error) {
pluginRouteName := "loganalyticsazure"
switch cloudName {
@@ -240,7 +244,6 @@ func (e *AzureLogAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourceP
}
var logAnalyticsRoute *plugins.AppPluginRoute
for _, route := range plugin.Routes {
if route.Path == pluginRouteName {
logAnalyticsRoute = route
@@ -11,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/stretchr/testify/require"
)
@@ -21,18 +20,18 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
tests := []struct {
name string
queryModel []*tsdb.Query
timeRange *tsdb.TimeRange
queryModel []plugins.DataSubQuery
timeRange plugins.DataTimeRange
azureLogAnalyticsQueries []*AzureLogAnalyticsQuery
Err require.ErrorAssertionFunc
}{
{
name: "Query with macros should be interpolated",
timeRange: &tsdb.TimeRange{
timeRange: plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
queryModel: []*tsdb.Query{
queryModel: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{}),
@@ -45,7 +44,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"resultFormat": "time_series",
},
}),
RefId: "A",
RefID: "A",
},
},
azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{
@@ -17,12 +17,11 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
opentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
"github.com/grafana/grafana/pkg/tsdb"
)
// AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported
@@ -42,25 +41,28 @@ const azureMonitorAPIVersion = "2018-01-01"
// 1. build the AzureMonitor url and querystring for each query
// 2. executes each query by calling the Azure Monitor API
// 3. parses the responses for each query into the timeseries format
func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{},
}
queries, err := e.buildQueries(originalQueries, timeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
for _, query := range queries {
queryRes, resp, err := e.executeQuery(ctx, query, originalQueries, timeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
err = e.parseResponse(queryRes, resp, query)
frames, err := e.parseResponse(resp, query)
if err != nil {
queryRes.Error = err
} else {
queryRes.Dataframes = frames
}
result.Results[query.RefID] = queryRes
}
@@ -68,7 +70,7 @@ func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, ori
return result, nil
}
func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*AzureMonitorQuery, error) {
func (e *AzureMonitorDatasource) buildQueries(queries []plugins.DataSubQuery, timeRange plugins.DataTimeRange) ([]*AzureMonitorQuery, error) {
azureMonitorQueries := []*AzureMonitorQuery{}
startTime, err := timeRange.ParseFrom()
if err != nil {
@@ -115,7 +117,7 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
timeGrain := azJSONModel.TimeGrain
timeGrains := azJSONModel.AllowedTimeGrainsMs
if timeGrain == "auto" {
timeGrain, err = setAutoTimeGrain(query.IntervalMs, timeGrains)
timeGrain, err = setAutoTimeGrain(query.IntervalMS, timeGrains)
if err != nil {
return nil, err
}
@@ -162,7 +164,7 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
UrlComponents: urlComponents,
Target: target,
Params: params,
RefID: query.RefId,
RefID: query.RefID,
Alias: alias,
})
}
@@ -170,8 +172,9 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
return azureMonitorQueries, nil
}
func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureMonitorQuery, queries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.QueryResult, AzureMonitorResponse, error) {
queryResult := &tsdb.QueryResult{RefId: query.RefID}
func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureMonitorQuery, queries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) (plugins.DataQueryResult, AzureMonitorResponse, error) {
queryResult := plugins.DataQueryResult{RefID: query.RefID}
req, err := e.createRequest(ctx, e.dsInfo)
if err != nil {
@@ -223,7 +226,7 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureM
func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
// find plugin
plugin, ok := plugins.DataSources[dsInfo.Type]
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
return nil, errors.New("unable to find datasource plugin Azure Monitor")
}
@@ -280,9 +283,10 @@ func (e *AzureMonitorDatasource) unmarshalResponse(res *http.Response) (AzureMon
return data, nil
}
func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, amr AzureMonitorResponse, query *AzureMonitorQuery) error {
func (e *AzureMonitorDatasource) parseResponse(amr AzureMonitorResponse, query *AzureMonitorQuery) (
plugins.DataFrames, error) {
if len(amr.Value) == 0 {
return nil
return nil, nil
}
frames := data.Frames{}
@@ -340,14 +344,13 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, amr A
frames = append(frames, frame)
}
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
return nil
return plugins.NewDecodedDataFrames(frames), nil
}
// formatAzureMonitorLegendKey builds the legend key or timeseries name
// Alias patterns like {{resourcename}} are replaced with the appropriate data values.
func formatAzureMonitorLegendKey(alias string, resourceName string, metricName string, metadataName string, metadataValue string, namespace string, seriesID string, labels data.Labels) string {
func formatAzureMonitorLegendKey(alias string, resourceName string, metricName string, metadataName string,
metadataValue string, namespace string, seriesID string, labels data.Labels) string {
startIndex := strings.Index(seriesID, "/resourceGroups/") + 16
endIndex := strings.Index(seriesID, "/providers")
resourceGroup := seriesID[startIndex:endIndex]
@@ -14,7 +14,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/require"
ptr "github.com/xorcare/pointer"
)
@@ -125,12 +125,12 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
for k, v := range commonAzureModelProps {
tt.azureMonitorVariedProperties[k] = v
}
tsdbQuery := &tsdb.TsdbQuery{
TimeRange: &tsdb.TimeRange{
tsdbQuery := plugins.DataQuery{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
Queries: []*tsdb.Query{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{
@@ -142,8 +142,8 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
"azureMonitor": tt.azureMonitorVariedProperties,
},
),
RefId: "A",
IntervalMs: tt.queryIntervalMS,
RefID: "A",
IntervalMS: tt.queryIntervalMS,
},
},
}
@@ -161,7 +161,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
Alias: "testalias",
}
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
queries, err := datasource.buildQueries(tsdbQuery.Queries, *tsdbQuery.TimeRange)
require.NoError(t, err)
if diff := cmp.Diff(azureMonitorQuery, queries[0], cmpopts.IgnoreUnexported(simplejson.Json{}), cmpopts.IgnoreFields(AzureMonitorQuery{}, "Params")); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
@@ -430,15 +430,16 @@ func TestAzureMonitorParseResponse(t *testing.T) {
}
datasource := &AzureMonitorDatasource{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
azData := loadTestFile(t, "azuremonitor/"+tt.responseFile)
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
err := datasource.parseResponse(res, azData, tt.mockQuery)
res := plugins.DataQueryResult{Meta: simplejson.New(), RefID: "A"}
require.NotNil(t, res)
dframes, err := datasource.parseResponse(azData, tt.mockQuery)
require.NoError(t, err)
require.NotNil(t, dframes)
frames, err := res.Dataframes.Decoded()
frames, err := dframes.Decoded()
require.NoError(t, err)
if diff := cmp.Diff(tt.expectedFrames, frames, data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
+35 -24
View File
@@ -8,14 +8,30 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
)
var (
azlog log.Logger
legendKeyFormat *regexp.Regexp
azlog = log.New("tsdb.azuremonitor")
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
)
func init() {
registry.Register(&registry.Descriptor{
Name: "AzureMonitorService",
InitPriority: registry.Low,
Instance: &Service{},
})
}
type Service struct {
}
func (s *Service) Init() error {
return nil
}
// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services
type AzureMonitorExecutor struct {
httpClient *http.Client
@@ -23,7 +39,7 @@ type AzureMonitorExecutor struct {
}
// NewAzureMonitorExecutor initializes a http client
func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, error) {
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return nil, err
@@ -35,23 +51,18 @@ func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint,
}, nil
}
func init() {
azlog = log.New("tsdb.azuremonitor")
tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor)
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
}
// Query takes in the frontend queries, parses them into the query format
// expected by chosen Azure Monitor service (Azure Monitor, App Insights etc.)
// executes the queries against the API and parses the response into
// the right format
func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *AzureMonitorExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
tsdbQuery plugins.DataQuery) (plugins.DataResponse, error) {
var err error
var azureMonitorQueries []*tsdb.Query
var applicationInsightsQueries []*tsdb.Query
var azureLogAnalyticsQueries []*tsdb.Query
var insightsAnalyticsQueries []*tsdb.Query
var azureMonitorQueries []plugins.DataSubQuery
var applicationInsightsQueries []plugins.DataSubQuery
var azureLogAnalyticsQueries []plugins.DataSubQuery
var insightsAnalyticsQueries []plugins.DataSubQuery
for _, query := range tsdbQuery.Queries {
queryType := query.Model.Get("queryType").MustString("")
@@ -66,7 +77,7 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou
case "Insights Analytics":
insightsAnalyticsQueries = append(insightsAnalyticsQueries, query)
default:
return nil, fmt.Errorf("alerting not supported for %q", queryType)
return plugins.DataResponse{}, fmt.Errorf("alerting not supported for %q", queryType)
}
}
@@ -90,24 +101,24 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou
dsInfo: e.dsInfo,
}
azResult, err := azDatasource.executeTimeSeriesQuery(ctx, azureMonitorQueries, tsdbQuery.TimeRange)
azResult, err := azDatasource.executeTimeSeriesQuery(ctx, azureMonitorQueries, *tsdbQuery.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
aiResult, err := aiDatasource.executeTimeSeriesQuery(ctx, applicationInsightsQueries, tsdbQuery.TimeRange)
aiResult, err := aiDatasource.executeTimeSeriesQuery(ctx, applicationInsightsQueries, *tsdbQuery.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
alaResult, err := alaDatasource.executeTimeSeriesQuery(ctx, azureLogAnalyticsQueries, tsdbQuery.TimeRange)
alaResult, err := alaDatasource.executeTimeSeriesQuery(ctx, azureLogAnalyticsQueries, *tsdbQuery.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
iaResult, err := iaDatasource.executeTimeSeriesQuery(ctx, insightsAnalyticsQueries, tsdbQuery.TimeRange)
iaResult, err := iaDatasource.executeTimeSeriesQuery(ctx, insightsAnalyticsQueries, *tsdbQuery.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
for k, v := range aiResult.Results {
@@ -15,8 +15,8 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
@@ -39,14 +39,15 @@ type InsightsAnalyticsQuery struct {
Target string
}
func (e *InsightsAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
func (e *InsightsAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context,
originalQueries []plugins.DataSubQuery, timeRange plugins.DataTimeRange) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{},
}
queries, err := e.buildQueries(originalQueries, timeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
for _, query := range queries {
@@ -56,7 +57,8 @@ func (e *InsightsAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context
return result, nil
}
func (e *InsightsAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*InsightsAnalyticsQuery, error) {
func (e *InsightsAnalyticsDatasource) buildQueries(queries []plugins.DataSubQuery,
timeRange plugins.DataTimeRange) ([]*InsightsAnalyticsQuery, error) {
iaQueries := []*InsightsAnalyticsQuery{}
for _, query := range queries {
@@ -74,7 +76,7 @@ func (e *InsightsAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRa
qm.RawQuery = queryJSONModel.InsightsAnalytics.Query
qm.ResultFormat = queryJSONModel.InsightsAnalytics.ResultFormat
qm.RefID = query.RefId
qm.RefID = query.RefID
if qm.RawQuery == "" {
return nil, fmt.Errorf("query is missing query string property")
@@ -94,10 +96,10 @@ func (e *InsightsAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRa
return iaQueries, nil
}
func (e *InsightsAnalyticsDatasource) executeQuery(ctx context.Context, query *InsightsAnalyticsQuery) *tsdb.QueryResult {
queryResult := &tsdb.QueryResult{RefId: query.RefID}
func (e *InsightsAnalyticsDatasource) executeQuery(ctx context.Context, query *InsightsAnalyticsQuery) plugins.DataQueryResult {
queryResult := plugins.DataQueryResult{RefID: query.RefID}
queryResultError := func(err error) *tsdb.QueryResult {
queryResultError := func(err error) plugins.DataQueryResult {
queryResult.Error = err
return queryResult
}
@@ -170,19 +172,22 @@ func (e *InsightsAnalyticsDatasource) executeQuery(ctx context.Context, query *I
if err == nil {
frame = wideFrame
} else {
frame.AppendNotices(data.Notice{Severity: data.NoticeSeverityWarning, Text: "could not convert frame to time series, returning raw table: " + err.Error()})
frame.AppendNotices(data.Notice{
Severity: data.NoticeSeverityWarning,
Text: "could not convert frame to time series, returning raw table: " + err.Error(),
})
}
}
}
frames := data.Frames{frame}
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryResult.Dataframes = plugins.NewDecodedDataFrames(frames)
return queryResult
}
func (e *InsightsAnalyticsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
// find plugin
plugin, ok := plugins.DataSources[dsInfo.Type]
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
return nil, errors.New("unable to find datasource plugin Azure Application Insights")
}
@@ -215,7 +220,8 @@ func (e *InsightsAnalyticsDatasource) createRequest(ctx context.Context, dsInfo
return req, nil
}
func (e *InsightsAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (*plugins.AppPluginRoute, string, error) {
func (e *InsightsAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (
*plugins.AppPluginRoute, string, error) {
pluginRouteName := "appinsights"
if cloudName == "chinaazuremonitor" {
@@ -223,7 +229,6 @@ func (e *InsightsAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourceP
}
var pluginRoute *plugins.AppPluginRoute
for _, route := range plugin.Routes {
if route.Path == pluginRouteName {
pluginRoute = route
+15 -12
View File
@@ -6,7 +6,8 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
const rsIdentifier = `__(timeFilter|timeFrom|timeTo|interval|contains|escapeMulti)`
@@ -14,8 +15,8 @@ const sExpr = `\$` + rsIdentifier + `(?:\(([^\)]*)\))?`
const escapeMultiExpr = `\$__escapeMulti\(('.*')\)`
type kqlMacroEngine struct {
timeRange *tsdb.TimeRange
query *tsdb.Query
timeRange plugins.DataTimeRange
query plugins.DataSubQuery
}
// Macros:
@@ -28,7 +29,7 @@ type kqlMacroEngine struct {
// - $__escapeMulti('\\vm\eth0\Total','\\vm\eth2\Total') -> @'\\vm\eth0\Total',@'\\vm\eth2\Total'
// KqlInterpolate interpolates macros for Kusto Query Language (KQL) queries
func KqlInterpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, kql string, defaultTimeField ...string) (string, error) {
func KqlInterpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, kql string, defaultTimeField ...string) (string, error) {
engine := kqlMacroEngine{}
defaultTimeFieldForAllDatasources := "timestamp"
@@ -38,7 +39,7 @@ func KqlInterpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, kql string, de
return engine.Interpolate(query, timeRange, kql, defaultTimeFieldForAllDatasources)
}
func (m *kqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, kql string, defaultTimeField string) (string, error) {
func (m *kqlMacroEngine) Interpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, kql string, defaultTimeField string) (string, error) {
m.timeRange = timeRange
m.query = query
rExp, _ := regexp.Compile(sExpr)
@@ -90,28 +91,30 @@ func (m *kqlMacroEngine) evaluateMacro(name string, defaultTimeField string, arg
if len(args) > 0 && args[0] != "" {
timeColumn = args[0]
}
return fmt.Sprintf("['%s'] >= datetime('%s') and ['%s'] <= datetime('%s')", timeColumn, m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), timeColumn, m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
return fmt.Sprintf("['%s'] >= datetime('%s') and ['%s'] <= datetime('%s')", timeColumn,
m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), timeColumn,
m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
case "timeFrom", "__from":
return fmt.Sprintf("datetime('%s')", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
case "timeTo", "__to":
return fmt.Sprintf("datetime('%s')", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
case "interval":
var interval time.Duration
if m.query.IntervalMs == 0 {
var it time.Duration
if m.query.IntervalMS == 0 {
to := m.timeRange.MustGetTo().UnixNano()
from := m.timeRange.MustGetFrom().UnixNano()
// default to "100 datapoints" if nothing in the query is more specific
defaultInterval := time.Duration((to - from) / 60)
var err error
interval, err = tsdb.GetIntervalFrom(m.query.DataSource, m.query.Model, defaultInterval)
it, err = interval.GetIntervalFrom(m.query.DataSource, m.query.Model, defaultInterval)
if err != nil {
azlog.Warn("Unable to get interval from query", "datasource", m.query.DataSource, "model", m.query.Model)
interval = defaultInterval
it = defaultInterval
}
} else {
interval = time.Millisecond * time.Duration(m.query.IntervalMs)
it = time.Millisecond * time.Duration(m.query.IntervalMS)
}
return fmt.Sprintf("%dms", int(interval/time.Millisecond)), nil
return fmt.Sprintf("%dms", int(it/time.Millisecond)), nil
case "contains":
if len(args) < 2 || args[0] == "" || args[1] == "" {
return "", fmt.Errorf("macro %v needs colName and variableSet", name)
+17 -17
View File
@@ -9,77 +9,77 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/require"
)
func TestAzureLogAnalyticsMacros(t *testing.T) {
fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
timeRange := &tsdb.TimeRange{
timeRange := plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
}
tests := []struct {
name string
query *tsdb.Query
timeRange *tsdb.TimeRange
query plugins.DataSubQuery
timeRange plugins.DataTimeRange
kql string
expected string
Err require.ErrorAssertionFunc
}{
{
name: "invalid macro should be ignored",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "$__invalid()",
expected: "$__invalid()",
Err: require.NoError,
},
{
name: "Kusto variables should be ignored",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: ") on $left.b == $right.y",
expected: ") on $left.b == $right.y",
Err: require.NoError,
},
{
name: "$__contains macro with a multi template variable that has multiple selected values as a parameter should build in clause",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "$__contains(col, 'val1','val2')",
expected: "['col'] in ('val1','val2')",
Err: require.NoError,
},
{
name: "$__contains macro with a multi template variable that has a single selected value as a parameter should build in clause",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "$__contains(col, 'val1' )",
expected: "['col'] in ('val1')",
Err: require.NoError,
},
{
name: "$__contains macro with multi template variable has custom All value as a parameter should return a true expression",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "$__contains(col, all)",
expected: "1 == 1",
Err: require.NoError,
},
{
name: "$__timeFilter has no column parameter should use default time field",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "$__timeFilter()",
expected: "['TimeGenerated'] >= datetime('2018-03-15T13:00:00Z') and ['TimeGenerated'] <= datetime('2018-03-15T13:34:00Z')",
Err: require.NoError,
},
{
name: "$__timeFilter has time field parameter",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "$__timeFilter(myTimeField)",
expected: "['myTimeField'] >= datetime('2018-03-15T13:00:00Z') and ['myTimeField'] <= datetime('2018-03-15T13:34:00Z')",
Err: require.NoError,
},
{
name: "$__timeFrom and $__timeTo is in the query and range is a specific interval",
query: &tsdb.Query{},
query: plugins.DataSubQuery{},
kql: "myTimeField >= $__timeFrom() and myTimeField <= $__timeTo()",
expected: "myTimeField >= datetime('2018-03-15T13:00:00Z') and myTimeField <= datetime('2018-03-15T13:34:00Z')",
Err: require.NoError,
@@ -87,7 +87,7 @@ func TestAzureLogAnalyticsMacros(t *testing.T) {
{
name: "$__interval should use the defined interval from the query",
timeRange: timeRange,
query: &tsdb.Query{
query: plugins.DataSubQuery{
Model: simplejson.NewFromAny(map[string]interface{}{
"interval": "5m",
}),
@@ -98,7 +98,7 @@ func TestAzureLogAnalyticsMacros(t *testing.T) {
},
{
name: "$__interval should use the default interval if none is specified",
query: &tsdb.Query{
query: plugins.DataSubQuery{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{}),
},
@@ -108,7 +108,7 @@ func TestAzureLogAnalyticsMacros(t *testing.T) {
},
{
name: "$__escapeMulti with multi template variable should replace values with KQL style escaped strings",
query: &tsdb.Query{
query: plugins.DataSubQuery{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{}),
},
@@ -118,7 +118,7 @@ func TestAzureLogAnalyticsMacros(t *testing.T) {
},
{
name: "$__escapeMulti with multi template variable and has one selected value that contains comma",
query: &tsdb.Query{
query: plugins.DataSubQuery{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{}),
},
@@ -128,7 +128,7 @@ func TestAzureLogAnalyticsMacros(t *testing.T) {
},
{
name: "$__escapeMulti with multi template variable and is not wrapped in single quotes should fail",
query: &tsdb.Query{
query: plugins.DataSubQuery{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{}),
},
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
// TimeGrain handles conversions between
@@ -18,8 +18,8 @@ var (
smallTimeUnits = []string{"hour", "minute", "h", "m"}
)
func (tg *TimeGrain) createISO8601DurationFromIntervalMS(interval int64) (string, error) {
formatted := tsdb.FormatDuration(time.Duration(interval) * time.Millisecond)
func (tg *TimeGrain) createISO8601DurationFromIntervalMS(it int64) (string, error) {
formatted := interval.FormatDuration(time.Duration(it) * time.Millisecond)
if strings.Contains(formatted, "ms") {
return "PT1M", nil
@@ -28,7 +28,7 @@ func (tg *TimeGrain) createISO8601DurationFromIntervalMS(interval int64) (string
timeValueString := formatted[0 : len(formatted)-1]
timeValue, err := strconv.Atoi(timeValueString)
if err != nil {
return "", fmt.Errorf("could not parse interval %q to an ISO 8061 duration: %w", interval, err)
return "", fmt.Errorf("could not parse interval %q to an ISO 8061 duration: %w", it, err)
}
unit := formatted[len(formatted)-1:]
+13 -12
View File
@@ -4,24 +4,25 @@ import (
"context"
"strings"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
func (e *CloudMonitoringExecutor) executeAnnotationQuery(ctx context.Context, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
func (e *Executor) executeAnnotationQuery(ctx context.Context, tsdbQuery plugins.DataQuery) (
plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
firstQuery := tsdbQuery.Queries[0]
queries, err := e.buildQueryExecutors(tsdbQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
queryRes, resp, _, err := queries[0].run(ctx, tsdbQuery, e)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
metricQuery := firstQuery.Model.Get("metricQuery")
@@ -29,16 +30,16 @@ func (e *CloudMonitoringExecutor) executeAnnotationQuery(ctx context.Context, ts
text := metricQuery.Get("text").MustString()
tags := metricQuery.Get("tags").MustString()
err = queries[0].parseToAnnotations(queryRes, resp, title, text, tags)
result.Results[firstQuery.RefId] = queryRes
err = queries[0].parseToAnnotations(&queryRes, resp, title, text, tags)
result.Results[firstQuery.RefID] = queryRes
return result, err
}
func transformAnnotationToTable(data []map[string]string, result *tsdb.QueryResult) {
table := &tsdb.Table{
Columns: make([]tsdb.TableColumn, 4),
Rows: make([]tsdb.RowValues, 0),
func transformAnnotationToTable(data []map[string]string, result *plugins.DataQueryResult) {
table := plugins.DataTable{
Columns: make([]plugins.DataTableColumn, 4),
Rows: make([]plugins.DataRowValues, 0),
}
table.Columns[0].Text = "time"
table.Columns[1].Text = "title"
@@ -4,23 +4,25 @@ import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCloudMonitoringExecutor_parseToAnnotations(t *testing.T) {
func TestExecutor_parseToAnnotations(t *testing.T) {
d, err := loadTestFile("./test-data/2-series-response-no-agg.json")
require.NoError(t, err)
require.Len(t, d.TimeSeries, 3)
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "annotationQuery"}
res := &plugins.DataQueryResult{Meta: simplejson.New(), RefID: "annotationQuery"}
query := &cloudMonitoringTimeSeriesFilter{}
err = query.parseToAnnotations(res, d, "atitle {{metric.label.instance_name}} {{metric.value}}", "atext {{resource.label.zone}}", "atag")
err = query.parseToAnnotations(res, d, "atitle {{metric.label.instance_name}} {{metric.value}}",
"atext {{resource.label.zone}}", "atag")
require.NoError(t, err)
decoded, _ := res.Dataframes.Decoded()
decoded, err := res.Dataframes.Decoded()
require.NoError(t, err)
require.Len(t, decoded, 3)
assert.Equal(t, "title", decoded[0].Fields[1].Name)
assert.Equal(t, "tags", decoded[0].Fields[2].Name)
@@ -28,7 +30,7 @@ func TestCloudMonitoringExecutor_parseToAnnotations(t *testing.T) {
}
func TestCloudMonitoringExecutor_parseToAnnotations_emptyTimeSeries(t *testing.T) {
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "annotationQuery"}
res := &plugins.DataQueryResult{Meta: simplejson.New(), RefID: "annotationQuery"}
query := &cloudMonitoringTimeSeriesFilter{}
response := cloudMonitoringResponse{
@@ -38,12 +40,13 @@ func TestCloudMonitoringExecutor_parseToAnnotations_emptyTimeSeries(t *testing.T
err := query.parseToAnnotations(res, response, "atitle", "atext", "atag")
require.NoError(t, err)
decoded, _ := res.Dataframes.Decoded()
decoded, err := res.Dataframes.Decoded()
require.NoError(t, err)
require.Len(t, decoded, 0)
}
func TestCloudMonitoringExecutor_parseToAnnotations_noPointsInSeries(t *testing.T) {
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "annotationQuery"}
res := &plugins.DataQueryResult{Meta: simplejson.New(), RefID: "annotationQuery"}
query := &cloudMonitoringTimeSeriesFilter{}
response := cloudMonitoringResponse{
+66 -41
View File
@@ -16,14 +16,16 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"golang.org/x/oauth2/google"
)
@@ -62,20 +64,35 @@ const (
mqlEditorMode string = "mql"
)
// CloudMonitoringExecutor executes queries for the CloudMonitoring datasource
type CloudMonitoringExecutor struct {
func init() {
registry.Register(&registry.Descriptor{
Name: "CloudMonitoringService",
InitPriority: registry.Low,
Instance: &Service{},
})
}
type Service struct {
}
func (s *Service) Init() error {
return nil
}
// Executor executes queries for the CloudMonitoring datasource.
type Executor struct {
httpClient *http.Client
dsInfo *models.DataSource
}
// NewCloudMonitoringExecutor initializes a http client
func NewCloudMonitoringExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
// NewExecutor returns an Executor.
func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, error) {
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return nil, err
}
return &CloudMonitoringExecutor{
return &Executor{
httpClient: httpClient,
dsInfo: dsInfo,
}, nil
@@ -83,14 +100,14 @@ func NewCloudMonitoringExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoi
func init() {
slog = log.New("tsdb.cloudMonitoring")
tsdb.RegisterTsdbQueryEndpoint("stackdriver", NewCloudMonitoringExecutor)
}
// Query takes in the frontend queries, parses them into the CloudMonitoring query format
// executes the queries against the CloudMonitoring API and parses the response into
// the time series or table format
func (e *CloudMonitoringExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
var result *tsdb.Response
func (e *Executor) DataQuery(ctx context.Context, dsInfo *models.DataSource, tsdbQuery plugins.DataQuery) (
plugins.DataResponse, error) {
var result plugins.DataResponse
var err error
queryType := tsdbQuery.Queries[0].Model.Get("type").MustString("")
@@ -108,32 +125,34 @@ func (e *CloudMonitoringExecutor) Query(ctx context.Context, dsInfo *models.Data
return result, err
}
func (e *CloudMonitoringExecutor) getGCEDefaultProject(ctx context.Context, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
func (e *Executor) getGCEDefaultProject(ctx context.Context, tsdbQuery plugins.DataQuery) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
refId := tsdbQuery.Queries[0].RefId
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: refId}
refID := tsdbQuery.Queries[0].RefID
queryResult := plugins.DataQueryResult{Meta: simplejson.New(), RefID: refID}
gceDefaultProject, err := e.getDefaultProject(ctx)
if err != nil {
return nil, fmt.Errorf("failed to retrieve default project from GCE metadata server, error: %w", err)
return plugins.DataResponse{}, fmt.Errorf(
"failed to retrieve default project from GCE metadata server, error: %w", err)
}
queryResult.Meta.Set("defaultProject", gceDefaultProject)
result.Results[refId] = queryResult
result.Results[refID] = queryResult
return result, nil
}
func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
func (e *Executor) executeTimeSeriesQuery(ctx context.Context, tsdbQuery plugins.DataQuery) (
plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
queryExecutors, err := e.buildQueryExecutors(tsdbQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
unit := e.resolvePanelUnitFromQueries(queryExecutors)
@@ -141,9 +160,9 @@ func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, ts
for _, queryExecutor := range queryExecutors {
queryRes, resp, executedQueryString, err := queryExecutor.run(ctx, tsdbQuery, e)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
err = queryExecutor.parseResponse(queryRes, resp, executedQueryString)
err = queryExecutor.parseResponse(&queryRes, resp, executedQueryString)
if err != nil {
queryRes.Error = err
}
@@ -158,7 +177,7 @@ func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, ts
}
frames[i].Fields[1].Config.Unit = unit
}
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryRes.Dataframes = plugins.NewDecodedDataFrames(frames)
}
result.Results[queryExecutor.getRefID()] = queryRes
}
@@ -166,7 +185,7 @@ func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, ts
return result, nil
}
func (e *CloudMonitoringExecutor) resolvePanelUnitFromQueries(executors []cloudMonitoringQueryExecutor) string {
func (e *Executor) resolvePanelUnitFromQueries(executors []cloudMonitoringQueryExecutor) string {
if len(executors) == 0 {
return ""
}
@@ -186,7 +205,7 @@ func (e *CloudMonitoringExecutor) resolvePanelUnitFromQueries(executors []cloudM
return ""
}
func (e *CloudMonitoringExecutor) buildQueryExecutors(tsdbQuery *tsdb.TsdbQuery) ([]cloudMonitoringQueryExecutor, error) {
func (e *Executor) buildQueryExecutors(tsdbQuery plugins.DataQuery) ([]cloudMonitoringQueryExecutor, error) {
cloudMonitoringQueryExecutors := []cloudMonitoringQueryExecutor{}
startTime, err := tsdbQuery.TimeRange.ParseFrom()
@@ -201,10 +220,14 @@ func (e *CloudMonitoringExecutor) buildQueryExecutors(tsdbQuery *tsdb.TsdbQuery)
durationSeconds := int(endTime.Sub(startTime).Seconds())
for _, query := range tsdbQuery.Queries {
migrateLegacyQueryModel(query)
for i := range tsdbQuery.Queries {
migrateLegacyQueryModel(&tsdbQuery.Queries[i])
query := tsdbQuery.Queries[i]
q := grafanaQuery{}
model, _ := query.Model.MarshalJSON()
model, err := query.Model.MarshalJSON()
if err != nil {
return nil, err
}
if err := json.Unmarshal(model, &q); err != nil {
return nil, fmt.Errorf("could not unmarshal CloudMonitoringQuery json: %w", err)
}
@@ -215,20 +238,19 @@ func (e *CloudMonitoringExecutor) buildQueryExecutors(tsdbQuery *tsdb.TsdbQuery)
var queryInterface cloudMonitoringQueryExecutor
cmtsf := &cloudMonitoringTimeSeriesFilter{
RefID: query.RefId,
RefID: query.RefID,
GroupBys: []string{},
}
switch q.QueryType {
case metricQueryType:
if q.MetricQuery.EditorMode == mqlEditorMode {
queryInterface = &cloudMonitoringTimeSeriesQuery{
RefID: query.RefId,
RefID: query.RefID,
ProjectName: q.MetricQuery.ProjectName,
Query: q.MetricQuery.Query,
IntervalMS: query.IntervalMs,
IntervalMS: query.IntervalMS,
AliasBy: q.MetricQuery.AliasBy,
timeRange: tsdbQuery.TimeRange,
timeRange: *tsdbQuery.TimeRange,
}
} else {
cmtsf.AliasBy = q.MetricQuery.AliasBy
@@ -239,7 +261,7 @@ func (e *CloudMonitoringExecutor) buildQueryExecutors(tsdbQuery *tsdb.TsdbQuery)
}
params.Add("filter", buildFilterString(q.MetricQuery.MetricType, q.MetricQuery.Filters))
params.Add("view", q.MetricQuery.View)
setMetricAggParams(&params, &q.MetricQuery, durationSeconds, query.IntervalMs)
setMetricAggParams(&params, &q.MetricQuery, durationSeconds, query.IntervalMS)
queryInterface = cmtsf
}
case sloQueryType:
@@ -249,8 +271,10 @@ func (e *CloudMonitoringExecutor) buildQueryExecutors(tsdbQuery *tsdb.TsdbQuery)
cmtsf.Service = q.SloQuery.ServiceId
cmtsf.Slo = q.SloQuery.SloId
params.Add("filter", buildSLOFilterExpression(q.SloQuery))
setSloAggParams(&params, &q.SloQuery, durationSeconds, query.IntervalMs)
setSloAggParams(&params, &q.SloQuery, durationSeconds, query.IntervalMS)
queryInterface = cmtsf
default:
panic(fmt.Sprintf("Unrecognized query type %q", q.QueryType))
}
target = params.Encode()
@@ -268,7 +292,7 @@ func (e *CloudMonitoringExecutor) buildQueryExecutors(tsdbQuery *tsdb.TsdbQuery)
return cloudMonitoringQueryExecutors, nil
}
func migrateLegacyQueryModel(query *tsdb.Query) {
func migrateLegacyQueryModel(query *plugins.DataSubQuery) {
mq := query.Model.Get("metricQuery").MustMap()
if mq == nil {
migratedModel := simplejson.NewFromAny(map[string]interface{}{
@@ -402,7 +426,8 @@ func containsLabel(labels []string, newLabel string) bool {
return false
}
func formatLegendKeys(metricType string, defaultMetricName string, labels map[string]string, additionalLabels map[string]string, query *cloudMonitoringTimeSeriesFilter) string {
func formatLegendKeys(metricType string, defaultMetricName string, labels map[string]string,
additionalLabels map[string]string, query *cloudMonitoringTimeSeriesFilter) string {
if query.AliasBy == "" {
return defaultMetricName
}
@@ -488,7 +513,7 @@ func calcBucketBound(bucketOptions cloudMonitoringBucketOptions, n int) string {
return bucketBound
}
func (e *CloudMonitoringExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource, proxyPass string, body io.Reader) (*http.Request, error) {
func (e *Executor) createRequest(ctx context.Context, dsInfo *models.DataSource, proxyPass string, body io.Reader) (*http.Request, error) {
u, err := url.Parse(dsInfo.Url)
if err != nil {
return nil, err
@@ -509,7 +534,7 @@ func (e *CloudMonitoringExecutor) createRequest(ctx context.Context, dsInfo *mod
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
// find plugin
plugin, ok := plugins.DataSources[dsInfo.Type]
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
return nil, errors.New("unable to find datasource plugin CloudMonitoring")
}
@@ -527,7 +552,7 @@ func (e *CloudMonitoringExecutor) createRequest(ctx context.Context, dsInfo *mod
return req, nil
}
func (e *CloudMonitoringExecutor) getDefaultProject(ctx context.Context) (string, error) {
func (e *Executor) getDefaultProject(ctx context.Context) (string, error) {
authenticationType := e.dsInfo.JsonData.Get("authenticationType").MustString(jwtAuthentication)
if authenticationType == gceAuthentication {
defaultCredentials, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/monitoring.read")
File diff suppressed because it is too large Load Diff
+26 -16
View File
@@ -12,13 +12,14 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
)
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) run(ctx context.Context, tsdbQuery *tsdb.TsdbQuery, e *CloudMonitoringExecutor) (*tsdb.QueryResult, cloudMonitoringResponse, string, error) {
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: timeSeriesFilter.RefID}
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) run(ctx context.Context, tsdbQuery plugins.DataQuery,
e *Executor) (plugins.DataQueryResult, cloudMonitoringResponse, string, error) {
queryResult := plugins.DataQueryResult{Meta: simplejson.New(), RefID: timeSeriesFilter.RefID}
projectName := timeSeriesFilter.ProjectName
if projectName == "" {
defaultProject, err := e.getDefaultProject(ctx)
@@ -78,7 +79,8 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) run(ctx context.Context
return queryResult, data, req.URL.RawQuery, nil
}
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseResponse(queryRes *tsdb.QueryResult, response cloudMonitoringResponse, executedQueryString string) error {
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseResponse(queryRes *plugins.DataQueryResult,
response cloudMonitoringResponse, executedQueryString string) error {
labels := make(map[string]map[string]bool)
frames := data.Frames{}
for _, series := range response.TimeSeries {
@@ -199,7 +201,8 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseResponse(queryRes
additionalLabels := data.Labels{"bucket": bucketBound}
timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, []time.Time{})
valueField := data.NewField(data.TimeSeriesValueFieldName, nil, []float64{})
frameName := formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels, additionalLabels, timeSeriesFilter)
frameName := formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels,
additionalLabels, timeSeriesFilter)
valueField.Name = frameName
valueField.Labels = seriesLabels
setDisplayNameAsFieldName(valueField)
@@ -224,7 +227,7 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseResponse(queryRes
frames = addConfigData(frames, dl)
}
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryRes.Dataframes = plugins.NewDecodedDataFrames(frames)
labelsByKey := make(map[string][]string)
for key, values := range labels {
@@ -238,8 +241,9 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseResponse(queryRes
return nil
}
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) handleNonDistributionSeries(series timeSeries, defaultMetricName string, seriesLabels map[string]string,
queryRes *tsdb.QueryResult, frame *data.Frame) {
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) handleNonDistributionSeries(series timeSeries,
defaultMetricName string, seriesLabels map[string]string, queryRes *plugins.DataQueryResult,
frame *data.Frame) {
for i := 0; i < len(series.Points); i++ {
point := series.Points[i]
value := point.Value.DoubleValue
@@ -268,7 +272,8 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) handleNonDistributionSe
setDisplayNameAsFieldName(dataField)
}
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseToAnnotations(queryRes *tsdb.QueryResult, response cloudMonitoringResponse, title string, text string, tags string) error {
func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseToAnnotations(queryRes *plugins.DataQueryResult,
response cloudMonitoringResponse, title string, text string, tags string) error {
frames := data.Frames{}
for _, series := range response.TimeSeries {
if len(series.Points) == 0 {
@@ -282,18 +287,20 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseToAnnotations(quer
value = point.Value.StringValue
}
annotation["time"] = append(annotation["time"], point.Interval.EndTime.UTC().Format(time.RFC3339))
annotation["title"] = append(annotation["title"], formatAnnotationText(title, value, series.Metric.Type, series.Metric.Labels, series.Resource.Labels))
annotation["title"] = append(annotation["title"], formatAnnotationText(title, value, series.Metric.Type,
series.Metric.Labels, series.Resource.Labels))
annotation["tags"] = append(annotation["tags"], tags)
annotation["text"] = append(annotation["text"], formatAnnotationText(text, value, series.Metric.Type, series.Metric.Labels, series.Resource.Labels))
annotation["text"] = append(annotation["text"], formatAnnotationText(text, value, series.Metric.Type,
series.Metric.Labels, series.Resource.Labels))
}
frames = append(frames, data.NewFrame(queryRes.RefId,
frames = append(frames, data.NewFrame(queryRes.RefID,
data.NewField("time", nil, annotation["time"]),
data.NewField("title", nil, annotation["title"]),
data.NewField("tags", nil, annotation["tags"]),
data.NewField("text", nil, annotation["text"]),
))
}
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryRes.Dataframes = plugins.NewDecodedDataFrames(frames)
return nil
}
@@ -313,7 +320,8 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) buildDeepLink() string
u, err := url.Parse("https://console.cloud.google.com/monitoring/metrics-explorer")
if err != nil {
slog.Error("Failed to generate deep link: unable to parse metrics explorer URL", "ProjectName", timeSeriesFilter.ProjectName, "query", timeSeriesFilter.RefID)
slog.Error("Failed to generate deep link: unable to parse metrics explorer URL", "ProjectName",
timeSeriesFilter.ProjectName, "query", timeSeriesFilter.RefID)
return ""
}
@@ -353,7 +361,8 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) buildDeepLink() string
blob, err := json.Marshal(pageState)
if err != nil {
slog.Error("Failed to generate deep link", "pageState", pageState, "ProjectName", timeSeriesFilter.ProjectName, "query", timeSeriesFilter.RefID)
slog.Error("Failed to generate deep link", "pageState", pageState, "ProjectName", timeSeriesFilter.ProjectName,
"query", timeSeriesFilter.RefID)
return ""
}
@@ -362,7 +371,8 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) buildDeepLink() string
accountChooserURL, err := url.Parse("https://accounts.google.com/AccountChooser")
if err != nil {
slog.Error("Failed to generate deep link: unable to parse account chooser URL", "ProjectName", timeSeriesFilter.ProjectName, "query", timeSeriesFilter.RefID)
slog.Error("Failed to generate deep link: unable to parse account chooser URL", "ProjectName",
timeSeriesFilter.ProjectName, "query", timeSeriesFilter.RefID)
return ""
}
accountChooserQuery := accountChooserURL.Query()
+16 -9
View File
@@ -13,13 +13,15 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
)
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, tsdbQuery *tsdb.TsdbQuery, e *CloudMonitoringExecutor) (*tsdb.QueryResult, cloudMonitoringResponse, string, error) {
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: timeSeriesQuery.RefID}
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, tsdbQuery plugins.DataQuery,
e *Executor) (plugins.DataQueryResult, cloudMonitoringResponse, string, error) {
queryResult := plugins.DataQueryResult{Meta: simplejson.New(), RefID: timeSeriesQuery.RefID}
projectName := timeSeriesQuery.ProjectName
if projectName == "" {
defaultProject, err := e.getDefaultProject(ctx)
@@ -41,8 +43,8 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, t
queryResult.Error = err
return queryResult, cloudMonitoringResponse{}, "", nil
}
intervalCalculator := tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{})
interval := intervalCalculator.Calculate(tsdbQuery.TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second)
intervalCalculator := interval.NewCalculator(interval.CalculatorOptions{})
interval := intervalCalculator.Calculate(*tsdbQuery.TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second)
timeFormat := "2006/01/02-15:04:05"
timeSeriesQuery.Query += fmt.Sprintf(" | graph_period %s | within d'%s', d'%s'", interval.Text, from.UTC().Format(timeFormat), to.UTC().Format(timeFormat))
@@ -92,7 +94,8 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, t
return queryResult, data, timeSeriesQuery.Query, nil
}
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *tsdb.QueryResult, response cloudMonitoringResponse, executedQueryString string) error {
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *plugins.DataQueryResult,
response cloudMonitoringResponse, executedQueryString string) error {
labels := make(map[string]map[string]bool)
frames := data.Frames{}
for _, series := range response.TimeSeriesData {
@@ -157,7 +160,10 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *ts
frame.SetRow(len(series.PointData)-1-i, series.PointData[i].TimeInterval.EndTime, value)
}
metricName := formatLegendKeys(d.Key, defaultMetricName, seriesLabels, nil, &cloudMonitoringTimeSeriesFilter{ProjectName: timeSeriesQuery.ProjectName, AliasBy: timeSeriesQuery.AliasBy})
metricName := formatLegendKeys(d.Key, defaultMetricName, seriesLabels, nil,
&cloudMonitoringTimeSeriesFilter{
ProjectName: timeSeriesQuery.ProjectName, AliasBy: timeSeriesQuery.AliasBy,
})
dataField := frame.Fields[1]
dataField.Name = metricName
dataField.Labels = seriesLabels
@@ -244,7 +250,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *ts
frames = addConfigData(frames, dl)
}
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryRes.Dataframes = plugins.NewDecodedDataFrames(frames)
labelsByKey := make(map[string][]string)
for key, values := range labels {
@@ -258,7 +264,8 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *ts
return nil
}
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRes *tsdb.QueryResult, data cloudMonitoringResponse, title string, text string, tags string) error {
func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRes *plugins.DataQueryResult,
data cloudMonitoringResponse, title string, text string, tags string) error {
annotations := make([]map[string]string, 0)
for _, series := range data.TimeSeriesData {
+6 -5
View File
@@ -5,14 +5,15 @@ import (
"net/url"
"time"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
type (
cloudMonitoringQueryExecutor interface {
run(ctx context.Context, tsdbQuery *tsdb.TsdbQuery, e *CloudMonitoringExecutor) (*tsdb.QueryResult, cloudMonitoringResponse, string, error)
parseResponse(queryRes *tsdb.QueryResult, data cloudMonitoringResponse, executedQueryString string) error
parseToAnnotations(queryRes *tsdb.QueryResult, data cloudMonitoringResponse, title string, text string, tags string) error
run(ctx context.Context, tsdbQuery plugins.DataQuery, e *Executor) (
plugins.DataQueryResult, cloudMonitoringResponse, string, error)
parseResponse(queryRes *plugins.DataQueryResult, data cloudMonitoringResponse, executedQueryString string) error
parseToAnnotations(queryRes *plugins.DataQueryResult, data cloudMonitoringResponse, title string, text string, tags string) error
buildDeepLink() string
getRefID() string
getUnit() string
@@ -39,7 +40,7 @@ type (
Query string
IntervalMS int64
AliasBy string
timeRange *tsdb.TimeRange
timeRange plugins.DataTimeRange
Unit string
}
+22 -20
View File
@@ -8,16 +8,17 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util/errutil"
)
func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryContext plugins.DataQuery) (
plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
firstQuery := queryContext.Queries[0]
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: firstQuery.RefId}
queryResult := plugins.DataQueryResult{Meta: simplejson.New(), RefID: firstQuery.RefID}
parameters := firstQuery.Model
usePrefixMatch := parameters.Get("prefixMatching").MustBool(false)
@@ -27,7 +28,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
dimensions := parameters.Get("dimensions").MustMap()
statistics, err := parseStatistics(parameters)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
period := int64(parameters.Get("period").MustInt(0))
if period == 0 && !usePrefixMatch {
@@ -38,7 +39,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
cli, err := e.getCWClient(region)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
var alarmNames []*string
@@ -50,7 +51,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
}
resp, err := cli.DescribeAlarms(params)
if err != nil {
return nil, errutil.Wrap("failed to call cloudwatch:DescribeAlarms", err)
return plugins.DataResponse{}, errutil.Wrap("failed to call cloudwatch:DescribeAlarms", err)
}
alarmNames = filterAlarms(resp, namespace, metricName, dimensions, statistics, period)
} else {
@@ -81,7 +82,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
}
resp, err := cli.DescribeAlarmsForMetric(params)
if err != nil {
return nil, errutil.Wrap("failed to call cloudwatch:DescribeAlarmsForMetric", err)
return plugins.DataResponse{}, errutil.Wrap("failed to call cloudwatch:DescribeAlarmsForMetric", err)
}
for _, alarm := range resp.MetricAlarms {
alarmNames = append(alarmNames, alarm.AlarmName)
@@ -91,11 +92,11 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
startTime, err := queryContext.TimeRange.ParseFrom()
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
endTime, err := queryContext.TimeRange.ParseTo()
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
annotations := make([]map[string]string, 0)
@@ -108,7 +109,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
}
resp, err := cli.DescribeAlarmHistory(params)
if err != nil {
return nil, errutil.Wrap("failed to call cloudwatch:DescribeAlarmHistory", err)
return plugins.DataResponse{}, errutil.Wrap("failed to call cloudwatch:DescribeAlarmHistory", err)
}
for _, history := range resp.AlarmHistoryItems {
annotation := make(map[string]string)
@@ -120,15 +121,15 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
}
}
transformAnnotationToTable(annotations, queryResult)
result.Results[firstQuery.RefId] = queryResult
return result, err
transformAnnotationToTable(annotations, &queryResult)
result.Results[firstQuery.RefID] = queryResult
return result, nil
}
func transformAnnotationToTable(data []map[string]string, result *tsdb.QueryResult) {
table := &tsdb.Table{
Columns: make([]tsdb.TableColumn, 4),
Rows: make([]tsdb.RowValues, 0),
func transformAnnotationToTable(data []map[string]string, result *plugins.DataQueryResult) {
table := plugins.DataTable{
Columns: make([]plugins.DataTableColumn, 4),
Rows: make([]plugins.DataRowValues, 0),
}
table.Columns[0].Text = "time"
table.Columns[1].Text = "title"
@@ -147,7 +148,8 @@ func transformAnnotationToTable(data []map[string]string, result *tsdb.QueryResu
result.Meta.Set("rowCount", len(data))
}
func filterAlarms(alarms *cloudwatch.DescribeAlarmsOutput, namespace string, metricName string, dimensions map[string]interface{}, statistics []string, period int64) []*string {
func filterAlarms(alarms *cloudwatch.DescribeAlarmsOutput, namespace string, metricName string,
dimensions map[string]interface{}, statistics []string, period int64) []*string {
alarmNames := make([]*string, 0)
for _, alarm := range alarms.MetricAlarms {
+33 -32
View File
@@ -26,9 +26,9 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
)
type datasourceInfo struct {
@@ -67,15 +67,13 @@ type CloudWatchService struct {
}
func (s *CloudWatchService) Init() error {
plog.Debug("initing")
tsdb.RegisterTsdbQueryEndpoint("cloudwatch", func(ds *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return newExecutor(s.LogsService), nil
})
return nil
}
func (s *CloudWatchService) NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
return newExecutor(s.LogsService), nil
}
func newExecutor(logsService *LogsService) *cloudWatchExecutor {
return &cloudWatchExecutor{
logsService: logsService,
@@ -248,12 +246,12 @@ func (e *cloudWatchExecutor) getRGTAClient(region string) (resourcegroupstagging
}
func (e *cloudWatchExecutor) alertQuery(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
queryContext *tsdb.TsdbQuery) (*cloudwatchlogs.GetQueryResultsOutput, error) {
queryContext plugins.DataQuery) (*cloudwatchlogs.GetQueryResultsOutput, error) {
const maxAttempts = 8
const pollPeriod = 1000 * time.Millisecond
queryParams := queryContext.Queries[0].Model
startQueryOutput, err := e.executeStartQuery(ctx, logsClient, queryParams, queryContext.TimeRange)
startQueryOutput, err := e.executeStartQuery(ctx, logsClient, queryParams, *queryContext.TimeRange)
if err != nil {
return nil, err
}
@@ -285,15 +283,17 @@ func (e *cloudWatchExecutor) alertQuery(ctx context.Context, logsClient cloudwat
return nil, nil
}
// Query executes a CloudWatch query.
func (e *cloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
// DataQuery executes a CloudWatch query.
func (e *cloudWatchExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
queryContext plugins.DataQuery) (plugins.DataResponse, error) {
e.DataSource = dsInfo
/*
Unlike many other data sources, with Cloudwatch Logs query requests don't receive the results as the response to the query, but rather
an ID is first returned. Following this, a client is expected to send requests along with the ID until the status of the query is complete,
receiving (possibly partial) results each time. For queries made via dashboards and Explore, the logic of making these repeated queries is handled on
the frontend, but because alerts are executed on the backend the logic needs to be reimplemented here.
Unlike many other data sources, with Cloudwatch Logs query requests don't receive the results as the response
to the query, but rather an ID is first returned. Following this, a client is expected to send requests along
with the ID until the status of the query is complete, receiving (possibly partial) results each time. For
queries made via dashboards and Explore, the logic of making these repeated queries is handled on the
frontend, but because alerts are executed on the backend the logic needs to be reimplemented here.
*/
queryParams := queryContext.Queries[0].Model
_, fromAlert := queryContext.Headers["FromAlert"]
@@ -306,7 +306,7 @@ func (e *cloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
queryType := queryParams.Get("type").MustString("")
var err error
var result *tsdb.Response
var result plugins.DataResponse
switch queryType {
case "metricFindQuery":
result, err = e.executeMetricFindQuery(ctx, queryContext)
@@ -325,7 +325,8 @@ func (e *cloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
return result, err
}
func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryContext plugins.DataQuery) (
plugins.DataResponse, error) {
queryParams := queryContext.Queries[0].Model
queryParams.Set("subtype", "StartQuery")
queryParams.Set("queryString", queryParams.Get("expression").MustString(""))
@@ -338,12 +339,12 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryCont
logsClient, err := e.getCWLogsClient(region)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
result, err := e.executeStartQuery(ctx, logsClient, queryParams, queryContext.TimeRange)
result, err := e.executeStartQuery(ctx, logsClient, queryParams, *queryContext.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
queryParams.Set("queryId", *result.QueryId)
@@ -351,38 +352,38 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryCont
// Get query results
getQueryResultsOutput, err := e.alertQuery(ctx, logsClient, queryContext)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
dataframe, err := logsResultsToDataframes(getQueryResultsOutput)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
statsGroups := queryParams.Get("statsGroups").MustStringArray()
if len(statsGroups) > 0 && len(dataframe.Fields) > 0 {
groupedFrames, err := groupResults(dataframe, statsGroups)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
response := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
response := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
response.Results["A"] = &tsdb.QueryResult{
RefId: "A",
Dataframes: tsdb.NewDecodedDataFrames(groupedFrames),
response.Results["A"] = plugins.DataQueryResult{
RefID: "A",
Dataframes: plugins.NewDecodedDataFrames(groupedFrames),
}
return response, nil
}
response := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
response := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"A": {
RefId: "A",
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{dataframe}),
RefID: "A",
Dataframes: plugins.NewDecodedDataFrames(data.Frames{dataframe}),
},
},
}
+22 -18
View File
@@ -18,8 +18,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/retryer"
"golang.org/x/sync/errgroup"
)
@@ -108,20 +108,21 @@ func (r *logQueryRunner) publishResults(channelName string) error {
// executeLiveLogQuery executes a CloudWatch Logs query with live updates over WebSocket.
// A WebSocket channel is created, which goroutines send responses over.
func (e *cloudWatchExecutor) executeLiveLogQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *cloudWatchExecutor) executeLiveLogQuery(ctx context.Context, queryContext plugins.DataQuery) (
plugins.DataResponse, error) {
responseChannelName := uuid.New().String()
responseChannel := make(chan *tsdb.Response)
responseChannel := make(chan plugins.DataResponse)
if err := e.logsService.AddResponseChannel("plugin/cloudwatch/"+responseChannelName, responseChannel); err != nil {
close(responseChannel)
return nil, err
return plugins.DataResponse{}, err
}
go e.sendLiveQueriesToChannel(queryContext, responseChannel)
response := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
response := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"A": {
RefId: "A",
RefID: "A",
Meta: simplejson.NewFromAny(map[string]interface{}{
"channelName": responseChannelName,
}),
@@ -132,7 +133,8 @@ func (e *cloudWatchExecutor) executeLiveLogQuery(ctx context.Context, queryConte
return response, nil
}
func (e *cloudWatchExecutor) sendLiveQueriesToChannel(queryContext *tsdb.TsdbQuery, responseChannel chan *tsdb.Response) {
func (e *cloudWatchExecutor) sendLiveQueriesToChannel(queryContext plugins.DataQuery,
responseChannel chan plugins.DataResponse) {
defer close(responseChannel)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
@@ -142,7 +144,7 @@ func (e *cloudWatchExecutor) sendLiveQueriesToChannel(queryContext *tsdb.TsdbQue
for _, query := range queryContext.Queries {
query := query
eg.Go(func() error {
return e.startLiveQuery(ectx, responseChannel, query, queryContext.TimeRange)
return e.startLiveQuery(ectx, responseChannel, query, *queryContext.TimeRange)
})
}
@@ -200,7 +202,8 @@ func (e *cloudWatchExecutor) fetchConcurrentQueriesQuota(region string) int {
return defaultConcurrentQueries
}
if defaultConcurrentQueriesQuota != nil && defaultConcurrentQueriesQuota.Quota != nil && defaultConcurrentQueriesQuota.Quota.Value != nil {
if defaultConcurrentQueriesQuota != nil && defaultConcurrentQueriesQuota.Quota != nil &&
defaultConcurrentQueriesQuota.Quota.Value != nil {
return int(*defaultConcurrentQueriesQuota.Quota.Value)
}
@@ -208,7 +211,8 @@ func (e *cloudWatchExecutor) fetchConcurrentQueriesQuota(region string) int {
return defaultConcurrentQueries
}
func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel chan *tsdb.Response, query *tsdb.Query, timeRange *tsdb.TimeRange) error {
func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel chan plugins.DataResponse,
query plugins.DataSubQuery, timeRange plugins.DataTimeRange) error {
defaultRegion := e.DataSource.JsonData.Get("defaultRegion").MustString()
parameters := query.Model
region := parameters.Get("region").MustString(defaultRegion)
@@ -250,8 +254,8 @@ func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel
return retryer.FuncError, err
}
dataFrame.Name = query.RefId
dataFrame.RefID = query.RefId
dataFrame.Name = query.RefID
dataFrame.RefID = query.RefID
var dataFrames data.Frames
// When a query of the form "stats ... by ..." is made, we want to return
@@ -281,11 +285,11 @@ func (e *cloudWatchExecutor) startLiveQuery(ctx context.Context, responseChannel
dataFrames = data.Frames{dataFrame}
}
responseChannel <- &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
query.RefId: {
RefId: query.RefId,
Dataframes: tsdb.NewDecodedDataFrames(dataFrames),
responseChannel <- plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
query.RefID: {
RefID: query.RefID,
Dataframes: plugins.NewDecodedDataFrames(dataFrames),
},
},
}
+24 -18
View File
@@ -12,12 +12,13 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"golang.org/x/sync/errgroup"
)
func (e *cloudWatchExecutor) executeLogActions(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
resultChan := make(chan *tsdb.QueryResult, len(queryContext.Queries))
func (e *cloudWatchExecutor) executeLogActions(ctx context.Context,
queryContext plugins.DataQuery) (plugins.DataResponse, error) {
resultChan := make(chan plugins.DataQueryResult, len(queryContext.Queries))
eg, ectx := errgroup.WithContext(ctx)
for _, query := range queryContext.Queries {
@@ -42,7 +43,10 @@ func (e *cloudWatchExecutor) executeLogActions(ctx context.Context, queryContext
return err
}
resultChan <- &tsdb.QueryResult{RefId: query.RefId, Dataframes: tsdb.NewDecodedDataFrames(groupedFrames)}
resultChan <- plugins.DataQueryResult{
RefID: query.RefID,
Dataframes: plugins.NewDecodedDataFrames(groupedFrames),
}
return nil
}
@@ -54,30 +58,31 @@ func (e *cloudWatchExecutor) executeLogActions(ctx context.Context, queryContext
}
}
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{dataframe}),
resultChan <- plugins.DataQueryResult{
RefID: query.RefID,
Dataframes: plugins.NewDecodedDataFrames(data.Frames{dataframe}),
}
return nil
})
}
if err := eg.Wait(); err != nil {
return nil, err
return plugins.DataResponse{}, err
}
close(resultChan)
response := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
response := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
for result := range resultChan {
response.Results[result.RefId] = result
response.Results[result.RefID] = result
}
return response, nil
}
func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, queryContext *tsdb.TsdbQuery, query *tsdb.Query) (*data.Frame, error) {
func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, queryContext plugins.DataQuery,
query plugins.DataSubQuery) (*data.Frame, error) {
parameters := query.Model
subType := query.Model.Get("subtype").MustString()
@@ -94,13 +99,13 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, queryContext
case "DescribeLogGroups":
data, err = e.handleDescribeLogGroups(ctx, logsClient, parameters)
case "GetLogGroupFields":
data, err = e.handleGetLogGroupFields(ctx, logsClient, parameters, query.RefId)
data, err = e.handleGetLogGroupFields(ctx, logsClient, parameters, query.RefID)
case "StartQuery":
data, err = e.handleStartQuery(ctx, logsClient, parameters, queryContext.TimeRange, query.RefId)
data, err = e.handleStartQuery(ctx, logsClient, parameters, *queryContext.TimeRange, query.RefID)
case "StopQuery":
data, err = e.handleStopQuery(ctx, logsClient, parameters)
case "GetQueryResults":
data, err = e.handleGetQueryResults(ctx, logsClient, parameters, query.RefId)
data, err = e.handleGetQueryResults(ctx, logsClient, parameters, query.RefID)
case "GetLogEvents":
data, err = e.handleGetLogEvents(ctx, logsClient, parameters)
}
@@ -195,7 +200,7 @@ func (e *cloudWatchExecutor) handleDescribeLogGroups(ctx context.Context,
}
func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
parameters *simplejson.Json, timeRange *tsdb.TimeRange) (*cloudwatchlogs.StartQueryOutput, error) {
parameters *simplejson.Json, timeRange plugins.DataTimeRange) (*cloudwatchlogs.StartQueryOutput, error) {
startTime, err := timeRange.ParseFrom()
if err != nil {
return nil, err
@@ -214,7 +219,8 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c
// so that a row's context can be retrieved later if necessary.
// The usage of ltrim around the @log/@logStream fields is a necessary workaround, as without it,
// CloudWatch wouldn't consider a query using a non-alised @log/@logStream valid.
modifiedQueryString := "fields @timestamp,ltrim(@log) as " + logIdentifierInternal + ",ltrim(@logStream) as " + logStreamIdentifierInternal + "|" + parameters.Get("queryString").MustString("")
modifiedQueryString := "fields @timestamp,ltrim(@log) as " + logIdentifierInternal + ",ltrim(@logStream) as " +
logStreamIdentifierInternal + "|" + parameters.Get("queryString").MustString("")
startQueryInput := &cloudwatchlogs.StartQueryInput{
StartTime: aws.Int64(startTime.Unix()),
@@ -231,7 +237,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c
}
func (e *cloudWatchExecutor) handleStartQuery(ctx context.Context, logsClient cloudwatchlogsiface.CloudWatchLogsAPI,
parameters *simplejson.Json, timeRange *tsdb.TimeRange, refID string) (*data.Frame, error) {
parameters *simplejson.Json, timeRange plugins.DataTimeRange, refID string) (*data.Frame, error) {
startQueryResponse, err := e.executeStartQuery(ctx, logsClient, parameters, timeRange)
if err != nil {
return nil, err
+45 -45
View File
@@ -12,7 +12,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -48,8 +48,8 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
@@ -62,10 +62,10 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{
Dataframes: plugins.NewDecodedDataFrames(data.Frames{
&data.Frame{
Name: "logGroups",
Fields: []*data.Field{
@@ -101,8 +101,8 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
@@ -115,10 +115,10 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{
Dataframes: plugins.NewDecodedDataFrames(data.Frames{
&data.Frame{
Name: "logGroups",
Fields: []*data.Field{
@@ -171,10 +171,10 @@ func TestQuery_GetLogGroupFields(t *testing.T) {
const refID = "A"
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
RefId: refID,
RefID: refID,
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
"subtype": "GetLogGroupFields",
@@ -202,11 +202,11 @@ func TestQuery_GetLogGroupFields(t *testing.T) {
},
}
expFrame.RefID = refID
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
refID: {
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{expFrame}),
RefId: refID,
Dataframes: plugins.NewDecodedDataFrames(data.Frames{expFrame}),
RefID: refID,
},
},
}, resp)
@@ -244,15 +244,15 @@ func TestQuery_StartQuery(t *testing.T) {
},
}
timeRange := &tsdb.TimeRange{
timeRange := plugins.DataTimeRange{
From: "1584873443000",
To: "1584700643000",
}
executor := newExecutor(nil)
_, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
TimeRange: timeRange,
Queries: []*tsdb.Query{
_, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
@@ -290,17 +290,17 @@ func TestQuery_StartQuery(t *testing.T) {
},
}
timeRange := &tsdb.TimeRange{
timeRange := plugins.DataTimeRange{
From: "1584700643000",
To: "1584873443000",
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
TimeRange: timeRange,
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{
RefId: refID,
RefID: refID,
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
"subtype": "StartQuery",
@@ -324,11 +324,11 @@ func TestQuery_StartQuery(t *testing.T) {
},
PreferredVisualization: "logs",
}
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
refID: {
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{expFrame}),
RefId: refID,
Dataframes: plugins.NewDecodedDataFrames(data.Frames{expFrame}),
RefID: refID,
},
},
}, resp)
@@ -366,15 +366,15 @@ func TestQuery_StopQuery(t *testing.T) {
},
}
timeRange := &tsdb.TimeRange{
timeRange := plugins.DataTimeRange{
From: "1584873443000",
To: "1584700643000",
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
TimeRange: timeRange,
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
@@ -395,10 +395,10 @@ func TestQuery_StopQuery(t *testing.T) {
PreferredVisualization: "logs",
},
}
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{expFrame}),
Dataframes: plugins.NewDecodedDataFrames(data.Frames{expFrame}),
},
},
}, resp)
@@ -459,10 +459,10 @@ func TestQuery_GetQueryResults(t *testing.T) {
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
RefId: refID,
RefID: refID,
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "logAction",
"subtype": "GetQueryResults",
@@ -507,11 +507,11 @@ func TestQuery_GetQueryResults(t *testing.T) {
PreferredVisualization: "logs",
}
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
refID: {
RefId: refID,
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{expFrame}),
RefID: refID,
Dataframes: plugins.NewDecodedDataFrames(data.Frames{expFrame}),
},
},
}, resp)
+5 -5
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"sync"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/tsdb"
)
func init() {
@@ -15,19 +15,19 @@ func init() {
// LogsService provides methods for querying CloudWatch Logs.
type LogsService struct {
channelMu sync.Mutex
responseChannels map[string]chan *tsdb.Response
responseChannels map[string]chan plugins.DataResponse
queues map[string](chan bool)
queueLock sync.Mutex
}
// Init is called by the DI framework to initialize the instance.
func (s *LogsService) Init() error {
s.responseChannels = make(map[string]chan *tsdb.Response)
s.responseChannels = make(map[string]chan plugins.DataResponse)
s.queues = make(map[string](chan bool))
return nil
}
func (s *LogsService) AddResponseChannel(name string, channel chan *tsdb.Response) error {
func (s *LogsService) AddResponseChannel(name string, channel chan plugins.DataResponse) error {
s.channelMu.Lock()
defer s.channelMu.Unlock()
@@ -39,7 +39,7 @@ func (s *LogsService) AddResponseChannel(name string, channel chan *tsdb.Respons
return nil
}
func (s *LogsService) GetResponseChannel(name string) (chan *tsdb.Response, error) {
func (s *LogsService) GetResponseChannel(name string) (chan plugins.DataResponse, error) {
s.channelMu.Lock()
defer s.channelMu.Unlock()
+21 -20
View File
@@ -17,7 +17,7 @@ import (
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util/errutil"
)
@@ -241,7 +241,8 @@ var dimensionsMap = map[string][]string{
var regionCache sync.Map
func (e *cloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *cloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queryContext plugins.DataQuery) (
plugins.DataResponse, error) {
firstQuery := queryContext.Queries[0]
parameters := firstQuery.Model
@@ -267,22 +268,22 @@ func (e *cloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queryCo
data, err = e.handleGetResourceArns(ctx, parameters, queryContext)
}
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: firstQuery.RefId}
transformToTable(data, queryResult)
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
firstQuery.RefId: queryResult,
queryResult := plugins.DataQueryResult{Meta: simplejson.New(), RefID: firstQuery.RefID}
transformToTable(data, &queryResult)
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
firstQuery.RefID: queryResult,
},
}
return result, nil
}
func transformToTable(data []suggestData, result *tsdb.QueryResult) {
table := &tsdb.Table{
Columns: []tsdb.TableColumn{
func transformToTable(data []suggestData, result *plugins.DataQueryResult) {
table := plugins.DataTable{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -290,7 +291,7 @@ func transformToTable(data []suggestData, result *tsdb.QueryResult) {
Text: "value",
},
},
Rows: make([]tsdb.RowValues, 0),
Rows: make([]plugins.DataRowValues, 0),
}
for _, r := range data {
@@ -321,7 +322,7 @@ func parseMultiSelectValue(input string) []string {
// Whenever this list is updated, the frontend list should also be updated.
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
func (e *cloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json,
queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
queryContext plugins.DataQuery) ([]suggestData, error) {
dsInfo := e.getDSInfo(defaultRegion)
profile := dsInfo.Profile
if cache, ok := regionCache.Load(profile); ok {
@@ -366,7 +367,7 @@ func (e *cloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *s
return result, nil
}
func (e *cloudWatchExecutor) handleGetNamespaces(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
func (e *cloudWatchExecutor) handleGetNamespaces(ctx context.Context, parameters *simplejson.Json, queryContext plugins.DataQuery) ([]suggestData, error) {
keys := []string{}
for key := range metricsMap {
keys = append(keys, key)
@@ -385,7 +386,7 @@ func (e *cloudWatchExecutor) handleGetNamespaces(ctx context.Context, parameters
return result, nil
}
func (e *cloudWatchExecutor) handleGetMetrics(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
func (e *cloudWatchExecutor) handleGetMetrics(ctx context.Context, parameters *simplejson.Json, queryContext plugins.DataQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
namespace := parameters.Get("namespace").MustString()
@@ -411,7 +412,7 @@ func (e *cloudWatchExecutor) handleGetMetrics(ctx context.Context, parameters *s
return result, nil
}
func (e *cloudWatchExecutor) handleGetDimensions(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
func (e *cloudWatchExecutor) handleGetDimensions(ctx context.Context, parameters *simplejson.Json, queryContext plugins.DataQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
namespace := parameters.Get("namespace").MustString()
@@ -437,7 +438,7 @@ func (e *cloudWatchExecutor) handleGetDimensions(ctx context.Context, parameters
return result, nil
}
func (e *cloudWatchExecutor) handleGetDimensionValues(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
func (e *cloudWatchExecutor) handleGetDimensionValues(ctx context.Context, parameters *simplejson.Json, queryContext plugins.DataQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
namespace := parameters.Get("namespace").MustString()
metricName := parameters.Get("metricName").MustString()
@@ -489,7 +490,7 @@ func (e *cloudWatchExecutor) handleGetDimensionValues(ctx context.Context, param
}
func (e *cloudWatchExecutor) handleGetEbsVolumeIds(ctx context.Context, parameters *simplejson.Json,
queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
queryContext plugins.DataQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
instanceId := parameters.Get("instanceId").MustString()
@@ -512,7 +513,7 @@ func (e *cloudWatchExecutor) handleGetEbsVolumeIds(ctx context.Context, paramete
}
func (e *cloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context, parameters *simplejson.Json,
queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
queryContext plugins.DataQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
attributeName := parameters.Get("attributeName").MustString()
filterJson := parameters.Get("filters").MustMap()
@@ -592,7 +593,7 @@ func (e *cloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context,
}
func (e *cloudWatchExecutor) handleGetResourceArns(ctx context.Context, parameters *simplejson.Json,
queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
queryContext plugins.DataQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
resourceType := parameters.Get("resourceType").MustString()
filterJson := parameters.Get("tags").MustMap()
+43 -43
View File
@@ -14,7 +14,7 @@ import (
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -45,8 +45,8 @@ func TestQuery_Metrics(t *testing.T) {
},
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "metricFindQuery",
@@ -59,15 +59,15 @@ func TestQuery_Metrics(t *testing.T) {
})
require.NoError(t, err)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Meta: simplejson.NewFromAny(map[string]interface{}{
"rowCount": 1,
}),
Tables: []*tsdb.Table{
Tables: []plugins.DataTable{
{
Columns: []tsdb.TableColumn{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -75,7 +75,7 @@ func TestQuery_Metrics(t *testing.T) {
Text: "value",
},
},
Rows: []tsdb.RowValues{
Rows: []plugins.DataRowValues{
{
"Test_MetricName",
"Test_MetricName",
@@ -102,8 +102,8 @@ func TestQuery_Metrics(t *testing.T) {
},
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "metricFindQuery",
@@ -116,15 +116,15 @@ func TestQuery_Metrics(t *testing.T) {
})
require.NoError(t, err)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Meta: simplejson.NewFromAny(map[string]interface{}{
"rowCount": 1,
}),
Tables: []*tsdb.Table{
Tables: []plugins.DataTable{
{
Columns: []tsdb.TableColumn{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -132,7 +132,7 @@ func TestQuery_Metrics(t *testing.T) {
Text: "value",
},
},
Rows: []tsdb.RowValues{
Rows: []plugins.DataRowValues{
{
"Test_DimensionName",
"Test_DimensionName",
@@ -164,8 +164,8 @@ func TestQuery_Regions(t *testing.T) {
regions: []string{regionName},
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "metricFindQuery",
@@ -178,7 +178,7 @@ func TestQuery_Regions(t *testing.T) {
})
require.NoError(t, err)
rows := []tsdb.RowValues{}
rows := []plugins.DataRowValues{}
for _, region := range knownRegions {
rows = append(rows, []interface{}{
region,
@@ -189,15 +189,15 @@ func TestQuery_Regions(t *testing.T) {
regionName,
regionName,
})
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Meta: simplejson.NewFromAny(map[string]interface{}{
"rowCount": len(knownRegions) + 1,
}),
Tables: []*tsdb.Table{
Tables: []plugins.DataTable{
{
Columns: []tsdb.TableColumn{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -246,8 +246,8 @@ func TestQuery_InstanceAttributes(t *testing.T) {
},
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "metricFindQuery",
@@ -263,15 +263,15 @@ func TestQuery_InstanceAttributes(t *testing.T) {
})
require.NoError(t, err)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Meta: simplejson.NewFromAny(map[string]interface{}{
"rowCount": 1,
}),
Tables: []*tsdb.Table{
Tables: []plugins.DataTable{
{
Columns: []tsdb.TableColumn{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -279,7 +279,7 @@ func TestQuery_InstanceAttributes(t *testing.T) {
Text: "value",
},
},
Rows: []tsdb.RowValues{
Rows: []plugins.DataRowValues{
{
instanceID,
instanceID,
@@ -349,8 +349,8 @@ func TestQuery_EBSVolumeIDs(t *testing.T) {
},
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "metricFindQuery",
@@ -363,15 +363,15 @@ func TestQuery_EBSVolumeIDs(t *testing.T) {
})
require.NoError(t, err)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Meta: simplejson.NewFromAny(map[string]interface{}{
"rowCount": 6,
}),
Tables: []*tsdb.Table{
Tables: []plugins.DataTable{
{
Columns: []tsdb.TableColumn{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -379,7 +379,7 @@ func TestQuery_EBSVolumeIDs(t *testing.T) {
Text: "value",
},
},
Rows: []tsdb.RowValues{
Rows: []plugins.DataRowValues{
{
"vol-1-1",
"vol-1-1",
@@ -449,8 +449,8 @@ func TestQuery_ResourceARNs(t *testing.T) {
},
}
executor := newExecutor(nil)
resp, err := executor.Query(context.Background(), fakeDataSource(), &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"type": "metricFindQuery",
@@ -466,15 +466,15 @@ func TestQuery_ResourceARNs(t *testing.T) {
})
require.NoError(t, err)
assert.Equal(t, &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
assert.Equal(t, plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"": {
Meta: simplejson.NewFromAny(map[string]interface{}{
"rowCount": 2,
}),
Tables: []*tsdb.Table{
Tables: []plugins.DataTable{
{
Columns: []tsdb.TableColumn{
Columns: []plugins.DataTableColumn{
{
Text: "text",
},
@@ -482,7 +482,7 @@ func TestQuery_ResourceARNs(t *testing.T) {
Text: "value",
},
},
Rows: []tsdb.RowValues{
Rows: []plugins.DataRowValues{
{
"arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567",
"arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567",
+13 -9
View File
@@ -9,7 +9,7 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
// returns a map of queries with query id as key. In the case a q request query
@@ -55,7 +55,8 @@ func (e *cloudWatchExecutor) transformRequestQueriesToCloudWatchQueries(requestQ
return cloudwatchQueries, nil
}
func (e *cloudWatchExecutor) transformQueryResponsesToQueryResult(cloudwatchResponses []*cloudwatchResponse, requestQueries []*requestQuery, startTime time.Time, endTime time.Time) (map[string]*tsdb.QueryResult, error) {
func (e *cloudWatchExecutor) transformQueryResponsesToQueryResult(cloudwatchResponses []*cloudwatchResponse,
requestQueries []*requestQuery, startTime time.Time, endTime time.Time) (map[string]plugins.DataQueryResult, error) {
responsesByRefID := make(map[string][]*cloudwatchResponse)
refIDs := sort.StringSlice{}
for _, res := range cloudwatchResponses {
@@ -65,12 +66,13 @@ func (e *cloudWatchExecutor) transformQueryResponsesToQueryResult(cloudwatchResp
// Ensure stable results
refIDs.Sort()
results := make(map[string]*tsdb.QueryResult)
results := make(map[string]plugins.DataQueryResult)
for _, refID := range refIDs {
responses := responsesByRefID[refID]
queryResult := tsdb.NewQueryResult()
queryResult.RefId = refID
queryResult.Series = tsdb.TimeSeriesSlice{}
queryResult := plugins.DataQueryResult{
RefID: refID,
Series: plugins.DataTimeSeriesSlice{},
}
frames := make(data.Frames, 0, len(responses))
requestExceededMaxLimit := false
@@ -133,15 +135,17 @@ func (e *cloudWatchExecutor) transformQueryResponsesToQueryResult(cloudwatchResp
frame.Fields[1].Config.Links = createDataLinks(link)
}
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryResult.Dataframes = plugins.NewDecodedDataFrames(frames)
results[refID] = queryResult
}
return results, nil
}
// buildDeepLink generates a deep link from Grafana to the CloudWatch console. The link params are based on metric(s) for a given query row in the Query Editor.
func buildDeepLink(refID string, requestQueries []*requestQuery, executedQueries []executedQuery, startTime time.Time, endTime time.Time) (string, error) {
// buildDeepLink generates a deep link from Grafana to the CloudWatch console. The link params are based on
// metric(s) for a given query row in the Query Editor.
func buildDeepLink(refID string, requestQueries []*requestQuery, executedQueries []executedQuery, startTime time.Time,
endTime time.Time) (string, error) {
if isMathExpression(executedQueries) {
return "", nil
}
+4 -3
View File
@@ -11,11 +11,12 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
// Parses the json queries and returns a requestQuery. The requestQuery has a 1 to 1 mapping to a query editor row
func (e *cloudWatchExecutor) parseQueries(queryContext *tsdb.TsdbQuery, startTime time.Time, endTime time.Time) (map[string][]*requestQuery, error) {
func (e *cloudWatchExecutor) parseQueries(queryContext plugins.DataQuery, startTime time.Time,
endTime time.Time) (map[string][]*requestQuery, error) {
requestQueries := make(map[string][]*requestQuery)
for i, query := range queryContext.Queries {
queryType := query.Model.Get("type").MustString()
@@ -23,7 +24,7 @@ func (e *cloudWatchExecutor) parseQueries(queryContext *tsdb.TsdbQuery, startTim
continue
}
refID := query.RefId
refID := query.RefID
query, err := parseRequestQuery(queryContext.Queries[i].Model, refID, startTime, endTime)
if err != nil {
return nil, &queryError{err: err, RefID: refID}
+3 -3
View File
@@ -5,13 +5,13 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRequestParser(t *testing.T) {
timeRange := tsdb.NewTimeRange("now-1h", "now-2h")
timeRange := plugins.NewDataTimeRange("now-1h", "now-2h")
from, err := timeRange.ParseFrom()
require.NoError(t, err)
to, err := timeRange.ParseTo()
@@ -102,7 +102,7 @@ func TestRequestParser(t *testing.T) {
"hide": false,
})
query.Set("period", "900")
timeRange := tsdb.NewTimeRange("now-1h", "now-2h")
timeRange := plugins.NewDataTimeRange("now-1h", "now-2h")
from, err := timeRange.ParseFrom()
require.NoError(t, err)
to, err := timeRange.ParseTo()
+23 -22
View File
@@ -5,37 +5,38 @@ import (
"fmt"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util/errutil"
"golang.org/x/sync/errgroup"
)
func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryContext plugins.DataQuery) (
plugins.DataResponse, error) {
plog.Debug("Executing time series query")
startTime, err := queryContext.TimeRange.ParseFrom()
if err != nil {
return nil, errutil.Wrap("failed to parse start time", err)
return plugins.DataResponse{}, errutil.Wrap("failed to parse start time", err)
}
endTime, err := queryContext.TimeRange.ParseTo()
if err != nil {
return nil, errutil.Wrap("failed to parse end time", err)
return plugins.DataResponse{}, errutil.Wrap("failed to parse end time", err)
}
if !startTime.Before(endTime) {
return nil, fmt.Errorf("invalid time range: start time must be before end time")
return plugins.DataResponse{}, fmt.Errorf("invalid time range: start time must be before end time")
}
requestQueriesByRegion, err := e.parseQueries(queryContext, startTime, endTime)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
if len(requestQueriesByRegion) == 0 {
return &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
return plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}, nil
}
resultChan := make(chan *tsdb.QueryResult, len(queryContext.Queries))
resultChan := make(chan plugins.DataQueryResult, len(queryContext.Queries))
eg, ectx := errgroup.WithContext(ctx)
for r, q := range requestQueriesByRegion {
requestQueries := q
@@ -45,7 +46,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
if err := recover(); err != nil {
plog.Error("Execute Get Metric Data Query Panic", "error", err, "stack", log.Stack(1))
if theErr, ok := err.(error); ok {
resultChan <- &tsdb.QueryResult{
resultChan <- plugins.DataQueryResult{
Error: theErr,
}
}
@@ -60,8 +61,8 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
queries, err := e.transformRequestQueriesToCloudWatchQueries(requestQueries)
if err != nil {
for _, query := range requestQueries {
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
resultChan <- plugins.DataQueryResult{
RefID: query.RefId,
Error: err,
}
}
@@ -77,8 +78,8 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
mdo, err := e.executeRequest(ectx, client, metricDataInput)
if err != nil {
for _, query := range requestQueries {
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
resultChan <- plugins.DataQueryResult{
RefID: query.RefId,
Error: err,
}
}
@@ -88,8 +89,8 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
responses, err := e.parseResponse(mdo, queries)
if err != nil {
for _, query := range requestQueries {
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
resultChan <- plugins.DataQueryResult{
RefID: query.RefId,
Error: err,
}
}
@@ -100,8 +101,8 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
res, err := e.transformQueryResponsesToQueryResult(cloudwatchResponses, requestQueries, startTime, endTime)
if err != nil {
for _, query := range requestQueries {
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
resultChan <- plugins.DataQueryResult{
RefID: query.RefId,
Error: err,
}
}
@@ -115,15 +116,15 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
})
}
if err := eg.Wait(); err != nil {
return nil, err
return plugins.DataResponse{}, err
}
close(resultChan)
results := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
results := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
for result := range resultChan {
results.Results[result.RefId] = result
results.Results[result.RefID] = result
}
return results, nil
}
@@ -4,7 +4,7 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
)
@@ -12,12 +12,16 @@ func TestTimeSeriesQuery(t *testing.T) {
executor := newExecutor(nil)
t.Run("End time before start time should result in error", func(t *testing.T) {
_, err := executor.executeTimeSeriesQuery(context.TODO(), &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-2h")})
timeRange := plugins.NewDataTimeRange("now-1h", "now-2h")
_, err := executor.executeTimeSeriesQuery(
context.TODO(), plugins.DataQuery{TimeRange: &timeRange})
assert.EqualError(t, err, "invalid time range: start time must be before end time")
})
t.Run("End time equals start time should result in error", func(t *testing.T) {
_, err := executor.executeTimeSeriesQuery(context.TODO(), &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-1h")})
timeRange := plugins.NewDataTimeRange("now-1h", "now-1h")
_, err := executor.executeTimeSeriesQuery(
context.TODO(), plugins.DataQuery{TimeRange: &timeRange})
assert.EqualError(t, err, "invalid time range: start time must be before end time")
})
}
+6 -5
View File
@@ -15,9 +15,10 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"golang.org/x/net/context/ctxhttp"
)
@@ -42,7 +43,7 @@ type Client interface {
}
// NewClient creates a new elasticsearch client
var NewClient = func(ctx context.Context, ds *models.DataSource, timeRange *tsdb.TimeRange) (Client, error) {
var NewClient = func(ctx context.Context, ds *models.DataSource, timeRange plugins.DataTimeRange) (Client, error) {
version, err := ds.JsonData.Get("esVersion").Int()
if err != nil {
return nil, fmt.Errorf("elasticsearch version is required, err=%v", err)
@@ -87,7 +88,7 @@ type baseClientImpl struct {
version int
timeField string
indices []string
timeRange *tsdb.TimeRange
timeRange plugins.DataTimeRange
debugEnabled bool
}
@@ -100,7 +101,7 @@ func (c *baseClientImpl) GetTimeField() string {
}
func (c *baseClientImpl) GetMinInterval(queryInterval string) (time.Duration, error) {
return tsdb.GetIntervalFrom(c.ds, simplejson.NewFromAny(map[string]interface{}{
return interval.GetIntervalFrom(c.ds, simplejson.NewFromAny(map[string]interface{}{
"interval": queryInterval,
}), 5*time.Second)
}
@@ -112,7 +113,7 @@ func (c *baseClientImpl) getSettings() *simplejson.Json {
type multiRequest struct {
header map[string]interface{}
body interface{}
interval tsdb.Interval
interval interval.Interval
}
func (c *baseClientImpl) executeBatchRequest(uriPath, uriQuery string, requests []*multiRequest) (*response, error) {
+12 -11
View File
@@ -12,7 +12,8 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -23,7 +24,7 @@ func TestNewClient(t *testing.T) {
JsonData: simplejson.NewFromAny(make(map[string]interface{})),
}
_, err := NewClient(context.Background(), ds, nil)
_, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.Error(t, err)
})
@@ -34,7 +35,7 @@ func TestNewClient(t *testing.T) {
}),
}
_, err := NewClient(context.Background(), ds, nil)
_, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.Error(t, err)
})
@@ -46,7 +47,7 @@ func TestNewClient(t *testing.T) {
}),
}
_, err := NewClient(context.Background(), ds, nil)
_, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.Error(t, err)
})
@@ -58,7 +59,7 @@ func TestNewClient(t *testing.T) {
}),
}
c, err := NewClient(context.Background(), ds, nil)
c, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.NoError(t, err)
assert.Equal(t, 2, c.GetVersion())
})
@@ -71,7 +72,7 @@ func TestNewClient(t *testing.T) {
}),
}
c, err := NewClient(context.Background(), ds, nil)
c, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.NoError(t, err)
assert.Equal(t, 5, c.GetVersion())
})
@@ -84,7 +85,7 @@ func TestNewClient(t *testing.T) {
}),
}
c, err := NewClient(context.Background(), ds, nil)
c, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.NoError(t, err)
assert.Equal(t, 56, c.GetVersion())
})
@@ -97,7 +98,7 @@ func TestNewClient(t *testing.T) {
}),
}
c, err := NewClient(context.Background(), ds, nil)
c, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.NoError(t, err)
assert.Equal(t, 60, c.GetVersion())
})
@@ -110,7 +111,7 @@ func TestNewClient(t *testing.T) {
}),
}
c, err := NewClient(context.Background(), ds, nil)
c, err := NewClient(context.Background(), ds, plugins.DataTimeRange{})
require.NoError(t, err)
assert.Equal(t, 70, c.GetVersion())
})
@@ -329,7 +330,7 @@ func createMultisearchForTest(t *testing.T, c Client) (*MultiSearchRequest, erro
t.Helper()
msb := c.MultiSearch()
s := msb.Search(tsdb.Interval{Value: 15 * time.Second, Text: "15s"})
s := msb.Search(interval.Interval{Value: 15 * time.Second, Text: "15s"})
s.Agg().DateHistogram("2", "@timestamp", func(a *DateHistogramAgg, ab AggBuilder) {
a.Interval = "$__interval"
@@ -376,7 +377,7 @@ func httpClientScenario(t *testing.T, desc string, ds *models.DataSource, fn sce
to := time.Date(2018, 5, 15, 17, 55, 0, 0, time.UTC)
fromStr := fmt.Sprintf("%d", from.UnixNano()/int64(time.Millisecond))
toStr := fmt.Sprintf("%d", to.UnixNano()/int64(time.Millisecond))
timeRange := tsdb.NewTimeRange(fromStr, toStr)
timeRange := plugins.NewDataTimeRange(fromStr, toStr)
c, err := NewClient(context.Background(), ds, timeRange)
require.NoError(t, err)
@@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
const (
@@ -19,7 +19,7 @@ const (
)
type indexPattern interface {
GetIndices(timeRange *tsdb.TimeRange) ([]string, error)
GetIndices(timeRange plugins.DataTimeRange) ([]string, error)
}
var newIndexPattern = func(interval string, pattern string) (indexPattern, error) {
@@ -34,7 +34,7 @@ type staticIndexPattern struct {
indexName string
}
func (ip *staticIndexPattern) GetIndices(timeRange *tsdb.TimeRange) ([]string, error) {
func (ip *staticIndexPattern) GetIndices(timeRange plugins.DataTimeRange) ([]string, error) {
return []string{ip.indexName}, nil
}
@@ -73,7 +73,7 @@ func newDynamicIndexPattern(interval, pattern string) (*dynamicIndexPattern, err
}, nil
}
func (ip *dynamicIndexPattern) GetIndices(timeRange *tsdb.TimeRange) ([]string, error) {
func (ip *dynamicIndexPattern) GetIndices(timeRange plugins.DataTimeRange) ([]string, error) {
from := timeRange.GetFromAsTimeUTC()
to := timeRange.GetToAsTimeUTC()
intervals := ip.intervalGenerator.Generate(from, to)
@@ -5,19 +5,18 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
. "github.com/smartystreets/goconvey/convey"
)
func TestIndexPattern(t *testing.T) {
Convey("Static index patterns", t, func() {
indexPatternScenario(noInterval, "data-*", nil, func(indices []string) {
indexPatternScenario(noInterval, "data-*", plugins.DataTimeRange{}, func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-*")
})
indexPatternScenario(noInterval, "es-index-name", nil, func(indices []string) {
indexPatternScenario(noInterval, "es-index-name", plugins.DataTimeRange{}, func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "es-index-name")
})
@@ -27,62 +26,62 @@ func TestIndexPattern(t *testing.T) {
from := fmt.Sprintf("%d", time.Date(2018, 5, 15, 17, 50, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
to := fmt.Sprintf("%d", time.Date(2018, 5, 15, 17, 55, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
indexPatternScenario(intervalHourly, "[data-]YYYY.MM.DD.HH", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalHourly, "[data-]YYYY.MM.DD.HH", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018.05.15.17")
})
indexPatternScenario(intervalHourly, "YYYY.MM.DD.HH[-data]", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalHourly, "YYYY.MM.DD.HH[-data]", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018.05.15.17-data")
})
indexPatternScenario(intervalDaily, "[data-]YYYY.MM.DD", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalDaily, "[data-]YYYY.MM.DD", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018.05.15")
})
indexPatternScenario(intervalDaily, "YYYY.MM.DD[-data]", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalDaily, "YYYY.MM.DD[-data]", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018.05.15-data")
})
indexPatternScenario(intervalWeekly, "[data-]GGGG.WW", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalWeekly, "[data-]GGGG.WW", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018.20")
})
indexPatternScenario(intervalWeekly, "GGGG.WW[-data]", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalWeekly, "GGGG.WW[-data]", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018.20-data")
})
indexPatternScenario(intervalMonthly, "[data-]YYYY.MM", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalMonthly, "[data-]YYYY.MM", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018.05")
})
indexPatternScenario(intervalMonthly, "YYYY.MM[-data]", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalMonthly, "YYYY.MM[-data]", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018.05-data")
})
indexPatternScenario(intervalYearly, "[data-]YYYY", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalYearly, "[data-]YYYY", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018")
})
indexPatternScenario(intervalYearly, "YYYY[-data]", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalYearly, "YYYY[-data]", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018-data")
})
indexPatternScenario(intervalDaily, "YYYY[-data-]MM.DD", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalDaily, "YYYY[-data-]MM.DD", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "2018-data-05.15")
})
indexPatternScenario(intervalDaily, "[data-]YYYY[-moredata-]MM.DD", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalDaily, "[data-]YYYY[-moredata-]MM.DD", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018-moredata-05.15")
})
@@ -90,7 +89,7 @@ func TestIndexPattern(t *testing.T) {
Convey("Should return 01 week", func() {
from = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 50, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
to = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 55, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
indexPatternScenario(intervalWeekly, "[data-]GGGG.WW", tsdb.NewTimeRange(from, to), func(indices []string) {
indexPatternScenario(intervalWeekly, "[data-]GGGG.WW", plugins.NewDataTimeRange(from, to), func(indices []string) {
So(indices, ShouldHaveLength, 1)
So(indices[0], ShouldEqual, "data-2018.03")
})
@@ -276,7 +275,7 @@ func TestIndexPattern(t *testing.T) {
})
}
func indexPatternScenario(interval string, pattern string, timeRange *tsdb.TimeRange, fn func(indices []string)) {
func indexPatternScenario(interval string, pattern string, timeRange plugins.DataTimeRange, fn func(indices []string)) {
Convey(fmt.Sprintf("Index pattern (interval=%s, index=%s", interval, pattern), func() {
ip, err := newIndexPattern(interval, pattern)
So(err, ShouldBeNil)
+2 -3
View File
@@ -5,8 +5,7 @@ import (
"net/http"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
type response struct {
@@ -33,7 +32,7 @@ type SearchDebugInfo struct {
// SearchRequest represents a search request
type SearchRequest struct {
Index string
Interval tsdb.Interval
Interval interval.Interval
Size int
Sort map[string]interface{}
Query *Query
@@ -3,13 +3,13 @@ package es
import (
"strings"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
// SearchRequestBuilder represents a builder which can build a search request
type SearchRequestBuilder struct {
version int
interval tsdb.Interval
interval interval.Interval
index string
size int
sort map[string]interface{}
@@ -19,7 +19,7 @@ type SearchRequestBuilder struct {
}
// NewSearchRequestBuilder create a new search request builder
func NewSearchRequestBuilder(version int, interval tsdb.Interval) *SearchRequestBuilder {
func NewSearchRequestBuilder(version int, interval interval.Interval) *SearchRequestBuilder {
builder := &SearchRequestBuilder{
version: version,
interval: interval,
@@ -131,7 +131,7 @@ func NewMultiSearchRequestBuilder(version int) *MultiSearchRequestBuilder {
}
// Search initiates and returns a new search request builder
func (m *MultiSearchRequestBuilder) Search(interval tsdb.Interval) *SearchRequestBuilder {
func (m *MultiSearchRequestBuilder) Search(interval interval.Interval) *SearchRequestBuilder {
b := NewSearchRequestBuilder(m.version, interval)
m.requestBuilders = append(m.requestBuilders, b)
return b
@@ -6,7 +6,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/interval"
. "github.com/smartystreets/goconvey/convey"
)
@@ -15,7 +15,7 @@ func TestSearchRequest(t *testing.T) {
Convey("Test elasticsearch search request", t, func() {
timeField := "@timestamp"
Convey("Given new search request builder for es version 5", func() {
b := NewSearchRequestBuilder(5, tsdb.Interval{Value: 15 * time.Second, Text: "15s"})
b := NewSearchRequestBuilder(5, interval.Interval{Value: 15 * time.Second, Text: "15s"})
Convey("When building search request", func() {
sr, err := b.Build()
@@ -390,7 +390,7 @@ func TestSearchRequest(t *testing.T) {
})
Convey("Given new search request builder for es version 2", func() {
b := NewSearchRequestBuilder(2, tsdb.Interval{Value: 15 * time.Second, Text: "15s"})
b := NewSearchRequestBuilder(2, interval.Interval{Value: 15 * time.Second, Text: "15s"})
Convey("When adding doc value field", func() {
b.AddDocValueField(timeField)
@@ -449,7 +449,7 @@ func TestMultiSearchRequest(t *testing.T) {
b := NewMultiSearchRequestBuilder(0)
Convey("When adding one search request", func() {
b.Search(tsdb.Interval{Value: 15 * time.Second, Text: "15s"})
b.Search(interval.Interval{Value: 15 * time.Second, Text: "15s"})
Convey("When building search request should contain one search request", func() {
mr, err := b.Build()
@@ -459,8 +459,8 @@ func TestMultiSearchRequest(t *testing.T) {
})
Convey("When adding two search requests", func() {
b.Search(tsdb.Interval{Value: 15 * time.Second, Text: "15s"})
b.Search(tsdb.Interval{Value: 15 * time.Second, Text: "15s"})
b.Search(interval.Interval{Value: 15 * time.Second, Text: "15s"})
b.Search(interval.Interval{Value: 15 * time.Second, Text: "15s"})
Convey("When building search request should contain two search requests", func() {
mr, err := b.Build()
+15 -18
View File
@@ -5,42 +5,39 @@ import (
"fmt"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
// ElasticsearchExecutor represents a handler for handling elasticsearch datasource request
type ElasticsearchExecutor struct{}
var (
intervalCalculator tsdb.IntervalCalculator
)
// NewElasticsearchExecutor creates a new elasticsearch executor
func NewElasticsearchExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return &ElasticsearchExecutor{}, nil
type Executor struct {
intervalCalculator interval.Calculator
}
func init() {
intervalCalculator = tsdb.NewIntervalCalculator(nil)
tsdb.RegisterTsdbQueryEndpoint("elasticsearch", NewElasticsearchExecutor)
// NewExecutor creates a new Executor.
func NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
return &Executor{
intervalCalculator: interval.NewCalculator(),
}, nil
}
// Query handles an elasticsearch datasource request
func (e *ElasticsearchExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *Executor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
tsdbQuery plugins.DataQuery) (plugins.DataResponse, error) {
if len(tsdbQuery.Queries) == 0 {
return nil, fmt.Errorf("query contains no queries")
return plugins.DataResponse{}, fmt.Errorf("query contains no queries")
}
client, err := es.NewClient(ctx, dsInfo, tsdbQuery.TimeRange)
client, err := es.NewClient(ctx, dsInfo, *tsdbQuery.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
if tsdbQuery.Debug {
client.EnableDebug()
}
query := newTimeSeriesQuery(client, tsdbQuery, intervalCalculator)
query := newTimeSeriesQuery(client, tsdbQuery, e.intervalCalculator)
return query.execute()
}
+54 -49
View File
@@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
)
@@ -40,10 +40,10 @@ var newResponseParser = func(responses []*es.SearchResponse, targets []*Query, d
}
}
func (rp *responseParser) getTimeSeries() (*tsdb.Response, error) {
result := &tsdb.Response{}
result.Results = make(map[string]*tsdb.QueryResult)
func (rp *responseParser) getTimeSeries() (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
if rp.Responses == nil {
return result, nil
}
@@ -57,27 +57,29 @@ func (rp *responseParser) getTimeSeries() (*tsdb.Response, error) {
}
if res.Error != nil {
result.Results[target.RefID] = getErrorFromElasticResponse(res)
result.Results[target.RefID].Meta = debugInfo
errRslt := getErrorFromElasticResponse(res)
errRslt.Meta = debugInfo
result.Results[target.RefID] = errRslt
continue
}
queryRes := tsdb.NewQueryResult()
queryRes.Meta = debugInfo
queryRes := plugins.DataQueryResult{
Meta: debugInfo,
}
props := make(map[string]string)
table := tsdb.Table{
Columns: make([]tsdb.TableColumn, 0),
Rows: make([]tsdb.RowValues, 0),
table := plugins.DataTable{
Columns: make([]plugins.DataTableColumn, 0),
Rows: make([]plugins.DataRowValues, 0),
}
err := rp.processBuckets(res.Aggregations, target, &queryRes.Series, &table, props, 0)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
rp.nameSeries(&queryRes.Series, target)
rp.trimDatapoints(&queryRes.Series, target)
rp.nameSeries(queryRes.Series, target)
rp.trimDatapoints(queryRes.Series, target)
if len(table.Rows) > 0 {
queryRes.Tables = append(queryRes.Tables, &table)
queryRes.Tables = append(queryRes.Tables, table)
}
result.Results[target.RefID] = queryRes
@@ -85,7 +87,8 @@ func (rp *responseParser) getTimeSeries() (*tsdb.Response, error) {
return result, nil
}
func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Query, series *tsdb.TimeSeriesSlice, table *tsdb.Table, props map[string]string, depth int) error {
func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Query,
series *plugins.DataTimeSeriesSlice, table *plugins.DataTable, props map[string]string, depth int) error {
var err error
maxDepth := len(target.BucketAggs) - 1
@@ -162,7 +165,8 @@ func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Qu
return nil
}
func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query, series *tsdb.TimeSeriesSlice, props map[string]string) error {
func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query, series *plugins.DataTimeSeriesSlice,
props map[string]string) error {
for _, metric := range target.Metrics {
if metric.Hide {
continue
@@ -170,7 +174,7 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
switch metric.Type {
case countType:
newSeries := tsdb.TimeSeries{
newSeries := plugins.DataTimeSeries{
Tags: make(map[string]string),
}
@@ -178,14 +182,14 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
bucket := simplejson.NewFromAny(v)
value := castToNullFloat(bucket.Get("doc_count"))
key := castToNullFloat(bucket.Get("key"))
newSeries.Points = append(newSeries.Points, tsdb.TimePoint{value, key})
newSeries.Points = append(newSeries.Points, plugins.DataTimePoint{value, key})
}
for k, v := range props {
newSeries.Tags[k] = v
}
newSeries.Tags["metric"] = countType
*series = append(*series, &newSeries)
*series = append(*series, newSeries)
case percentilesType:
buckets := esAgg.Get("buckets").MustArray()
@@ -202,7 +206,7 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
}
sort.Strings(percentileKeys)
for _, percentileName := range percentileKeys {
newSeries := tsdb.TimeSeries{
newSeries := plugins.DataTimeSeries{
Tags: make(map[string]string),
}
for k, v := range props {
@@ -214,9 +218,9 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
bucket := simplejson.NewFromAny(v)
value := castToNullFloat(bucket.GetPath(metric.ID, "values", percentileName))
key := castToNullFloat(bucket.Get("key"))
newSeries.Points = append(newSeries.Points, tsdb.TimePoint{value, key})
newSeries.Points = append(newSeries.Points, plugins.DataTimePoint{value, key})
}
*series = append(*series, &newSeries)
*series = append(*series, newSeries)
}
case extendedStatsType:
buckets := esAgg.Get("buckets").MustArray()
@@ -233,7 +237,7 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
continue
}
newSeries := tsdb.TimeSeries{
newSeries := plugins.DataTimeSeries{
Tags: make(map[string]string),
}
for k, v := range props {
@@ -254,12 +258,12 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
default:
value = castToNullFloat(bucket.GetPath(metric.ID, statName))
}
newSeries.Points = append(newSeries.Points, tsdb.TimePoint{value, key})
newSeries.Points = append(newSeries.Points, plugins.DataTimePoint{value, key})
}
*series = append(*series, &newSeries)
*series = append(*series, newSeries)
}
default:
newSeries := tsdb.TimeSeries{
newSeries := plugins.DataTimeSeries{
Tags: make(map[string]string),
}
for k, v := range props {
@@ -282,15 +286,16 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
} else {
value = castToNullFloat(bucket.GetPath(metric.ID, "value"))
}
newSeries.Points = append(newSeries.Points, tsdb.TimePoint{value, key})
newSeries.Points = append(newSeries.Points, plugins.DataTimePoint{value, key})
}
*series = append(*series, &newSeries)
*series = append(*series, newSeries)
}
}
return nil
}
func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef *BucketAgg, target *Query, table *tsdb.Table, props map[string]string) error {
func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef *BucketAgg, target *Query,
table *plugins.DataTable, props map[string]string) error {
propKeys := make([]string, 0)
for k := range props {
propKeys = append(propKeys, k)
@@ -299,12 +304,12 @@ func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef
if len(table.Columns) == 0 {
for _, propKey := range propKeys {
table.Columns = append(table.Columns, tsdb.TableColumn{Text: propKey})
table.Columns = append(table.Columns, plugins.DataTableColumn{Text: propKey})
}
table.Columns = append(table.Columns, tsdb.TableColumn{Text: aggDef.Field})
table.Columns = append(table.Columns, plugins.DataTableColumn{Text: aggDef.Field})
}
addMetricValue := func(values *tsdb.RowValues, metricName string, value null.Float) {
addMetricValue := func(values *plugins.DataRowValues, metricName string, value null.Float) {
found := false
for _, c := range table.Columns {
if c.Text == metricName {
@@ -313,14 +318,14 @@ func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef
}
}
if !found {
table.Columns = append(table.Columns, tsdb.TableColumn{Text: metricName})
table.Columns = append(table.Columns, plugins.DataTableColumn{Text: metricName})
}
*values = append(*values, value)
}
for _, v := range esAgg.Get("buckets").MustArray() {
bucket := simplejson.NewFromAny(v)
values := make(tsdb.RowValues, 0)
values := make(plugins.DataRowValues, 0)
for _, propKey := range propKeys {
values = append(values, props[propKey])
@@ -390,7 +395,7 @@ func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef
return nil
}
func (rp *responseParser) trimDatapoints(series *tsdb.TimeSeriesSlice, target *Query) {
func (rp *responseParser) trimDatapoints(series plugins.DataTimeSeriesSlice, target *Query) {
var histogram *BucketAgg
for _, bucketAgg := range target.BucketAggs {
if bucketAgg.Type == dateHistType {
@@ -408,31 +413,31 @@ func (rp *responseParser) trimDatapoints(series *tsdb.TimeSeriesSlice, target *Q
return
}
for _, s := range *series {
if len(s.Points) > trimEdges*2 {
s.Points = s.Points[trimEdges : len(s.Points)-trimEdges]
for i := range series {
if len(series[i].Points) > trimEdges*2 {
series[i].Points = series[i].Points[trimEdges : len(series[i].Points)-trimEdges]
}
}
}
func (rp *responseParser) nameSeries(seriesList *tsdb.TimeSeriesSlice, target *Query) {
set := make(map[string]string)
for _, v := range *seriesList {
func (rp *responseParser) nameSeries(seriesList plugins.DataTimeSeriesSlice, target *Query) {
set := make(map[string]struct{})
for _, v := range seriesList {
if metricType, exists := v.Tags["metric"]; exists {
if _, ok := set[metricType]; !ok {
set[metricType] = ""
set[metricType] = struct{}{}
}
}
}
metricTypeCount := len(set)
for _, series := range *seriesList {
series.Name = rp.getSeriesName(series, target, metricTypeCount)
for i := range seriesList {
seriesList[i].Name = rp.getSeriesName(seriesList[i], target, metricTypeCount)
}
}
var aliasPatternRegex = regexp.MustCompile(`\{\{([\s\S]+?)\}\}`)
func (rp *responseParser) getSeriesName(series *tsdb.TimeSeries, target *Query, metricTypeCount int) string {
func (rp *responseParser) getSeriesName(series plugins.DataTimeSeries, target *Query, metricTypeCount int) string {
metricType := series.Tags["metric"]
metricName := rp.getMetricName(metricType)
delete(series.Tags, "metric")
@@ -564,8 +569,8 @@ func findAgg(target *Query, aggID string) (*BucketAgg, error) {
return nil, errors.New("can't found aggDef, aggID:" + aggID)
}
func getErrorFromElasticResponse(response *es.SearchResponse) *tsdb.QueryResult {
result := tsdb.NewQueryResult()
func getErrorFromElasticResponse(response *es.SearchResponse) plugins.DataQueryResult {
var result plugins.DataQueryResult
json := simplejson.NewFromAny(response.Error)
reason := json.Get("reason").MustString()
rootCauseReason := json.Get("root_cause").GetIndex(0).Get("reason").MustString()
@@ -8,9 +8,9 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/plugins"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
"github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey"
)
@@ -999,9 +999,10 @@ func newResponseParserForTest(tsdbQueries map[string]string, responseBody string
to := time.Date(2018, 5, 15, 17, 55, 0, 0, time.UTC)
fromStr := fmt.Sprintf("%d", from.UnixNano()/int64(time.Millisecond))
toStr := fmt.Sprintf("%d", to.UnixNano()/int64(time.Millisecond))
tsdbQuery := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{},
TimeRange: tsdb.NewTimeRange(fromStr, toStr),
timeRange := plugins.NewDataTimeRange(fromStr, toStr)
tsdbQuery := plugins.DataQuery{
Queries: []plugins.DataSubQuery{},
TimeRange: &timeRange,
}
for refID, tsdbQueryBody := range tsdbQueries {
@@ -1010,9 +1011,9 @@ func newResponseParserForTest(tsdbQueries map[string]string, responseBody string
return nil, err
}
tsdbQuery.Queries = append(tsdbQuery.Queries, &tsdb.Query{
tsdbQuery.Queries = append(tsdbQuery.Queries, plugins.DataSubQuery{
Model: tsdbQueryJSON,
RefId: refID,
RefID: refID,
})
}
+20 -18
View File
@@ -6,52 +6,54 @@ import (
"strconv"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
type timeSeriesQuery struct {
client es.Client
tsdbQuery *tsdb.TsdbQuery
intervalCalculator tsdb.IntervalCalculator
tsdbQuery plugins.DataQuery
intervalCalculator interval.Calculator
}
var newTimeSeriesQuery = func(client es.Client, tsdbQuery *tsdb.TsdbQuery, intervalCalculator tsdb.IntervalCalculator) *timeSeriesQuery {
var newTimeSeriesQuery = func(client es.Client, dataQuery plugins.DataQuery,
intervalCalculator interval.Calculator) *timeSeriesQuery {
return &timeSeriesQuery{
client: client,
tsdbQuery: tsdbQuery,
tsdbQuery: dataQuery,
intervalCalculator: intervalCalculator,
}
}
func (e *timeSeriesQuery) execute() (*tsdb.Response, error) {
func (e *timeSeriesQuery) execute() (plugins.DataResponse, error) {
tsQueryParser := newTimeSeriesQueryParser()
queries, err := tsQueryParser.parse(e.tsdbQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
ms := e.client.MultiSearch()
from := fmt.Sprintf("%d", e.tsdbQuery.TimeRange.GetFromAsMsEpoch())
to := fmt.Sprintf("%d", e.tsdbQuery.TimeRange.GetToAsMsEpoch())
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
for _, q := range queries {
if err := e.processQuery(q, ms, from, to, result); err != nil {
return nil, err
return plugins.DataResponse{}, err
}
}
req, err := ms.Build()
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
res, err := e.client.ExecuteMultisearch(req)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
rp := newResponseParser(res.Responses, queries, res.DebugInfo)
@@ -59,12 +61,12 @@ func (e *timeSeriesQuery) execute() (*tsdb.Response, error) {
}
func (e *timeSeriesQuery) processQuery(q *Query, ms *es.MultiSearchRequestBuilder, from, to string,
result *tsdb.Response) error {
result plugins.DataResponse) error {
minInterval, err := e.client.GetMinInterval(q.Interval)
if err != nil {
return err
}
interval := e.intervalCalculator.Calculate(e.tsdbQuery.TimeRange, minInterval)
interval := e.intervalCalculator.Calculate(*e.tsdbQuery.TimeRange, minInterval)
b := ms.Search(interval)
b.Size(0)
@@ -77,8 +79,8 @@ func (e *timeSeriesQuery) processQuery(q *Query, ms *es.MultiSearchRequestBuilde
if len(q.BucketAggs) == 0 {
if len(q.Metrics) == 0 || q.Metrics[0].Type != "raw_document" {
result.Results[q.RefID] = &tsdb.QueryResult{
RefId: q.RefID,
result.Results[q.RefID] = plugins.DataQueryResult{
RefID: q.RefID,
Error: fmt.Errorf("invalid query, missing metrics and aggregations"),
ErrorString: "invalid query, missing metrics and aggregations",
}
@@ -308,7 +310,7 @@ func newTimeSeriesQueryParser() *timeSeriesQueryParser {
return &timeSeriesQueryParser{}
}
func (p *timeSeriesQueryParser) parse(tsdbQuery *tsdb.TsdbQuery) ([]*Query, error) {
func (p *timeSeriesQueryParser) parse(tsdbQuery plugins.DataQuery) ([]*Query, error) {
queries := make([]*Query, 0)
for _, q := range tsdbQuery.Queries {
model := q.Model
@@ -335,7 +337,7 @@ func (p *timeSeriesQueryParser) parse(tsdbQuery *tsdb.TsdbQuery) ([]*Query, erro
Metrics: metrics,
Alias: alias,
Interval: interval,
RefID: q.RefId,
RefID: q.RefID,
})
}
@@ -5,10 +5,11 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/plugins"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey"
)
@@ -897,13 +898,13 @@ func (c *fakeClient) MultiSearch() *es.MultiSearchRequestBuilder {
return c.builder
}
func newTsdbQuery(body string) (*tsdb.TsdbQuery, error) {
func newDataQuery(body string) (plugins.DataQuery, error) {
json, err := simplejson.NewJson([]byte(body))
if err != nil {
return nil, err
return plugins.DataQuery{}, err
}
return &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
return plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: json,
},
@@ -911,22 +912,24 @@ func newTsdbQuery(body string) (*tsdb.TsdbQuery, error) {
}, nil
}
func executeTsdbQuery(c es.Client, body string, from, to time.Time, minInterval time.Duration) (*tsdb.Response, error) {
func executeTsdbQuery(c es.Client, body string, from, to time.Time, minInterval time.Duration) (
plugins.DataResponse, error) {
json, err := simplejson.NewJson([]byte(body))
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
fromStr := fmt.Sprintf("%d", from.UnixNano()/int64(time.Millisecond))
toStr := fmt.Sprintf("%d", to.UnixNano()/int64(time.Millisecond))
tsdbQuery := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
timeRange := plugins.NewDataTimeRange(fromStr, toStr)
tsdbQuery := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: json,
},
},
TimeRange: tsdb.NewTimeRange(fromStr, toStr),
TimeRange: &timeRange,
}
query := newTimeSeriesQuery(c, tsdbQuery, tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{MinInterval: minInterval}))
query := newTimeSeriesQuery(c, tsdbQuery, interval.NewCalculator(interval.CalculatorOptions{MinInterval: minInterval}))
return query.execute()
}
@@ -985,7 +988,7 @@ func TestTimeSeriesQueryParser(t *testing.T) {
}
]
}`
tsdbQuery, err := newTsdbQuery(body)
tsdbQuery, err := newDataQuery(body)
So(err, ShouldBeNil)
queries, err := p.parse(tsdbQuery)
So(err, ShouldBeNil)
-45
View File
@@ -1,45 +0,0 @@
package tsdb
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type FakeExecutor struct {
results map[string]*QueryResult
resultsFn map[string]ResultsFn
}
type ResultsFn func(context *TsdbQuery) *QueryResult
func NewFakeExecutor(dsInfo *models.DataSource) (*FakeExecutor, error) {
return &FakeExecutor{
results: make(map[string]*QueryResult),
resultsFn: make(map[string]ResultsFn),
}, nil
}
func (e *FakeExecutor) Query(ctx context.Context, dsInfo *models.DataSource, context *TsdbQuery) (*Response, error) {
result := &Response{Results: make(map[string]*QueryResult)}
for _, query := range context.Queries {
if results, has := e.results[query.RefId]; has {
result.Results[query.RefId] = results
}
if testFunc, has := e.resultsFn[query.RefId]; has {
result.Results[query.RefId] = testFunc(context)
}
}
return result, nil
}
func (e *FakeExecutor) Return(refId string, series TimeSeriesSlice) {
e.results[refId] = &QueryResult{
RefId: refId, Series: series,
}
}
func (e *FakeExecutor) HandleQuery(refId string, fn ResultsFn) {
e.resultsFn[refId] = fn
}
-37
View File
@@ -1,37 +0,0 @@
package tsdb
import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
)
// SeriesToFrame converts a TimeSeries to a sdk Frame
func SeriesToFrame(series *TimeSeries) (*data.Frame, error) {
timeVec := make([]*time.Time, len(series.Points))
floatVec := make([]*float64, len(series.Points))
for idx, point := range series.Points {
timeVec[idx], floatVec[idx] = convertTSDBTimePoint(point)
}
frame := data.NewFrame(series.Name,
data.NewField("time", nil, timeVec),
data.NewField("value", data.Labels(series.Tags), floatVec),
)
return frame, nil
}
// convertTSDBTimePoint coverts a tsdb.TimePoint into two values appropriate
// for Series values.
func convertTSDBTimePoint(point TimePoint) (t *time.Time, f *float64) {
timeIdx, valueIdx := 1, 0
if point[timeIdx].Valid { // Assuming valid is null?
tI := int64(point[timeIdx].Float64)
uT := time.Unix(tI/int64(1e+3), (tI%int64(1e+3))*int64(1e+6)) // time.Time from millisecond unix ts
t = &uT
}
if point[valueIdx].Valid {
f = &point[valueIdx].Float64
}
return
}
+20 -24
View File
@@ -17,8 +17,8 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/opentracing/opentracing-go"
)
@@ -26,29 +26,24 @@ type GraphiteExecutor struct {
HttpClient *http.Client
}
func NewGraphiteExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
return &GraphiteExecutor{}, nil
}
var glog = log.New("tsdb.graphite")
func init() {
tsdb.RegisterTsdbQueryEndpoint("graphite", NewGraphiteExecutor)
}
func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
func (e *GraphiteExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource, tsdbQuery plugins.DataQuery) (
plugins.DataResponse, error) {
// This logic is used when called from Dashboard Alerting.
from := "-" + formatTimeRange(tsdbQuery.TimeRange.From)
until := formatTimeRange(tsdbQuery.TimeRange.To)
// This logic is used when called through server side expressions.
if isTimeRangeNumeric(tsdbQuery.TimeRange) {
if isTimeRangeNumeric(*tsdbQuery.TimeRange) {
var err error
from, until, err = epochMStoGraphiteTime(tsdbQuery.TimeRange)
from, until, err = epochMStoGraphiteTime(*tsdbQuery.TimeRange)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
}
@@ -80,7 +75,7 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
if target == "" {
glog.Error("No targets in query model", "models without targets", strings.Join(emptyQueries, "\n"))
return nil, errors.New("no query target found for the alert rule")
return plugins.DataResponse{}, errors.New("no query target found for the alert rule")
}
formData["target"] = []string{target}
@@ -91,12 +86,12 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
req, err := e.createRequest(dsInfo, formData)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
span, ctx := opentracing.StartSpanFromContext(ctx, "graphite query")
@@ -112,24 +107,25 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header)); err != nil {
return nil, err
return plugins.DataResponse{}, err
}
res, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
data, err := e.parseResponse(res)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
result.Results = make(map[string]*tsdb.QueryResult)
queryRes := tsdb.NewQueryResult()
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
queryRes := plugins.DataQueryResult{}
for _, series := range data {
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
queryRes.Series = append(queryRes.Series, plugins.DataTimeSeries{
Name: series.Target,
Points: series.DataPoints,
})
@@ -215,7 +211,7 @@ func fixIntervalFormat(target string) string {
return target
}
func isTimeRangeNumeric(tr *tsdb.TimeRange) bool {
func isTimeRangeNumeric(tr plugins.DataTimeRange) bool {
if _, err := strconv.ParseInt(tr.From, 10, 64); err != nil {
return false
}
@@ -225,7 +221,7 @@ func isTimeRangeNumeric(tr *tsdb.TimeRange) bool {
return true
}
func epochMStoGraphiteTime(tr *tsdb.TimeRange) (string, string, error) {
func epochMStoGraphiteTime(tr plugins.DataTimeRange) (string, string, error) {
from, err := strconv.ParseInt(tr.From, 10, 64)
if err != nil {
return "", "", err
+3 -3
View File
@@ -1,8 +1,8 @@
package graphite
import "github.com/grafana/grafana/pkg/tsdb"
import "github.com/grafana/grafana/pkg/plugins"
type TargetResponseDTO struct {
Target string `json:"target"`
DataPoints tsdb.TimeSeriesPoints `json:"datapoints"`
Target string `json:"target"`
DataPoints plugins.DataTimeSeriesPoints `json:"datapoints"`
}
+18 -17
View File
@@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api"
)
@@ -21,21 +21,22 @@ func init() {
}
// Query builds flux queries, executes them, and returns the results.
func Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
glog.Debug("Received a query", "query", *tsdbQuery)
tRes := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
func Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery plugins.DataQuery) (
plugins.DataResponse, error) {
glog.Debug("Received a query", "query", tsdbQuery)
tRes := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
r, err := runnerFromDataSource(dsInfo)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
defer r.client.Close()
for _, query := range tsdbQuery.Queries {
qm, err := getQueryModelTSDB(query, tsdbQuery.TimeRange, dsInfo)
qm, err := getQueryModelTSDB(query, *tsdbQuery.TimeRange, dsInfo)
if err != nil {
tRes.Results[query.RefId] = &tsdb.QueryResult{Error: err}
tRes.Results[query.RefID] = plugins.DataQueryResult{Error: err}
continue
}
@@ -43,7 +44,7 @@ func Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQ
maxSeries := dsInfo.JsonData.Get("maxSeries").MustInt(1000)
res := executeQuery(ctx, *qm, r, maxSeries)
tRes.Results[query.RefId] = backendDataResponseToTSDBResponse(&res, query.RefId)
tRes.Results[query.RefID] = backendDataResponseToDataResponse(&res, query.RefID)
}
return tRes, nil
}
@@ -94,16 +95,16 @@ func runnerFromDataSource(dsInfo *models.DataSource) (*runner, error) {
}, nil
}
// backendDataResponseToTSDBResponse takes the SDK's style response and changes it into a
// tsdb.QueryResult. This is a wrapper so less of existing code needs to be changed. This should
// backendDataResponseToDataResponse takes the SDK's style response and changes it into a
// plugins.DataQueryResult. This is a wrapper so less of existing code needs to be changed. This should
// be able to be removed in the near future https://github.com/grafana/grafana/pull/25472.
func backendDataResponseToTSDBResponse(dr *backend.DataResponse, refID string) *tsdb.QueryResult {
qr := &tsdb.QueryResult{RefId: refID}
qr.Error = dr.Error
func backendDataResponseToDataResponse(dr *backend.DataResponse, refID string) plugins.DataQueryResult {
qr := plugins.DataQueryResult{
RefID: refID,
Error: dr.Error,
}
if dr.Frames != nil {
qr.Dataframes = tsdb.NewDecodedDataFrames(dr.Frames)
qr.Dataframes = plugins.NewDecodedDataFrames(dr.Frames)
}
return qr
}
+5 -4
View File
@@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
// queryOptions represents datasource configuration options
@@ -46,8 +46,9 @@ type queryModel struct {
// return model, nil
// }
// getQueryModelTSDB builds a queryModel from tsdb.Query information and datasource configuration (dsInfo).
func getQueryModelTSDB(query *tsdb.Query, timeRange *tsdb.TimeRange, dsInfo *models.DataSource) (*queryModel, error) {
// getQueryModelTSDB builds a queryModel from plugins.DataQuery information and datasource configuration (dsInfo).
func getQueryModelTSDB(query plugins.DataSubQuery, timeRange plugins.DataTimeRange,
dsInfo *models.DataSource) (*queryModel, error) {
model := &queryModel{}
queryBytes, err := query.Model.Encode()
if err != nil {
@@ -86,7 +87,7 @@ func getQueryModelTSDB(query *tsdb.Query, timeRange *tsdb.TimeRange, dsInfo *mod
if model.MaxDataPoints == 0 {
model.MaxDataPoints = 10000 // 10k/series should be a reasonable place to abort!
}
model.Interval = time.Millisecond * time.Duration(query.IntervalMs)
model.Interval = time.Millisecond * time.Duration(query.IntervalMS)
if model.Interval.Milliseconds() == 0 {
model.Interval = time.Millisecond // 1ms
}
+24 -26
View File
@@ -12,19 +12,19 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/influxdb/flux"
)
type InfluxDBExecutor struct {
type Executor struct {
// *models.DataSource
QueryParser *InfluxdbQueryParser
ResponseParser *ResponseParser
}
func NewInfluxDBExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return &InfluxDBExecutor{
func NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
return &Executor{
QueryParser: &InfluxdbQueryParser{},
ResponseParser: &ResponseParser{},
}, nil
@@ -38,10 +38,10 @@ var ErrInvalidHttpMode error = errors.New("'httpMode' should be either 'GET' or
func init() {
glog = log.New("tsdb.influxdb")
tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor)
}
func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
func (e *Executor) DataQuery(ctx context.Context, dsInfo *models.DataSource, tsdbQuery plugins.DataQuery) (
plugins.DataResponse, error) {
glog.Debug("Received a query request", "numQueries", len(tsdbQuery.Queries))
version := dsInfo.JsonData.Get("version").MustString("")
@@ -54,14 +54,14 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
// NOTE: the following path is currently only called from alerting queries
// In dashboards, the request runs through proxy and are managed in the frontend
query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
query, err := e.getQuery(dsInfo, tsdbQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
rawQuery, err := query.Build(tsdbQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
if setting.Env == setting.Dev {
@@ -70,17 +70,17 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
req, err := e.createRequest(ctx, dsInfo, rawQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
@@ -88,41 +88,39 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
}
}()
if resp.StatusCode/100 != 2 {
return nil, fmt.Errorf("InfluxDB returned error status: %s", resp.Status)
return plugins.DataResponse{}, fmt.Errorf("InfluxDB returned error status: %s", resp.Status)
}
var response Response
dec := json.NewDecoder(resp.Body)
dec.UseNumber()
if err := dec.Decode(&response); err != nil {
return nil, err
return plugins.DataResponse{}, err
}
if response.Err != nil {
return nil, response.Err
return plugins.DataResponse{}, response.Err
}
result := &tsdb.Response{}
result.Results = make(map[string]*tsdb.QueryResult)
result.Results["A"] = e.ResponseParser.Parse(&response, query)
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"A": e.ResponseParser.Parse(&response, query),
},
}
return result, nil
}
func (e *InfluxDBExecutor) getQuery(dsInfo *models.DataSource, queries []*tsdb.Query, context *tsdb.TsdbQuery) (*Query, error) {
if len(queries) == 0 {
func (e *Executor) getQuery(dsInfo *models.DataSource, query plugins.DataQuery) (*Query, error) {
if len(query.Queries) == 0 {
return nil, fmt.Errorf("query request contains no queries")
}
// The model supports multiple queries, but right now this is only used from
// alerting so we only needed to support batch executing 1 query at a time.
query, err := e.QueryParser.Parse(queries[0].Model, dsInfo)
if err != nil {
return nil, err
}
return query, nil
return e.QueryParser.Parse(query.Queries[0].Model, dsInfo)
}
func (e *InfluxDBExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource, query string) (*http.Request, error) {
func (e *Executor) createRequest(ctx context.Context, dsInfo *models.DataSource, query string) (*http.Request, error) {
u, err := url.Parse(dsInfo.Url)
if err != nil {
return nil, err
+2 -2
View File
@@ -12,14 +12,14 @@ import (
"github.com/stretchr/testify/require"
)
func TestInfluxDBExecutor_createRequest(t *testing.T) {
func TestExecutor_createRequest(t *testing.T) {
datasource := &models.DataSource{
Url: "http://awesome-influxdb:1337",
Database: "awesome-db",
JsonData: simplejson.New(),
}
query := "SELECT awesomeness FROM somewhere"
e := &InfluxDBExecutor{
e := &Executor{
QueryParser: &InfluxdbQueryParser{},
ResponseParser: &ResponseParser{},
}
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
type InfluxdbQueryParser struct{}
@@ -40,7 +40,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data
return nil, err
}
parsedInterval, err := tsdb.GetIntervalFrom(dsInfo, model, time.Millisecond*1)
parsedInterval, err := interval.GetIntervalFrom(dsInfo, model, time.Millisecond*1)
if err != nil {
return nil, err
}
+11 -10
View File
@@ -6,7 +6,8 @@ import (
"strconv"
"strings"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
var (
@@ -14,7 +15,7 @@ var (
regexpMeasurementPattern = regexp.MustCompile(`^\/.*\/$`)
)
func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) {
func (query *Query) Build(queryContext plugins.DataQuery) (string, error) {
var res string
if query.UseRawQuery && query.RawQuery != "" {
res = query.RawQuery
@@ -27,13 +28,13 @@ func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) {
res += query.renderTz()
}
calculator := tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{})
interval := calculator.Calculate(queryContext.TimeRange, query.Interval)
calculator := interval.NewCalculator(interval.CalculatorOptions{})
i := calculator.Calculate(*queryContext.TimeRange, query.Interval)
res = strings.ReplaceAll(res, "$timeFilter", query.renderTimeFilter(queryContext))
res = strings.ReplaceAll(res, "$interval", interval.Text)
res = strings.ReplaceAll(res, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10))
res = strings.ReplaceAll(res, "$__interval", interval.Text)
res = strings.ReplaceAll(res, "$interval", i.Text)
res = strings.ReplaceAll(res, "$__interval_ms", strconv.FormatInt(i.Milliseconds(), 10))
res = strings.ReplaceAll(res, "$__interval", i.Text)
return res, nil
}
@@ -77,7 +78,7 @@ func (query *Query) renderTags() []string {
return res
}
func (query *Query) renderTimeFilter(queryContext *tsdb.TsdbQuery) string {
func (query *Query) renderTimeFilter(queryContext plugins.DataQuery) string {
from := "now() - " + queryContext.TimeRange.From
to := ""
@@ -88,7 +89,7 @@ func (query *Query) renderTimeFilter(queryContext *tsdb.TsdbQuery) string {
return fmt.Sprintf("time > %s%s", from, to)
}
func (query *Query) renderSelectors(queryContext *tsdb.TsdbQuery) string {
func (query *Query) renderSelectors(queryContext plugins.DataQuery) string {
res := "SELECT "
var selectors []string
@@ -135,7 +136,7 @@ func (query *Query) renderWhereClause() string {
return res
}
func (query *Query) renderGroupBy(queryContext *tsdb.TsdbQuery) string {
func (query *Query) renderGroupBy(queryContext plugins.DataQuery) string {
groupBy := ""
for i, group := range query.GroupBy {
if i == 0 {
+7 -7
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
var renders map[string]QueryDefinition
@@ -15,7 +15,7 @@ type DefinitionParameters struct {
}
type QueryDefinition struct {
Renderer func(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string
Renderer func(query *Query, queryContext plugins.DataQuery, part *QueryPart, innerExpr string) string
Params []DefinitionParameters
}
@@ -97,14 +97,14 @@ func init() {
renders["alias"] = QueryDefinition{Renderer: aliasRenderer}
}
func fieldRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
func fieldRenderer(query *Query, queryContext plugins.DataQuery, part *QueryPart, innerExpr string) string {
if part.Params[0] == "*" {
return "*"
}
return fmt.Sprintf(`"%s"`, part.Params[0])
}
func functionRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
func functionRenderer(query *Query, queryContext plugins.DataQuery, part *QueryPart, innerExpr string) string {
for i, param := range part.Params {
if part.Type == "time" && param == "auto" {
part.Params[i] = "$__interval"
@@ -120,11 +120,11 @@ func functionRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPar
return fmt.Sprintf("%s(%s)", part.Type, params)
}
func suffixRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
func suffixRenderer(query *Query, queryContext plugins.DataQuery, part *QueryPart, innerExpr string) string {
return fmt.Sprintf("%s %s", innerExpr, part.Params[0])
}
func aliasRenderer(query *Query, queryContext *tsdb.TsdbQuery, part *QueryPart, innerExpr string) string {
func aliasRenderer(query *Query, queryContext plugins.DataQuery, part *QueryPart, innerExpr string) string {
return fmt.Sprintf(`%s AS "%s"`, innerExpr, part.Params[0])
}
@@ -147,6 +147,6 @@ type QueryPart struct {
Params []string
}
func (qp *QueryPart) Render(query *Query, queryContext *tsdb.TsdbQuery, expr string) string {
func (qp *QueryPart) Render(query *Query, queryContext plugins.DataQuery, expr string) string {
return qp.Def.Renderer(query, queryContext, qp, expr)
}
+3 -2
View File
@@ -3,7 +3,7 @@ package influxdb
import (
"testing"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
func TestInfluxdbQueryPart(t *testing.T) {
@@ -27,7 +27,8 @@ func TestInfluxdbQueryPart(t *testing.T) {
{mode: "non_negative_difference", params: []string{}, input: "max(value)", expected: `non_negative_difference(max(value))`},
}
queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("5m", "now")}
timeRange := plugins.NewDataTimeRange("5m", "now")
queryContext := plugins.DataQuery{TimeRange: &timeRange}
query := &Query{}
for _, tc := range tcs {
+8 -5
View File
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
. "github.com/smartystreets/goconvey/convey"
)
@@ -27,8 +27,9 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
tag1 := &Tag{Key: "hostname", Value: "server1", Operator: "="}
tag2 := &Tag{Key: "hostname", Value: "server2", Operator: "=", Condition: "OR"}
queryContext := &tsdb.TsdbQuery{
TimeRange: tsdb.NewTimeRange("5m", "now"),
timeRange := plugins.NewDataTimeRange("5m", "now")
queryContext := plugins.DataQuery{
TimeRange: &timeRange,
}
Convey("can build simple query", func() {
@@ -114,12 +115,14 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
query := Query{}
Convey("render from: 2h to now-1h", func() {
query := Query{}
queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("2h", "now-1h")}
timeRange := plugins.NewDataTimeRange("2h", "now-1h")
queryContext := plugins.DataQuery{TimeRange: &timeRange}
So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 2h and time < now() - 1h")
})
Convey("render from: 10m", func() {
queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("10m", "now")}
timeRange := plugins.NewDataTimeRange("10m", "now")
queryContext := plugins.DataQuery{TimeRange: &timeRange}
So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 10m")
})
})
+11 -11
View File
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
type ResponseParser struct{}
@@ -21,8 +21,8 @@ func init() {
legendFormat = regexp.MustCompile(`\[\[(\w+)(\.\w+)*\]\]*|\$\s*(\w+?)*`)
}
func (rp *ResponseParser) Parse(response *Response, query *Query) *tsdb.QueryResult {
queryRes := tsdb.NewQueryResult()
func (rp *ResponseParser) Parse(response *Response, query *Query) plugins.DataQueryResult {
var queryRes plugins.DataQueryResult
for _, result := range response.Results {
queryRes.Series = append(queryRes.Series, rp.transformRows(result.Series, queryRes, query)...)
@@ -34,22 +34,22 @@ func (rp *ResponseParser) Parse(response *Response, query *Query) *tsdb.QueryRes
return queryRes
}
func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResult, query *Query) tsdb.TimeSeriesSlice {
var result tsdb.TimeSeriesSlice
func (rp *ResponseParser) transformRows(rows []Row, queryResult plugins.DataQueryResult, query *Query) plugins.DataTimeSeriesSlice {
var result plugins.DataTimeSeriesSlice
for _, row := range rows {
for columnIndex, column := range row.Columns {
if column == "time" {
continue
}
var points tsdb.TimeSeriesPoints
var points plugins.DataTimeSeriesPoints
for _, valuePair := range row.Values {
point, err := rp.parseTimepoint(valuePair, columnIndex)
if err == nil {
points = append(points, point)
}
}
result = append(result, &tsdb.TimeSeries{
result = append(result, plugins.DataTimeSeries{
Name: rp.formatSeriesName(row, column, query),
Points: points,
Tags: row.Tags,
@@ -115,19 +115,19 @@ func (rp *ResponseParser) buildSeriesNameFromQuery(row Row, column string) strin
return fmt.Sprintf("%s.%s%s", row.Name, column, tagText)
}
func (rp *ResponseParser) parseTimepoint(valuePair []interface{}, valuePosition int) (tsdb.TimePoint, error) {
func (rp *ResponseParser) parseTimepoint(valuePair []interface{}, valuePosition int) (plugins.DataTimePoint, error) {
value := rp.parseValue(valuePair[valuePosition])
timestampNumber, ok := valuePair[0].(json.Number)
if !ok {
return tsdb.TimePoint{}, fmt.Errorf("valuePair[0] has invalid type: %#v", valuePair[0])
return plugins.DataTimePoint{}, fmt.Errorf("valuePair[0] has invalid type: %#v", valuePair[0])
}
timestamp, err := timestampNumber.Float64()
if err != nil {
return tsdb.TimePoint{}, err
return plugins.DataTimePoint{}, err
}
return tsdb.NewTimePoint(value, timestamp), nil
return plugins.DataTimePoint{value, null.FloatFrom(timestamp)}, nil
}
func (rp *ResponseParser) parseValue(value interface{}) null.Float {
@@ -1,4 +1,4 @@
package tsdb
package interval
import (
"fmt"
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
var (
@@ -26,25 +27,23 @@ type intervalCalculator struct {
minInterval time.Duration
}
type IntervalCalculator interface {
Calculate(timeRange *TimeRange, minInterval time.Duration) Interval
type Calculator interface {
Calculate(timeRange plugins.DataTimeRange, minInterval time.Duration) Interval
}
type IntervalOptions struct {
type CalculatorOptions struct {
MinInterval time.Duration
}
func NewIntervalCalculator(opt *IntervalOptions) *intervalCalculator {
if opt == nil {
opt = &IntervalOptions{}
}
func NewCalculator(opts ...CalculatorOptions) *intervalCalculator {
calc := &intervalCalculator{}
if opt.MinInterval == 0 {
calc.minInterval = defaultMinInterval
} else {
calc.minInterval = opt.MinInterval
for _, o := range opts {
if o.MinInterval == 0 {
calc.minInterval = defaultMinInterval
} else {
calc.minInterval = o.MinInterval
}
}
return calc
@@ -54,7 +53,7 @@ func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond)
}
func (ic *intervalCalculator) Calculate(timerange *TimeRange, minInterval time.Duration) Interval {
func (ic *intervalCalculator) Calculate(timerange plugins.DataTimeRange, minInterval time.Duration) Interval {
to := timerange.MustGetTo().UnixNano()
from := timerange.MustGetFrom().UnixNano()
interval := time.Duration((to - from) / defaultRes)
@@ -1,4 +1,4 @@
package tsdb
package interval
import (
"testing"
@@ -6,21 +6,22 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
)
func TestIntervalCalculator_Calculate(t *testing.T) {
calculator := NewIntervalCalculator(&IntervalOptions{})
calculator := NewCalculator(CalculatorOptions{})
testCases := []struct {
name string
timeRange *TimeRange
timeRange plugins.DataTimeRange
expected string
}{
{"from 5m to now", NewTimeRange("5m", "now"), "200ms"},
{"from 15m to now", NewTimeRange("15m", "now"), "500ms"},
{"from 30m to now", NewTimeRange("30m", "now"), "1s"},
{"from 1h to now", NewTimeRange("1h", "now"), "2s"},
{"from 5m to now", plugins.NewDataTimeRange("5m", "now"), "200ms"},
{"from 15m to now", plugins.NewDataTimeRange("15m", "now"), "500ms"},
{"from 30m to now", plugins.NewDataTimeRange("30m", "now"), "1s"},
{"from 1h to now", plugins.NewDataTimeRange("1h", "now"), "2s"},
}
for _, tc := range testCases {
+41 -37
View File
@@ -10,7 +10,8 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/grafana/loki/pkg/logcli/client"
"github.com/grafana/loki/pkg/loghttp"
"github.com/grafana/loki/pkg/logproto"
@@ -18,28 +19,30 @@ import (
"github.com/prometheus/common/model"
)
type LokiExecutor struct{}
type LokiExecutor struct {
intervalCalculator interval.Calculator
}
func NewLokiExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return &LokiExecutor{}, nil
func NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, error) {
return newExecutor(), nil
}
func newExecutor() *LokiExecutor {
return &LokiExecutor{
intervalCalculator: interval.NewCalculator(interval.CalculatorOptions{MinInterval: time.Second * 1}),
}
}
var (
plog log.Logger
legendFormat *regexp.Regexp
intervalCalculator tsdb.IntervalCalculator
plog = log.New("tsdb.loki")
legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
)
func init() {
plog = log.New("tsdb.loki")
tsdb.RegisterTsdbQueryEndpoint("loki", NewLokiExecutor)
legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
intervalCalculator = tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{MinInterval: time.Second * 1})
}
func (e *LokiExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
// DataQuery executes a Loki query.
func (e *LokiExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
queryContext plugins.DataQuery) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{},
}
client := &client.DefaultClient{
@@ -48,9 +51,9 @@ func (e *LokiExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsd
Password: dsInfo.DecryptedBasicAuthPassword(),
}
queries, err := parseQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
queries, err := e.parseQuery(dsInfo, queryContext)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
for _, query := range queries {
@@ -67,23 +70,22 @@ func (e *LokiExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsd
interval := time.Second * 1
value, err := client.QueryRange(query.Expr, limit, query.Start, query.End, logproto.BACKWARD, query.Step, interval, false)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
queryResult, err := parseResponse(value, query)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
result.Results[query.RefId] = queryResult
result.Results[query.RefID] = queryResult
}
return result, nil
}
//If legend (using of name or pattern instead of time series name) is used, use that name/pattern for formatting
func formatLegend(metric model.Metric, query *LokiQuery) string {
func formatLegend(metric model.Metric, query *lokiQuery) string {
if query.LegendFormat == "" {
return metric.String()
}
@@ -101,9 +103,9 @@ func formatLegend(metric model.Metric, query *LokiQuery) string {
return string(result)
}
func parseQuery(dsInfo *models.DataSource, queries []*tsdb.Query, queryContext *tsdb.TsdbQuery) ([]*LokiQuery, error) {
qs := []*LokiQuery{}
for _, queryModel := range queries {
func (e *LokiExecutor) parseQuery(dsInfo *models.DataSource, queryContext plugins.DataQuery) ([]*lokiQuery, error) {
qs := []*lokiQuery{}
for _, queryModel := range queryContext.Queries {
expr, err := queryModel.Model.Get("expr").String()
if err != nil {
return nil, fmt.Errorf("failed to parse Expr: %v", err)
@@ -121,29 +123,29 @@ func parseQuery(dsInfo *models.DataSource, queries []*tsdb.Query, queryContext *
return nil, fmt.Errorf("failed to parse To: %v", err)
}
dsInterval, err := tsdb.GetIntervalFrom(dsInfo, queryModel.Model, time.Second)
dsInterval, err := interval.GetIntervalFrom(dsInfo, queryModel.Model, time.Second)
if err != nil {
return nil, fmt.Errorf("failed to parse Interval: %v", err)
}
interval := intervalCalculator.Calculate(queryContext.TimeRange, dsInterval)
interval := e.intervalCalculator.Calculate(*queryContext.TimeRange, dsInterval)
step := time.Duration(int64(interval.Value))
qs = append(qs, &LokiQuery{
qs = append(qs, &lokiQuery{
Expr: expr,
Step: step,
LegendFormat: format,
Start: start,
End: end,
RefId: queryModel.RefId,
RefID: queryModel.RefID,
})
}
return qs, nil
}
func parseResponse(value *loghttp.QueryResponse, query *LokiQuery) (*tsdb.QueryResult, error) {
queryRes := tsdb.NewQueryResult()
func parseResponse(value *loghttp.QueryResponse, query *lokiQuery) (plugins.DataQueryResult, error) {
var queryRes plugins.DataQueryResult
//We are currently processing only matrix results (for alerting)
data, ok := value.Data.Result.(loghttp.Matrix)
@@ -152,10 +154,10 @@ func parseResponse(value *loghttp.QueryResponse, query *LokiQuery) (*tsdb.QueryR
}
for _, v := range data {
series := tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: formatLegend(v.Metric, query),
Tags: make(map[string]string, len(v.Metric)),
Points: make([]tsdb.TimePoint, 0, len(v.Values)),
Points: make([]plugins.DataTimePoint, 0, len(v.Values)),
}
for k, v := range v.Metric {
@@ -163,10 +165,12 @@ func parseResponse(value *loghttp.QueryResponse, query *LokiQuery) (*tsdb.QueryR
}
for _, k := range v.Values {
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(k.Value)), float64(k.Timestamp.Unix()*1000)))
series.Points = append(series.Points, plugins.DataTimePoint{
null.FloatFrom(float64(k.Value)), null.FloatFrom(float64(k.Timestamp.Unix() * 1000)),
})
}
queryRes.Series = append(queryRes.Series, &series)
queryRes.Series = append(queryRes.Series, series)
}
return queryRes, nil
+28 -19
View File
@@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
p "github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
@@ -22,7 +22,7 @@ func TestLoki(t *testing.T) {
p.LabelName("device"): p.LabelValue("mobile"),
}
query := &LokiQuery{
query := &lokiQuery{
LegendFormat: "legend {{app}} {{ device }} {{broken}}",
}
@@ -36,7 +36,7 @@ func TestLoki(t *testing.T) {
p.LabelName("device"): p.LabelValue("mobile"),
}
query := &LokiQuery{
query := &lokiQuery{
LegendFormat: "",
}
@@ -49,15 +49,19 @@ func TestLoki(t *testing.T) {
"format": "time_series",
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
{Model: jsonModel},
jsonModel, err := simplejson.NewJson([]byte(json))
require.NoError(t, err)
timeRange := plugins.NewDataTimeRange("12h", "now")
queryContext := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{Model: jsonModel},
},
TimeRange: &timeRange,
}
queryContext.TimeRange = tsdb.NewTimeRange("12h", "now")
models, err := parseQuery(dsInfo, queryModels, queryContext)
exe := newExecutor()
require.NoError(t, err)
models, err := exe.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Second*30, models[0].Step)
})
@@ -68,19 +72,24 @@ func TestLoki(t *testing.T) {
"format": "time_series",
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
{Model: jsonModel},
jsonModel, err := simplejson.NewJson([]byte(json))
require.NoError(t, err)
timeRange := plugins.NewDataTimeRange("48h", "now")
queryContext := plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{Model: jsonModel},
},
}
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")
models, err := parseQuery(dsInfo, queryModels, queryContext)
exe := newExecutor()
require.NoError(t, err)
models, err := exe.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Minute*2, models[0].Step)
queryContext.TimeRange = tsdb.NewTimeRange("1h", "now")
models, err = parseQuery(dsInfo, queryModels, queryContext)
timeRange = plugins.NewDataTimeRange("1h", "now")
queryContext.TimeRange = &timeRange
models, err = exe.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Second*2, models[0].Step)
})
+2 -2
View File
@@ -2,11 +2,11 @@ package loki
import "time"
type LokiQuery struct {
type lokiQuery struct {
Expr string
Step time.Duration
LegendFormat string
Start time.Time
End time.Time
RefId string
RefID string
}
-272
View File
@@ -1,272 +0,0 @@
package tsdb
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/data"
"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.
type TsdbQuery struct {
TimeRange *TimeRange
Queries []*Query
Headers map[string]string
Debug bool
User *models.SignedInUser
}
type Query struct {
RefId string `json:"refId"`
Model *simplejson.Json `json:"model,omitempty"`
DataSource *models.DataSource `json:"datasource"`
MaxDataPoints int64 `json:"maxDataPoints"`
IntervalMs int64 `json:"intervalMs"`
QueryType string `json:"queryType"`
}
type Response struct {
Results map[string]*QueryResult `json:"results"`
Message string `json:"message,omitempty"`
}
type QueryResult struct {
Error error `json:"-"`
ErrorString string `json:"error,omitempty"`
RefId string `json:"refId"`
Meta *simplejson.Json `json:"meta,omitempty"`
Series TimeSeriesSlice `json:"series"`
Tables []*Table `json:"tables"`
Dataframes DataFrames `json:"dataframes"`
}
// UnmarshalJSON deserializes a QueryResult from JSON.
//
// Deserialization support is required by tests.
func (r *QueryResult) UnmarshalJSON(b []byte) error {
m := map[string]interface{}{}
// TODO: Use JSON decoder
if err := json.Unmarshal(b, &m); err != nil {
return err
}
refID, ok := m["refId"].(string)
if !ok {
return fmt.Errorf("can't decode field refId - not a string")
}
var meta *simplejson.Json
if m["meta"] != nil {
mm, ok := m["meta"].(map[string]interface{})
if !ok {
return fmt.Errorf("can't decode field meta - not a JSON object")
}
meta = simplejson.NewFromAny(mm)
}
var series TimeSeriesSlice
/* TODO
if m["series"] != nil {
}
*/
var tables []*Table
if m["tables"] != nil {
ts, ok := m["tables"].([]interface{})
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
for _, ti := range ts {
tm, ok := ti.(map[string]interface{})
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
var columns []TableColumn
cs, ok := tm["columns"].([]interface{})
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
for _, ci := range cs {
cm, ok := ci.(map[string]interface{})
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
val, ok := cm["text"].(string)
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
columns = append(columns, TableColumn{Text: val})
}
rs, ok := tm["rows"].([]interface{})
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
var rows []RowValues
for _, ri := range rs {
vals, ok := ri.([]interface{})
if !ok {
return fmt.Errorf("can't decode field tables - not an array of Tables")
}
rows = append(rows, vals)
}
tables = append(tables, &Table{
Columns: columns,
Rows: rows,
})
}
}
var dfs *dataFrames
if m["dataframes"] != nil {
raw, ok := m["dataframes"].([]interface{})
if !ok {
return fmt.Errorf("can't decode field dataframes - not an array of byte arrays")
}
var encoded [][]byte
for _, ra := range raw {
encS, ok := ra.(string)
if !ok {
return fmt.Errorf("can't decode field dataframes - not an array of byte arrays")
}
enc, err := base64.StdEncoding.DecodeString(encS)
if err != nil {
return fmt.Errorf("can't decode field dataframes - not an array of arrow frames")
}
encoded = append(encoded, enc)
}
decoded, err := data.UnmarshalArrowFrames(encoded)
if err != nil {
return err
}
dfs = &dataFrames{
decoded: decoded,
encoded: encoded,
}
}
r.RefId = refID
r.Meta = meta
r.Series = series
r.Tables = tables
if dfs != nil {
r.Dataframes = dfs
}
return nil
}
type TimeSeries struct {
Name string `json:"name"`
Points TimeSeriesPoints `json:"points"`
Tags map[string]string `json:"tags,omitempty"`
}
type Table struct {
Columns []TableColumn `json:"columns"`
Rows []RowValues `json:"rows"`
}
type TableColumn struct {
Text string `json:"text"`
}
type RowValues []interface{}
type TimePoint [2]null.Float
type TimeSeriesPoints []TimePoint
type TimeSeriesSlice []*TimeSeries
func NewQueryResult() *QueryResult {
return &QueryResult{
Series: make(TimeSeriesSlice, 0),
}
}
func NewTimePoint(value null.Float, timestamp float64) TimePoint {
return TimePoint{value, null.FloatFrom(timestamp)}
}
// DataFrames is an interface for retrieving encoded and decoded data frames.
//
// See NewDecodedDataFrames and NewEncodedDataFrames for more information.
type DataFrames interface {
// Encoded encodes Frames into a slice of []byte.
// If an error occurs [][]byte will be nil.
// The encoded result, if any, will be cached and returned next time Encoded is called.
Encoded() ([][]byte, error)
// Decoded decodes a slice of Arrow encoded frames to data.Frames ([]*data.Frame).
// If an error occurs Frames will be nil.
// The decoded result, if any, will be cached and returned next time Decoded is called.
Decoded() (data.Frames, error)
}
type dataFrames struct {
decoded data.Frames
encoded [][]byte
}
// NewDecodedDataFrames instantiates DataFrames from decoded frames.
//
// This should be the primary function for creating DataFrames if you're implementing a plugin.
// In a Grafana alerting scenario it needs to operate on decoded frames, which is why this function is
// preferrable. When encoded data frames are needed, e.g. returned from Grafana HTTP API, it will
// happen automatically when MarshalJSON() is called.
func NewDecodedDataFrames(decodedFrames data.Frames) DataFrames {
return &dataFrames{
decoded: decodedFrames,
}
}
// NewEncodedDataFrames instantiates DataFrames from encoded frames.
//
// This one is primarily used for creating DataFrames when receiving encoded data frames from an external
// plugin or similar. This may allow the encoded data frames to be returned to Grafana UI without any additional
// decoding/encoding required. In Grafana alerting scenario it needs to operate on decoded data frames why encoded
// frames needs to be decoded before usage.
func NewEncodedDataFrames(encodedFrames [][]byte) DataFrames {
return &dataFrames{
encoded: encodedFrames,
}
}
func (df *dataFrames) Encoded() ([][]byte, error) {
if df.encoded == nil {
encoded, err := df.decoded.MarshalArrow()
if err != nil {
return nil, err
}
df.encoded = encoded
}
return df.encoded, nil
}
func (df *dataFrames) Decoded() (data.Frames, error) {
if df.decoded == nil {
decoded, err := data.UnmarshalArrowFrames(df.encoded)
if err != nil {
return nil, err
}
df.decoded = decoded
}
return df.decoded, nil
}
func (df *dataFrames) MarshalJSON() ([]byte, error) {
encoded, err := df.Encoded()
if err != nil {
return nil, err
}
// Use a configuration that's compatible with the standard library
// to minimize the risk of introducing bugs. This will make sure
// that map keys is ordered.
jsonCfg := jsoniter.ConfigCompatibleWithStandardLibrary
return jsonCfg.Marshal(encoded)
}
+11 -9
View File
@@ -7,26 +7,28 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
type msSqlMacroEngine struct {
*sqleng.SqlMacroEngineBase
timeRange *tsdb.TimeRange
query *tsdb.Query
type msSQLMacroEngine struct {
*sqleng.SQLMacroEngineBase
timeRange plugins.DataTimeRange
query plugins.DataSubQuery
}
func newMssqlMacroEngine() sqleng.SqlMacroEngine {
return &msSqlMacroEngine{SqlMacroEngineBase: sqleng.NewSqlMacroEngineBase()}
func newMssqlMacroEngine() sqleng.SQLMacroEngine {
return &msSQLMacroEngine{SQLMacroEngineBase: sqleng.NewSQLMacroEngineBase()}
}
func (m *msSqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
func (m *msSQLMacroEngine) Interpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange,
sql string) (string, error) {
m.timeRange = timeRange
m.query = query
// TODO: Return any error
rExp, _ := regexp.Compile(sExpr)
var macroError error
@@ -50,7 +52,7 @@ func (m *msSqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
return sql, nil
}
func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, error) {
func (m *msSQLMacroEngine) evaluateMacro(name string, args []string) (string, error) {
switch name {
case "__time":
if len(args) == 0 {
+15 -9
View File
@@ -8,38 +8,40 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
. "github.com/smartystreets/goconvey/convey"
)
func TestMacroEngine(t *testing.T) {
Convey("MacroEngine", t, func() {
engine := &msSqlMacroEngine{}
query := &tsdb.Query{
engine := &msSQLMacroEngine{}
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
dfltTimeRange := plugins.DataTimeRange{}
Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := tsdb.NewFakeTimeRange("5m", "now", to)
timeRange := plugins.DataTimeRange{From: "5m", Now: to, To: "now"}
Convey("interpolate __time function", func() {
sql, err := engine.Interpolate(query, nil, "select $__time(time_column)")
sql, err := engine.Interpolate(query, dfltTimeRange, "select $__time(time_column)")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "select time_column AS time")
})
Convey("interpolate __timeEpoch function", func() {
sql, err := engine.Interpolate(query, nil, "select $__timeEpoch(time_column)")
sql, err := engine.Interpolate(query, dfltTimeRange, "select $__timeEpoch(time_column)")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "select DATEDIFF(second, '1970-01-01', time_column) AS time")
})
Convey("interpolate __timeEpoch function wrapped in aggregation", func() {
sql, err := engine.Interpolate(query, nil, "select min($__timeEpoch(time_column))")
sql, err := engine.Interpolate(query, dfltTimeRange, "select min($__timeEpoch(time_column))")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "select min(DATEDIFF(second, '1970-01-01', time_column) AS time)")
@@ -166,7 +168,9 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10),
strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -193,7 +197,9 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10),
strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
+7 -9
View File
@@ -13,27 +13,24 @@ import (
mssql "github.com/denisenkom/go-mssqldb"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"xorm.io/core"
)
func init() {
tsdb.RegisterTsdbQueryEndpoint("mssql", newMssqlQueryEndpoint)
}
var logger = log.New("tsdb.mssql")
func newMssqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func NewExecutor(datasource *models.DataSource) (plugins.DataPlugin, error) {
cnnstr, err := generateConnectionString(datasource)
if err != nil {
return nil, err
}
// TODO: Don't use global
if setting.Env == setting.Dev {
logger.Debug("getEngine", "connection", cnnstr)
}
config := sqleng.SqlQueryEndpointConfiguration{
config := sqleng.DataPluginConfiguration{
DriverName: "mssql",
ConnectionString: cnnstr,
Datasource: datasource,
@@ -44,7 +41,7 @@ func newMssqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
log: logger,
}
return sqleng.NewSqlQueryEndpoint(&config, &queryResultTransformer, newMssqlMacroEngine(), logger)
return sqleng.NewDataPlugin(config, &queryResultTransformer, newMssqlMacroEngine(), logger)
}
// ParseURL tries to parse an MSSQL URL string into a URL object.
@@ -105,7 +102,8 @@ type mssqlQueryResultTransformer struct {
log log.Logger
}
func (t *mssqlQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
func (t *mssqlQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (
plugins.DataRowValues, error) {
values := make([]interface{}, len(columnTypes))
valuePtrs := make([]interface{}, len(columnTypes))
+120 -119
View File
@@ -11,8 +11,8 @@ import (
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
. "github.com/smartystreets/goconvey/convey"
"xorm.io/xorm"
@@ -38,11 +38,11 @@ func TestMSSQL(t *testing.T) {
}
origInterpolate := sqleng.Interpolate
sqleng.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
sqleng.Interpolate = func(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, sql string) (string, error) {
return sql, nil
}
endpoint, err := newMssqlQueryEndpoint(&models.DataSource{
endpoint, err := NewExecutor(&models.DataSource{
JsonData: simplejson.New(),
SecureJsonData: securejsondata.SecureJsonData{},
})
@@ -122,19 +122,19 @@ func TestMSSQL(t *testing.T) {
So(err, ShouldBeNil)
Convey("When doing a table query should map MSSQL column types to Go types", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT * FROM mssql_types",
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["A"]
So(err, ShouldBeNil)
@@ -214,19 +214,19 @@ func TestMSSQL(t *testing.T) {
So(err, ShouldBeNil)
Convey("When doing a metric query using timeGroup", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -257,23 +257,23 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -317,24 +317,24 @@ func TestMSSQL(t *testing.T) {
})
Convey("Should replace $__interval", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, $__interval) ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -343,23 +343,23 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with float fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -437,19 +437,19 @@ func TestMSSQL(t *testing.T) {
So(err, ShouldBeNil)
Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeInt64 as time, timeInt64 FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -459,19 +459,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -481,19 +481,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -503,19 +503,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -525,19 +525,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeInt32 as time, timeInt32 FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -547,19 +547,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -569,19 +569,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -591,19 +591,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT TOP 1 timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -613,19 +613,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -636,19 +636,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query grouping by time should return correct series", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -659,19 +659,19 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query with metric column and multiple value columns", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeEpoch(time), measurement, valueOne, valueTwo FROM metric_values ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -685,21 +685,22 @@ func TestMSSQL(t *testing.T) {
Convey("When doing a query with timeFrom,timeTo,unixEpochFrom,unixEpochTo macros", func() {
sqleng.Interpolate = origInterpolate
query := &tsdb.TsdbQuery{
TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
Queries: []*tsdb.Query{
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: fromStart}
query := plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{JsonData: simplejson.New()},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -751,8 +752,8 @@ func TestMSSQL(t *testing.T) {
Convey("When doing a metric query using stored procedure should return correct result", func() {
sqleng.Interpolate = origInterpolate
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{JsonData: simplejson.New()},
Model: simplejson.NewFromAny(map[string]interface{}{
@@ -763,16 +764,16 @@ func TestMSSQL(t *testing.T) {
EXEC dbo.sp_test_epoch @from, @to`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: "1521117000000",
To: "1521122100000",
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["A"]
So(err, ShouldBeNil)
So(queryResult.Error, ShouldBeNil)
@@ -830,8 +831,8 @@ func TestMSSQL(t *testing.T) {
Convey("When doing a metric query using stored procedure should return correct result", func() {
sqleng.Interpolate = origInterpolate
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{JsonData: simplejson.New()},
Model: simplejson.NewFromAny(map[string]interface{}{
@@ -842,16 +843,16 @@ func TestMSSQL(t *testing.T) {
EXEC dbo.sp_test_epoch @from, @to`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: "1521117000000",
To: "1521122100000",
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["A"]
So(err, ShouldBeNil)
So(queryResult.Error, ShouldBeNil)
@@ -911,46 +912,46 @@ func TestMSSQL(t *testing.T) {
}
Convey("When doing an annotation query of deploy events should return expected result", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
"format": "table",
}),
RefId: "Deploys",
RefID: "Deploys",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["Deploys"]
So(err, ShouldBeNil)
So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
})
Convey("When doing an annotation query of ticket events should return expected result", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
"format": "table",
}),
RefId: "Tickets",
RefID: "Tickets",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["Tickets"]
So(err, ShouldBeNil)
So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
@@ -960,8 +961,8 @@ func TestMSSQL(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
dtFormat := "2006-01-02 15:04:05.999999999"
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -971,12 +972,12 @@ func TestMSSQL(t *testing.T) {
`, dt.Format(dtFormat)),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -990,8 +991,8 @@ func TestMSSQL(t *testing.T) {
Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -1001,12 +1002,12 @@ func TestMSSQL(t *testing.T) {
`, dt.Unix()),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -1020,8 +1021,8 @@ func TestMSSQL(t *testing.T) {
Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -1031,12 +1032,12 @@ func TestMSSQL(t *testing.T) {
`, dt.Unix()),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -1050,8 +1051,8 @@ func TestMSSQL(t *testing.T) {
Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -1061,12 +1062,12 @@ func TestMSSQL(t *testing.T) {
`, dt.Unix()*1000),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -1078,8 +1079,8 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT
@@ -1089,12 +1090,12 @@ func TestMSSQL(t *testing.T) {
`,
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -1106,8 +1107,8 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing an annotation query with a time column holding a datetime null value should return nil", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT
@@ -1117,12 +1118,12 @@ func TestMSSQL(t *testing.T) {
`,
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := endpoint.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
+10 -9
View File
@@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
@@ -17,18 +17,18 @@ const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
var restrictedRegExp = regexp.MustCompile(`(?im)([\s]*show[\s]+grants|[\s,]session_user\([^\)]*\)|[\s,]current_user(\([^\)]*\))?|[\s,]system_user\([^\)]*\)|[\s,]user\([^\)]*\))([\s,;]|$)`)
type mySqlMacroEngine struct {
*sqleng.SqlMacroEngineBase
timeRange *tsdb.TimeRange
query *tsdb.Query
type mySQLMacroEngine struct {
*sqleng.SQLMacroEngineBase
timeRange plugins.DataTimeRange
query plugins.DataSubQuery
logger log.Logger
}
func newMysqlMacroEngine(logger log.Logger) sqleng.SqlMacroEngine {
return &mySqlMacroEngine{SqlMacroEngineBase: sqleng.NewSqlMacroEngineBase(), logger: logger}
func newMysqlMacroEngine(logger log.Logger) sqleng.SQLMacroEngine {
return &mySQLMacroEngine{SQLMacroEngineBase: sqleng.NewSQLMacroEngineBase(), logger: logger}
}
func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
func (m *mySQLMacroEngine) Interpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, sql string) (string, error) {
m.timeRange = timeRange
m.query = query
@@ -38,6 +38,7 @@ func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
return "", errors.New("invalid query - inspect Grafana server log for details")
}
// TODO: Handle error
rExp, _ := regexp.Compile(sExpr)
var macroError error
@@ -61,7 +62,7 @@ func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
return sql, nil
}
func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, error) {
func (m *mySQLMacroEngine) evaluateMacro(name string, args []string) (string, error) {
switch name {
case "__timeEpoch", "__time":
if len(args) == 0 {
+9 -7
View File
@@ -7,21 +7,21 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
. "github.com/smartystreets/goconvey/convey"
)
func TestMacroEngine(t *testing.T) {
Convey("MacroEngine", t, func() {
engine := &mySqlMacroEngine{
engine := &mySQLMacroEngine{
logger: log.New("test"),
}
query := &tsdb.Query{}
query := plugins.DataSubQuery{}
Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := tsdb.NewFakeTimeRange("5m", "now", to)
timeRange := plugins.DataTimeRange{From: "5m", Now: to, To: "now"}
Convey("interpolate __time function", func() {
sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)")
@@ -120,7 +120,8 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -140,7 +141,8 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -180,7 +182,7 @@ func TestMacroEngine(t *testing.T) {
}
for _, tc := range tcs {
_, err := engine.Interpolate(nil, nil, tc)
_, err := engine.Interpolate(plugins.DataSubQuery{}, plugins.DataTimeRange{}, tc)
So(err.Error(), ShouldEqual, "invalid query - inspect Grafana server log for details")
}
})
+6 -9
View File
@@ -15,20 +15,16 @@ import (
"github.com/go-sql-driver/mysql"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"xorm.io/core"
)
func init() {
tsdb.RegisterTsdbQueryEndpoint("mysql", newMysqlQueryEndpoint)
}
func characterEscape(s string, escapeChar string) string {
return strings.ReplaceAll(s, escapeChar, url.QueryEscape(escapeChar))
}
func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func NewExecutor(datasource *models.DataSource) (plugins.DataPlugin, error) {
logger := log.New("tsdb.mysql")
protocol := "tcp"
@@ -61,7 +57,7 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
logger.Debug("getEngine", "connection", cnnstr)
}
config := sqleng.SqlQueryEndpointConfiguration{
config := sqleng.DataPluginConfiguration{
DriverName: "mysql",
ConnectionString: cnnstr,
Datasource: datasource,
@@ -73,14 +69,15 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
log: logger,
}
return sqleng.NewSqlQueryEndpoint(&config, &rowTransformer, newMysqlMacroEngine(logger), logger)
return sqleng.NewDataPlugin(config, &rowTransformer, newMysqlMacroEngine(logger), logger)
}
type mysqlQueryResultTransformer struct {
log log.Logger
}
func (t *mysqlQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
func (t *mysqlQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (
plugins.DataRowValues, error) {
values := make([]interface{}, len(columnTypes))
for i := range values {
+122 -122
View File
@@ -13,9 +13,9 @@ import (
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"xorm.io/xorm"
@@ -48,11 +48,11 @@ func TestMySQL(t *testing.T) {
}
origInterpolate := sqleng.Interpolate
sqleng.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
sqleng.Interpolate = func(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, sql string) (string, error) {
return sql, nil
}
endpoint, err := newMysqlQueryEndpoint(&models.DataSource{
exe, err := NewExecutor(&models.DataSource{
JsonData: simplejson.New(),
SecureJsonData: securejsondata.SecureJsonData{},
})
@@ -123,19 +123,19 @@ func TestMySQL(t *testing.T) {
So(err, ShouldBeNil)
Convey("Query with Table format should map MySQL column types to Go types", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT * FROM mysql_types",
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -212,19 +212,19 @@ func TestMySQL(t *testing.T) {
So(err, ShouldBeNil)
Convey("When doing a metric query using timeGroup", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m') as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -255,23 +255,23 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', NULL) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -316,24 +316,24 @@ func TestMySQL(t *testing.T) {
})
Convey("Should replace $__interval", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{JsonData: simplejson.New()},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -342,23 +342,23 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with value fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', 1.5) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -368,23 +368,23 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with previous fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', previous) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -467,19 +467,19 @@ func TestMySQL(t *testing.T) {
So(err, ShouldBeNil)
Convey("When doing a metric query using time as time column should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -489,19 +489,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using time (nullable) as time column should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeNullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -511,19 +511,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeInt64 as time, timeInt64 FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -533,19 +533,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -555,19 +555,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -577,19 +577,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -599,19 +599,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeInt32 as time, timeInt32 FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -621,19 +621,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -643,19 +643,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -665,19 +665,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -687,19 +687,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values ORDER BY 1,2`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -710,19 +710,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query with metric column and multiple value columns", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT $__time(time), measurement as metric, valueOne, valueTwo FROM metric_values ORDER BY 1,2`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -735,19 +735,19 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query grouping by time should return correct series", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT $__time(time), valueOne, valueTwo FROM metric_values ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -760,21 +760,21 @@ func TestMySQL(t *testing.T) {
Convey("When doing a query with timeFrom,timeTo,unixEpochFrom,unixEpochTo macros", func() {
sqleng.Interpolate = origInterpolate
query := &tsdb.TsdbQuery{
TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
Queries: []*tsdb.Query{
query := plugins.DataQuery{
TimeRange: &plugins.DataTimeRange{From: "5m", To: "now", Now: fromStart},
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{JsonData: simplejson.New()},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeTo() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -816,46 +816,46 @@ func TestMySQL(t *testing.T) {
}
Convey("When doing an annotation query of deploy events should return expected result", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
"format": "table",
}),
RefId: "Deploys",
RefID: "Deploys",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["Deploys"]
So(err, ShouldBeNil)
So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
})
Convey("When doing an annotation query of ticket events should return expected result", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
"format": "table",
}),
RefId: "Tickets",
RefID: "Tickets",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["Tickets"]
So(err, ShouldBeNil)
So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
@@ -865,8 +865,8 @@ func TestMySQL(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.UTC)
dtFormat := "2006-01-02 15:04:05.999999999"
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -876,12 +876,12 @@ func TestMySQL(t *testing.T) {
`, dt.Format(dtFormat)),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -895,8 +895,8 @@ func TestMySQL(t *testing.T) {
Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -906,12 +906,12 @@ func TestMySQL(t *testing.T) {
`, dt.Unix()),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -925,8 +925,8 @@ func TestMySQL(t *testing.T) {
Convey("When doing an annotation query with a time column in epoch second format (signed integer) should return ms", func() {
dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.Local)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -936,12 +936,12 @@ func TestMySQL(t *testing.T) {
`, dt.Unix()),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -955,8 +955,8 @@ func TestMySQL(t *testing.T) {
Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -966,12 +966,12 @@ func TestMySQL(t *testing.T) {
`, dt.Unix()*1000),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -983,8 +983,8 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing an annotation query with a time column holding a unsigned integer null value should return nil", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT
@@ -994,12 +994,12 @@ func TestMySQL(t *testing.T) {
`,
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
@@ -1011,8 +1011,8 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing an annotation query with a time column holding a DATETIME null value should return nil", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT
@@ -1022,12 +1022,12 @@ func TestMySQL(t *testing.T) {
`,
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
+23 -25
View File
@@ -17,29 +17,23 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
)
type OpenTsdbExecutor struct {
}
func NewOpenTsdbExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
return &OpenTsdbExecutor{}, nil
}
var (
plog log.Logger
plog = log.New("tsdb.opentsdb")
)
func init() {
plog = log.New("tsdb.opentsdb")
tsdb.RegisterTsdbQueryEndpoint("opentsdb", NewOpenTsdbExecutor)
}
func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
func (e *OpenTsdbExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
queryContext plugins.DataQuery) (plugins.DataResponse, error) {
var tsdbQuery OpenTsdbQuery
tsdbQuery.Start = queryContext.TimeRange.GetFromAsMsEpoch()
@@ -50,32 +44,34 @@ func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
tsdbQuery.Queries = append(tsdbQuery.Queries, metric)
}
// TODO: Don't use global variable
if setting.Env == setting.Dev {
plog.Debug("OpenTsdb request", "params", tsdbQuery)
}
req, err := e.createRequest(dsInfo, tsdbQuery)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
res, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
queryResult, err := e.parseResponse(tsdbQuery, res)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
result.Results = queryResult
return result, nil
return plugins.DataResponse{
Results: queryResult,
}, nil
}
func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsdbQuery) (*http.Request, error) {
@@ -102,12 +98,12 @@ func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsd
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.DecryptedBasicAuthPassword())
}
return req, err
return req, nil
}
func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response) (map[string]*tsdb.QueryResult, error) {
queryResults := make(map[string]*tsdb.QueryResult)
queryRes := tsdb.NewQueryResult()
func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response) (map[string]plugins.DataQueryResult, error) {
queryResults := make(map[string]plugins.DataQueryResult)
queryRes := plugins.DataQueryResult{}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
@@ -132,7 +128,7 @@ func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response
}
for _, val := range data {
series := tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: val.Metric,
}
@@ -142,17 +138,19 @@ func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response
plog.Info("Failed to unmarshal opentsdb timestamp", "timestamp", timeString)
return nil, err
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(value), timestamp))
series.Points = append(series.Points, plugins.DataTimePoint{
null.FloatFrom(value), null.FloatFrom(timestamp),
})
}
queryRes.Series = append(queryRes.Series, &series)
queryRes.Series = append(queryRes.Series, series)
}
queryResults["A"] = queryRes
return queryResults, nil
}
func (e *OpenTsdbExecutor) buildMetric(query *tsdb.Query) map[string]interface{} {
func (e *OpenTsdbExecutor) buildMetric(query plugins.DataSubQuery) map[string]interface{} {
metric := make(map[string]interface{})
// Setting metric and aggregator
+7 -7
View File
@@ -4,7 +4,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/require"
)
@@ -12,7 +12,7 @@ func TestOpenTsdbExecutor(t *testing.T) {
exec := &OpenTsdbExecutor{}
t.Run("Build metric with downsampling enabled", func(t *testing.T) {
query := &tsdb.Query{
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
@@ -32,7 +32,7 @@ func TestOpenTsdbExecutor(t *testing.T) {
})
t.Run("Build metric with downsampling disabled", func(t *testing.T) {
query := &tsdb.Query{
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
@@ -51,7 +51,7 @@ func TestOpenTsdbExecutor(t *testing.T) {
})
t.Run("Build metric with downsampling enabled with params", func(t *testing.T) {
query := &tsdb.Query{
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
@@ -71,7 +71,7 @@ func TestOpenTsdbExecutor(t *testing.T) {
})
t.Run("Build metric with tags with downsampling disabled", func(t *testing.T) {
query := &tsdb.Query{
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
@@ -102,7 +102,7 @@ func TestOpenTsdbExecutor(t *testing.T) {
})
t.Run("Build metric with rate enabled but counter disabled", func(t *testing.T) {
query := &tsdb.Query{
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
@@ -134,7 +134,7 @@ func TestOpenTsdbExecutor(t *testing.T) {
})
t.Run("Build metric with rate and counter enabled", func(t *testing.T) {
query := &tsdb.Query{
query := plugins.DataSubQuery{
Model: simplejson.New(),
}
+9 -7
View File
@@ -7,7 +7,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
@@ -15,22 +15,24 @@ const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
type postgresMacroEngine struct {
*sqleng.SqlMacroEngineBase
timeRange *tsdb.TimeRange
query *tsdb.Query
*sqleng.SQLMacroEngineBase
timeRange plugins.DataTimeRange
query plugins.DataSubQuery
timescaledb bool
}
func newPostgresMacroEngine(timescaledb bool) sqleng.SqlMacroEngine {
func newPostgresMacroEngine(timescaledb bool) sqleng.SQLMacroEngine {
return &postgresMacroEngine{
SqlMacroEngineBase: sqleng.NewSqlMacroEngineBase(),
SQLMacroEngineBase: sqleng.NewSQLMacroEngineBase(),
timescaledb: timescaledb,
}
}
func (m *postgresMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
func (m *postgresMacroEngine) Interpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange,
sql string) (string, error) {
m.timeRange = timeRange
m.query = query
// TODO: Handle error
rExp, _ := regexp.Compile(sExpr)
var macroError error
+11 -6
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
. "github.com/smartystreets/goconvey/convey"
)
@@ -16,12 +16,12 @@ func TestMacroEngine(t *testing.T) {
engine := newPostgresMacroEngine(timescaledbEnabled)
timescaledbEnabled = true
engineTS := newPostgresMacroEngine(timescaledbEnabled)
query := &tsdb.Query{}
query := plugins.DataSubQuery{}
Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := tsdb.NewFakeTimeRange("5m", "now", to)
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: to}
Convey("interpolate __time function", func() {
sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)")
@@ -151,7 +151,9 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10),
strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -177,7 +179,9 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10),
strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -203,7 +207,8 @@ func TestMacroEngine(t *testing.T) {
Convey("Given a time range between 1960-02-01 07:00:00.5 and 1980-02-03 08:00:00.5", func() {
from := time.Date(1960, 2, 1, 7, 0, 0, 500e6, time.UTC)
to := time.Date(1980, 2, 3, 8, 0, 0, 500e6, time.UTC)
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
So(from.Format(time.RFC3339Nano), ShouldEqual, "1960-02-01T07:00:00.5Z")
So(to.Format(time.RFC3339Nano), ShouldEqual, "1980-02-03T08:00:00.5Z")
+11 -15
View File
@@ -12,9 +12,8 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"xorm.io/core"
)
@@ -22,26 +21,23 @@ func init() {
registry.Register(&registry.Descriptor{
Name: "PostgresService",
InitPriority: registry.Low,
Instance: &postgresService{},
Instance: &PostgresService{},
})
}
type postgresService struct {
type PostgresService struct {
Cfg *setting.Cfg `inject:""`
logger log.Logger
tlsManager tlsSettingsProvider
}
func (s *postgresService) Init() error {
func (s *PostgresService) Init() error {
s.logger = log.New("tsdb.postgres")
s.tlsManager = newTLSManager(s.logger, s.Cfg.DataPath)
tsdb.RegisterTsdbQueryEndpoint("postgres", func(ds *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return s.newPostgresQueryEndpoint(ds)
})
return nil
}
func (s *postgresService) newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func (s *PostgresService) NewExecutor(datasource *models.DataSource) (plugins.DataPlugin, error) {
s.logger.Debug("Creating Postgres query endpoint")
cnnstr, err := s.generateConnectionString(datasource)
@@ -53,7 +49,7 @@ func (s *postgresService) newPostgresQueryEndpoint(datasource *models.DataSource
s.logger.Debug("getEngine", "connection", cnnstr)
}
config := sqleng.SqlQueryEndpointConfiguration{
config := sqleng.DataPluginConfiguration{
DriverName: "postgres",
ConnectionString: cnnstr,
Datasource: datasource,
@@ -66,7 +62,7 @@ func (s *postgresService) newPostgresQueryEndpoint(datasource *models.DataSource
timescaledb := datasource.JsonData.Get("timescaledb").MustBool(false)
endpoint, err := sqleng.NewSqlQueryEndpoint(&config, &queryResultTransformer, newPostgresMacroEngine(timescaledb),
plugin, err := sqleng.NewDataPlugin(config, &queryResultTransformer, newPostgresMacroEngine(timescaledb),
s.logger)
if err != nil {
s.logger.Error("Failed connecting to Postgres", "err", err)
@@ -74,7 +70,7 @@ func (s *postgresService) newPostgresQueryEndpoint(datasource *models.DataSource
}
s.logger.Debug("Successfully connected to Postgres")
return endpoint, err
return plugin, nil
}
// escape single quotes and backslashes in Postgres connection string parameters.
@@ -82,10 +78,9 @@ func escape(input string) string {
return strings.ReplaceAll(strings.ReplaceAll(input, `\`, `\\`), "'", `\'`)
}
func (s *postgresService) generateConnectionString(datasource *models.DataSource) (string, error) {
func (s *PostgresService) generateConnectionString(datasource *models.DataSource) (string, error) {
var host string
var port int
var err error
if strings.HasPrefix(datasource.Url, "/") {
host = datasource.Url
s.logger.Debug("Generating connection string with Unix socket specifier", "socket", host)
@@ -141,7 +136,8 @@ type postgresQueryResultTransformer struct {
log log.Logger
}
func (t *postgresQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
func (t *postgresQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (
plugins.DataRowValues, error) {
values := make([]interface{}, len(columnTypes))
valuePtrs := make([]interface{}, len(columnTypes))
+116 -116
View File
@@ -14,10 +14,10 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -113,7 +113,7 @@ func TestGenerateConnectionString(t *testing.T) {
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
svc := postgresService{
svc := PostgresService{
Cfg: cfg,
logger: log.New("tsdb.postgres"),
tlsManager: &tlsTestManager{settings: tt.tlsSettings},
@@ -169,19 +169,19 @@ func TestPostgres(t *testing.T) {
sqleng.NewXormEngine = func(d, c string) (*xorm.Engine, error) {
return x, nil
}
sqleng.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
sqleng.Interpolate = func(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, sql string) (string, error) {
return sql, nil
}
cfg := setting.NewCfg()
cfg.DataPath = t.TempDir()
svc := postgresService{
svc := PostgresService{
Cfg: cfg,
logger: log.New("tsdb.postgres"),
tlsManager: &tlsTestManager{settings: tlsSettings{Mode: "disable"}},
}
endpoint, err := svc.newPostgresQueryEndpoint(&models.DataSource{
exe, err := svc.NewExecutor(&models.DataSource{
JsonData: simplejson.New(),
SecureJsonData: securejsondata.SecureJsonData{},
})
@@ -233,19 +233,19 @@ func TestPostgres(t *testing.T) {
require.NoError(t, err)
t.Run("When doing a table query should map Postgres column types to Go types", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT * FROM postgres_types",
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -318,19 +318,19 @@ func TestPostgres(t *testing.T) {
require.NoError(t, err)
t.Run("When doing a metric query using timeGroup", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -368,24 +368,24 @@ func TestPostgres(t *testing.T) {
sqleng.Interpolate = mockInterpolate
})
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -395,23 +395,23 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing a metric query using timeGroup with NULL fill enabled", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -448,23 +448,23 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing a metric query using timeGroup with value fill enabled", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -475,23 +475,23 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing a metric query using timeGroup with previous fill enabled", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', previous), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -572,19 +572,19 @@ func TestPostgres(t *testing.T) {
t.Run(
"When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeInt64" as time, "timeInt64" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -595,19 +595,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable,) should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeInt64Nullable" as time, "timeInt64Nullable" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -618,19 +618,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (float64) as time column and value column (float64), should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeFloat64" as time, "timeFloat64" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -641,19 +641,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable), should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeFloat64Nullable" as time, "timeFloat64Nullable" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -664,19 +664,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (int32) as time column and value column (int32), should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeInt32" as time, "timeInt32" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -687,19 +687,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable), should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeInt32Nullable" as time, "timeInt32Nullable" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -710,19 +710,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (float32) as time column and value column (float32), should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeFloat32" as time, "timeFloat32" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -733,19 +733,19 @@ func TestPostgres(t *testing.T) {
t.Run("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable), should return metric with time in milliseconds",
func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "timeFloat32Nullable" as time, "timeFloat32Nullable" FROM metric_values ORDER BY time LIMIT 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -755,19 +755,19 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing a metric query grouping by time and select metric column should return correct series", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT $__timeEpoch(time), measurement || ' - value one' as metric, "valueOne" FROM metric_values ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -778,19 +778,19 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing a metric query with metric column and multiple value columns", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT $__timeEpoch(time), measurement as metric, "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -803,19 +803,19 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing a metric query grouping by time should return correct series", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT $__timeEpoch(time), "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -832,21 +832,21 @@ func TestPostgres(t *testing.T) {
})
sqleng.Interpolate = origInterpolate
query := &tsdb.TsdbQuery{
TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
Queries: []*tsdb.Query{
query := plugins.DataQuery{
TimeRange: &plugins.DataTimeRange{From: "5m", To: "now", Now: fromStart},
Queries: []plugins.DataSubQuery{
{
DataSource: &models.DataSource{JsonData: simplejson.New()},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
"format": "time_series",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -891,46 +891,46 @@ func TestPostgres(t *testing.T) {
}
t.Run("When doing an annotation query of deploy events should return expected result", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
"format": "table",
}),
RefId: "Deploys",
RefID: "Deploys",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["Deploys"]
require.NoError(t, err)
require.Len(t, queryResult.Tables[0].Rows, 3)
})
t.Run("When doing an annotation query of ticket events should return expected result", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
"format": "table",
}),
RefId: "Tickets",
RefID: "Tickets",
},
},
TimeRange: &tsdb.TimeRange{
TimeRange: &plugins.DataTimeRange{
From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
queryResult := resp.Results["Tickets"]
require.NoError(t, err)
require.Len(t, queryResult.Tables[0].Rows, 3)
@@ -940,8 +940,8 @@ func TestPostgres(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
dtFormat := "2006-01-02 15:04:05.999999999"
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -951,12 +951,12 @@ func TestPostgres(t *testing.T) {
`, dt.Format(dtFormat)),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -970,8 +970,8 @@ func TestPostgres(t *testing.T) {
t.Run("When doing an annotation query with a time column in epoch second format should return ms", func(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -981,12 +981,12 @@ func TestPostgres(t *testing.T) {
`, dt.Unix()),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -1000,8 +1000,8 @@ func TestPostgres(t *testing.T) {
t.Run("When doing an annotation query with a time column in epoch second format (t *testing.Tint) should return ms", func(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -1011,12 +1011,12 @@ func TestPostgres(t *testing.T) {
`, dt.Unix()),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -1030,8 +1030,8 @@ func TestPostgres(t *testing.T) {
t.Run("When doing an annotation query with a time column in epoch millisecond format should return ms", func(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": fmt.Sprintf(`SELECT
@@ -1041,12 +1041,12 @@ func TestPostgres(t *testing.T) {
`, dt.Unix()*1000),
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -1058,8 +1058,8 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing an annotation query with a time column holding a bigint null value should return nil", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT
@@ -1069,12 +1069,12 @@ func TestPostgres(t *testing.T) {
`,
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
@@ -1086,8 +1086,8 @@ func TestPostgres(t *testing.T) {
})
t.Run("When doing an annotation query with a time column holding a timestamp null value should return nil", func(t *testing.T) {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
query := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `SELECT
@@ -1097,12 +1097,12 @@ func TestPostgres(t *testing.T) {
`,
"format": "table",
}),
RefId: "A",
RefID: "A",
},
},
}
resp, err := endpoint.Query(context.Background(), nil, query)
resp, err := exe.DataQuery(context.Background(), nil, query)
require.NoError(t, err)
queryResult := resp.Results["A"]
require.NoError(t, queryResult.Error)
+35 -30
View File
@@ -15,7 +15,8 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/prometheus/client_golang/api"
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/model"
@@ -23,6 +24,8 @@ import (
type PrometheusExecutor struct {
Transport http.RoundTripper
intervalCalculator interval.Calculator
}
type basicAuthTransport struct {
@@ -37,28 +40,25 @@ func (bat basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro
return bat.Transport.RoundTrip(req)
}
func NewPrometheusExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, error) {
transport, err := dsInfo.GetHttpTransport()
if err != nil {
return nil, err
}
return &PrometheusExecutor{
Transport: transport,
Transport: transport,
intervalCalculator: interval.NewCalculator(interval.CalculatorOptions{MinInterval: time.Second * 1}),
}, nil
}
var (
plog log.Logger
legendFormat *regexp.Regexp
intervalCalculator tsdb.IntervalCalculator
plog log.Logger
legendFormat *regexp.Regexp = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
)
func init() {
plog = log.New("tsdb.prometheus")
tsdb.RegisterTsdbQueryEndpoint("prometheus", NewPrometheusExecutor)
legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
intervalCalculator = tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{MinInterval: time.Second * 1})
}
func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, error) {
@@ -83,19 +83,20 @@ func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, er
return apiv1.NewAPI(client), nil
}
func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
func (e *PrometheusExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
tsdbQuery plugins.DataQuery) (plugins.DataResponse, error) {
result := plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{},
}
client, err := e.getClient(dsInfo)
if err != nil {
return nil, err
return result, err
}
queries, err := parseQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
queries, err := e.parseQuery(dsInfo, tsdbQuery)
if err != nil {
return nil, err
return result, err
}
for _, query := range queries {
@@ -116,12 +117,12 @@ func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
value, _, err := client.QueryRange(ctx, query.Expr, timeRange)
if err != nil {
return nil, err
return result, err
}
queryResult, err := parseResponse(value, query)
if err != nil {
return nil, err
return result, err
}
result.Results[query.RefId] = queryResult
}
@@ -147,9 +148,10 @@ func formatLegend(metric model.Metric, query *PrometheusQuery) string {
return string(result)
}
func parseQuery(dsInfo *models.DataSource, queries []*tsdb.Query, queryContext *tsdb.TsdbQuery) ([]*PrometheusQuery, error) {
func (e *PrometheusExecutor) parseQuery(dsInfo *models.DataSource, query plugins.DataQuery) (
[]*PrometheusQuery, error) {
qs := []*PrometheusQuery{}
for _, queryModel := range queries {
for _, queryModel := range query.Queries {
expr, err := queryModel.Model.Get("expr").String()
if err != nil {
return nil, err
@@ -157,23 +159,23 @@ func parseQuery(dsInfo *models.DataSource, queries []*tsdb.Query, queryContext *
format := queryModel.Model.Get("legendFormat").MustString("")
start, err := queryContext.TimeRange.ParseFrom()
start, err := query.TimeRange.ParseFrom()
if err != nil {
return nil, err
}
end, err := queryContext.TimeRange.ParseTo()
end, err := query.TimeRange.ParseTo()
if err != nil {
return nil, err
}
dsInterval, err := tsdb.GetIntervalFrom(dsInfo, queryModel.Model, time.Second*15)
dsInterval, err := interval.GetIntervalFrom(dsInfo, queryModel.Model, time.Second*15)
if err != nil {
return nil, err
}
intervalFactor := queryModel.Model.Get("intervalFactor").MustInt64(1)
interval := intervalCalculator.Calculate(queryContext.TimeRange, dsInterval)
interval := e.intervalCalculator.Calculate(*query.TimeRange, dsInterval)
step := time.Duration(int64(interval.Value) * intervalFactor)
qs = append(qs, &PrometheusQuery{
@@ -182,15 +184,15 @@ func parseQuery(dsInfo *models.DataSource, queries []*tsdb.Query, queryContext *
LegendFormat: format,
Start: start,
End: end,
RefId: queryModel.RefId,
RefId: queryModel.RefID,
})
}
return qs, nil
}
func parseResponse(value model.Value, query *PrometheusQuery) (*tsdb.QueryResult, error) {
queryRes := tsdb.NewQueryResult()
func parseResponse(value model.Value, query *PrometheusQuery) (plugins.DataQueryResult, error) {
var queryRes plugins.DataQueryResult
data, ok := value.(model.Matrix)
if !ok {
@@ -198,10 +200,10 @@ func parseResponse(value model.Value, query *PrometheusQuery) (*tsdb.QueryResult
}
for _, v := range data {
series := tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: formatLegend(v.Metric, query),
Tags: make(map[string]string, len(v.Metric)),
Points: make([]tsdb.TimePoint, 0, len(v.Values)),
Points: make([]plugins.DataTimePoint, 0, len(v.Values)),
}
for k, v := range v.Metric {
@@ -209,10 +211,13 @@ func parseResponse(value model.Value, query *PrometheusQuery) (*tsdb.QueryResult
}
for _, k := range v.Values {
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(k.Value)), float64(k.Timestamp.Unix()*1000)))
series.Points = append(series.Points, plugins.DataTimePoint{
null.FloatFrom(float64(k.Value)),
null.FloatFrom(float64(k.Timestamp.Unix() * 1000)),
})
}
queryRes.Series = append(queryRes.Series, &series)
queryRes.Series = append(queryRes.Series, series)
}
return queryRes, nil
+35 -19
View File
@@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
p "github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
@@ -15,6 +15,9 @@ func TestPrometheus(t *testing.T) {
dsInfo := &models.DataSource{
JsonData: simplejson.New(),
}
plug, err := NewExecutor(dsInfo)
executor := plug.(*PrometheusExecutor)
require.NoError(t, err)
t.Run("converting metric name", func(t *testing.T) {
metric := map[p.LabelName]p.LabelValue{
@@ -50,14 +53,17 @@ func TestPrometheus(t *testing.T) {
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
queryModels := []plugins.DataSubQuery{
{Model: jsonModel},
}
queryContext.TimeRange = tsdb.NewTimeRange("12h", "now")
timeRange := plugins.NewDataTimeRange("12h", "now")
queryContext := plugins.DataQuery{
Queries: queryModels,
TimeRange: &timeRange,
}
models, err := parseQuery(dsInfo, queryModels, queryContext)
models, err := executor.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Second*30, models[0].Step)
})
@@ -70,18 +76,22 @@ func TestPrometheus(t *testing.T) {
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
queryModels := []plugins.DataSubQuery{
{Model: jsonModel},
}
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")
models, err := parseQuery(dsInfo, queryModels, queryContext)
timeRange := plugins.NewDataTimeRange("48h", "now")
queryContext := plugins.DataQuery{
Queries: queryModels,
TimeRange: &timeRange,
}
models, err := executor.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Minute*2, models[0].Step)
queryContext.TimeRange = tsdb.NewTimeRange("1h", "now")
models, err = parseQuery(dsInfo, queryModels, queryContext)
timeRange = plugins.NewDataTimeRange("1h", "now")
queryContext.TimeRange = &timeRange
models, err = executor.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Second*15, models[0].Step)
})
@@ -94,14 +104,17 @@ func TestPrometheus(t *testing.T) {
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
queryModels := []plugins.DataSubQuery{
{Model: jsonModel},
}
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")
timeRange := plugins.NewDataTimeRange("48h", "now")
queryContext := plugins.DataQuery{
TimeRange: &timeRange,
Queries: queryModels,
}
models, err := parseQuery(dsInfo, queryModels, queryContext)
models, err := executor.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Minute*20, models[0].Step)
})
@@ -114,14 +127,17 @@ func TestPrometheus(t *testing.T) {
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
queryModels := []plugins.DataSubQuery{
{Model: jsonModel},
}
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")
timeRange := plugins.NewDataTimeRange("48h", "now")
queryContext := plugins.DataQuery{
TimeRange: &timeRange,
Queries: queryModels,
}
models, err := parseQuery(dsInfo, queryModels, queryContext)
models, err := executor.parseQuery(dsInfo, queryContext)
require.NoError(t, err)
require.Equal(t, time.Minute*2, models[0].Step)
})
-23
View File
@@ -1,23 +0,0 @@
package tsdb
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type TsdbQueryEndpoint interface {
Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) (*Response, error)
}
var registry map[string]GetTsdbQueryEndpointFn
type GetTsdbQueryEndpointFn func(dsInfo *models.DataSource) (TsdbQueryEndpoint, error)
func init() {
registry = make(map[string]GetTsdbQueryEndpointFn)
}
func RegisterTsdbQueryEndpoint(pluginId string, fn GetTsdbQueryEndpointFn) {
registry[pluginId] = fn
}
-26
View File
@@ -1,26 +0,0 @@
package tsdb
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/models"
)
type HandleRequestFunc func(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error)
func HandleRequest(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error) {
var endpoint TsdbQueryEndpoint
fn, exists := registry[dsInfo.Type]
if !exists {
return nil, fmt.Errorf("could not find executor for data source type: %s", dsInfo.Type)
}
var err error
endpoint, err = fn(dsInfo)
if err != nil {
return nil, err
}
return endpoint.Query(ctx, dsInfo, req)
}
+76 -25
View File
@@ -5,39 +5,42 @@ import (
"testing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/stretchr/testify/require"
)
func TestHandleRequest(t *testing.T) {
t.Run("Should return query result when handling request for query", func(t *testing.T) {
req := &TsdbQuery{
Queries: []*Query{
{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
req := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{RefID: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
},
}
fakeExecutor := registerFakeExecutor()
fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}})
svc, exe := createService()
exe.Return("A", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "argh"}})
res, err := HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
res, err := svc.HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
require.NoError(t, err)
require.NotEmpty(t, res.Results["A"].Series)
require.Equal(t, "argh", res.Results["A"].Series[0].Name)
})
t.Run("Should return query results when handling request for two queries with same data source", func(t *testing.T) {
req := &TsdbQuery{
Queries: []*Query{
{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
{RefId: "B", DataSource: &models.DataSource{Id: 1, Type: "test"}},
req := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{RefID: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}},
{RefID: "B", DataSource: &models.DataSource{Id: 1, Type: "test"}},
},
}
fakeExecutor := registerFakeExecutor()
fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}})
fakeExecutor.Return("B", TimeSeriesSlice{&TimeSeries{Name: "barg"}})
svc, exe := createService()
exe.Return("A", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "argh"}})
exe.Return("B", plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "barg"}})
res, err := HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
res, err := svc.HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
require.NoError(t, err)
require.Len(t, res.Results, 2)
@@ -46,22 +49,70 @@ func TestHandleRequest(t *testing.T) {
})
t.Run("Should return error when handling request for query with unknown type", func(t *testing.T) {
req := &TsdbQuery{
Queries: []*Query{
{RefId: "A", DataSource: &models.DataSource{Id: 1, Type: "asdasdas"}},
svc, _ := createService()
req := plugins.DataQuery{
Queries: []plugins.DataSubQuery{
{RefID: "A", DataSource: &models.DataSource{Id: 1, Type: "asdasdas"}},
},
}
_, err := HandleRequest(context.TODO(), &models.DataSource{Id: 12, Type: "testjughjgjg"}, req)
_, err := svc.HandleRequest(context.TODO(), &models.DataSource{Id: 12, Type: "testjughjgjg"}, req)
require.Error(t, err)
})
}
func registerFakeExecutor() *FakeExecutor {
executor, _ := NewFakeExecutor(nil)
RegisterTsdbQueryEndpoint("test", func(dsInfo *models.DataSource) (TsdbQueryEndpoint, error) {
return executor, nil
})
type resultsFn func(context plugins.DataQuery) plugins.DataQueryResult
return executor
type fakeExecutor struct {
results map[string]plugins.DataQueryResult
resultsFn map[string]resultsFn
}
func (e *fakeExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource, context plugins.DataQuery) (
plugins.DataResponse, error) {
result := plugins.DataResponse{Results: make(map[string]plugins.DataQueryResult)}
for _, query := range context.Queries {
if results, has := e.results[query.RefID]; has {
result.Results[query.RefID] = results
}
if testFunc, has := e.resultsFn[query.RefID]; has {
result.Results[query.RefID] = testFunc(context)
}
}
return result, nil
}
func (e *fakeExecutor) Return(refID string, series plugins.DataTimeSeriesSlice) {
e.results[refID] = plugins.DataQueryResult{
RefID: refID, Series: series,
}
}
func (e *fakeExecutor) HandleQuery(refId string, fn resultsFn) {
e.resultsFn[refId] = fn
}
type fakeBackendPM struct {
backendplugin.Manager
}
func (pm fakeBackendPM) GetDataPlugin(string) interface{} {
return nil
}
func createService() (Service, *fakeExecutor) {
s := NewService()
s.PluginManager = &manager.PluginManager{
BackendPluginManager: fakeBackendPM{},
}
e := &fakeExecutor{
results: make(map[string]plugins.DataQueryResult),
resultsFn: make(map[string]resultsFn),
}
s.registry["test"] = func(*models.DataSource) (plugins.DataPlugin, error) {
return e, nil
}
return s, e
}
+97
View File
@@ -0,0 +1,97 @@
package tsdb
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor"
"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch"
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
"github.com/grafana/grafana/pkg/tsdb/loki"
"github.com/grafana/grafana/pkg/tsdb/mssql"
"github.com/grafana/grafana/pkg/tsdb/mysql"
"github.com/grafana/grafana/pkg/tsdb/opentsdb"
"github.com/grafana/grafana/pkg/tsdb/postgres"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
"github.com/grafana/grafana/pkg/tsdb/tempo"
)
// NewService returns a new Service.
func NewService() Service {
return Service{
registry: map[string]func(*models.DataSource) (plugins.DataPlugin, error){},
}
}
func init() {
svc := NewService()
registry.Register(&registry.Descriptor{
Name: "DataService",
Instance: &svc,
})
}
// Service handles data requests to data sources.
type Service struct {
Cfg *setting.Cfg `inject:""`
CloudWatchService *cloudwatch.CloudWatchService `inject:""`
PostgresService *postgres.PostgresService `inject:""`
CloudMonitoringService *cloudmonitoring.Service `inject:""`
AzureMonitorService *azuremonitor.Service `inject:""`
PluginManager *manager.PluginManager `inject:""`
registry map[string]func(*models.DataSource) (plugins.DataPlugin, error)
}
// Init initialises the service.
func (s *Service) Init() error {
s.registry["graphite"] = graphite.NewExecutor
s.registry["opentsdb"] = opentsdb.NewExecutor
s.registry["prometheus"] = prometheus.NewExecutor
s.registry["influxdb"] = influxdb.NewExecutor
s.registry["mssql"] = mssql.NewExecutor
s.registry["postgres"] = s.PostgresService.NewExecutor
s.registry["mysql"] = mysql.NewExecutor
s.registry["elasticsearch"] = elasticsearch.NewExecutor
s.registry["cloudwatch"] = s.CloudWatchService.NewExecutor
s.registry["stackdriver"] = s.CloudMonitoringService.NewExecutor
s.registry["grafana-azure-monitor-datasource"] = s.AzureMonitorService.NewExecutor
s.registry["loki"] = loki.NewExecutor
s.registry["tempo"] = tempo.NewExecutor
return nil
}
func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (
plugins.DataResponse, error) {
plugin := s.PluginManager.GetDataPlugin(ds.Type)
if plugin == nil {
factory, exists := s.registry[ds.Type]
if !exists {
return plugins.DataResponse{}, fmt.Errorf(
"could not find plugin corresponding to data source type: %q", ds.Type)
}
var err error
plugin, err = factory(ds)
if err != nil {
return plugins.DataResponse{}, fmt.Errorf("could not instantiate endpoint for data plugin %q: %w",
ds.Type, err)
}
}
return plugin.DataQuery(ctx, ds, query)
}
// RegisterQueryHandler registers a query handler factory.
// This is only exposed for tests!
func (s *Service) RegisterQueryHandler(name string, factory func(*models.DataSource) (plugins.DataPlugin, error)) {
s.registry[name] = factory
}
+147 -100
View File
@@ -12,10 +12,11 @@ import (
"sync"
"time"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/interval"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/components/null"
@@ -28,16 +29,16 @@ import (
// MetaKeyExecutedQueryString is the key where the executed query should get stored
const MetaKeyExecutedQueryString = "executedQueryString"
// SqlMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and
// SQLMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and
// timeRange to be able to generate queries that use from and to.
type SqlMacroEngine interface {
Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error)
type SQLMacroEngine interface {
Interpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, sql string) (string, error)
}
// SqlQueryResultTransformer transforms a query result row to RowValues with proper types.
type SqlQueryResultTransformer interface {
// TransformQueryResult transforms a query result row to RowValues with proper types.
TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error)
TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (plugins.DataRowValues, error)
// TransformQueryError transforms a query error.
TransformQueryError(err error) error
}
@@ -53,7 +54,7 @@ var engineCache = engineCacheType{
versions: make(map[int64]int),
}
var sqlIntervalCalculator = tsdb.NewIntervalCalculator(nil)
var sqlIntervalCalculator = interval.NewCalculator()
// NewXormEngine is an xorm.Engine factory, that can be stubbed by tests.
//nolint:gocritic
@@ -63,8 +64,8 @@ var NewXormEngine = func(driverName string, connectionString string) (*xorm.Engi
const timeEndColumnName = "timeend"
type sqlQueryEndpoint struct {
macroEngine SqlMacroEngine
type dataPlugin struct {
macroEngine SQLMacroEngine
queryResultTransformer SqlQueryResultTransformer
engine *xorm.Engine
timeColumnNames []string
@@ -72,7 +73,7 @@ type sqlQueryEndpoint struct {
log log.Logger
}
type SqlQueryEndpointConfiguration struct {
type DataPluginConfiguration struct {
DriverName string
Datasource *models.DataSource
ConnectionString string
@@ -80,8 +81,10 @@ type SqlQueryEndpointConfiguration struct {
MetricColumnTypes []string
}
var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, queryResultTransformer SqlQueryResultTransformer, macroEngine SqlMacroEngine, log log.Logger) (tsdb.TsdbQueryEndpoint, error) {
queryEndpoint := sqlQueryEndpoint{
// NewDataPlugin returns a new plugins.DataPlugin
func NewDataPlugin(config DataPluginConfiguration, queryResultTransformer SqlQueryResultTransformer,
macroEngine SQLMacroEngine, log log.Logger) (plugins.DataPlugin, error) {
plugin := dataPlugin{
queryResultTransformer: queryResultTransformer,
macroEngine: macroEngine,
timeColumnNames: []string{"time"},
@@ -89,11 +92,11 @@ var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, queryResul
}
if len(config.TimeColumnNames) > 0 {
queryEndpoint.timeColumnNames = config.TimeColumnNames
plugin.timeColumnNames = config.TimeColumnNames
}
if len(config.MetricColumnTypes) > 0 {
queryEndpoint.metricColumnTypes = config.MetricColumnTypes
plugin.metricColumnTypes = config.MetricColumnTypes
}
engineCache.Lock()
@@ -101,8 +104,8 @@ var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, queryResul
if engine, present := engineCache.cache[config.Datasource.Id]; present {
if version := engineCache.versions[config.Datasource.Id]; version == config.Datasource.Version {
queryEndpoint.engine = engine
return &queryEndpoint, nil
plugin.engine = engine
return &plugin, nil
}
}
@@ -120,50 +123,61 @@ var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, queryResul
engineCache.versions[config.Datasource.Id] = config.Datasource.Version
engineCache.cache[config.Datasource.Id] = engine
queryEndpoint.engine = engine
plugin.engine = engine
return &queryEndpoint, nil
return &plugin, nil
}
const rowLimit = 1000000
// Query is the main function for the SqlQueryEndpoint
func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
func (e *dataPlugin) DataQuery(ctx context.Context, dsInfo *models.DataSource,
queryContext plugins.DataQuery) (plugins.DataResponse, error) {
var timeRange plugins.DataTimeRange
if queryContext.TimeRange != nil {
timeRange = *queryContext.TimeRange
}
ch := make(chan plugins.DataQueryResult, len(queryContext.Queries))
var wg sync.WaitGroup
for _, query := range tsdbQuery.Queries {
rawSQL := query.Model.Get("rawSql").MustString()
if rawSQL == "" {
// Execute each query in a goroutine and wait for them to finish afterwards
for _, query := range queryContext.Queries {
if query.Model.Get("rawSql").MustString() == "" {
continue
}
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefId}
result.Results[query.RefId] = queryResult
// global substitutions
rawSQL, err := Interpolate(query, tsdbQuery.TimeRange, rawSQL)
if err != nil {
queryResult.Error = err
continue
}
// datasource specific substitutions
rawSQL, err = e.macroEngine.Interpolate(query, tsdbQuery.TimeRange, rawSQL)
if err != nil {
queryResult.Error = err
continue
}
queryResult.Meta.Set(MetaKeyExecutedQueryString, rawSQL)
wg.Add(1)
go func(rawSQL string, query *tsdb.Query, queryResult *tsdb.QueryResult) {
go func(query plugins.DataSubQuery) {
defer wg.Done()
queryResult := plugins.DataQueryResult{
Meta: simplejson.New(),
RefID: query.RefID,
}
rawSQL := query.Model.Get("rawSql").MustString()
if rawSQL == "" {
panic("Query model property rawSql should not be empty at this point")
}
// global substitutions
rawSQL, err := Interpolate(query, timeRange, rawSQL)
if err != nil {
queryResult.Error = err
ch <- queryResult
return
}
// datasource specific substitutions
rawSQL, err = e.macroEngine.Interpolate(query, timeRange, rawSQL)
if err != nil {
queryResult.Error = err
ch <- queryResult
return
}
queryResult.Meta.Set(MetaKeyExecutedQueryString, rawSQL)
session := e.engine.NewSession()
defer session.Close()
db := session.DB()
@@ -183,28 +197,40 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
switch format {
case "time_series":
err := e.transformToTimeSeries(query, rows, queryResult, tsdbQuery)
err := e.transformToTimeSeries(query, rows, &queryResult, queryContext)
if err != nil {
queryResult.Error = err
return
}
case "table":
err := e.transformToTable(query, rows, queryResult, tsdbQuery)
err := e.transformToTable(query, rows, &queryResult, queryContext)
if err != nil {
queryResult.Error = err
return
}
}
}(rawSQL, query, queryResult)
ch <- queryResult
}(query)
}
wg.Wait()
// Read results from channels
close(ch)
result := plugins.DataResponse{
Results: make(map[string]plugins.DataQueryResult),
}
for queryResult := range ch {
result.Results[queryResult.RefID] = queryResult
}
return result, nil
}
// Interpolate provides global macros/substitutions for all sql datasources.
var Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
minInterval, err := tsdb.GetIntervalFrom(query.DataSource, query.Model, time.Second*60)
var Interpolate = func(query plugins.DataSubQuery, timeRange plugins.DataTimeRange, sql string) (string, error) {
minInterval, err := interval.GetIntervalFrom(query.DataSource, query.Model, time.Second*60)
if err != nil {
return sql, nil
}
@@ -218,21 +244,22 @@ var Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string)
return sql, nil
}
func (e *sqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult, tsdbQuery *tsdb.TsdbQuery) error {
func (e *dataPlugin) transformToTable(query plugins.DataSubQuery, rows *core.Rows,
result *plugins.DataQueryResult, queryContext plugins.DataQuery) error {
columnNames, err := rows.Columns()
columnCount := len(columnNames)
if err != nil {
return err
}
columnCount := len(columnNames)
rowCount := 0
timeIndex := -1
timeEndIndex := -1
table := &tsdb.Table{
Columns: make([]tsdb.TableColumn, columnCount),
Rows: make([]tsdb.RowValues, 0),
table := plugins.DataTable{
Columns: make([]plugins.DataTableColumn, columnCount),
Rows: make([]plugins.DataRowValues, 0),
}
for i, name := range columnNames {
@@ -279,7 +306,7 @@ func (e *sqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
return nil
}
func newProcessCfg(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery, rows *core.Rows) (*processCfg, error) {
func newProcessCfg(query plugins.DataSubQuery, queryContext plugins.DataQuery, rows *core.Rows) (*processCfg, error) {
columnNames, err := rows.Columns()
if err != nil {
return nil, err
@@ -301,15 +328,15 @@ func newProcessCfg(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery, rows *core.Rows
metricPrefix: false,
fillMissing: fillMissing,
seriesByQueryOrder: list.New(),
pointsBySeries: make(map[string]*tsdb.TimeSeries),
tsdbQuery: tsdbQuery,
pointsBySeries: make(map[string]*plugins.DataTimeSeries),
queryContext: queryContext,
}
return cfg, nil
}
func (e *sqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult,
tsdbQuery *tsdb.TsdbQuery) error {
cfg, err := newProcessCfg(query, tsdbQuery, rows)
func (e *dataPlugin) transformToTimeSeries(query plugins.DataSubQuery, rows *core.Rows,
result *plugins.DataQueryResult, queryContext plugins.DataQuery) error {
cfg, err := newProcessCfg(query, queryContext, rows)
if err != nil {
return err
}
@@ -369,15 +396,15 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.R
for elem := cfg.seriesByQueryOrder.Front(); elem != nil; elem = elem.Next() {
key := elem.Value.(string)
result.Series = append(result.Series, cfg.pointsBySeries[key])
if !cfg.fillMissing {
result.Series = append(result.Series, *cfg.pointsBySeries[key])
continue
}
series := cfg.pointsBySeries[key]
// fill in values from last fetched value till interval end
intervalStart := series.Points[len(series.Points)-1][1].Float64
intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6)
intervalEnd := float64(queryContext.TimeRange.MustGetTo().UnixNano() / 1e6)
if cfg.fillPrevious {
if len(series.Points) > 0 {
@@ -390,9 +417,11 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.R
// align interval start
intervalStart = math.Floor(intervalStart/cfg.fillInterval) * cfg.fillInterval
for i := intervalStart + cfg.fillInterval; i < intervalEnd; i += cfg.fillInterval {
series.Points = append(series.Points, tsdb.TimePoint{cfg.fillValue, null.FloatFrom(i)})
series.Points = append(series.Points, plugins.DataTimePoint{cfg.fillValue, null.FloatFrom(i)})
cfg.rowCount++
}
result.Series = append(result.Series, *series)
}
result.Meta.Set("rowCount", cfg.rowCount)
@@ -409,15 +438,15 @@ type processCfg struct {
metricPrefix bool
metricPrefixValue string
fillMissing bool
pointsBySeries map[string]*tsdb.TimeSeries
pointsBySeries map[string]*plugins.DataTimeSeries
seriesByQueryOrder *list.List
fillValue null.Float
tsdbQuery *tsdb.TsdbQuery
queryContext plugins.DataQuery
fillInterval float64
fillPrevious bool
}
func (e *sqlQueryEndpoint) processRow(cfg *processCfg) error {
func (e *dataPlugin) processRow(cfg *processCfg) error {
var timestamp float64
var value null.Float
var metric string
@@ -447,17 +476,18 @@ func (e *sqlQueryEndpoint) processRow(cfg *processCfg) error {
}
if cfg.metricIndex >= 0 {
if columnValue, ok := values[cfg.metricIndex].(string); ok {
if cfg.metricPrefix {
cfg.metricPrefixValue = columnValue
} else {
metric = columnValue
}
} else {
columnValue, ok := values[cfg.metricIndex].(string)
if !ok {
return fmt.Errorf("column metric must be of type %s. metric column name: %s type: %s but datatype is %T",
strings.Join(e.metricColumnTypes, ", "), cfg.columnNames[cfg.metricIndex],
cfg.columnTypes[cfg.metricIndex].DatabaseTypeName(), values[cfg.metricIndex])
}
if cfg.metricPrefix {
cfg.metricPrefixValue = columnValue
} else {
metric = columnValue
}
}
for i, col := range cfg.columnNames {
@@ -475,17 +505,17 @@ func (e *sqlQueryEndpoint) processRow(cfg *processCfg) error {
metric = cfg.metricPrefixValue + " " + col
}
series, exist := cfg.pointsBySeries[metric]
if !exist {
series = &tsdb.TimeSeries{Name: metric}
series, exists := cfg.pointsBySeries[metric]
if !exists {
series = &plugins.DataTimeSeries{Name: metric}
cfg.pointsBySeries[metric] = series
cfg.seriesByQueryOrder.PushBack(metric)
}
if cfg.fillMissing {
var intervalStart float64
if !exist {
intervalStart = float64(cfg.tsdbQuery.TimeRange.MustGetFrom().UnixNano() / 1e6)
if !exists {
intervalStart = float64(cfg.queryContext.TimeRange.MustGetFrom().UnixNano() / 1e6)
} else {
intervalStart = series.Points[len(series.Points)-1][1].Float64 + cfg.fillInterval
}
@@ -502,13 +532,15 @@ func (e *sqlQueryEndpoint) processRow(cfg *processCfg) error {
intervalStart = math.Floor(intervalStart/cfg.fillInterval) * cfg.fillInterval
for i := intervalStart; i < timestamp; i += cfg.fillInterval {
series.Points = append(series.Points, tsdb.TimePoint{cfg.fillValue, null.FloatFrom(i)})
series.Points = append(series.Points, plugins.DataTimePoint{cfg.fillValue, null.FloatFrom(i)})
cfg.rowCount++
}
}
series.Points = append(series.Points, tsdb.TimePoint{value, null.FloatFrom(timestamp)})
series.Points = append(series.Points, plugins.DataTimePoint{value, null.FloatFrom(timestamp)})
cfg.pointsBySeries[metric] = series
// TODO: Make non-global
if setting.Env == setting.Dev {
e.log.Debug("Rows", "metric", metric, "time", timestamp, "value", value)
}
@@ -519,7 +551,7 @@ func (e *sqlQueryEndpoint) processRow(cfg *processCfg) error {
// ConvertSqlTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds
// to make native datetime types and epoch dates work in annotation and table queries.
func ConvertSqlTimeColumnToEpochMs(values tsdb.RowValues, timeIndex int) {
func ConvertSqlTimeColumnToEpochMs(values plugins.DataRowValues, timeIndex int) {
if timeIndex >= 0 {
switch value := values[timeIndex].(type) {
case time.Time:
@@ -529,40 +561,40 @@ func ConvertSqlTimeColumnToEpochMs(values tsdb.RowValues, timeIndex int) {
values[timeIndex] = float64(value.UnixNano()) / float64(time.Millisecond)
}
case int64:
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(value)))
case *int64:
if value != nil {
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(*value)))
}
case uint64:
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(value)))
case *uint64:
if value != nil {
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(*value)))
}
case int32:
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(value)))
case *int32:
if value != nil {
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(*value)))
}
case uint32:
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(value)))
case *uint32:
if value != nil {
values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value)))
values[timeIndex] = int64(epochPrecisionToMS(float64(*value)))
}
case float64:
values[timeIndex] = tsdb.EpochPrecisionToMs(value)
values[timeIndex] = epochPrecisionToMS(value)
case *float64:
if value != nil {
values[timeIndex] = tsdb.EpochPrecisionToMs(*value)
values[timeIndex] = epochPrecisionToMS(*value)
}
case float32:
values[timeIndex] = tsdb.EpochPrecisionToMs(float64(value))
values[timeIndex] = epochPrecisionToMS(float64(value))
case *float32:
if value != nil {
values[timeIndex] = tsdb.EpochPrecisionToMs(float64(*value))
values[timeIndex] = epochPrecisionToMS(float64(*value))
}
}
}
@@ -678,7 +710,7 @@ func ConvertSqlValueColumnToFloat(columnName string, columnValue interface{}) (n
return value, nil
}
func SetupFillmode(query *tsdb.Query, interval time.Duration, fillmode string) error {
func SetupFillmode(query plugins.DataSubQuery, interval time.Duration, fillmode string) error {
query.Model.Set("fill", true)
query.Model.Set("fillInterval", interval.Seconds())
switch fillmode {
@@ -698,13 +730,13 @@ func SetupFillmode(query *tsdb.Query, interval time.Duration, fillmode string) e
return nil
}
type SqlMacroEngineBase struct{}
type SQLMacroEngineBase struct{}
func NewSqlMacroEngineBase() *SqlMacroEngineBase {
return &SqlMacroEngineBase{}
func NewSQLMacroEngineBase() *SQLMacroEngineBase {
return &SQLMacroEngineBase{}
}
func (m *SqlMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
func (m *SQLMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
result := ""
lastIndex := 0
@@ -720,3 +752,18 @@ func (m *SqlMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str
return result + str[lastIndex:]
}
// epochPrecisionToMS converts epoch precision to millisecond, if needed.
// Only seconds to milliseconds supported right now
func epochPrecisionToMS(value float64) float64 {
s := strconv.FormatFloat(value, 'e', -1, 64)
if strings.HasSuffix(s, "e+09") {
return value * float64(1e3)
}
if strings.HasSuffix(s, "e+18") {
return value / float64(time.Millisecond)
}
return value
}
+4 -4
View File
@@ -8,19 +8,19 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/require"
)
func TestSqlEngine(t *testing.T) {
func TestSQLEngine(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC)
earlyDt := time.Date(1970, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC)
t.Run("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func(t *testing.T) {
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := tsdb.NewFakeTimeRange("5m", "now", to)
query := &tsdb.Query{DataSource: &models.DataSource{}, Model: simplejson.New()}
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: to}
query := plugins.DataSubQuery{DataSource: &models.DataSource{}, Model: simplejson.New()}
t.Run("interpolate $__interval", func(t *testing.T) {
sql, err := Interpolate(query, timeRange, "select $__interval ")
+25 -25
View File
@@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
jaeger "github.com/jaegertracing/jaeger/model"
jaeger_json "github.com/jaegertracing/jaeger/model/converter/json"
@@ -23,7 +23,7 @@ type tempoExecutor struct {
httpClient *http.Client
}
func newTempoExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
func NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, error) {
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return nil, err
@@ -35,29 +35,21 @@ func newTempoExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error)
}
var (
tlog log.Logger
tlog = log.New("tsdb.tempo")
)
func init() {
tlog = log.New("tsdb.tempo")
tsdb.RegisterTsdbQueryEndpoint("tempo", newTempoExecutor)
}
func (e *tempoExecutor) DataQuery(ctx context.Context, dsInfo *models.DataSource,
queryContext plugins.DataQuery) (plugins.DataResponse, error) {
refID := queryContext.Queries[0].RefID
queryResult := plugins.DataQueryResult{}
func (e *tempoExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: map[string]*tsdb.QueryResult{},
}
refID := tsdbQuery.Queries[0].RefId
queryResult := &tsdb.QueryResult{}
result.Results[refID] = queryResult
traceID := tsdbQuery.Queries[0].Model.Get("query").MustString("")
traceID := queryContext.Queries[0].Model.Get("query").MustString("")
tlog.Debug("Querying tempo with traceID", "traceID", traceID)
req, err := http.NewRequestWithContext(ctx, "GET", dsInfo.Url+"/api/traces/"+traceID, nil)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
if dsInfo.BasicAuth {
@@ -68,7 +60,7 @@ func (e *tempoExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts
resp, err := e.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed get to tempo: %w", err)
return plugins.DataResponse{}, fmt.Errorf("failed get to tempo: %w", err)
}
defer func() {
@@ -79,24 +71,28 @@ func (e *tempoExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
return plugins.DataResponse{}, err
}
if resp.StatusCode != http.StatusOK {
queryResult.Error = fmt.Errorf("failed to get trace: %s", traceID)
tlog.Error("Request to tempo failed", "Status", resp.Status, "Body", string(body))
return result, nil
return plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
refID: queryResult,
},
}, nil
}
otTrace := ot_pdata.NewTraces()
err = otTrace.FromOtlpProtoBytes(body)
if err != nil {
return nil, fmt.Errorf("failed to convert tempo response to Otlp: %w", err)
return plugins.DataResponse{}, fmt.Errorf("failed to convert tempo response to Otlp: %w", err)
}
jaegerBatches, err := ot_jaeger.InternalTracesToJaegerProto(otTrace)
if err != nil {
return nil, fmt.Errorf("failed to translate to jaegerBatches %v: %w", traceID, err)
return plugins.DataResponse{}, fmt.Errorf("failed to translate to jaegerBatches %v: %w", traceID, err)
}
jaegerTrace := &jaeger.Trace{
@@ -120,13 +116,17 @@ func (e *tempoExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts
traceBytes, err := json.Marshal(jsonTrace)
if err != nil {
return nil, fmt.Errorf("failed to json.Marshal trace \"%s\" :%w", traceID, err)
return plugins.DataResponse{}, fmt.Errorf("failed to json.Marshal trace \"%s\" :%w", traceID, err)
}
frames := []*data.Frame{
{Name: "Traces", RefID: refID, Fields: []*data.Field{data.NewField("trace", nil, []string{string(traceBytes)})}},
}
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
queryResult.Dataframes = plugins.NewDecodedDataFrames(frames)
return result, nil
return plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
refID: queryResult,
},
}, nil
}
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -19,7 +19,7 @@ func TestTestdataScenarios(t *testing.T) {
t.Run("random walk ", func(t *testing.T) {
t.Run("Should start at the requested value", func(t *testing.T) {
timeRange := tsdb.NewFakeTimeRange("5m", "now", time.Now())
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: time.Now()}
model := simplejson.New()
model.Set("startValue", 1.234)
@@ -63,7 +63,7 @@ func TestTestdataScenarios(t *testing.T) {
t.Run("random walk table", func(t *testing.T) {
t.Run("Should return a table that looks like value/min/max", func(t *testing.T) {
timeRange := tsdb.NewFakeTimeRange("5m", "now", time.Now())
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: time.Now()}
model := simplejson.New()
modelBytes, err := model.MarshalJSON()
@@ -117,7 +117,7 @@ func TestTestdataScenarios(t *testing.T) {
})
t.Run("Should return a table with some nil values", func(t *testing.T) {
timeRange := tsdb.NewFakeTimeRange("5m", "now", time.Now())
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: time.Now()}
model := simplejson.New()
model.Set("withNil", true)
-16
View File
@@ -2,7 +2,6 @@ package tsdb
import (
"strconv"
"strings"
"time"
"github.com/timberio/go-datemath"
@@ -115,18 +114,3 @@ func parse(s string, now time.Time, withRoundUp bool, location *time.Location) (
return now.Add(diff), nil
}
// EpochPrecisionToMs converts epoch precision to millisecond, if needed.
// Only seconds to milliseconds supported right now
func EpochPrecisionToMs(value float64) float64 {
s := strconv.FormatFloat(value, 'e', -1, 64)
if strings.HasSuffix(s, "e+09") {
return value * float64(1e3)
}
if strings.HasSuffix(s, "e+18") {
return value / float64(time.Millisecond)
}
return value
}
+13
View File
@@ -0,0 +1,13 @@
package tsdbifaces
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
// RequestHandler is a data request handler interface.
type RequestHandler interface {
HandleRequest(context.Context, *models.DataSource, plugins.DataQuery) (plugins.DataResponse, error)
}