|
@@ -1,81 +1,103 @@
|
|
|
package elasticsearch
|
|
package elasticsearch
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "bytes"
|
|
|
|
|
+ "encoding/json"
|
|
|
"errors"
|
|
"errors"
|
|
|
|
|
+ "fmt"
|
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
|
|
|
+ "github.com/grafana/grafana/pkg/models"
|
|
|
|
|
+ "github.com/grafana/grafana/pkg/tsdb"
|
|
|
"strconv"
|
|
"strconv"
|
|
|
|
|
+ "strings"
|
|
|
|
|
+ "time"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
var rangeFilterSetting = RangeFilterSetting{Gte: "$timeFrom",
|
|
var rangeFilterSetting = RangeFilterSetting{Gte: "$timeFrom",
|
|
|
- Lte: "$timeTo",
|
|
|
|
|
|
|
+ Lte: "$timeTo",
|
|
|
Format: "epoch_millis"}
|
|
Format: "epoch_millis"}
|
|
|
|
|
|
|
|
-type QueryBuilder struct {
|
|
|
|
|
- TimeField string
|
|
|
|
|
- RawQuery string
|
|
|
|
|
- BucketAggs []interface{}
|
|
|
|
|
- Metrics []interface{}
|
|
|
|
|
- Alias string
|
|
|
|
|
|
|
+type Query struct {
|
|
|
|
|
+ TimeField string `json:"timeField"`
|
|
|
|
|
+ RawQuery string `json:"query"`
|
|
|
|
|
+ BucketAggs []interface{} `json:"bucketAggs"`
|
|
|
|
|
+ Metrics []interface{} `json:"metrics"`
|
|
|
|
|
+ Alias string `json:"Alias"`
|
|
|
|
|
+ Interval time.Duration
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *QueryBuilder) Build() (Query, error) {
|
|
|
|
|
- var err error
|
|
|
|
|
- var res Query
|
|
|
|
|
- res.Query = make(map[string]interface{})
|
|
|
|
|
- res.Size = 0
|
|
|
|
|
|
|
+func (q *Query) Build(queryContext *tsdb.TsdbQuery, dsInfo *models.DataSource) (string, error) {
|
|
|
|
|
+ var req Request
|
|
|
|
|
+ payload := bytes.Buffer{}
|
|
|
|
|
|
|
|
|
|
+ req.Size = 0
|
|
|
|
|
+ q.renderReqQuery(&req)
|
|
|
|
|
+
|
|
|
|
|
+ // handle document query
|
|
|
|
|
+ if q.isRawDocumentQuery() {
|
|
|
|
|
+ return "", errors.New("alert not support Raw_Document")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ err := q.parseAggs(&req)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return res, err
|
|
|
|
|
|
|
+ return "", err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- boolQuery := BoolQuery{}
|
|
|
|
|
- boolQuery.Filter = append(boolQuery.Filter, newRangeFilter(b.TimeField, rangeFilterSetting))
|
|
|
|
|
- boolQuery.Filter = append(boolQuery.Filter, newQueryStringFilter(true, b.RawQuery))
|
|
|
|
|
- res.Query["bool"] = boolQuery
|
|
|
|
|
|
|
+ reqBytes, err := json.Marshal(req)
|
|
|
|
|
+ reqHeader := getRequestHeader(queryContext.TimeRange, dsInfo)
|
|
|
|
|
+ payload.WriteString(reqHeader.String() + "\n")
|
|
|
|
|
+ payload.WriteString(string(reqBytes) + "\n")
|
|
|
|
|
+ return q.renderTemplate(payload.String(), queryContext)
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // handle document query
|
|
|
|
|
- if len(b.BucketAggs) == 0 {
|
|
|
|
|
- if len(b.Metrics) > 0 {
|
|
|
|
|
- metric := simplejson.NewFromAny(b.Metrics[0])
|
|
|
|
|
|
|
+func (q *Query) isRawDocumentQuery() bool {
|
|
|
|
|
+ if len(q.BucketAggs) == 0 {
|
|
|
|
|
+ if len(q.Metrics) > 0 {
|
|
|
|
|
+ metric := simplejson.NewFromAny(q.Metrics[0])
|
|
|
if metric.Get("type").MustString("") == "raw_document" {
|
|
if metric.Get("type").MustString("") == "raw_document" {
|
|
|
- return res, errors.New("alert not support Raw_Document")
|
|
|
|
|
|
|
+ return true
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- aggs, err := b.parseAggs(b.BucketAggs, b.Metrics)
|
|
|
|
|
- res.Aggs = aggs["aggs"].(Aggs)
|
|
|
|
|
|
|
+ return false
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- return res, err
|
|
|
|
|
|
|
+func (q *Query) renderReqQuery(req *Request) {
|
|
|
|
|
+ req.Query = make(map[string]interface{})
|
|
|
|
|
+ boolQuery := BoolQuery{}
|
|
|
|
|
+ boolQuery.Filter = append(boolQuery.Filter, newRangeFilter(q.TimeField, rangeFilterSetting))
|
|
|
|
|
+ boolQuery.Filter = append(boolQuery.Filter, newQueryStringFilter(true, q.RawQuery))
|
|
|
|
|
+ req.Query["bool"] = boolQuery
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *QueryBuilder) parseAggs(bucketAggs []interface{}, metrics []interface{}) (Aggs, error) {
|
|
|
|
|
- query := make(Aggs)
|
|
|
|
|
- nestedAggs := query
|
|
|
|
|
- for _, aggRaw := range bucketAggs {
|
|
|
|
|
|
|
+func (q *Query) parseAggs(req *Request) error {
|
|
|
|
|
+ aggs := make(Aggs)
|
|
|
|
|
+ nestedAggs := aggs
|
|
|
|
|
+ for _, aggRaw := range q.BucketAggs {
|
|
|
esAggs := make(Aggs)
|
|
esAggs := make(Aggs)
|
|
|
aggJson := simplejson.NewFromAny(aggRaw)
|
|
aggJson := simplejson.NewFromAny(aggRaw)
|
|
|
aggType, err := aggJson.Get("type").String()
|
|
aggType, err := aggJson.Get("type").String()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
id, err := aggJson.Get("id").String()
|
|
id, err := aggJson.Get("id").String()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
switch aggType {
|
|
switch aggType {
|
|
|
case "date_histogram":
|
|
case "date_histogram":
|
|
|
- esAggs["date_histogram"] = b.getDateHistogramAgg(aggJson)
|
|
|
|
|
|
|
+ esAggs["date_histogram"] = q.getDateHistogramAgg(aggJson)
|
|
|
case "histogram":
|
|
case "histogram":
|
|
|
- esAggs["histogram"] = b.getHistogramAgg(aggJson)
|
|
|
|
|
|
|
+ esAggs["histogram"] = q.getHistogramAgg(aggJson)
|
|
|
case "filters":
|
|
case "filters":
|
|
|
- esAggs["filters"] = b.getFilters(aggJson)
|
|
|
|
|
|
|
+ esAggs["filters"] = q.getFilters(aggJson)
|
|
|
case "terms":
|
|
case "terms":
|
|
|
- terms := b.getTerms(aggJson)
|
|
|
|
|
|
|
+ terms := q.getTerms(aggJson)
|
|
|
esAggs["terms"] = terms.Terms
|
|
esAggs["terms"] = terms.Terms
|
|
|
esAggs["aggs"] = terms.Aggs
|
|
esAggs["aggs"] = terms.Aggs
|
|
|
case "geohash_grid":
|
|
case "geohash_grid":
|
|
|
- return nil, errors.New("alert not support Geo_Hash_Grid")
|
|
|
|
|
|
|
+ return errors.New("alert not support Geo_Hash_Grid")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if _, ok := nestedAggs["aggs"]; !ok {
|
|
if _, ok := nestedAggs["aggs"]; !ok {
|
|
@@ -90,40 +112,51 @@ func (b *QueryBuilder) parseAggs(bucketAggs []interface{}, metrics []interface{}
|
|
|
}
|
|
}
|
|
|
nestedAggs["aggs"] = make(Aggs)
|
|
nestedAggs["aggs"] = make(Aggs)
|
|
|
|
|
|
|
|
- for _, metricRaw := range metrics {
|
|
|
|
|
|
|
+ for _, metricRaw := range q.Metrics {
|
|
|
metric := make(Metric)
|
|
metric := make(Metric)
|
|
|
metricJson := simplejson.NewFromAny(metricRaw)
|
|
metricJson := simplejson.NewFromAny(metricRaw)
|
|
|
|
|
|
|
|
id, err := metricJson.Get("id").String()
|
|
id, err := metricJson.Get("id").String()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
metricType, err := metricJson.Get("type").String()
|
|
metricType, err := metricJson.Get("type").String()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
if metricType == "count" {
|
|
if metricType == "count" {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // todo support pipeline Agg
|
|
|
|
|
|
|
+ settings := metricJson.Get("settings").MustMap(map[string]interface{}{})
|
|
|
|
|
+
|
|
|
|
|
+ if isPipelineAgg(metricType) {
|
|
|
|
|
+ pipelineAgg := metricJson.Get("pipelineAgg").MustString("")
|
|
|
|
|
+ if _, err := strconv.Atoi(pipelineAgg); err == nil {
|
|
|
|
|
+ settings["buckets_path"] = pipelineAgg
|
|
|
|
|
+ } else {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+ settings["field"] = metricJson.Get("field").MustString()
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- settings := metricJson.Get("settings").MustMap()
|
|
|
|
|
- settings["field"] = metricJson.Get("field").MustString()
|
|
|
|
|
metric[metricType] = settings
|
|
metric[metricType] = settings
|
|
|
nestedAggs["aggs"].(Aggs)[id] = metric
|
|
nestedAggs["aggs"].(Aggs)[id] = metric
|
|
|
}
|
|
}
|
|
|
- return query, nil
|
|
|
|
|
|
|
+ req.Aggs = aggs["aggs"].(Aggs)
|
|
|
|
|
+ return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *QueryBuilder) getDateHistogramAgg(model *simplejson.Json) DateHistogramAgg {
|
|
|
|
|
|
|
+func (q *Query) getDateHistogramAgg(model *simplejson.Json) *DateHistogramAgg {
|
|
|
agg := &DateHistogramAgg{}
|
|
agg := &DateHistogramAgg{}
|
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
|
interval, err := settings.Get("interval").String()
|
|
interval, err := settings.Get("interval").String()
|
|
|
if err == nil {
|
|
if err == nil {
|
|
|
agg.Interval = interval
|
|
agg.Interval = interval
|
|
|
}
|
|
}
|
|
|
- agg.Field = b.TimeField
|
|
|
|
|
|
|
+ agg.Field = q.TimeField
|
|
|
agg.MinDocCount = settings.Get("min_doc_count").MustInt(0)
|
|
agg.MinDocCount = settings.Get("min_doc_count").MustInt(0)
|
|
|
agg.ExtendedBounds = ExtendedBounds{"$timeFrom", "$timeTo"}
|
|
agg.ExtendedBounds = ExtendedBounds{"$timeFrom", "$timeTo"}
|
|
|
agg.Format = "epoch_millis"
|
|
agg.Format = "epoch_millis"
|
|
@@ -136,10 +169,10 @@ func (b *QueryBuilder) getDateHistogramAgg(model *simplejson.Json) DateHistogram
|
|
|
if err == nil {
|
|
if err == nil {
|
|
|
agg.Missing = missing
|
|
agg.Missing = missing
|
|
|
}
|
|
}
|
|
|
- return *agg
|
|
|
|
|
|
|
+ return agg
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *QueryBuilder) getHistogramAgg(model *simplejson.Json) HistogramAgg {
|
|
|
|
|
|
|
+func (q *Query) getHistogramAgg(model *simplejson.Json) *HistogramAgg {
|
|
|
agg := &HistogramAgg{}
|
|
agg := &HistogramAgg{}
|
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
|
interval, err := settings.Get("interval").String()
|
|
interval, err := settings.Get("interval").String()
|
|
@@ -155,10 +188,10 @@ func (b *QueryBuilder) getHistogramAgg(model *simplejson.Json) HistogramAgg {
|
|
|
if err == nil {
|
|
if err == nil {
|
|
|
agg.Missing = missing
|
|
agg.Missing = missing
|
|
|
}
|
|
}
|
|
|
- return *agg
|
|
|
|
|
|
|
+ return agg
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *QueryBuilder) getFilters(model *simplejson.Json) FiltersAgg {
|
|
|
|
|
|
|
+func (q *Query) getFilters(model *simplejson.Json) *FiltersAgg {
|
|
|
agg := &FiltersAgg{}
|
|
agg := &FiltersAgg{}
|
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
|
for filter := range settings.Get("filters").MustArray() {
|
|
for filter := range settings.Get("filters").MustArray() {
|
|
@@ -170,15 +203,15 @@ func (b *QueryBuilder) getFilters(model *simplejson.Json) FiltersAgg {
|
|
|
}
|
|
}
|
|
|
agg.Filter[label] = newQueryStringFilter(true, query)
|
|
agg.Filter[label] = newQueryStringFilter(true, query)
|
|
|
}
|
|
}
|
|
|
- return *agg
|
|
|
|
|
|
|
+ return agg
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *QueryBuilder) getTerms(model *simplejson.Json) TermsAgg {
|
|
|
|
|
|
|
+func (q *Query) getTerms(model *simplejson.Json) *TermsAgg {
|
|
|
agg := &TermsAgg{Aggs: make(Aggs)}
|
|
agg := &TermsAgg{Aggs: make(Aggs)}
|
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
settings := simplejson.NewFromAny(model.Get("settings").Interface())
|
|
|
agg.Terms.Field = model.Get("field").MustString()
|
|
agg.Terms.Field = model.Get("field").MustString()
|
|
|
if settings == nil {
|
|
if settings == nil {
|
|
|
- return *agg
|
|
|
|
|
|
|
+ return agg
|
|
|
}
|
|
}
|
|
|
sizeStr := settings.Get("size").MustString("")
|
|
sizeStr := settings.Get("size").MustString("")
|
|
|
size, err := strconv.Atoi(sizeStr)
|
|
size, err := strconv.Atoi(sizeStr)
|
|
@@ -186,17 +219,25 @@ func (b *QueryBuilder) getTerms(model *simplejson.Json) TermsAgg {
|
|
|
size = 500
|
|
size = 500
|
|
|
}
|
|
}
|
|
|
agg.Terms.Size = size
|
|
agg.Terms.Size = size
|
|
|
- orderBy := settings.Get("orderBy").MustString("")
|
|
|
|
|
- if orderBy != "" {
|
|
|
|
|
|
|
+ orderBy, err := settings.Get("orderBy").String()
|
|
|
|
|
+ if err == nil {
|
|
|
agg.Terms.Order = make(map[string]interface{})
|
|
agg.Terms.Order = make(map[string]interface{})
|
|
|
agg.Terms.Order[orderBy] = settings.Get("order").MustString("")
|
|
agg.Terms.Order[orderBy] = settings.Get("order").MustString("")
|
|
|
- // if orderBy is a int, means this fields is metric result value
|
|
|
|
|
- // TODO set subAggs
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- minDocCount, err := settings.Get("min_doc_count").Int()
|
|
|
|
|
- if err == nil {
|
|
|
|
|
- agg.Terms.MinDocCount = minDocCount
|
|
|
|
|
|
|
+ if _, err := strconv.Atoi(orderBy); err != nil {
|
|
|
|
|
+ for _, metricI := range q.Metrics {
|
|
|
|
|
+ metric := simplejson.NewFromAny(metricI)
|
|
|
|
|
+ metricId := metric.Get("id").MustString()
|
|
|
|
|
+ if metricId == orderBy {
|
|
|
|
|
+ subAggs := make(Aggs)
|
|
|
|
|
+ metricField := metric.Get("field").MustString()
|
|
|
|
|
+ metricType := metric.Get("type").MustString()
|
|
|
|
|
+ subAggs[metricType] = map[string]string{"field": metricField}
|
|
|
|
|
+ agg.Aggs = make(Aggs)
|
|
|
|
|
+ agg.Aggs[metricId] = subAggs
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
missing, err := settings.Get("missing").String()
|
|
missing, err := settings.Get("missing").String()
|
|
@@ -204,5 +245,16 @@ func (b *QueryBuilder) getTerms(model *simplejson.Json) TermsAgg {
|
|
|
agg.Terms.Missing = missing
|
|
agg.Terms.Missing = missing
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return *agg
|
|
|
|
|
|
|
+ return agg
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (q *Query) renderTemplate(payload string, queryContext *tsdb.TsdbQuery) (string, error) {
|
|
|
|
|
+ timeRange := queryContext.TimeRange
|
|
|
|
|
+ interval := intervalCalculator.Calculate(timeRange, q.Interval)
|
|
|
|
|
+ payload = strings.Replace(payload, "$timeFrom", fmt.Sprintf("%d", timeRange.GetFromAsMsEpoch()), -1)
|
|
|
|
|
+ payload = strings.Replace(payload, "$timeTo", fmt.Sprintf("%d", timeRange.GetToAsMsEpoch()), -1)
|
|
|
|
|
+ payload = strings.Replace(payload, "$interval", interval.Text, -1)
|
|
|
|
|
+ payload = strings.Replace(payload, "$__interval_ms", strconv.FormatInt(interval.Value.Nanoseconds()/int64(time.Millisecond), 10), -1)
|
|
|
|
|
+ payload = strings.Replace(payload, "$__interval", interval.Text, -1)
|
|
|
|
|
+ return payload, nil
|
|
|
}
|
|
}
|