From 8f90c6115d111b35b5be52b0bab0ca67ba76aa43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 29 Mar 2017 22:54:07 +0200 Subject: [PATCH] mysql: progress on mysql data source --- pkg/api/frontendsettings.go | 1 + pkg/api/metrics.go | 20 ++- pkg/cmd/grafana-server/main.go | 1 + pkg/tsdb/mysql/mysql.go | 160 ++++++++++++++++++ .../app/testdata/datasource/datasource.ts | 6 +- .../plugins/datasource/mysql/datasource.ts | 10 +- public/app/plugins/datasource/mysql/module.ts | 4 + .../datasource/mysql/partials/config.html | 27 +++ 8 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 pkg/tsdb/mysql/mysql.go create mode 100644 public/app/plugins/datasource/mysql/partials/config.html diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 11e70dccc0d..5c3dd19bd87 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -42,6 +42,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro } var dsMap = map[string]interface{}{ + "id": ds.Id, "type": ds.Type, "name": ds.Name, "url": url, diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 9194637af15..9a92a27f8fe 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" @@ -18,6 +19,20 @@ import ( func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response { timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To) + if len(reqDto.Queries) == 0 { + return ApiError(400, "No queries found in query", nil) + } + + dsId, err := reqDto.Queries[0].Get("datasourceId").Int64() + if err != nil { + return ApiError(400, "Query missing datasourceId", nil) + } + + dsQuery := models.GetDataSourceByIdQuery{Id: dsId} + if err := bus.Dispatch(&dsQuery); err != nil { + return ApiError(500, "failed to fetch data source", err) + } + request := &tsdb.Request{TimeRange: timeRange} for _, query := range reqDto.Queries { @@ -26,10 +41,7 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response { MaxDataPoints: query.Get("maxDataPoints").MustInt64(100), IntervalMs: query.Get("intervalMs").MustInt64(1000), Model: query, - DataSource: &models.DataSource{ - Name: "Grafana TestDataDB", - Type: "grafana-testdata-datasource", - }, + DataSource: dsQuery.Result, }) } diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 46b1d5f2a95..7e8a6061a3f 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -22,6 +22,7 @@ import ( _ "github.com/grafana/grafana/pkg/tsdb/graphite" _ "github.com/grafana/grafana/pkg/tsdb/influxdb" _ "github.com/grafana/grafana/pkg/tsdb/mqe" + _ "github.com/grafana/grafana/pkg/tsdb/mysql" _ "github.com/grafana/grafana/pkg/tsdb/opentsdb" _ "github.com/grafana/grafana/pkg/tsdb/prometheus" _ "github.com/grafana/grafana/pkg/tsdb/testdata" diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go new file mode 100644 index 00000000000..42bf80e578b --- /dev/null +++ b/pkg/tsdb/mysql/mysql.go @@ -0,0 +1,160 @@ +package mysql + +import ( + "context" + "fmt" + "strings" + "sync" + + "github.com/go-xorm/core" + "github.com/go-xorm/xorm" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/tsdb" +) + +type MysqlExecutor struct { + *models.DataSource + engine *xorm.Engine + log log.Logger +} + +type engineCacheType struct { + cache map[int64]*xorm.Engine + versions map[int64]int + sync.Mutex +} + +var engineCache = engineCacheType{ + cache: make(map[int64]*xorm.Engine), + versions: make(map[int64]int), +} + +func NewMysqlExecutor(datasource *models.DataSource) (tsdb.Executor, error) { + engine, err := getEngineFor(datasource) + if err != nil { + return nil, err + } + + return &MysqlExecutor{ + log: log.New("tsdb.mysql"), + engine: engine, + }, nil +} + +func getEngineFor(ds *models.DataSource) (*xorm.Engine, error) { + engineCache.Lock() + defer engineCache.Unlock() + + if engine, present := engineCache.cache[ds.Id]; present { + if version, _ := engineCache.versions[ds.Id]; version == ds.Version { + return engine, nil + } + } + + cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8mb4", ds.User, ds.Password, "tcp", ds.Url, ds.Database) + engine, err := xorm.NewEngine("mysql", cnnstr) + engine.SetMaxConns(10) + engine.SetMaxIdleConns(10) + if err != nil { + return nil, err + } + + engineCache.cache[ds.Id] = engine + return engine, nil +} + +func init() { + tsdb.RegisterExecutor("graphite", NewMysqlExecutor) +} + +func (e *MysqlExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult { + result := &tsdb.BatchResult{} + + session := engine.NewSession() + defer session.Close() + + db := session.DB() + result, err := getData(db, &req) + + if err != nil { + return + } +} + +func getData(db *core.DB, req *sqlDataRequest) (interface{}, error) { + queries := strings.Split(req.Query, ";") + + data := dataStruct{} + data.Results = make([]resultsStruct, 1) + data.Results[0].Series = make([]seriesStruct, 0) + + for i := range queries { + if queries[i] == "" { + continue + } + + rows, err := db.Query(queries[i]) + if err != nil { + return nil, err + } + defer rows.Close() + + name := fmt.Sprintf("table_%d", i+1) + series, err := arrangeResult(rows, name) + if err != nil { + return nil, err + } + data.Results[0].Series = append(data.Results[0].Series, series.(seriesStruct)) + } + + return data, nil +} + +func arrangeResult(rows *core.Rows, name string) (interface{}, error) { + columnNames, err := rows.Columns() + + series := seriesStruct{} + series.Columns = columnNames + series.Name = name + + for rows.Next() { + columnValues := make([]interface{}, len(columnNames)) + + err = rows.ScanSlice(&columnValues) + if err != nil { + return nil, err + } + + // bytes -> string + for i := range columnValues { + switch columnValues[i].(type) { + case []byte: + columnValues[i] = fmt.Sprintf("%s", columnValues[i]) + } + } + + series.Values = append(series.Values, columnValues) + } + + return series, err +} + +type sqlDataRequest struct { + Query string `json:"query"` + Body []byte `json:"-"` +} + +type seriesStruct struct { + Columns []string `json:"columns"` + Name string `json:"name"` + Values [][]interface{} `json:"values"` +} + +type resultsStruct struct { + Series []seriesStruct `json:"series"` +} + +type dataStruct struct { + Results []resultsStruct `json:"results"` +} diff --git a/public/app/plugins/app/testdata/datasource/datasource.ts b/public/app/plugins/app/testdata/datasource/datasource.ts index e0846d99ab6..90ae9e3aa47 100644 --- a/public/app/plugins/app/testdata/datasource/datasource.ts +++ b/public/app/plugins/app/testdata/datasource/datasource.ts @@ -4,9 +4,12 @@ import _ from 'lodash'; import angular from 'angular'; class TestDataDatasource { + id: any; /** @ngInject */ - constructor(private backendSrv, private $q) {} + constructor(instanceSettings, private backendSrv, private $q) { + this.id = instanceSettings.id; + } query(options) { var queries = _.filter(options.targets, item => { @@ -19,6 +22,7 @@ class TestDataDatasource { maxDataPoints: options.maxDataPoints, stringInput: item.stringInput, jsonInput: angular.fromJson(item.jsonInput), + datasourceId: this.id, }; }); diff --git a/public/app/plugins/datasource/mysql/datasource.ts b/public/app/plugins/datasource/mysql/datasource.ts index 385ebee43f3..33c42cee8ab 100644 --- a/public/app/plugins/datasource/mysql/datasource.ts +++ b/public/app/plugins/datasource/mysql/datasource.ts @@ -3,23 +3,25 @@ import _ from 'lodash'; export class MysqlDatasource { + id: any; + name: any; /** @ngInject */ - constructor(private instanceSettings, private backendSrv) { + constructor(instanceSettings, private backendSrv) { + this.name = instanceSettings.name; + this.id = instanceSettings.id; } query(options) { - console.log('test'); - console.log(this.instanceSettings); return this.backendSrv.post('/api/tsdb/query', { from: options.range.from.valueOf().toString(), to: options.range.to.valueOf().toString(), queries: [ { "refId": "A", - "scenarioId": "random_walk", "intervalMs": options.intervalMs, "maxDataPoints": options.maxDataPoints, + "datasourceId": this.id, } ] }).then(res => { diff --git a/public/app/plugins/datasource/mysql/module.ts b/public/app/plugins/datasource/mysql/module.ts index 40136e6235d..af7f525a69d 100644 --- a/public/app/plugins/datasource/mysql/module.ts +++ b/public/app/plugins/datasource/mysql/module.ts @@ -8,6 +8,10 @@ class MysqlQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; } +class InfluxConfigCtrl { + static templateUrl = 'partials/config.html'; +} + export { MysqlDatasource, MysqlDatasource as Datasource, diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html new file mode 100644 index 00000000000..60ea72a1ba7 --- /dev/null +++ b/public/app/plugins/datasource/mysql/partials/config.html @@ -0,0 +1,27 @@ + +

MySQL Connection

+ +
+ +
+ Host + +
+ +
+ Database + +
+ +
+
+ User + +
+
+ Password + +
+
+
+