| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | package plugins | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-11 03:42:15 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/bus" | 
					
						
							|  |  |  | 	"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/backendplugin" | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/datasource/wrapper" | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/tsdb" | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util/errutil" | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type TransformPlugin struct { | 
					
						
							|  |  |  | 	PluginBase | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Executable string `json:"executable,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*TransformWrapper | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 03:18:45 +08:00
										 |  |  | func (p *TransformPlugin) Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error { | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 	if err := decoder.Decode(p); err != nil { | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 	if err := p.registerPlugin(pluginDir); err != nil { | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 	cmd := ComposePluginStartCommmand(p.Executable) | 
					
						
							|  |  |  | 	fullpath := path.Join(p.PluginDir, cmd) | 
					
						
							|  |  |  | 	descriptor := backendplugin.NewBackendPluginDescriptor(p.Id, fullpath, backendplugin.PluginStartFuncs{ | 
					
						
							|  |  |  | 		OnStart: p.onPluginStart, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2020-01-14 03:18:45 +08:00
										 |  |  | 	if err := backendPluginManager.Register(descriptor); err != nil { | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 		return errutil.Wrapf(err, "Failed to register backend plugin") | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 	Transform = p | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | func (p *TransformPlugin) onPluginStart(pluginID string, client *backendplugin.Client, logger log.Logger) error { | 
					
						
							|  |  |  | 	p.TransformWrapper = NewTransformWrapper(logger, client.TransformPlugin) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-10 19:59:03 +08:00
										 |  |  | 	if client.DataPlugin != nil { | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 		tsdb.RegisterTsdbQueryEndpoint(pluginID, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) { | 
					
						
							| 
									
										
										
										
											2020-03-10 19:59:03 +08:00
										 |  |  | 			return wrapper.NewDatasourcePluginWrapperV2(logger, p.Id, p.Type, client.DataPlugin), nil | 
					
						
							| 
									
										
										
										
											2020-01-14 00:13:17 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ...
 | 
					
						
							|  |  |  | // Wrapper Code
 | 
					
						
							|  |  |  | // ...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | func NewTransformWrapper(log log.Logger, plugin backendplugin.TransformPlugin) *TransformWrapper { | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 	return &TransformWrapper{plugin, log, &transformCallback{log}} | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type TransformWrapper struct { | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | 	backendplugin.TransformPlugin | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 	logger   log.Logger | 
					
						
							|  |  |  | 	callback *transformCallback | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 02:38:28 +08:00
										 |  |  | func (tw *TransformWrapper) Transform(ctx context.Context, query *tsdb.TsdbQuery) (*tsdb.Response, error) { | 
					
						
							| 
									
										
										
										
											2020-03-10 19:59:03 +08:00
										 |  |  | 	pbQuery := &pluginv2.QueryDataRequest{ | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 		Config:  &pluginv2.PluginConfig{}, | 
					
						
							|  |  |  | 		Queries: []*pluginv2.DataQuery{}, | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, q := range query.Queries { | 
					
						
							|  |  |  | 		modelJSON, err := q.Model.MarshalJSON() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 		pbQuery.Queries = append(pbQuery.Queries, &pluginv2.DataQuery{ | 
					
						
							|  |  |  | 			Json:          modelJSON, | 
					
						
							|  |  |  | 			IntervalMS:    q.IntervalMs, | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 			RefId:         q.RefId, | 
					
						
							|  |  |  | 			MaxDataPoints: q.MaxDataPoints, | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 			TimeRange: &pluginv2.TimeRange{ | 
					
						
							|  |  |  | 				ToEpochMS:   query.TimeRange.GetToAsMsEpoch(), | 
					
						
							|  |  |  | 				FromEpochMS: query.TimeRange.GetFromAsMsEpoch(), | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-10 19:59:03 +08:00
										 |  |  | 	pbRes, err := tw.TransformPlugin.TransformData(ctx, pbQuery, tw.callback) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 	return &tsdb.Response{ | 
					
						
							|  |  |  | 		Results: map[string]*tsdb.QueryResult{ | 
					
						
							|  |  |  | 			"": { | 
					
						
							|  |  |  | 				Dataframes: pbRes.Frames, | 
					
						
							|  |  |  | 				Meta:       simplejson.NewFromAny(pbRes.Metadata), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | type transformCallback struct { | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	logger log.Logger | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-10 19:59:03 +08:00
										 |  |  | func (s *transformCallback) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest) (*pluginv2.QueryDataResponse, error) { | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	if len(req.Queries) == 0 { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("zero queries found in datasource request") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-31 02:38:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-20 02:17:05 +08:00
										 |  |  | 	datasourceID := int64(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if req.Config.DatasourceConfig != nil { | 
					
						
							|  |  |  | 		datasourceID = req.Config.DatasourceConfig.Id | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	getDsInfo := &models.GetDataSourceByIdQuery{ | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 		OrgId: req.Config.OrgId, | 
					
						
							| 
									
										
										
										
											2020-02-20 02:17:05 +08:00
										 |  |  | 		Id:    datasourceID, | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := bus.Dispatch(getDsInfo); err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("Could not find datasource %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Convert plugin-model (datasource) queries to tsdb queries
 | 
					
						
							|  |  |  | 	queries := make([]*tsdb.Query, len(req.Queries)) | 
					
						
							|  |  |  | 	for i, query := range req.Queries { | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 		sj, err := simplejson.NewJson(query.Json) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		queries[i] = &tsdb.Query{ | 
					
						
							|  |  |  | 			RefId:         query.RefId, | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 			IntervalMs:    query.IntervalMS, | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 			MaxDataPoints: query.MaxDataPoints, | 
					
						
							|  |  |  | 			DataSource:    getDsInfo.Result, | 
					
						
							|  |  |  | 			Model:         sj, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 	// For now take Time Range from first query.
 | 
					
						
							|  |  |  | 	timeRange := tsdb.NewTimeRange(strconv.FormatInt(req.Queries[0].TimeRange.FromEpochMS, 10), strconv.FormatInt(req.Queries[0].TimeRange.ToEpochMS, 10)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	tQ := &tsdb.TsdbQuery{ | 
					
						
							|  |  |  | 		TimeRange: timeRange, | 
					
						
							|  |  |  | 		Queries:   queries, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Execute the converted queries
 | 
					
						
							|  |  |  | 	tsdbRes, err := tsdb.HandleRequest(ctx, getDsInfo.Result, tQ) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-11-13 05:21:10 +08:00
										 |  |  | 	// Convert tsdb results (map) to plugin-model/datasource (slice) results.
 | 
					
						
							|  |  |  | 	// Only error, tsdb.Series, and encoded Dataframes responses are mapped.
 | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	encodedFrames := [][]byte{} | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 	for refID, res := range tsdbRes.Results { | 
					
						
							| 
									
										
										
										
											2019-11-13 05:21:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 		if res.Error != nil { | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 			// TODO add Errors property to Frame
 | 
					
						
							|  |  |  | 			encodedFrames = append(encodedFrames, nil) | 
					
						
							| 
									
										
										
										
											2019-11-13 05:21:10 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if res.Dataframes != nil { | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 			encodedFrames = append(encodedFrames, res.Dataframes...) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 		for _, series := range res.Series { | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 			frame, err := tsdb.SeriesToFrame(series) | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 			frame.RefID = refID | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-03-11 03:42:15 +08:00
										 |  |  | 			encFrame, err := data.MarshalArrow(frame) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-01-09 00:43:28 +08:00
										 |  |  | 			encodedFrames = append(encodedFrames, encFrame) | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-03-10 19:59:03 +08:00
										 |  |  | 	return &pluginv2.QueryDataResponse{Frames: encodedFrames}, nil | 
					
						
							| 
									
										
										
										
											2019-10-30 00:22:31 +08:00
										 |  |  | } |