|
|
@@ -2,109 +2,879 @@ package elasticsearch
|
|
|
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
+ "fmt"
|
|
|
"testing"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/grafana/grafana/pkg/components/null"
|
|
|
+ "github.com/grafana/grafana/pkg/components/simplejson"
|
|
|
+ "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
)
|
|
|
|
|
|
-func testElasticsearchResponse(body string, target Query) *tsdb.QueryResult {
|
|
|
- var responses Responses
|
|
|
- err := json.Unmarshal([]byte(body), &responses)
|
|
|
- So(err, ShouldBeNil)
|
|
|
+func TestResponseParser(t *testing.T) {
|
|
|
+ Convey("Elasticsearch response parser test", t, func() {
|
|
|
+ Convey("Simple query and count", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }],
|
|
|
+ "bucketAggs": [{ "type": "date_histogram", "field": "@timestamp", "id": "2" }]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": 1000
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "doc_count": 15,
|
|
|
+ "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, 1)
|
|
|
+ series := queryRes.Series[0]
|
|
|
+ So(series.Name, ShouldEqual, "Count")
|
|
|
+ So(series.Points, ShouldHaveLength, 2)
|
|
|
+ So(series.Points[0][0].Float64, ShouldEqual, 10)
|
|
|
+ So(series.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(series.Points[1][0].Float64, ShouldEqual, 15)
|
|
|
+ So(series.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
|
|
|
- responseParser := ElasticsearchResponseParser{responses.Responses, []*Query{&target}}
|
|
|
- return responseParser.getTimeSeries()
|
|
|
-}
|
|
|
+ Convey("Simple query count & avg aggregation", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }, {"type": "avg", "field": "value", "id": "2" }],
|
|
|
+ "bucketAggs": [{ "type": "date_histogram", "field": "@timestamp", "id": "3" }]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "3": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "2": { "value": 88 },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": 1000
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "2": { "value": 99 },
|
|
|
+ "doc_count": 15,
|
|
|
+ "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, 2)
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "Count")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 10)
|
|
|
+ So(seriesOne.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesOne.Points[1][0].Float64, ShouldEqual, 15)
|
|
|
+ So(seriesOne.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+
|
|
|
+ seriesTwo := queryRes.Series[1]
|
|
|
+ So(seriesTwo.Name, ShouldEqual, "Average value")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 88)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesTwo.Points[1][0].Float64, ShouldEqual, 99)
|
|
|
+ So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("Single group by query one metric", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }],
|
|
|
+ "bucketAggs": [
|
|
|
+ { "type": "terms", "field": "host", "id": "2" },
|
|
|
+ { "type": "date_histogram", "field": "@timestamp", "id": "3" }
|
|
|
+ ]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 1, "key": 1000 }, { "doc_count": 3, "key": 2000 }]
|
|
|
+ },
|
|
|
+ "doc_count": 4,
|
|
|
+ "key": "server1"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 2, "key": 1000 }, { "doc_count": 8, "key": 2000 }]
|
|
|
+ },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": "server2"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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, 2)
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "server1")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 1)
|
|
|
+ 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, "server2")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 2)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesTwo.Points[1][0].Float64, ShouldEqual, 8)
|
|
|
+ So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("Single group by query two metrics", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }, { "type": "avg", "field": "@value", "id": "4" }],
|
|
|
+ "bucketAggs": [
|
|
|
+ { "type": "terms", "field": "host", "id": "2" },
|
|
|
+ { "type": "date_histogram", "field": "@timestamp", "id": "3" }
|
|
|
+ ]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [
|
|
|
+ { "4": { "value": 10 }, "doc_count": 1, "key": 1000 },
|
|
|
+ { "4": { "value": 12 }, "doc_count": 3, "key": 2000 }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "doc_count": 4,
|
|
|
+ "key": "server1"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [
|
|
|
+ { "4": { "value": 20 }, "doc_count": 1, "key": 1000 },
|
|
|
+ { "4": { "value": 32 }, "doc_count": 3, "key": 2000 }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": "server2"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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, 4)
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "server1 Count")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 1)
|
|
|
+ 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, "server1 Average @value")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 10)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesTwo.Points[1][0].Float64, ShouldEqual, 12)
|
|
|
+ So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+
|
|
|
+ seriesThree := queryRes.Series[2]
|
|
|
+ So(seriesThree.Name, ShouldEqual, "server2 Count")
|
|
|
+ So(seriesThree.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesThree.Points[0][0].Float64, ShouldEqual, 1)
|
|
|
+ So(seriesThree.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesThree.Points[1][0].Float64, ShouldEqual, 3)
|
|
|
+ So(seriesThree.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
|
|
|
-func TestElasticSearchResponseParser(t *testing.T) {
|
|
|
- Convey("Elasticsearch Response query testing", t, func() {
|
|
|
- Convey("Build test average metric with moving average", func() {
|
|
|
- responses := `{
|
|
|
- "responses": [
|
|
|
- {
|
|
|
- "took": 1,
|
|
|
- "timed_out": false,
|
|
|
- "_shards": {
|
|
|
- "total": 5,
|
|
|
- "successful": 5,
|
|
|
- "skipped": 0,
|
|
|
- "failed": 0
|
|
|
- },
|
|
|
- "hits": {
|
|
|
- "total": 4500,
|
|
|
- "max_score": 0,
|
|
|
- "hits": []
|
|
|
- },
|
|
|
- "aggregations": {
|
|
|
- "2": {
|
|
|
- "buckets": [
|
|
|
- {
|
|
|
- "1": {
|
|
|
- "value": null
|
|
|
- },
|
|
|
- "key_as_string": "1522205880000",
|
|
|
- "key": 1522205880000,
|
|
|
- "doc_count": 0
|
|
|
- },
|
|
|
- {
|
|
|
- "1": {
|
|
|
- "value": 10
|
|
|
- },
|
|
|
- "key_as_string": "1522205940000",
|
|
|
- "key": 1522205940000,
|
|
|
- "doc_count": 300
|
|
|
- },
|
|
|
- {
|
|
|
- "1": {
|
|
|
- "value": 10
|
|
|
- },
|
|
|
+ seriesFour := queryRes.Series[3]
|
|
|
+ So(seriesFour.Name, ShouldEqual, "server2 Average @value")
|
|
|
+ So(seriesFour.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesFour.Points[0][0].Float64, ShouldEqual, 20)
|
|
|
+ So(seriesFour.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesFour.Points[1][0].Float64, ShouldEqual, 32)
|
|
|
+ So(seriesFour.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("With percentiles", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "percentiles", "settings": { "percents": [75, 90] }, "id": "1" }],
|
|
|
+ "bucketAggs": [{ "type": "date_histogram", "field": "@timestamp", "id": "3" }]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
"3": {
|
|
|
- "value": 20
|
|
|
- },
|
|
|
- "key_as_string": "1522206000000",
|
|
|
- "key": 1522206000000,
|
|
|
- "doc_count": 300
|
|
|
- },
|
|
|
- {
|
|
|
- "1": {
|
|
|
- "value": 10
|
|
|
- },
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "1": { "values": { "75": 3.3, "90": 5.5 } },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": 1000
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "1": { "values": { "75": 2.3, "90": 4.5 } },
|
|
|
+ "doc_count": 15,
|
|
|
+ "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, 2)
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "p75")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 3.3)
|
|
|
+ So(seriesOne.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesOne.Points[1][0].Float64, ShouldEqual, 2.3)
|
|
|
+ So(seriesOne.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+
|
|
|
+ seriesTwo := queryRes.Series[1]
|
|
|
+ So(seriesTwo.Name, ShouldEqual, "p90")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 5.5)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesTwo.Points[1][0].Float64, ShouldEqual, 4.5)
|
|
|
+ So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("With extended stats", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "extended_stats", "meta": { "max": true, "std_deviation_bounds_upper": true, "std_deviation_bounds_lower": true }, "id": "1" }],
|
|
|
+ "bucketAggs": [
|
|
|
+ { "type": "terms", "field": "host", "id": "3" },
|
|
|
+ { "type": "date_histogram", "field": "@timestamp", "id": "4" }
|
|
|
+ ]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
"3": {
|
|
|
- "value": 20
|
|
|
- },
|
|
|
- "key_as_string": "1522206060000",
|
|
|
- "key": 1522206060000,
|
|
|
- "doc_count": 300
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "key": "server1",
|
|
|
+ "4": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "1": {
|
|
|
+ "max": 10.2,
|
|
|
+ "min": 5.5,
|
|
|
+ "std_deviation_bounds": { "upper": 3, "lower": -2 }
|
|
|
+ },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": 1000
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "key": "server2",
|
|
|
+ "4": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "1": {
|
|
|
+ "max": 15.5,
|
|
|
+ "min": 3.4,
|
|
|
+ "std_deviation_bounds": { "upper": 4, "lower": -1 }
|
|
|
+ },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": 1000
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
}
|
|
|
- ]
|
|
|
- }
|
|
|
- },
|
|
|
- "status": 200
|
|
|
- }
|
|
|
- ]
|
|
|
-}
|
|
|
-`
|
|
|
- res := testElasticsearchResponse(responses, avgWithMovingAvg)
|
|
|
- So(len(res.Series), ShouldEqual, 2)
|
|
|
- So(res.Series[0].Name, ShouldEqual, "Average value")
|
|
|
- So(len(res.Series[0].Points), ShouldEqual, 4)
|
|
|
- for i, p := range res.Series[0].Points {
|
|
|
- if i == 0 {
|
|
|
- So(p[0].Valid, ShouldBeFalse)
|
|
|
- } else {
|
|
|
- So(p[0].Float64, ShouldEqual, 10)
|
|
|
- }
|
|
|
- So(p[1].Float64, ShouldEqual, 1522205880000+60000*i)
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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, 6)
|
|
|
+
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "server1 Max")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 10.2)
|
|
|
+ So(seriesOne.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+
|
|
|
+ seriesTwo := queryRes.Series[1]
|
|
|
+ So(seriesTwo.Name, ShouldEqual, "server1 Std Dev Lower")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, -2)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+
|
|
|
+ seriesThree := queryRes.Series[2]
|
|
|
+ So(seriesThree.Name, ShouldEqual, "server1 Std Dev Upper")
|
|
|
+ So(seriesThree.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesThree.Points[0][0].Float64, ShouldEqual, 3)
|
|
|
+ So(seriesThree.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+
|
|
|
+ seriesFour := queryRes.Series[3]
|
|
|
+ So(seriesFour.Name, ShouldEqual, "server2 Max")
|
|
|
+ So(seriesFour.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesFour.Points[0][0].Float64, ShouldEqual, 15.5)
|
|
|
+ So(seriesFour.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+
|
|
|
+ seriesFive := queryRes.Series[4]
|
|
|
+ So(seriesFive.Name, ShouldEqual, "server2 Std Dev Lower")
|
|
|
+ So(seriesFive.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesFive.Points[0][0].Float64, ShouldEqual, -1)
|
|
|
+ So(seriesFive.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+
|
|
|
+ seriesSix := queryRes.Series[5]
|
|
|
+ So(seriesSix.Name, ShouldEqual, "server2 Std Dev Upper")
|
|
|
+ So(seriesSix.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesSix.Points[0][0].Float64, ShouldEqual, 4)
|
|
|
+ So(seriesSix.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("Single group by with alias pattern", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "alias": "{{term @host}} {{metric}} and {{not_exist}} {{@host}}",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }],
|
|
|
+ "bucketAggs": [
|
|
|
+ { "type": "terms", "field": "@host", "id": "2" },
|
|
|
+ { "type": "date_histogram", "field": "@timestamp", "id": "3" }
|
|
|
+ ]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 1, "key": 1000 }, { "doc_count": 3, "key": 2000 }]
|
|
|
+ },
|
|
|
+ "doc_count": 4,
|
|
|
+ "key": "server1"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 2, "key": 1000 }, { "doc_count": 8, "key": 2000 }]
|
|
|
+ },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": "server2"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 2, "key": 1000 }, { "doc_count": 8, "key": 2000 }]
|
|
|
+ },
|
|
|
+ "doc_count": 10,
|
|
|
+ "key": 0
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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, "server1 Count and {{not_exist}} server1")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 1)
|
|
|
+ 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, "server2 Count and {{not_exist}} server2")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 2)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesTwo.Points[1][0].Float64, ShouldEqual, 8)
|
|
|
+ So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+
|
|
|
+ seriesThree := queryRes.Series[2]
|
|
|
+ So(seriesThree.Name, ShouldEqual, "0 Count and {{not_exist}} 0")
|
|
|
+ So(seriesThree.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesThree.Points[0][0].Float64, ShouldEqual, 2)
|
|
|
+ So(seriesThree.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesThree.Points[1][0].Float64, ShouldEqual, 8)
|
|
|
+ So(seriesThree.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("Histogram response", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }],
|
|
|
+ "bucketAggs": [{ "type": "histogram", "field": "bytes", "id": "3" }]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 1, "key": 1000 }, { "doc_count": 3, "key": 2000 }, { "doc_count": 2, "key": 3000 }]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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.Tables, ShouldHaveLength, 1)
|
|
|
+
|
|
|
+ rows := queryRes.Tables[0].Rows
|
|
|
+ So(rows, ShouldHaveLength, 3)
|
|
|
+ cols := queryRes.Tables[0].Columns
|
|
|
+ So(cols, ShouldHaveLength, 2)
|
|
|
+
|
|
|
+ So(cols[0].Text, ShouldEqual, "bytes")
|
|
|
+ So(cols[1].Text, ShouldEqual, "Count")
|
|
|
+
|
|
|
+ So(rows[0][0].(null.Float).Float64, ShouldEqual, 1000)
|
|
|
+ So(rows[0][1].(null.Float).Float64, ShouldEqual, 1)
|
|
|
+ So(rows[1][0].(null.Float).Float64, ShouldEqual, 2000)
|
|
|
+ So(rows[1][1].(null.Float).Float64, ShouldEqual, 3)
|
|
|
+ So(rows[2][0].(null.Float).Float64, ShouldEqual, 3000)
|
|
|
+ So(rows[2][1].(null.Float).Float64, ShouldEqual, 2)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("With two filters agg", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "count", "id": "1" }],
|
|
|
+ "bucketAggs": [
|
|
|
+ {
|
|
|
+ "type": "filters",
|
|
|
+ "id": "2",
|
|
|
+ "settings": {
|
|
|
+ "filters": [{ "query": "@metric:cpu" }, { "query": "@metric:logins.count" }]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { "type": "date_histogram", "field": "@timestamp", "id": "3" }
|
|
|
+ ]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": {
|
|
|
+ "@metric:cpu": {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 1, "key": 1000 }, { "doc_count": 3, "key": 2000 }]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "@metric:logins.count": {
|
|
|
+ "3": {
|
|
|
+ "buckets": [{ "doc_count": 2, "key": 1000 }, { "doc_count": 8, "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, 2)
|
|
|
+
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "@metric:cpu")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 1)
|
|
|
+ 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, "@metric:logins.count")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 2)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 2)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 1000)
|
|
|
+ So(seriesTwo.Points[1][0].Float64, ShouldEqual, 8)
|
|
|
+ So(seriesTwo.Points[1][1].Float64, ShouldEqual, 2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("With dropfirst and last aggregation", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "avg", "id": "1" }, { "type": "count" }],
|
|
|
+ "bucketAggs": [
|
|
|
+ {
|
|
|
+ "type": "date_histogram",
|
|
|
+ "field": "@timestamp",
|
|
|
+ "id": "2",
|
|
|
+ "settings": { "trimEdges": 1 }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`,
|
|
|
+ }
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "1": { "value": 1000 },
|
|
|
+ "key": 1,
|
|
|
+ "doc_count": 369
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "1": { "value": 2000 },
|
|
|
+ "key": 2,
|
|
|
+ "doc_count": 200
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "1": { "value": 2000 },
|
|
|
+ "key": 3,
|
|
|
+ "doc_count": 200
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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, 2)
|
|
|
+
|
|
|
+ seriesOne := queryRes.Series[0]
|
|
|
+ So(seriesOne.Name, ShouldEqual, "Average")
|
|
|
+ So(seriesOne.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesOne.Points[0][0].Float64, ShouldEqual, 2000)
|
|
|
+ So(seriesOne.Points[0][1].Float64, ShouldEqual, 2)
|
|
|
+
|
|
|
+ seriesTwo := queryRes.Series[1]
|
|
|
+ So(seriesTwo.Name, ShouldEqual, "Count")
|
|
|
+ So(seriesTwo.Points, ShouldHaveLength, 1)
|
|
|
+ So(seriesTwo.Points[0][0].Float64, ShouldEqual, 200)
|
|
|
+ So(seriesTwo.Points[0][1].Float64, ShouldEqual, 2)
|
|
|
+ })
|
|
|
+
|
|
|
+ Convey("No group by time", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "avg", "id": "1" }, { "type": "count" }],
|
|
|
+ "bucketAggs": [{ "type": "terms", "field": "host", "id": "2" }]
|
|
|
+ }`,
|
|
|
}
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "1": { "value": 1000 },
|
|
|
+ "key": "server-1",
|
|
|
+ "doc_count": 369
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "1": { "value": 2000 },
|
|
|
+ "key": "server-2",
|
|
|
+ "doc_count": 200
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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.Tables, ShouldHaveLength, 1)
|
|
|
+
|
|
|
+ rows := queryRes.Tables[0].Rows
|
|
|
+ So(rows, ShouldHaveLength, 2)
|
|
|
+ cols := queryRes.Tables[0].Columns
|
|
|
+ So(cols, ShouldHaveLength, 3)
|
|
|
+
|
|
|
+ So(cols[0].Text, ShouldEqual, "host")
|
|
|
+ So(cols[1].Text, ShouldEqual, "Average")
|
|
|
+ So(cols[2].Text, ShouldEqual, "Count")
|
|
|
|
|
|
- So(res.Series[1].Name, ShouldEqual, "Moving Average Average 1")
|
|
|
- So(len(res.Series[1].Points), ShouldEqual, 2)
|
|
|
+ So(rows[0][0].(string), ShouldEqual, "server-1")
|
|
|
+ So(rows[0][1].(null.Float).Float64, ShouldEqual, 1000)
|
|
|
+ So(rows[0][2].(null.Float).Float64, ShouldEqual, 369)
|
|
|
+ So(rows[1][0].(string), ShouldEqual, "server-2")
|
|
|
+ So(rows[1][1].(null.Float).Float64, ShouldEqual, 2000)
|
|
|
+ So(rows[1][2].(null.Float).Float64, ShouldEqual, 200)
|
|
|
+ })
|
|
|
|
|
|
- for _, p := range res.Series[1].Points {
|
|
|
- So(p[0].Float64, ShouldEqual, 20)
|
|
|
+ Convey("Multiple metrics of same type", func() {
|
|
|
+ targets := map[string]string{
|
|
|
+ "A": `{
|
|
|
+ "timeField": "@timestamp",
|
|
|
+ "metrics": [{ "type": "avg", "field": "test", "id": "1" }, { "type": "avg", "field": "test2", "id": "2" }],
|
|
|
+ "bucketAggs": [{ "type": "terms", "field": "host", "id": "2" }]
|
|
|
+ }`,
|
|
|
}
|
|
|
+ response := `{
|
|
|
+ "responses": [
|
|
|
+ {
|
|
|
+ "aggregations": {
|
|
|
+ "2": {
|
|
|
+ "buckets": [
|
|
|
+ {
|
|
|
+ "1": { "value": 1000 },
|
|
|
+ "2": { "value": 3000 },
|
|
|
+ "key": "server-1",
|
|
|
+ "doc_count": 369
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }`
|
|
|
+ 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.Tables, ShouldHaveLength, 1)
|
|
|
|
|
|
+ rows := queryRes.Tables[0].Rows
|
|
|
+ So(rows, ShouldHaveLength, 1)
|
|
|
+ cols := queryRes.Tables[0].Columns
|
|
|
+ So(cols, ShouldHaveLength, 3)
|
|
|
+
|
|
|
+ So(cols[0].Text, ShouldEqual, "host")
|
|
|
+ So(cols[1].Text, ShouldEqual, "Average test")
|
|
|
+ So(cols[2].Text, ShouldEqual, "Average test2")
|
|
|
+
|
|
|
+ So(rows[0][0].(string), ShouldEqual, "server-1")
|
|
|
+ So(rows[0][1].(null.Float).Float64, ShouldEqual, 1000)
|
|
|
+ So(rows[0][2].(null.Float).Float64, ShouldEqual, 3000)
|
|
|
})
|
|
|
+
|
|
|
+ // Convey("Raw documents query", func() {
|
|
|
+ // targets := map[string]string{
|
|
|
+ // "A": `{
|
|
|
+ // "timeField": "@timestamp",
|
|
|
+ // "metrics": [{ "type": "raw_document", "id": "1" }]
|
|
|
+ // }`,
|
|
|
+ // }
|
|
|
+ // response := `{
|
|
|
+ // "responses": [
|
|
|
+ // {
|
|
|
+ // "hits": {
|
|
|
+ // "total": 100,
|
|
|
+ // "hits": [
|
|
|
+ // {
|
|
|
+ // "_id": "1",
|
|
|
+ // "_type": "type",
|
|
|
+ // "_index": "index",
|
|
|
+ // "_source": { "sourceProp": "asd" },
|
|
|
+ // "fields": { "fieldProp": "field" }
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // "_source": { "sourceProp": "asd2" },
|
|
|
+ // "fields": { "fieldProp": "field2" }
|
|
|
+ // }
|
|
|
+ // ]
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // ]
|
|
|
+ // }`
|
|
|
+ // 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.Tables, ShouldHaveLength, 1)
|
|
|
+
|
|
|
+ // rows := queryRes.Tables[0].Rows
|
|
|
+ // So(rows, ShouldHaveLength, 1)
|
|
|
+ // cols := queryRes.Tables[0].Columns
|
|
|
+ // So(cols, ShouldHaveLength, 3)
|
|
|
+
|
|
|
+ // So(cols[0].Text, ShouldEqual, "host")
|
|
|
+ // So(cols[1].Text, ShouldEqual, "Average test")
|
|
|
+ // So(cols[2].Text, ShouldEqual, "Average test2")
|
|
|
+
|
|
|
+ // So(rows[0][0].(string), ShouldEqual, "server-1")
|
|
|
+ // So(rows[0][1].(null.Float).Float64, ShouldEqual, 1000)
|
|
|
+ // So(rows[0][2].(null.Float).Float64, ShouldEqual, 3000)
|
|
|
+ // })
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+func newResponseParserForTest(tsdbQueries map[string]string, responseBody string) (*responseParser, error) {
|
|
|
+ from := time.Date(2018, 5, 15, 17, 50, 0, 0, time.UTC)
|
|
|
+ to := time.Date(2018, 5, 15, 17, 55, 0, 0, time.UTC)
|
|
|
+ fromStr := fmt.Sprintf("%d", from.UnixNano()/int64(time.Millisecond))
|
|
|
+ toStr := fmt.Sprintf("%d", to.UnixNano()/int64(time.Millisecond))
|
|
|
+ tsdbQuery := &tsdb.TsdbQuery{
|
|
|
+ Queries: []*tsdb.Query{},
|
|
|
+ TimeRange: tsdb.NewTimeRange(fromStr, toStr),
|
|
|
+ }
|
|
|
+
|
|
|
+ for refID, tsdbQueryBody := range tsdbQueries {
|
|
|
+ tsdbQueryJSON, err := simplejson.NewJson([]byte(tsdbQueryBody))
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ tsdbQuery.Queries = append(tsdbQuery.Queries, &tsdb.Query{
|
|
|
+ Model: tsdbQueryJSON,
|
|
|
+ RefId: refID,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ var response es.MultiSearchResponse
|
|
|
+ err := json.Unmarshal([]byte(responseBody), &response)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ tsQueryParser := newTimeSeriesQueryParser()
|
|
|
+ queries, err := tsQueryParser.parse(tsdbQuery)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return newResponseParser(response.Responses, queries), nil
|
|
|
+}
|