mirror of https://github.com/grafana/grafana
parent
1ecdf34938
commit
8f90c6115d
@ -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"` |
||||
} |
@ -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> |
||||
|
Loading…
Reference in new issue