mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch: Expand alias variables when query yields no result (#22695)
* Return empty time series with expanded aliases in case query yielded no results * PR feedback
This commit is contained in:
parent
73c4bef70f
commit
763fb3bc32
@ -55,6 +55,22 @@ func (q *cloudWatchQuery) isInferredSearchExpression() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *cloudWatchQuery) isMultiValuedDimensionExpression() bool {
|
||||||
|
for _, values := range q.Dimensions {
|
||||||
|
for _, v := range values {
|
||||||
|
if v == "*" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(values) > 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (q *cloudWatchQuery) isMetricStat() bool {
|
func (q *cloudWatchQuery) isMetricStat() bool {
|
||||||
return !q.isSearchExpression() && !q.isMathExpression()
|
return !q.isSearchExpression() && !q.isMathExpression()
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,29 @@ func TestCloudWatchQuery(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("and query has a multi-valued dimension", func() {
|
||||||
|
query := &cloudWatchQuery{
|
||||||
|
RefId: "A",
|
||||||
|
Region: "us-east-1",
|
||||||
|
Expression: "",
|
||||||
|
Stats: "Average",
|
||||||
|
Period: 300,
|
||||||
|
Id: "id1",
|
||||||
|
Dimensions: map[string][]string{
|
||||||
|
"InstanceId": {"i-12345678", "i-12345679"},
|
||||||
|
"InstanceType": {"abc"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Convey("it is a search expression", func() {
|
||||||
|
So(query.isSearchExpression(), ShouldBeTrue)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("it is a multi-valued dimension expression", func() {
|
||||||
|
So(query.isMultiValuedDimensionExpression(), ShouldBeTrue)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Convey("and no dimensions were added", func() {
|
Convey("and no dimensions were added", func() {
|
||||||
query := &cloudWatchQuery{
|
query := &cloudWatchQuery{
|
||||||
RefId: "A",
|
RefId: "A",
|
||||||
|
@ -83,44 +83,72 @@ func parseGetMetricDataTimeSeries(metricDataResults map[string]*cloudwatch.Metri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
series := tsdb.TimeSeries{
|
// In case a multi-valued dimension is used and the cloudwatch query yields no values, create one empty time series for each dimension value.
|
||||||
Tags: make(map[string]string),
|
// Use that dimension value to expand the alias field
|
||||||
Points: make([]tsdb.TimePoint, 0),
|
if len(metricDataResult.Values) == 0 && query.isMultiValuedDimensionExpression() {
|
||||||
}
|
series := 0
|
||||||
|
multiValuedDimension := ""
|
||||||
|
for key, values := range query.Dimensions {
|
||||||
|
if len(values) > series {
|
||||||
|
series = len(values)
|
||||||
|
multiValuedDimension = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keys := make([]string, 0)
|
for _, value := range query.Dimensions[multiValuedDimension] {
|
||||||
for k := range query.Dimensions {
|
emptySeries := tsdb.TimeSeries{
|
||||||
keys = append(keys, k)
|
Tags: map[string]string{multiValuedDimension: value},
|
||||||
}
|
Points: make([]tsdb.TimePoint, 0),
|
||||||
sort.Strings(keys)
|
}
|
||||||
|
for key, values := range query.Dimensions {
|
||||||
|
if key != multiValuedDimension && len(values) > 0 {
|
||||||
|
emptySeries.Tags[key] = values[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, key := range keys {
|
emptySeries.Name = formatAlias(query, query.Stats, emptySeries.Tags, label)
|
||||||
values := query.Dimensions[key]
|
result = append(result, &emptySeries)
|
||||||
if len(values) == 1 && values[0] != "*" {
|
}
|
||||||
series.Tags[key] = values[0]
|
} else {
|
||||||
} else {
|
keys := make([]string, 0)
|
||||||
for _, value := range values {
|
for k := range query.Dimensions {
|
||||||
if value == label || value == "*" {
|
keys = append(keys, k)
|
||||||
series.Tags[key] = label
|
}
|
||||||
} else if strings.Contains(label, value) {
|
sort.Strings(keys)
|
||||||
series.Tags[key] = value
|
|
||||||
|
series := tsdb.TimeSeries{
|
||||||
|
Tags: make(map[string]string),
|
||||||
|
Points: make([]tsdb.TimePoint, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
values := query.Dimensions[key]
|
||||||
|
if len(values) == 1 && values[0] != "*" {
|
||||||
|
series.Tags[key] = values[0]
|
||||||
|
} else {
|
||||||
|
for _, value := range values {
|
||||||
|
if value == label || value == "*" {
|
||||||
|
series.Tags[key] = label
|
||||||
|
} else if strings.Contains(label, value) {
|
||||||
|
series.Tags[key] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
series.Name = formatAlias(query, query.Stats, series.Tags, label)
|
series.Name = formatAlias(query, query.Stats, series.Tags, label)
|
||||||
|
|
||||||
for j, t := range metricDataResult.Timestamps {
|
for j, t := range metricDataResult.Timestamps {
|
||||||
if j > 0 {
|
if j > 0 {
|
||||||
expectedTimestamp := metricDataResult.Timestamps[j-1].Add(time.Duration(query.Period) * time.Second)
|
expectedTimestamp := metricDataResult.Timestamps[j-1].Add(time.Duration(query.Period) * time.Second)
|
||||||
if expectedTimestamp.Before(*t) {
|
if expectedTimestamp.Before(*t) {
|
||||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), float64(expectedTimestamp.Unix()*1000)))
|
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), float64(expectedTimestamp.Unix()*1000)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(*metricDataResult.Values[j]), float64((*t).Unix())*1000))
|
||||||
}
|
}
|
||||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(*metricDataResult.Values[j]), float64((*t).Unix())*1000))
|
result = append(result, &series)
|
||||||
}
|
}
|
||||||
result = append(result, &series)
|
|
||||||
}
|
}
|
||||||
return &result, partialData, nil
|
return &result, partialData, nil
|
||||||
}
|
}
|
||||||
@ -142,7 +170,7 @@ func formatAlias(query *cloudWatchQuery, stat string, dimensions map[string]stri
|
|||||||
return query.Id
|
return query.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(query.Alias) == 0 && query.isInferredSearchExpression() {
|
if len(query.Alias) == 0 && query.isInferredSearchExpression() && !query.isMultiValuedDimensionExpression() {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +189,82 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
|||||||
So((*series)[1].Name, ShouldEqual, "lb4 Expanded")
|
So((*series)[1].Name, ShouldEqual, "lb4 Expanded")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("can expand dimension value when no values are returned and a multi-valued template variabel is used", func() {
|
||||||
|
timestamp := time.Unix(0, 0)
|
||||||
|
resp := map[string]*cloudwatch.MetricDataResult{
|
||||||
|
"lb3": {
|
||||||
|
Id: aws.String("lb3"),
|
||||||
|
Label: aws.String("lb3"),
|
||||||
|
Timestamps: []*time.Time{
|
||||||
|
aws.Time(timestamp),
|
||||||
|
aws.Time(timestamp.Add(60 * time.Second)),
|
||||||
|
aws.Time(timestamp.Add(180 * time.Second)),
|
||||||
|
},
|
||||||
|
Values: []*float64{},
|
||||||
|
StatusCode: aws.String("Complete"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &cloudWatchQuery{
|
||||||
|
RefId: "refId1",
|
||||||
|
Region: "us-east-1",
|
||||||
|
Namespace: "AWS/ApplicationELB",
|
||||||
|
MetricName: "TargetResponseTime",
|
||||||
|
Dimensions: map[string][]string{
|
||||||
|
"LoadBalancer": {"lb1", "lb2"},
|
||||||
|
},
|
||||||
|
Stats: "Average",
|
||||||
|
Period: 60,
|
||||||
|
Alias: "{{LoadBalancer}} Expanded",
|
||||||
|
}
|
||||||
|
series, partialData, err := parseGetMetricDataTimeSeries(resp, query)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(partialData, ShouldBeFalse)
|
||||||
|
So(len(*series), ShouldEqual, 2)
|
||||||
|
So((*series)[0].Name, ShouldEqual, "lb1 Expanded")
|
||||||
|
So((*series)[1].Name, ShouldEqual, "lb2 Expanded")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("can expand dimension value when no values are returned and a multi-valued template variable and two single-valued dimensions are used", func() {
|
||||||
|
timestamp := time.Unix(0, 0)
|
||||||
|
resp := map[string]*cloudwatch.MetricDataResult{
|
||||||
|
"lb3": {
|
||||||
|
Id: aws.String("lb3"),
|
||||||
|
Label: aws.String("lb3"),
|
||||||
|
Timestamps: []*time.Time{
|
||||||
|
aws.Time(timestamp),
|
||||||
|
aws.Time(timestamp.Add(60 * time.Second)),
|
||||||
|
aws.Time(timestamp.Add(180 * time.Second)),
|
||||||
|
},
|
||||||
|
Values: []*float64{},
|
||||||
|
StatusCode: aws.String("Complete"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &cloudWatchQuery{
|
||||||
|
RefId: "refId1",
|
||||||
|
Region: "us-east-1",
|
||||||
|
Namespace: "AWS/ApplicationELB",
|
||||||
|
MetricName: "TargetResponseTime",
|
||||||
|
Dimensions: map[string][]string{
|
||||||
|
"LoadBalancer": {"lb1", "lb2"},
|
||||||
|
"InstanceType": {"micro"},
|
||||||
|
"Resource": {"res"},
|
||||||
|
},
|
||||||
|
Stats: "Average",
|
||||||
|
Period: 60,
|
||||||
|
Alias: "{{LoadBalancer}} Expanded {{InstanceType}} - {{Resource}}",
|
||||||
|
}
|
||||||
|
series, partialData, err := parseGetMetricDataTimeSeries(resp, query)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(partialData, ShouldBeFalse)
|
||||||
|
So(len(*series), ShouldEqual, 2)
|
||||||
|
So((*series)[0].Name, ShouldEqual, "lb1 Expanded micro - res")
|
||||||
|
So((*series)[1].Name, ShouldEqual, "lb2 Expanded micro - res")
|
||||||
|
})
|
||||||
|
|
||||||
Convey("can parse cloudwatch response", func() {
|
Convey("can parse cloudwatch response", func() {
|
||||||
timestamp := time.Unix(0, 0)
|
timestamp := time.Unix(0, 0)
|
||||||
resp := map[string]*cloudwatch.MetricDataResult{
|
resp := map[string]*cloudwatch.MetricDataResult{
|
||||||
|
Loading…
Reference in New Issue
Block a user