Commit 9164a352 by Kyle Brandt Committed by GitHub

Azure: Restore Insights Metrics alias feature (#26098)

also fix case sensitivity for azure monitor metrics
parent eb4391a2
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"sort"
"strings" "strings"
"time" "time"
...@@ -192,6 +193,9 @@ func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query ...@@ -192,6 +193,9 @@ func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query
queryResult.Error = err queryResult.Error = err
return queryResult, nil return queryResult, nil
} }
applyInsightsMetricAlias(frame, query.Alias)
queryResult.Dataframes = tsdb.NewDecodedDataFrames(data.Frames{frame}) queryResult.Dataframes = tsdb.NewDecodedDataFrames(data.Frames{frame})
return queryResult, nil return queryResult, nil
} }
...@@ -249,3 +253,62 @@ func (e *ApplicationInsightsDatasource) getPluginRoute(plugin *plugins.DataSourc ...@@ -249,3 +253,62 @@ func (e *ApplicationInsightsDatasource) getPluginRoute(plugin *plugins.DataSourc
return pluginRoute, pluginRouteName, nil return pluginRoute, pluginRouteName, nil
} }
// formatApplicationInsightsLegendKey builds the legend key or timeseries name
// Alias patterns like {{metric}} are replaced with the appropriate data values.
func formatApplicationInsightsLegendKey(alias string, metricName string, labels data.Labels) string {
// Could be a collision problem if there were two keys that varied only in case, but I don't think that would happen in azure.
lowerLabels := data.Labels{}
for k, v := range labels {
lowerLabels[strings.ToLower(k)] = v
}
keys := make([]string, 0, len(labels))
for k := range lowerLabels {
keys = append(keys, k)
}
keys = sort.StringSlice(keys)
result := legendKeyFormat.ReplaceAllFunc([]byte(alias), func(in []byte) []byte {
metaPartName := strings.Replace(string(in), "{{", "", 1)
metaPartName = strings.Replace(metaPartName, "}}", "", 1)
metaPartName = strings.ToLower(strings.TrimSpace(metaPartName))
switch metaPartName {
case "metric":
return []byte(metricName)
case "dimensionname", "groupbyname":
return []byte(keys[0])
case "dimensionvalue", "groupbyvalue":
return []byte(lowerLabels[keys[0]])
}
if v, ok := lowerLabels[metaPartName]; ok {
return []byte(v)
}
return in
})
return string(result)
}
func applyInsightsMetricAlias(frame *data.Frame, alias string) {
if alias == "" {
return
}
for _, field := range frame.Fields {
if field.Type() == data.FieldTypeTime || field.Type() == data.FieldTypeNullableTime {
continue
}
displayName := formatApplicationInsightsLegendKey(alias, field.Name, field.Labels)
if field.Config == nil {
field.Config = &data.FieldConfig{}
}
field.Config.DisplayName = displayName
}
}
...@@ -18,6 +18,7 @@ func TestInsightsMetricsResultToFrame(t *testing.T) { ...@@ -18,6 +18,7 @@ func TestInsightsMetricsResultToFrame(t *testing.T) {
name string name string
testFile string testFile string
metric string metric string
alias string
agg string agg string
dimensions []string dimensions []string
expectedFrame func() *data.Frame expectedFrame func() *data.Frame
...@@ -102,6 +103,49 @@ func TestInsightsMetricsResultToFrame(t *testing.T) { ...@@ -102,6 +103,49 @@ func TestInsightsMetricsResultToFrame(t *testing.T) {
return frame return frame
}, },
}, },
{
name: "segmented series with alias",
testFile: "applicationinsights/4-application-insights-response-metrics-multi-segmented.json",
metric: "traces/count",
alias: "{{ metric }}: Country,City: {{ client/countryOrRegion }},{{ client/city }}",
agg: "sum",
dimensions: []string{"client/countryOrRegion", "client/city"},
expectedFrame: func() *data.Frame {
frame := data.NewFrame("",
data.NewField("StartTime", nil, []time.Time{
time.Date(2020, 6, 25, 16, 15, 32, 14e7, time.UTC),
time.Date(2020, 6, 25, 16, 16, 0, 0, time.UTC),
}),
data.NewField("traces/count", data.Labels{"client/city": "Washington", "client/countryOrRegion": "United States"}, []*float64{
pointer.Float64(2),
nil,
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,Washington"}),
data.NewField("traces/count", data.Labels{"client/city": "Des Moines", "client/countryOrRegion": "United States"}, []*float64{
pointer.Float64(2),
pointer.Float64(1),
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,Des Moines"}),
data.NewField("traces/count", data.Labels{"client/city": "", "client/countryOrRegion": "United States"}, []*float64{
nil,
pointer.Float64(11),
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,"}),
data.NewField("traces/count", data.Labels{"client/city": "Chicago", "client/countryOrRegion": "United States"}, []*float64{
nil,
pointer.Float64(3),
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: United States,Chicago"}),
data.NewField("traces/count", data.Labels{"client/city": "Tokyo", "client/countryOrRegion": "Japan"}, []*float64{
nil,
pointer.Float64(1),
}).SetConfig(&data.FieldConfig{DisplayName: "traces/count: Country,City: Japan,Tokyo"}),
)
return frame
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
...@@ -110,6 +154,9 @@ func TestInsightsMetricsResultToFrame(t *testing.T) { ...@@ -110,6 +154,9 @@ func TestInsightsMetricsResultToFrame(t *testing.T) {
frame, err := InsightsMetricsResultToFrame(res, tt.metric, tt.agg, tt.dimensions) frame, err := InsightsMetricsResultToFrame(res, tt.metric, tt.agg, tt.dimensions)
require.NoError(t, err) require.NoError(t, err)
applyInsightsMetricAlias(frame, tt.alias)
if diff := cmp.Diff(tt.expectedFrame(), frame, data.FrameTestCompareOptions()...); diff != "" { if diff := cmp.Diff(tt.expectedFrame(), frame, data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff) t.Errorf("Result mismatch (-want +got):\n%s", diff)
} }
......
...@@ -338,6 +338,17 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s ...@@ -338,6 +338,17 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
endIndex := strings.Index(seriesID, "/providers") endIndex := strings.Index(seriesID, "/providers")
resourceGroup := seriesID[startIndex:endIndex] resourceGroup := seriesID[startIndex:endIndex]
// Could be a collision problem if there were two keys that varied only in case, but I don't think that would happen in azure.
lowerLabels := data.Labels{}
for k, v := range labels {
lowerLabels[strings.ToLower(k)] = v
}
keys := make([]string, 0, len(labels))
for k := range lowerLabels {
keys = append(keys, k)
}
keys = sort.StringSlice(keys)
result := legendKeyFormat.ReplaceAllFunc([]byte(alias), func(in []byte) []byte { result := legendKeyFormat.ReplaceAllFunc([]byte(alias), func(in []byte) []byte {
metaPartName := strings.Replace(string(in), "{{", "", 1) metaPartName := strings.Replace(string(in), "{{", "", 1)
metaPartName = strings.Replace(metaPartName, "}}", "", 1) metaPartName = strings.Replace(metaPartName, "}}", "", 1)
...@@ -359,23 +370,15 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s ...@@ -359,23 +370,15 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
return []byte(metricName) return []byte(metricName)
} }
keys := make([]string, 0, len(labels))
if metaPartName == "dimensionname" || metaPartName == "dimensionvalue" {
for k := range labels {
keys = append(keys, k)
}
keys = sort.StringSlice(keys)
}
if metaPartName == "dimensionname" { if metaPartName == "dimensionname" {
return []byte(keys[0]) return []byte(keys[0])
} }
if metaPartName == "dimensionvalue" { if metaPartName == "dimensionvalue" {
return []byte(labels[keys[0]]) return []byte(lowerLabels[keys[0]])
} }
if v, ok := labels[metaPartName]; ok { if v, ok := lowerLabels[metaPartName]; ok {
return []byte(v) return []byte(v)
} }
return in return in
......
...@@ -374,7 +374,7 @@ func TestAzureMonitorParseResponse(t *testing.T) { ...@@ -374,7 +374,7 @@ func TestAzureMonitorParseResponse(t *testing.T) {
name: "multiple dimension time series response with label alias", name: "multiple dimension time series response with label alias",
responseFile: "7-azure-monitor-response-multi-dimension.json", responseFile: "7-azure-monitor-response-multi-dimension.json",
mockQuery: &AzureMonitorQuery{ mockQuery: &AzureMonitorQuery{
Alias: "{{resourcegroup}} {Blob Type={{blobtype}}, Tier={{tier}}}", Alias: "{{resourcegroup}} {Blob Type={{blobtype}}, Tier={{Tier}}}",
UrlComponents: map[string]string{ UrlComponents: map[string]string{
"resourceName": "grafana", "resourceName": "grafana",
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment