elasticsearch: support bucket script pipeline aggregations
This commit is contained in:
@@ -292,7 +292,7 @@ func (a *MetricAggregation) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// PipelineAggregation represents a metric aggregation
|
||||
type PipelineAggregation struct {
|
||||
BucketPath string
|
||||
BucketPath interface{}
|
||||
Settings map[string]interface{}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ type AggBuilder interface {
|
||||
Filters(key string, fn func(a *FiltersAggregation, b AggBuilder)) AggBuilder
|
||||
GeoHashGrid(key, field string, fn func(a *GeoHashGridAggregation, b AggBuilder)) AggBuilder
|
||||
Metric(key, metricType, field string, fn func(a *MetricAggregation)) AggBuilder
|
||||
Pipeline(key, pipelineType, bucketPath string, fn func(a *PipelineAggregation)) AggBuilder
|
||||
Pipeline(key, pipelineType string, bucketPath interface{}, fn func(a *PipelineAggregation)) AggBuilder
|
||||
Build() (AggArray, error)
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ func (b *aggBuilderImpl) Metric(key, metricType, field string, fn func(a *Metric
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *aggBuilderImpl) Pipeline(key, pipelineType, bucketPath string, fn func(a *PipelineAggregation)) AggBuilder {
|
||||
func (b *aggBuilderImpl) Pipeline(key, pipelineType string, bucketPath interface{}, fn func(a *PipelineAggregation)) AggBuilder {
|
||||
innerAgg := &PipelineAggregation{
|
||||
BucketPath: bucketPath,
|
||||
Settings: make(map[string]interface{}),
|
||||
|
||||
@@ -25,13 +25,14 @@ type BucketAgg struct {
|
||||
|
||||
// MetricAgg represents a metric aggregation of the time series query model of the datasource
|
||||
type MetricAgg struct {
|
||||
Field string `json:"field"`
|
||||
Hide bool `json:"hide"`
|
||||
ID string `json:"id"`
|
||||
PipelineAggregate string `json:"pipelineAgg"`
|
||||
Settings *simplejson.Json `json:"settings"`
|
||||
Meta *simplejson.Json `json:"meta"`
|
||||
Type string `json:"type"`
|
||||
Field string `json:"field"`
|
||||
Hide bool `json:"hide"`
|
||||
ID string `json:"id"`
|
||||
PipelineAggregate string `json:"pipelineAgg"`
|
||||
PipelineVariables map[string]string `json:"pipelineVariables"`
|
||||
Settings *simplejson.Json `json:"settings"`
|
||||
Meta *simplejson.Json `json:"meta"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
var metricAggType = map[string]string{
|
||||
@@ -45,6 +46,7 @@ var metricAggType = map[string]string{
|
||||
"cardinality": "Unique Count",
|
||||
"moving_avg": "Moving Average",
|
||||
"derivative": "Derivative",
|
||||
"bucket_script": "Bucket Script",
|
||||
"raw_document": "Raw Document",
|
||||
}
|
||||
|
||||
@@ -60,8 +62,13 @@ var extendedStats = map[string]string{
|
||||
}
|
||||
|
||||
var pipelineAggType = map[string]string{
|
||||
"moving_avg": "moving_avg",
|
||||
"derivative": "derivative",
|
||||
"moving_avg": "moving_avg",
|
||||
"derivative": "derivative",
|
||||
"bucket_script": "bucket_script",
|
||||
}
|
||||
|
||||
var pipelineAggWithMultipleBucketPathsType = map[string]string{
|
||||
"bucket_script": "bucket_script",
|
||||
}
|
||||
|
||||
func isPipelineAgg(metricType string) bool {
|
||||
@@ -71,6 +78,13 @@ func isPipelineAgg(metricType string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isPipelineAggWithMultipleBucketPaths(metricType string) bool {
|
||||
if _, ok := pipelineAggWithMultipleBucketPathsType[metricType]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func describeMetric(metricType, field string) string {
|
||||
text := metricAggType[metricType]
|
||||
if metricType == countType {
|
||||
|
||||
@@ -260,6 +260,7 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
|
||||
|
||||
newSeries.Tags["metric"] = metric.Type
|
||||
newSeries.Tags["field"] = metric.Field
|
||||
newSeries.Tags["metricId"] = metric.ID
|
||||
for _, v := range esAgg.Get("buckets").MustArray() {
|
||||
bucket := simplejson.NewFromAny(v)
|
||||
key := castToNullFloat(bucket.Get("key"))
|
||||
@@ -459,20 +460,42 @@ func (rp *responseParser) getSeriesName(series *tsdb.TimeSeries, target *Query,
|
||||
}
|
||||
// todo, if field and pipelineAgg
|
||||
if field != "" && isPipelineAgg(metricType) {
|
||||
found := false
|
||||
for _, metric := range target.Metrics {
|
||||
if metric.ID == field {
|
||||
metricName += " " + describeMetric(metric.Type, field)
|
||||
found = true
|
||||
if isPipelineAggWithMultipleBucketPaths(metricType) {
|
||||
metricID := ""
|
||||
if v, ok := series.Tags["metricId"]; ok {
|
||||
metricID = v
|
||||
}
|
||||
|
||||
for _, metric := range target.Metrics {
|
||||
if metric.ID == metricID {
|
||||
metricName = metric.Settings.Get("script").MustString()
|
||||
for name, pipelineAgg := range metric.PipelineVariables {
|
||||
for _, m := range target.Metrics {
|
||||
if m.ID == pipelineAgg {
|
||||
metricName = strings.Replace(metricName, "params."+name, describeMetric(m.Type, m.Field), -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
found := false
|
||||
for _, metric := range target.Metrics {
|
||||
if metric.ID == field {
|
||||
metricName += " " + describeMetric(metric.Type, field)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
metricName = "Unset"
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
metricName = "Unset"
|
||||
}
|
||||
} else if field != "" {
|
||||
metricName += " " + field
|
||||
}
|
||||
|
||||
delete(series.Tags, "metricId")
|
||||
|
||||
if len(series.Tags) == 0 {
|
||||
return metricName
|
||||
}
|
||||
|
||||
@@ -787,6 +787,84 @@ func TestResponseParser(t *testing.T) {
|
||||
So(rows[0][2].(null.Float).Float64, ShouldEqual, 3000)
|
||||
})
|
||||
|
||||
Convey("With bucket_script", func() {
|
||||
targets := map[string]string{
|
||||
"A": `{
|
||||
"timeField": "@timestamp",
|
||||
"metrics": [
|
||||
{ "id": "1", "type": "sum", "field": "@value" },
|
||||
{ "id": "3", "type": "max", "field": "@value" },
|
||||
{
|
||||
"id": "4",
|
||||
"field": "select field",
|
||||
"pipelineVariables": [{ "name": "var1", "pipelineAgg": "1" }, { "name": "var2", "pipelineAgg": "3" }],
|
||||
"settings": { "script": "params.var1 * params.var2" },
|
||||
"type": "bucket_script"
|
||||
}
|
||||
],
|
||||
"bucketAggs": [{ "type": "date_histogram", "field": "@timestamp", "id": "2" }]
|
||||
}`,
|
||||
}
|
||||
response := `{
|
||||
"responses": [
|
||||
{
|
||||
"aggregations": {
|
||||
"2": {
|
||||
"buckets": [
|
||||
{
|
||||
"1": { "value": 2 },
|
||||
"3": { "value": 3 },
|
||||
"4": { "value": 6 },
|
||||
"doc_count": 60,
|
||||
"key": 1000
|
||||
},
|
||||
{
|
||||
"1": { "value": 3 },
|
||||
"3": { "value": 4 },
|
||||
"4": { "value": 12 },
|
||||
"doc_count": 60,
|
||||
"key": 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`
|
||||
rp, err := newResponseParserForTest(targets, response)
|
||||
So(err, ShouldBeNil)
|
||||
result, err := rp.getTimeSeries()
|
||||
So(err, ShouldBeNil)
|
||||
So(result.Results, ShouldHaveLength, 1)
|
||||
|
||||
queryRes := result.Results["A"]
|
||||
So(queryRes, ShouldNotBeNil)
|
||||
So(queryRes.Series, ShouldHaveLength, 3)
|
||||
seriesOne := queryRes.Series[0]
|
||||
So(seriesOne.Name, ShouldEqual, "Sum @value")
|
||||
So(seriesOne.Points, ShouldHaveLength, 2)
|
||||
So(seriesOne.Points[0][0].Float64, ShouldEqual, 2)
|
||||
So(seriesOne.Points[0][1].Float64, ShouldEqual, 1000)
|
||||
So(seriesOne.Points[1][0].Float64, ShouldEqual, 3)
|
||||
So(seriesOne.Points[1][1].Float64, ShouldEqual, 2000)
|
||||
|
||||
seriesTwo := queryRes.Series[1]
|
||||
So(seriesTwo.Name, ShouldEqual, "Max @value")
|
||||
So(seriesTwo.Points, ShouldHaveLength, 2)
|
||||
So(seriesTwo.Points[0][0].Float64, ShouldEqual, 3)
|
||||
So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
||||
So(seriesTwo.Points[1][0].Float64, ShouldEqual, 4)
|
||||
So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
||||
|
||||
seriesThree := queryRes.Series[2]
|
||||
So(seriesThree.Name, ShouldEqual, "Sum @value * Max @value")
|
||||
So(seriesThree.Points, ShouldHaveLength, 2)
|
||||
So(seriesThree.Points[0][0].Float64, ShouldEqual, 6)
|
||||
So(seriesThree.Points[0][1].Float64, ShouldEqual, 1000)
|
||||
So(seriesThree.Points[1][0].Float64, ShouldEqual, 12)
|
||||
So(seriesThree.Points[1][1].Float64, ShouldEqual, 2000)
|
||||
})
|
||||
|
||||
// Convey("Raw documents query", func() {
|
||||
// targets := map[string]string{
|
||||
// "A": `{
|
||||
|
||||
@@ -94,26 +94,56 @@ func (e *timeSeriesQuery) execute() (*tsdb.Response, error) {
|
||||
}
|
||||
|
||||
if isPipelineAgg(m.Type) {
|
||||
if _, err := strconv.Atoi(m.PipelineAggregate); err == nil {
|
||||
var appliedAgg *MetricAgg
|
||||
for _, pipelineMetric := range q.Metrics {
|
||||
if pipelineMetric.ID == m.PipelineAggregate {
|
||||
appliedAgg = pipelineMetric
|
||||
break
|
||||
}
|
||||
}
|
||||
if appliedAgg != nil {
|
||||
bucketPath := m.PipelineAggregate
|
||||
if appliedAgg.Type == countType {
|
||||
bucketPath = "_count"
|
||||
if isPipelineAggWithMultipleBucketPaths(m.Type) {
|
||||
if len(m.PipelineVariables) > 0 {
|
||||
bucketPaths := map[string]interface{}{}
|
||||
for name, pipelineAgg := range m.PipelineVariables {
|
||||
if _, err := strconv.Atoi(pipelineAgg); err == nil {
|
||||
var appliedAgg *MetricAgg
|
||||
for _, pipelineMetric := range q.Metrics {
|
||||
if pipelineMetric.ID == pipelineAgg {
|
||||
appliedAgg = pipelineMetric
|
||||
break
|
||||
}
|
||||
}
|
||||
if appliedAgg != nil {
|
||||
if appliedAgg.Type == countType {
|
||||
bucketPaths[name] = "_count"
|
||||
} else {
|
||||
bucketPaths[name] = pipelineAgg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aggBuilder.Pipeline(m.ID, m.Type, bucketPath, func(a *es.PipelineAggregation) {
|
||||
aggBuilder.Pipeline(m.ID, m.Type, bucketPaths, func(a *es.PipelineAggregation) {
|
||||
a.Settings = m.Settings.MustMap()
|
||||
})
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
if _, err := strconv.Atoi(m.PipelineAggregate); err == nil {
|
||||
var appliedAgg *MetricAgg
|
||||
for _, pipelineMetric := range q.Metrics {
|
||||
if pipelineMetric.ID == m.PipelineAggregate {
|
||||
appliedAgg = pipelineMetric
|
||||
break
|
||||
}
|
||||
}
|
||||
if appliedAgg != nil {
|
||||
bucketPath := m.PipelineAggregate
|
||||
if appliedAgg.Type == countType {
|
||||
bucketPath = "_count"
|
||||
}
|
||||
|
||||
aggBuilder.Pipeline(m.ID, m.Type, bucketPath, func(a *es.PipelineAggregation) {
|
||||
a.Settings = m.Settings.MustMap()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
aggBuilder.Metric(m.ID, m.Type, m.Field, func(a *es.MetricAggregation) {
|
||||
@@ -328,12 +358,20 @@ func (p *timeSeriesQueryParser) parseMetrics(model *simplejson.Json) ([]*MetricA
|
||||
metric.PipelineAggregate = metricJSON.Get("pipelineAgg").MustString()
|
||||
metric.Settings = simplejson.NewFromAny(metricJSON.Get("settings").MustMap())
|
||||
metric.Meta = simplejson.NewFromAny(metricJSON.Get("meta").MustMap())
|
||||
|
||||
metric.Type, err = metricJSON.Get("type").String()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isPipelineAggWithMultipleBucketPaths(metric.Type) {
|
||||
metric.PipelineVariables = map[string]string{}
|
||||
pvArr := metricJSON.Get("pipelineVariables").MustArray()
|
||||
for _, v := range pvArr {
|
||||
kv := v.(map[string]interface{})
|
||||
metric.PipelineVariables[kv["name"].(string)] = kv["pipelineAgg"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, metric)
|
||||
}
|
||||
return result, nil
|
||||
|
||||
@@ -543,6 +543,77 @@ func TestExecuteTimeSeriesQuery(t *testing.T) {
|
||||
plAgg := derivativeAgg.Aggregation.Aggregation.(*es.PipelineAggregation)
|
||||
So(plAgg.BucketPath, ShouldEqual, "_count")
|
||||
})
|
||||
|
||||
Convey("With bucket_script", func() {
|
||||
c := newFakeClient(5)
|
||||
_, err := executeTsdbQuery(c, `{
|
||||
"timeField": "@timestamp",
|
||||
"bucketAggs": [
|
||||
{ "type": "date_histogram", "field": "@timestamp", "id": "4" }
|
||||
],
|
||||
"metrics": [
|
||||
{ "id": "3", "type": "sum", "field": "@value" },
|
||||
{ "id": "5", "type": "max", "field": "@value" },
|
||||
{
|
||||
"id": "2",
|
||||
"type": "bucket_script",
|
||||
"pipelineVariables": [
|
||||
{ "name": "var1", "pipelineAgg": "3" },
|
||||
{ "name": "var2", "pipelineAgg": "5" }
|
||||
],
|
||||
"settings": { "script": "params.var1 * params.var2" }
|
||||
}
|
||||
]
|
||||
}`, from, to, 15*time.Second)
|
||||
So(err, ShouldBeNil)
|
||||
sr := c.multisearchRequests[0].Requests[0]
|
||||
|
||||
firstLevel := sr.Aggs[0]
|
||||
So(firstLevel.Key, ShouldEqual, "4")
|
||||
So(firstLevel.Aggregation.Type, ShouldEqual, "date_histogram")
|
||||
|
||||
bucketScriptAgg := firstLevel.Aggregation.Aggs[2]
|
||||
So(bucketScriptAgg.Key, ShouldEqual, "2")
|
||||
plAgg := bucketScriptAgg.Aggregation.Aggregation.(*es.PipelineAggregation)
|
||||
So(plAgg.BucketPath.(map[string]interface{}), ShouldResemble, map[string]interface{}{
|
||||
"var1": "3",
|
||||
"var2": "5",
|
||||
})
|
||||
})
|
||||
|
||||
Convey("With bucket_script doc count", func() {
|
||||
c := newFakeClient(5)
|
||||
_, err := executeTsdbQuery(c, `{
|
||||
"timeField": "@timestamp",
|
||||
"bucketAggs": [
|
||||
{ "type": "date_histogram", "field": "@timestamp", "id": "4" }
|
||||
],
|
||||
"metrics": [
|
||||
{ "id": "3", "type": "count", "field": "select field" },
|
||||
{
|
||||
"id": "2",
|
||||
"type": "bucket_script",
|
||||
"pipelineVariables": [
|
||||
{ "name": "var1", "pipelineAgg": "3" }
|
||||
],
|
||||
"settings": { "script": "params.var1 * 1000" }
|
||||
}
|
||||
]
|
||||
}`, from, to, 15*time.Second)
|
||||
So(err, ShouldBeNil)
|
||||
sr := c.multisearchRequests[0].Requests[0]
|
||||
|
||||
firstLevel := sr.Aggs[0]
|
||||
So(firstLevel.Key, ShouldEqual, "4")
|
||||
So(firstLevel.Aggregation.Type, ShouldEqual, "date_histogram")
|
||||
|
||||
bucketScriptAgg := firstLevel.Aggregation.Aggs[0]
|
||||
So(bucketScriptAgg.Key, ShouldEqual, "2")
|
||||
plAgg := bucketScriptAgg.Aggregation.Aggregation.(*es.PipelineAggregation)
|
||||
So(plAgg.BucketPath.(map[string]interface{}), ShouldResemble, map[string]interface{}{
|
||||
"var1": "_count",
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user