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:
co-authored by
Erik Sundell
Will Browne
Torkel Ödegaard
Will Browne
Zoltán Bedi
parent
c899bf3592
commit
b79e61656a
@@ -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×pan=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×pan=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)
|
||||
|
||||
@@ -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(®istry.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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{}{}),
|
||||
},
|
||||
|
||||
@@ -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:]
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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(®istry.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(¶ms, &q.MetricQuery, durationSeconds, query.IntervalMs)
|
||||
setMetricAggParams(¶ms, &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(¶ms, &q.SloQuery, durationSeconds, query.IntervalMs)
|
||||
setSloAggParams(¶ms, &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
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{},
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(®istry.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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(®istry.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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user