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
|
||||
}
|
||||
|
||||
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 {
|
||||
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() {
|
||||
query := &cloudWatchQuery{
|
||||
RefId: "A",
|
||||
|
@ -83,44 +83,72 @@ func parseGetMetricDataTimeSeries(metricDataResults map[string]*cloudwatch.Metri
|
||||
}
|
||||
}
|
||||
|
||||
series := tsdb.TimeSeries{
|
||||
Tags: make(map[string]string),
|
||||
Points: make([]tsdb.TimePoint, 0),
|
||||
}
|
||||
// In case a multi-valued dimension is used and the cloudwatch query yields no values, create one empty time series for each dimension value.
|
||||
// Use that dimension value to expand the alias field
|
||||
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 k := range query.Dimensions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, value := range query.Dimensions[multiValuedDimension] {
|
||||
emptySeries := tsdb.TimeSeries{
|
||||
Tags: map[string]string{multiValuedDimension: value},
|
||||
Points: make([]tsdb.TimePoint, 0),
|
||||
}
|
||||
for key, values := range query.Dimensions {
|
||||
if key != multiValuedDimension && len(values) > 0 {
|
||||
emptySeries.Tags[key] = values[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
|
||||
emptySeries.Name = formatAlias(query, query.Stats, emptySeries.Tags, label)
|
||||
result = append(result, &emptySeries)
|
||||
}
|
||||
} else {
|
||||
keys := make([]string, 0)
|
||||
for k := range query.Dimensions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
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 {
|
||||
if j > 0 {
|
||||
expectedTimestamp := metricDataResult.Timestamps[j-1].Add(time.Duration(query.Period) * time.Second)
|
||||
if expectedTimestamp.Before(*t) {
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), float64(expectedTimestamp.Unix()*1000)))
|
||||
for j, t := range metricDataResult.Timestamps {
|
||||
if j > 0 {
|
||||
expectedTimestamp := metricDataResult.Timestamps[j-1].Add(time.Duration(query.Period) * time.Second)
|
||||
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.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
|
||||
}
|
||||
@ -142,7 +170,7 @@ func formatAlias(query *cloudWatchQuery, stat string, dimensions map[string]stri
|
||||
return query.Id
|
||||
}
|
||||
|
||||
if len(query.Alias) == 0 && query.isInferredSearchExpression() {
|
||||
if len(query.Alias) == 0 && query.isInferredSearchExpression() && !query.isMultiValuedDimensionExpression() {
|
||||
return label
|
||||
}
|
||||
|
||||
|
@ -189,6 +189,82 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
||||
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() {
|
||||
timestamp := time.Unix(0, 0)
|
||||
resp := map[string]*cloudwatch.MetricDataResult{
|
||||
|
Loading…
Reference in New Issue
Block a user