diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index 959f12f708f..a64a6af2ee9 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -51,11 +51,16 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, return result.WithError(err) } - if setting.Env == setting.DEV { - glog.Debug("Influxdb query", "raw query", query) + rawQuery, err := e.QueryBuilder.Build(query, context) + if err != nil { + return result.WithError(err) } - req, err := e.createRequest(query) + if setting.Env == setting.DEV { + glog.Debug("Influxdb query", "raw query", rawQuery) + } + + req, err := e.createRequest(rawQuery) if err != nil { return result.WithError(err) } @@ -80,28 +85,23 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, } result.QueryResults = make(map[string]*tsdb.QueryResult) - result.QueryResults["A"] = e.ResponseParser.Parse(&response) + result.QueryResults["A"] = e.ResponseParser.Parse(&response, query) return result } -func (e *InfluxDBExecutor) getQuery(queries tsdb.QuerySlice, context *tsdb.QueryContext) (string, error) { +func (e *InfluxDBExecutor) getQuery(queries tsdb.QuerySlice, context *tsdb.QueryContext) (*Query, error) { for _, v := range queries { query, err := e.QueryParser.Parse(v.Model, e.DataSourceInfo) if err != nil { - return "", err + return nil, err } - rawQuery, err := e.QueryBuilder.Build(query, context) - if err != nil { - return "", err - } - - return rawQuery, nil + return query, nil } - return "", fmt.Errorf("query request contains no queries") + return nil, fmt.Errorf("query request contains no queries") } func (e *InfluxDBExecutor) createRequest(query string) (*http.Request, error) { diff --git a/pkg/tsdb/influxdb/model_parser.go b/pkg/tsdb/influxdb/model_parser.go index af727771f1f..2db7a78ed75 100644 --- a/pkg/tsdb/influxdb/model_parser.go +++ b/pkg/tsdb/influxdb/model_parser.go @@ -12,6 +12,7 @@ type InfluxdbQueryParser struct{} func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSourceInfo) (*Query, error) { policy := model.Get("policy").MustString("default") rawQuery := model.Get("query").MustString("") + alias := model.Get("alias").MustString("") measurement := model.Get("measurement").MustString("") @@ -52,6 +53,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSo Selects: selects, RawQuery: rawQuery, Interval: interval, + Alias: alias, }, nil } diff --git a/pkg/tsdb/influxdb/model_parser_test.go b/pkg/tsdb/influxdb/model_parser_test.go index 8f43cc7d70f..eae6e163cc0 100644 --- a/pkg/tsdb/influxdb/model_parser_test.go +++ b/pkg/tsdb/influxdb/model_parser_test.go @@ -90,6 +90,7 @@ func TestInfluxdbQueryParser(t *testing.T) { } ] ], + "alias": "serie alias", "tags": [ { "key": "datacenter", @@ -115,6 +116,7 @@ func TestInfluxdbQueryParser(t *testing.T) { So(len(res.Selects), ShouldEqual, 3) So(len(res.Tags), ShouldEqual, 2) So(res.Interval, ShouldEqual, ">20s") + So(res.Alias, ShouldEqual, "serie alias") }) Convey("can part raw query json model", func() { diff --git a/pkg/tsdb/influxdb/models.go b/pkg/tsdb/influxdb/models.go index 7903616ff22..0dcecd20773 100644 --- a/pkg/tsdb/influxdb/models.go +++ b/pkg/tsdb/influxdb/models.go @@ -8,6 +8,7 @@ type Query struct { GroupBy []*QueryPart Selects []*Select RawQuery string + Alias string Interval string } diff --git a/pkg/tsdb/influxdb/response_parser.go b/pkg/tsdb/influxdb/response_parser.go index 44afa910b27..8e9fa0022c3 100644 --- a/pkg/tsdb/influxdb/response_parser.go +++ b/pkg/tsdb/influxdb/response_parser.go @@ -3,6 +3,8 @@ package influxdb import ( "encoding/json" "fmt" + "regexp" + "strconv" "strings" "github.com/grafana/grafana/pkg/tsdb" @@ -11,17 +13,25 @@ import ( type ResponseParser struct{} -func (rp *ResponseParser) Parse(response *Response) *tsdb.QueryResult { +var ( + legendFormat *regexp.Regexp +) + +func init() { + legendFormat = regexp.MustCompile(`\[\[(\w+?)*\]\]*|\$\s*(\w+?)*`) +} + +func (rp *ResponseParser) Parse(response *Response, query *Query) *tsdb.QueryResult { queryRes := tsdb.NewQueryResult() for _, result := range response.Results { - queryRes.Series = append(queryRes.Series, rp.transformRows(result.Series, queryRes)...) + queryRes.Series = append(queryRes.Series, rp.transformRows(result.Series, queryRes, query)...) } return queryRes } -func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResult) tsdb.TimeSeriesSlice { +func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResult, query *Query) tsdb.TimeSeriesSlice { var result tsdb.TimeSeriesSlice for _, row := range rows { @@ -38,7 +48,7 @@ func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResul } } result = append(result, &tsdb.TimeSeries{ - Name: rp.formatSerieName(row, column), + Name: rp.formatSerieName(row, column, query), Points: points, }) } @@ -47,7 +57,48 @@ func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResul return result } -func (rp *ResponseParser) formatSerieName(row Row, column string) string { +func (rp *ResponseParser) formatSerieName(row Row, column string, query *Query) string { + if query.Alias == "" { + return rp.buildSerieNameFromQuery(row, column) + } + + nameSegment := strings.Split(row.Name, ".") + + result := legendFormat.ReplaceAllFunc([]byte(query.Alias), func(in []byte) []byte { + aliasFormat := string(in) + aliasFormat = strings.Replace(aliasFormat, "[[", "", 1) + aliasFormat = strings.Replace(aliasFormat, "]]", "", 1) + aliasFormat = strings.Replace(aliasFormat, "$", "", 1) + + if aliasFormat == "m" || aliasFormat == "measurement" { + return []byte(query.Measurement) + } + if aliasFormat == "col" { + return []byte(column) + } + + pos, err := strconv.Atoi(aliasFormat) + if err == nil && len(nameSegment) >= pos { + return []byte(nameSegment[pos]) + } + + if !strings.HasPrefix(aliasFormat, "tag_") { + return in + } + + tagKey := strings.Replace(aliasFormat, "tag_", "", 1) + tagValue, exist := row.Tags[tagKey] + if exist { + return []byte(tagValue) + } + + return in + }) + + return string(result) +} + +func (rp *ResponseParser) buildSerieNameFromQuery(row Row, column string) string { var tags []string for k, v := range row.Tags { diff --git a/pkg/tsdb/influxdb/response_parser_test.go b/pkg/tsdb/influxdb/response_parser_test.go index b45f98a1fff..67667f565d5 100644 --- a/pkg/tsdb/influxdb/response_parser_test.go +++ b/pkg/tsdb/influxdb/response_parser_test.go @@ -4,56 +4,161 @@ import ( "encoding/json" "testing" + "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) func TestInfluxdbResponseParser(t *testing.T) { Convey("Influxdb response parser", t, func() { + Convey("Response parser", func() { + parser := &ResponseParser{} - parser := &ResponseParser{} + setting.NewConfigContext(&setting.CommandLineArgs{ + HomePath: "../../../", + }) - response := &Response{ - Results: []Result{ - Result{ - Series: []Row{ - { - Name: "cpu", - Columns: []string{"time", "mean", "sum"}, - Tags: map[string]string{"datacenter": "America"}, - Values: [][]interface{}{ - {json.Number("111"), json.Number("222"), json.Number("333")}, - {json.Number("111"), json.Number("222"), json.Number("333")}, - {json.Number("111"), json.Number("null"), json.Number("333")}, + response := &Response{ + Results: []Result{ + Result{ + Series: []Row{ + { + Name: "cpu", + Columns: []string{"time", "mean", "sum"}, + Tags: map[string]string{"datacenter": "America"}, + Values: [][]interface{}{ + {json.Number("111"), json.Number("222"), json.Number("333")}, + {json.Number("111"), json.Number("222"), json.Number("333")}, + {json.Number("111"), json.Number("null"), json.Number("333")}, + }, }, }, }, }, - }, - } + } - result := parser.Parse(response) + query := &Query{} - Convey("can parse all series", func() { - So(len(result.Series), ShouldEqual, 2) + result := parser.Parse(response, query) + + Convey("can parse all series", func() { + So(len(result.Series), ShouldEqual, 2) + }) + + Convey("can parse all points", func() { + So(len(result.Series[0].Points), ShouldEqual, 3) + So(len(result.Series[1].Points), ShouldEqual, 3) + }) + + Convey("can parse multi row result", func() { + So(result.Series[0].Points[1][0].Float64, ShouldEqual, float64(222)) + So(result.Series[1].Points[1][0].Float64, ShouldEqual, float64(333)) + }) + + Convey("can parse null points", func() { + So(result.Series[0].Points[2][0].Valid, ShouldBeFalse) + }) + + Convey("can format serie names", func() { + So(result.Series[0].Name, ShouldEqual, "cpu.mean { datacenter: America }") + So(result.Series[1].Name, ShouldEqual, "cpu.sum { datacenter: America }") + }) }) - Convey("can parse all points", func() { - So(len(result.Series[0].Points), ShouldEqual, 3) - So(len(result.Series[1].Points), ShouldEqual, 3) - }) + Convey("Response parser with alias", func() { + parser := &ResponseParser{} - Convey("can parse multi row result", func() { - So(result.Series[0].Points[1][0].Float64, ShouldEqual, float64(222)) - So(result.Series[1].Points[1][0].Float64, ShouldEqual, float64(333)) - }) + response := &Response{ + Results: []Result{ + Result{ + Series: []Row{ + { + Name: "cpu.upc", + Columns: []string{"time", "mean", "sum"}, + Tags: map[string]string{"datacenter": "America"}, + Values: [][]interface{}{ + {json.Number("111"), json.Number("222"), json.Number("333")}, + }, + }, + }, + }, + }, + } - Convey("can parse null points", func() { - So(result.Series[0].Points[2][0].Valid, ShouldBeFalse) - }) + Convey("$ alias", func() { + Convey("simple alias", func() { + query := &Query{Alias: "serie alias"} + result := parser.Parse(response, query) - Convey("can format serie names", func() { - So(result.Series[0].Name, ShouldEqual, "cpu.mean { datacenter: America }") - So(result.Series[1].Name, ShouldEqual, "cpu.sum { datacenter: America }") + So(result.Series[0].Name, ShouldEqual, "serie alias") + }) + + Convey("measurement alias", func() { + query := &Query{Alias: "alias $m $measurement", Measurement: "10m"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias 10m 10m") + }) + + Convey("column alias", func() { + query := &Query{Alias: "alias $col", Measurement: "10m"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias mean") + So(result.Series[1].Name, ShouldEqual, "alias sum") + }) + + Convey("tag alias", func() { + query := &Query{Alias: "alias $tag_datacenter"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias America") + }) + + Convey("segment alias", func() { + query := &Query{Alias: "alias $1"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias upc") + }) + + Convey("segment position out of bound", func() { + query := &Query{Alias: "alias $5"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias $5") + }) + }) + + Convey("[[]] alias", func() { + Convey("simple alias", func() { + query := &Query{Alias: "serie alias"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "serie alias") + }) + + Convey("measurement alias", func() { + query := &Query{Alias: "alias [[m]] [[measurement]]", Measurement: "10m"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias 10m 10m") + }) + + Convey("column alias", func() { + query := &Query{Alias: "alias [[col]]", Measurement: "10m"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias mean") + So(result.Series[1].Name, ShouldEqual, "alias sum") + }) + + Convey("tag alias", func() { + query := &Query{Alias: "alias [[tag_datacenter]]"} + result := parser.Parse(response, query) + + So(result.Series[0].Name, ShouldEqual, "alias America") + }) + }) }) }) } diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index ec5b098cbaf..21e1dfdef24 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -24,12 +24,14 @@ func NewPrometheusExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor { } var ( - plog log.Logger + plog log.Logger + legendFormat *regexp.Regexp ) func init() { plog = log.New("tsdb.prometheus") tsdb.RegisterExecutor("prometheus", NewPrometheusExecutor) + legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) } func (e *PrometheusExecutor) getClient() (prometheus.QueryAPI, error) { @@ -79,13 +81,11 @@ func (e *PrometheusExecutor) Execute(ctx context.Context, queries tsdb.QuerySlic } func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string { - reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) - if query.LegendFormat == "" { return metric.String() } - result := reg.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { + result := legendFormat.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { labelName := strings.Replace(string(in), "{{", "", 1) labelName = strings.Replace(labelName, "}}", "", 1) labelName = strings.TrimSpace(labelName) diff --git a/public/app/plugins/datasource/influxdb/partials/query.options.html b/public/app/plugins/datasource/influxdb/partials/query.options.html index feb8600fcc9..2dd6b4d376b 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.options.html +++ b/public/app/plugins/datasource/influxdb/partials/query.options.html @@ -4,16 +4,15 @@
Group by time interval - - + + Set a low limit by having a greater sign: example: >60s +
-