소스 검색

mysql: progress on mysql data source

Torkel Ödegaard 8 년 전
부모
커밋
8f90c6115d

+ 1 - 0
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,

+ 16 - 4
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,
 		})
 	}
 

+ 1 - 0
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"

+ 160 - 0
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"`
+}

+ 5 - 1
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,
       };
     });
 

+ 6 - 4
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 => {

+ 4 - 0
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,

+ 27 - 0
public/app/plugins/datasource/mysql/partials/config.html

@@ -0,0 +1,27 @@
+
+<h3 class="page-heading">MySQL Connection</h3>
+
+<div class="gf-form-group">
+
+	<div class="gf-form max-width-30">
+		<span class="gf-form-label width-7">Host</span>
+		<input type="text" class="gf-form-input" ng-model='ctrl.current.url' placeholder="" required></input>
+	</div>
+
+	<div class="gf-form max-width-30">
+		<span class="gf-form-label width-7">Database</span>
+		<input type="text" class="gf-form-input" ng-model='ctrl.current.database' placeholder="" required></input>
+	</div>
+
+	<div class="gf-form-inline">
+		<div class="gf-form max-width-15">
+			<span class="gf-form-label width-7">User</span>
+			<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder=""></input>
+		</div>
+		<div class="gf-form max-width-15">
+			<span class="gf-form-label width-7">Password</span>
+			<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder=""></input>
+		</div>
+	</div>
+</div>
+