mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure: Multiple dimension support for Azure Monitor Service (#25947)
Azure Monitor (metrics) support multiple dimensions instead of just one. Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
72fa5ccb7b
commit
4be56cde0d
@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -21,7 +22,6 @@ import (
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
)
|
||||
|
||||
@ -57,7 +57,6 @@ func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, ori
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// azlog.Debug("AzureMonitor", "Response", resp)
|
||||
|
||||
err = e.parseResponse(queryRes, resp, query)
|
||||
if err != nil {
|
||||
@ -130,10 +129,25 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
|
||||
params.Add("metricnames", azJSONModel.MetricName) // MetricName or MetricNames ?
|
||||
params.Add("metricnamespace", azJSONModel.MetricNamespace)
|
||||
|
||||
// old model
|
||||
dimension := strings.TrimSpace(azJSONModel.Dimension)
|
||||
dimensionFilter := strings.TrimSpace(azJSONModel.DimensionFilter)
|
||||
if dimension != "" && dimensionFilter != "" && dimension != "None" {
|
||||
params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
|
||||
|
||||
dimSB := strings.Builder{}
|
||||
|
||||
if dimension != "" && dimensionFilter != "" && dimension != "None" && len(azJSONModel.DimensionsFilters) == 0 {
|
||||
dimSB.WriteString(fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
|
||||
} else {
|
||||
for i, filter := range azJSONModel.DimensionsFilters {
|
||||
dimSB.WriteString(filter.String())
|
||||
if i != len(azJSONModel.DimensionsFilters)-1 {
|
||||
dimSB.WriteString(" and ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dimSB.String() != "" {
|
||||
params.Add("$filter", dimSB.String())
|
||||
params.Add("top", azJSONModel.Top)
|
||||
}
|
||||
|
||||
@ -157,7 +171,7 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
|
||||
}
|
||||
|
||||
func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureMonitorQuery, queries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.QueryResult, AzureMonitorResponse, error) {
|
||||
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}
|
||||
queryResult := &tsdb.QueryResult{RefId: query.RefID}
|
||||
|
||||
req, err := e.createRequest(ctx, e.dsInfo)
|
||||
if err != nil {
|
||||
@ -167,7 +181,6 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureM
|
||||
|
||||
req.URL.Path = path.Join(req.URL.Path, query.URL)
|
||||
req.URL.RawQuery = query.Params.Encode()
|
||||
queryResult.Meta.Set("rawQuery", req.URL.RawQuery)
|
||||
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "azuremonitor query")
|
||||
span.SetTag("target", query.Target)
|
||||
@ -270,20 +283,23 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, amr A
|
||||
|
||||
frames := data.Frames{}
|
||||
for _, series := range amr.Value[0].Timeseries {
|
||||
metadataName := ""
|
||||
metadataValue := ""
|
||||
if len(series.Metadatavalues) > 0 {
|
||||
metadataName = series.Metadatavalues[0].Name.LocalizedValue
|
||||
metadataValue = series.Metadatavalues[0].Value
|
||||
labels := data.Labels{}
|
||||
for _, md := range series.Metadatavalues {
|
||||
labels[md.Name.LocalizedValue] = md.Value
|
||||
}
|
||||
metricName := formatAzureMonitorLegendKey(query.Alias, query.UrlComponents["resourceName"], amr.Value[0].Name.LocalizedValue, metadataName, metadataValue, amr.Namespace, amr.Value[0].ID)
|
||||
|
||||
frame := data.NewFrameOfFieldTypes("", len(series.Data), data.FieldTypeTime, data.FieldTypeFloat64)
|
||||
frame.RefID = query.RefID
|
||||
frame.Fields[1].Name = metricName
|
||||
frame.Fields[1].SetConfig(&data.FieldConfig{
|
||||
dataField := frame.Fields[1]
|
||||
dataField.Name = amr.Value[0].Name.LocalizedValue
|
||||
dataField.Labels = labels
|
||||
dataField.SetConfig(&data.FieldConfig{
|
||||
Unit: amr.Value[0].Unit,
|
||||
})
|
||||
if query.Alias != "" {
|
||||
dataField.Config.DisplayName = formatAzureMonitorLegendKey(query.Alias, query.UrlComponents["resourceName"],
|
||||
amr.Value[0].Name.LocalizedValue, "", "", amr.Namespace, amr.Value[0].ID, labels)
|
||||
}
|
||||
|
||||
requestedAgg := query.Params.Get("aggregation")
|
||||
|
||||
@ -317,14 +333,7 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, amr A
|
||||
|
||||
// formatAzureMonitorLegendKey builds the legend key or timeseries name
|
||||
// Alias patterns like {{resourcename}} are replaced with the appropriate data values.
|
||||
func formatAzureMonitorLegendKey(alias string, resourceName string, metricName string, metadataName string, metadataValue string, namespace string, seriesID string) string {
|
||||
if alias == "" {
|
||||
if len(metadataName) > 0 {
|
||||
return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", resourceName, metricName)
|
||||
}
|
||||
|
||||
func formatAzureMonitorLegendKey(alias string, resourceName string, metricName string, metadataName string, metadataValue string, namespace string, seriesID string, labels data.Labels) string {
|
||||
startIndex := strings.Index(seriesID, "/resourceGroups/") + 16
|
||||
endIndex := strings.Index(seriesID, "/providers")
|
||||
resourceGroup := seriesID[startIndex:endIndex]
|
||||
@ -350,14 +359,25 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
|
||||
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" {
|
||||
return []byte(metadataName)
|
||||
return []byte(keys[0])
|
||||
}
|
||||
|
||||
if metaPartName == "dimensionvalue" {
|
||||
return []byte(metadataValue)
|
||||
return []byte(labels[keys[0]])
|
||||
}
|
||||
|
||||
if v, ok := labels[metaPartName]; ok {
|
||||
return []byte(v)
|
||||
}
|
||||
return in
|
||||
})
|
||||
|
||||
|
@ -72,7 +72,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
|
||||
azureMonitorQueryTarget: "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
|
||||
},
|
||||
{
|
||||
name: "has a dimension filter",
|
||||
name: "has a dimension filter and none Dimension",
|
||||
azureMonitorVariedProperties: map[string]interface{}{
|
||||
"timeGrain": "PT1M",
|
||||
"dimension": "None",
|
||||
@ -83,6 +83,28 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
|
||||
expectedInterval: "PT1M",
|
||||
azureMonitorQueryTarget: "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z",
|
||||
},
|
||||
{
|
||||
name: "has dimensionFilter*s* property with one dimension",
|
||||
azureMonitorVariedProperties: map[string]interface{}{
|
||||
"timeGrain": "PT1M",
|
||||
"dimensionsFilters": []azureMonitorDimensionFilter{{"blob", "eq", "*"}},
|
||||
"top": "30",
|
||||
},
|
||||
queryIntervalMS: 400000,
|
||||
expectedInterval: "PT1M",
|
||||
azureMonitorQueryTarget: "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
|
||||
},
|
||||
{
|
||||
name: "has dimensionFilter*s* property with two dimensions",
|
||||
azureMonitorVariedProperties: map[string]interface{}{
|
||||
"timeGrain": "PT1M",
|
||||
"dimensionsFilters": []azureMonitorDimensionFilter{{"blob", "eq", "*"}, {"tier", "eq", "*"}},
|
||||
"top": "30",
|
||||
},
|
||||
queryIntervalMS: 400000,
|
||||
expectedInterval: "PT1M",
|
||||
azureMonitorQueryTarget: "%24filter=blob+eq+%27%2A%27+and+tier+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z&top=30",
|
||||
},
|
||||
}
|
||||
|
||||
commonAzureModelProps := map[string]interface{}{
|
||||
@ -139,9 +161,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
|
||||
}
|
||||
|
||||
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if diff := cmp.Diff(azureMonitorQuery, queries[0], cmpopts.IgnoreUnexported(simplejson.Json{}), cmpopts.IgnoreFields(AzureMonitorQuery{}, "Params")); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@ -179,7 +199,7 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 8, 10, 13, 0, 0, time.UTC), 5, time.Minute)),
|
||||
data.NewField("grafana.Percentage CPU", nil, []float64{
|
||||
data.NewField("Percentage CPU", nil, []float64{
|
||||
2.0875, 2.1525, 2.155, 3.6925, 2.44,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent"})),
|
||||
},
|
||||
@ -199,7 +219,7 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 13, 29, 0, 0, time.UTC), 5, time.Minute)),
|
||||
data.NewField("grafana.Percentage CPU", nil, []float64{
|
||||
data.NewField("Percentage CPU", nil, []float64{
|
||||
8.26, 8.7, 14.82, 10.07, 8.52,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent"})),
|
||||
},
|
||||
@ -219,7 +239,7 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 14, 26, 0, 0, time.UTC), 5, time.Minute)),
|
||||
data.NewField("grafana.Percentage CPU", nil, []float64{
|
||||
data.NewField("Percentage CPU", nil, []float64{
|
||||
3.07, 2.92, 2.87, 2.27, 2.52,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent"})),
|
||||
},
|
||||
@ -239,7 +259,7 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 14, 43, 0, 0, time.UTC), 5, time.Minute)),
|
||||
data.NewField("grafana.Percentage CPU", nil, []float64{
|
||||
data.NewField("Percentage CPU", nil, []float64{
|
||||
1.51, 2.38, 1.69, 2.27, 1.96,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent"})),
|
||||
},
|
||||
@ -259,14 +279,14 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 14, 44, 0, 0, time.UTC), 5, time.Minute)),
|
||||
data.NewField("grafana.Percentage CPU", nil, []float64{
|
||||
data.NewField("Percentage CPU", nil, []float64{
|
||||
4, 4, 4, 4, 4,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent"})),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi dimension time series response",
|
||||
responseFile: "6-azure-monitor-response-multi-dimension.json",
|
||||
name: "single dimension time series response",
|
||||
responseFile: "6-azure-monitor-response-single-dimension.json",
|
||||
mockQuery: &AzureMonitorQuery{
|
||||
UrlComponents: map[string]string{
|
||||
"resourceName": "grafana",
|
||||
@ -275,31 +295,24 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
"aggregation": {"Average"},
|
||||
},
|
||||
},
|
||||
// Regarding multi-dimensional response:
|
||||
// - It seems they all share the same time index, so maybe can be a wide frame.
|
||||
// - Due to the type for the Azure monitor response, nulls currently become 0.
|
||||
// - blogtype=X should maybe become labels.
|
||||
expectedFrames: data.Frames{
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 15, 21, 0, 0, time.UTC), 6, time.Hour)),
|
||||
data.NewField("grafana{blobtype=PageBlob}.Blob Count", nil, []float64{
|
||||
3, 3, 3, 3, 3, 0,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
data.NewField("Blob Count", data.Labels{"blobtype": "PageBlob"},
|
||||
[]float64{3, 3, 3, 3, 3, 0}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 15, 21, 0, 0, time.UTC), 6, time.Hour)),
|
||||
data.NewField("grafana{blobtype=BlockBlob}.Blob Count", nil, []float64{
|
||||
1, 1, 1, 1, 1, 0,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
data.NewField("Blob Count", data.Labels{"blobtype": "BlockBlob"},
|
||||
[]float64{1, 1, 1, 1, 1, 0}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 15, 21, 0, 0, time.UTC), 6, time.Hour)),
|
||||
data.NewField("grafana{blobtype=Azure Data Lake Storage}.Blob Count", nil, []float64{
|
||||
0, 0, 0, 0, 0, 0,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
data.NewField("Blob Count", data.Labels{"blobtype": "Azure Data Lake Storage"},
|
||||
[]float64{0, 0, 0, 0, 0, 0}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -318,14 +331,14 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 13, 29, 0, 0, time.UTC), 5, time.Minute)),
|
||||
data.NewField("custom grafanastaging Microsoft.Compute/virtualMachines grafana Percentage CPU", nil, []float64{
|
||||
data.NewField("Percentage CPU", nil, []float64{
|
||||
8.26, 8.7, 14.82, 10.07, 8.52,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent"})),
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Percent", DisplayName: "custom grafanastaging Microsoft.Compute/virtualMachines grafana Percentage CPU"})),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi dimension with alias",
|
||||
responseFile: "6-azure-monitor-response-multi-dimension.json",
|
||||
name: "single dimension with alias",
|
||||
responseFile: "6-azure-monitor-response-single-dimension.json",
|
||||
mockQuery: &AzureMonitorQuery{
|
||||
Alias: "{{dimensionname}}={{DimensionValue}}",
|
||||
UrlComponents: map[string]string{
|
||||
@ -339,23 +352,57 @@ func TestAzureMonitorParseResponse(t *testing.T) {
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 15, 21, 0, 0, time.UTC), 6, time.Hour)),
|
||||
data.NewField("blobtype=PageBlob", nil, []float64{
|
||||
3, 3, 3, 3, 3, 0,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
data.NewField("Blob Count", data.Labels{"blobtype": "PageBlob"},
|
||||
[]float64{3, 3, 3, 3, 3, 0}).SetConfig(&data.FieldConfig{Unit: "Count", DisplayName: "blobtype=PageBlob"})),
|
||||
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 15, 21, 0, 0, time.UTC), 6, time.Hour)),
|
||||
data.NewField("blobtype=BlockBlob", nil, []float64{
|
||||
data.NewField("Blob Count", data.Labels{"blobtype": "BlockBlob"}, []float64{
|
||||
1, 1, 1, 1, 1, 0,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count", DisplayName: "blobtype=BlockBlob"})),
|
||||
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2019, 2, 9, 15, 21, 0, 0, time.UTC), 6, time.Hour)),
|
||||
data.NewField("blobtype=Azure Data Lake Storage", nil, []float64{
|
||||
data.NewField("Blob Count", data.Labels{"blobtype": "Azure Data Lake Storage"}, []float64{
|
||||
0, 0, 0, 0, 0, 0,
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count"})),
|
||||
}).SetConfig(&data.FieldConfig{Unit: "Count", DisplayName: "blobtype=Azure Data Lake Storage"})),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple dimension time series response with label alias",
|
||||
responseFile: "7-azure-monitor-response-multi-dimension.json",
|
||||
mockQuery: &AzureMonitorQuery{
|
||||
Alias: "{{resourcegroup}} {Blob Type={{blobtype}}, Tier={{tier}}}",
|
||||
UrlComponents: map[string]string{
|
||||
"resourceName": "grafana",
|
||||
},
|
||||
Params: url.Values{
|
||||
"aggregation": {"Average"},
|
||||
},
|
||||
},
|
||||
expectedFrames: data.Frames{
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2020, 06, 30, 9, 58, 0, 0, time.UTC), 3, time.Hour)),
|
||||
data.NewField("Blob Capacity", data.Labels{"blobtype": "PageBlob", "tier": "Standard"},
|
||||
[]float64{675530, 675530, 675530}).SetConfig(
|
||||
&data.FieldConfig{Unit: "Bytes", DisplayName: "danieltest {Blob Type=PageBlob, Tier=Standard}"})),
|
||||
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2020, 06, 30, 9, 58, 0, 0, time.UTC), 3, time.Hour)),
|
||||
data.NewField("Blob Capacity", data.Labels{"blobtype": "BlockBlob", "tier": "Hot"},
|
||||
[]float64{0, 0, 0}).SetConfig(
|
||||
&data.FieldConfig{Unit: "Bytes", DisplayName: "danieltest {Blob Type=BlockBlob, Tier=Hot}"})),
|
||||
|
||||
data.NewFrame("",
|
||||
data.NewField("", nil,
|
||||
makeDates(time.Date(2020, 06, 30, 9, 58, 0, 0, time.UTC), 3, time.Hour)),
|
||||
data.NewField("Blob Capacity", data.Labels{"blobtype": "Azure Data Lake Storage", "tier": "Cool"},
|
||||
[]float64{0, 0, 0}).SetConfig(
|
||||
&data.FieldConfig{Unit: "Bytes", DisplayName: "danieltest {Blob Type=Azure Data Lake Storage, Tier=Cool}"})),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
1
pkg/tsdb/azuremonitor/testdata/applicationinsights/bigger.json
vendored
Normal file
1
pkg/tsdb/azuremonitor/testdata/applicationinsights/bigger.json
vendored
Normal file
File diff suppressed because one or more lines are too long
119
pkg/tsdb/azuremonitor/testdata/azuremonitor/7-azure-monitor-response-multi-dimension.json
vendored
Normal file
119
pkg/tsdb/azuremonitor/testdata/azuremonitor/7-azure-monitor-response-multi-dimension.json
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
{
|
||||
"cost": 0,
|
||||
"timespan": "2020-06-30T09:58:58Z/2020-06-30T12:58:58Z",
|
||||
"interval": "PT1H",
|
||||
"value": [
|
||||
{
|
||||
"id": "/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/danieltest/providers/Microsoft.Storage/storageAccounts/danieltestdiag187/blobServices/default/providers/Microsoft.Insights/metrics/BlobCapacity",
|
||||
"type": "Microsoft.Insights/metrics",
|
||||
"name": {
|
||||
"value": "BlobCapacity",
|
||||
"localizedValue": "Blob Capacity"
|
||||
},
|
||||
"displayDescription": "The amount of storage used by the storage account’s Blob service in bytes.",
|
||||
"unit": "Bytes",
|
||||
"timeseries": [
|
||||
{
|
||||
"metadatavalues": [
|
||||
{
|
||||
"name": {
|
||||
"value": "blobtype",
|
||||
"localizedValue": "blobtype"
|
||||
},
|
||||
"value": "PageBlob"
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"value": "tier",
|
||||
"localizedValue": "tier"
|
||||
},
|
||||
"value": "Standard"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"timeStamp": "2020-06-30T09:58:00Z",
|
||||
"average": 675530
|
||||
},
|
||||
{
|
||||
"timeStamp": "2020-06-30T10:58:00Z",
|
||||
"average": 675530
|
||||
},
|
||||
{
|
||||
"timeStamp": "2020-06-30T11:58:00Z",
|
||||
"average": 675530
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadatavalues": [
|
||||
{
|
||||
"name": {
|
||||
"value": "blobtype",
|
||||
"localizedValue": "blobtype"
|
||||
},
|
||||
"value": "BlockBlob"
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"value": "tier",
|
||||
"localizedValue": "tier"
|
||||
},
|
||||
"value": "Hot"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"timeStamp": "2020-06-30T09:58:00Z",
|
||||
"average": 0
|
||||
},
|
||||
{
|
||||
"timeStamp": "2020-06-30T10:58:00Z",
|
||||
"average": 0
|
||||
},
|
||||
{
|
||||
"timeStamp": "2020-06-30T11:58:00Z",
|
||||
"average": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadatavalues": [
|
||||
{
|
||||
"name": {
|
||||
"value": "blobtype",
|
||||
"localizedValue": "blobtype"
|
||||
},
|
||||
"value": "Azure Data Lake Storage"
|
||||
},
|
||||
{
|
||||
"name": {
|
||||
"value": "tier",
|
||||
"localizedValue": "tier"
|
||||
},
|
||||
"value": "Cool"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"timeStamp": "2020-06-30T09:58:00Z",
|
||||
"average": 0
|
||||
},
|
||||
{
|
||||
"timeStamp": "2020-06-30T10:58:00Z",
|
||||
"average": 0
|
||||
},
|
||||
{
|
||||
"timeStamp": "2020-06-30T11:58:00Z",
|
||||
"average": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errorCode": "Success"
|
||||
}
|
||||
],
|
||||
"namespace": "Microsoft.Storage/storageAccounts/blobServices",
|
||||
"resourceregion": "westeurope"
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ type azureMonitorJSONQuery struct {
|
||||
Aggregation string `json:"aggregation"`
|
||||
Alias string `json:"alias"`
|
||||
AllowedTimeGrainsMs []int64 `json:"allowedTimeGrainsMs"`
|
||||
Dimension string `json:"dimension"`
|
||||
DimensionFilter string `json:"dimensionFilter"`
|
||||
Dimension string `json:"dimension"` // old model
|
||||
DimensionFilter string `json:"dimensionFilter"` // old model
|
||||
Format string `json:"format"`
|
||||
MetricDefinition string `json:"metricDefinition"`
|
||||
MetricName string `json:"metricName"`
|
||||
@ -97,10 +97,24 @@ type azureMonitorJSONQuery struct {
|
||||
ResourceName string `json:"resourceName"`
|
||||
TimeGrain string `json:"timeGrain"`
|
||||
Top string `json:"top"`
|
||||
|
||||
DimensionsFilters []azureMonitorDimensionFilter `json:"dimensionsFilters"` // new model
|
||||
} `json:"azureMonitor"`
|
||||
Subscription string `json:"subscription"`
|
||||
}
|
||||
|
||||
// azureMonitorDimensionFilter is the model for the frontend sent for azureMonitor metric
|
||||
// queries like "BlobType", "eq", "*"
|
||||
type azureMonitorDimensionFilter struct {
|
||||
Dimension string `json:"dimension"`
|
||||
Operator string `json:"operator"`
|
||||
Filter string `json:"filter"`
|
||||
}
|
||||
|
||||
func (a azureMonitorDimensionFilter) String() string {
|
||||
return fmt.Sprintf("%v %v '%v'", a.Dimension, a.Operator, a.Filter)
|
||||
}
|
||||
|
||||
// insightsJSONQuery is the frontend JSON query model for an Azure Application Insights query.
|
||||
type insightsJSONQuery struct {
|
||||
AppInsights struct {
|
||||
|
@ -74,11 +74,18 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
const aggregation = templateSrv.replace(item.aggregation, scopedVars);
|
||||
const top = templateSrv.replace(item.top || '', scopedVars);
|
||||
|
||||
const dimensionsFilters = item.dimensionFilters.map(f => {
|
||||
return {
|
||||
dimension: templateSrv.replace(f.dimension, scopedVars),
|
||||
operator: f.operator || 'eq',
|
||||
filter: templateSrv.replace(f.filter, scopedVars),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
refId: target.refId,
|
||||
subscription: subscriptionId,
|
||||
queryType: AzureQueryType.AzureMonitor,
|
||||
type: 'timeSeriesQuery',
|
||||
azureMonitor: {
|
||||
resourceGroup,
|
||||
resourceName,
|
||||
@ -89,9 +96,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
metricNamespace:
|
||||
metricNamespace && metricNamespace !== defaultDropdownValue ? metricNamespace : metricDefinition,
|
||||
aggregation: aggregation,
|
||||
dimension: templateSrv.replace(item.dimension, scopedVars),
|
||||
dimensionsFilters,
|
||||
top: top || '10',
|
||||
dimensionFilter: templateSrv.replace(item.dimensionFilter, scopedVars),
|
||||
alias: item.alias,
|
||||
format: target.format,
|
||||
},
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
import { Observable, of, from } from 'rxjs';
|
||||
import { DataSourceWithBackend } from '@grafana/runtime';
|
||||
import InsightsAnalyticsDatasource from './insights_analytics/insights_analytics_datasource';
|
||||
import { migrateMetricsDimensionFilters } from './query_ctrl';
|
||||
|
||||
export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||
azureMonitorDatasource: AzureMonitorDatasource;
|
||||
@ -64,6 +65,10 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
|
||||
target.queryType = AzureQueryType.AzureMonitor;
|
||||
}
|
||||
|
||||
if (target.queryType === AzureQueryType.AzureMonitor) {
|
||||
migrateMetricsDimensionFilters(target.azureMonitor);
|
||||
}
|
||||
|
||||
// Check that we have options
|
||||
const opts = (target as any)[this.optionsKey[target.queryType]];
|
||||
|
||||
|
@ -132,29 +132,56 @@
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-inline" ng-show="ctrl.target.azureMonitor.dimensions.length > 0">
|
||||
|
||||
<!-- NO Filters-->
|
||||
<ng-container ng-if="ctrl.target.azureMonitor.dimensionFilters.length < 1">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-9">Dimension</label>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"><icon name="'plus'"></icon></a>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- YES Filters-->
|
||||
<ng-container ng-if="ctrl.target.azureMonitor.dimensionFilters.length > 0">
|
||||
<div ng-repeat="dim in ctrl.target.azureMonitor.dimensionFilters track by $index" class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-9">Dimension</label>
|
||||
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
|
||||
<select
|
||||
class="gf-form-input min-width-12"
|
||||
ng-model="ctrl.target.azureMonitor.dimension"
|
||||
ng-model="dim.dimension"
|
||||
ng-options="f.value as f.text for f in ctrl.target.azureMonitor.dimensions"
|
||||
ng-change="ctrl.refresh()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-3">eq</label>
|
||||
<input
|
||||
type="text"
|
||||
class="gf-form-input width-17"
|
||||
ng-model="ctrl.target.azureMonitor.dimensionFilter"
|
||||
ng-model="dim.filter"
|
||||
spellcheck="false"
|
||||
placeholder="auto"
|
||||
placeholder="Anything (*)"
|
||||
ng-blur="ctrl.refresh()"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<a ng-click="ctrl.azureMonitorRemoveDimensionFilter($index)" class="gf-form-label query-part"><icon name="'minus'"></icon></a>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="$last">
|
||||
<a ng-click="ctrl.azureMonitorAddDimensionFilter()" class="gf-form-label query-part"><icon name="'plus'"></icon></a>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-9">Top</label>
|
||||
<input
|
||||
@ -170,6 +197,8 @@
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-9">Legend Format</label>
|
||||
|
@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { auto, IPromise } from 'angular';
|
||||
import { DataFrame, PanelEvents } from '@grafana/data';
|
||||
import { AzureQueryType } from './types';
|
||||
import { AzureQueryType, AzureMetricQuery } from './types';
|
||||
|
||||
export interface ResultFormat {
|
||||
text: string;
|
||||
@ -27,23 +27,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
refId: string;
|
||||
queryType: AzureQueryType;
|
||||
subscription: string;
|
||||
azureMonitor: {
|
||||
resourceGroup: string;
|
||||
resourceName: string;
|
||||
metricDefinition: string;
|
||||
metricNamespace: string;
|
||||
metricName: string;
|
||||
dimensionFilter: string;
|
||||
timeGrain: string;
|
||||
timeGrainUnit: string;
|
||||
allowedTimeGrainsMs: number[];
|
||||
dimensions: any[];
|
||||
dimension: any;
|
||||
top: string;
|
||||
aggregation: string;
|
||||
aggOptions: string[];
|
||||
timeGrains: Array<{ text: string; value: string }>;
|
||||
};
|
||||
azureMonitor: AzureMetricQuery;
|
||||
azureLogAnalytics: {
|
||||
query: string;
|
||||
resultFormat: string;
|
||||
@ -139,6 +123,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
|
||||
this.migrateApplicationInsightsDimensions();
|
||||
|
||||
migrateMetricsDimensionFilters(this.target.azureMonitor);
|
||||
|
||||
this.panelCtrl.events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this), $scope);
|
||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||
this.resultFormats = [
|
||||
@ -219,12 +205,13 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
const oldAzureTimeGrains = (this.target.azureMonitor as any).timeGrains;
|
||||
if (
|
||||
this.target.azureMonitor.timeGrains &&
|
||||
this.target.azureMonitor.timeGrains.length > 0 &&
|
||||
oldAzureTimeGrains &&
|
||||
oldAzureTimeGrains.length > 0 &&
|
||||
(!this.target.azureMonitor.allowedTimeGrainsMs || this.target.azureMonitor.allowedTimeGrainsMs.length === 0)
|
||||
) {
|
||||
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(this.target.azureMonitor.timeGrains);
|
||||
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(oldAzureTimeGrains);
|
||||
}
|
||||
|
||||
if (
|
||||
@ -328,10 +315,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.metricName = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.aggregation = '';
|
||||
this.target.azureMonitor.timeGrains = [];
|
||||
this.target.azureMonitor.timeGrain = '';
|
||||
this.target.azureMonitor.dimensions = [];
|
||||
this.target.azureMonitor.dimension = '';
|
||||
this.target.azureMonitor.dimensionFilters = [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,10 +424,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
this.target.azureMonitor.metricNamespace = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.metricName = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.aggregation = '';
|
||||
this.target.azureMonitor.timeGrains = [];
|
||||
this.target.azureMonitor.timeGrain = '';
|
||||
this.target.azureMonitor.dimensions = [];
|
||||
this.target.azureMonitor.dimension = '';
|
||||
this.target.azureMonitor.dimensionFilters = [];
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@ -451,27 +434,22 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
this.target.azureMonitor.metricNamespace = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.metricName = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.aggregation = '';
|
||||
this.target.azureMonitor.timeGrains = [];
|
||||
this.target.azureMonitor.timeGrain = '';
|
||||
this.target.azureMonitor.dimensions = [];
|
||||
this.target.azureMonitor.dimension = '';
|
||||
this.target.azureMonitor.dimensionFilters = [];
|
||||
}
|
||||
|
||||
onResourceNameChange() {
|
||||
this.target.azureMonitor.metricNamespace = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.metricName = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.aggregation = '';
|
||||
this.target.azureMonitor.timeGrains = [];
|
||||
this.target.azureMonitor.timeGrain = '';
|
||||
this.target.azureMonitor.dimensions = [];
|
||||
this.target.azureMonitor.dimension = '';
|
||||
this.target.azureMonitor.dimensionFilters = [];
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
onMetricNamespacesChange() {
|
||||
this.target.azureMonitor.metricName = this.defaultDropdownValue;
|
||||
this.target.azureMonitor.dimensions = [];
|
||||
this.target.azureMonitor.dimension = '';
|
||||
this.target.azureMonitor.dimensionFilters = [];
|
||||
}
|
||||
|
||||
onMetricNameChange(): IPromise<void> {
|
||||
@ -489,16 +467,20 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
this.replace(this.target.azureMonitor.metricName)
|
||||
)
|
||||
.then((metadata: any) => {
|
||||
this.target.azureMonitor.aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType];
|
||||
this.target.azureMonitor.aggregation = metadata.primaryAggType;
|
||||
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
|
||||
this.target.azureMonitor.timeGrain = 'auto';
|
||||
console.log('Update metadata', metadata);
|
||||
|
||||
this.target.azureMonitor.aggregation = metadata.primaryAggType;
|
||||
this.target.azureMonitor.timeGrain = 'auto';
|
||||
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(metadata.supportedTimeGrains || []);
|
||||
|
||||
this.target.azureMonitor.dimensions = metadata.dimensions;
|
||||
// HACK: this saves the last metadata values in the panel json ¯\_(ツ)_/¯
|
||||
const hackState = this.target.azureMonitor as any;
|
||||
hackState.aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType];
|
||||
hackState.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
|
||||
hackState.dimensions = metadata.dimensions;
|
||||
|
||||
if (metadata.dimensions.length > 0) {
|
||||
this.target.azureMonitor.dimension = metadata.dimensions[0].value;
|
||||
// this.target.azureMonitor.dimension = metadata.dimensions[0].value;
|
||||
}
|
||||
|
||||
return this.refresh();
|
||||
@ -537,13 +519,29 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
getAzureMonitorAutoInterval() {
|
||||
return this.generateAutoUnits(this.target.azureMonitor.timeGrain, this.target.azureMonitor.timeGrains);
|
||||
return this.generateAutoUnits(this.target.azureMonitor.timeGrain, (this.target.azureMonitor as any).timeGrains);
|
||||
}
|
||||
|
||||
getApplicationInsightAutoInterval() {
|
||||
return this.generateAutoUnits(this.target.appInsights.timeGrain, this.target.appInsights.timeGrains);
|
||||
}
|
||||
|
||||
azureMonitorAddDimensionFilter() {
|
||||
console.log('Add dimension', this.target.azureMonitor);
|
||||
this.target.azureMonitor.dimensionFilters.push({
|
||||
dimension: '',
|
||||
operator: 'eq',
|
||||
filter: '',
|
||||
});
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
azureMonitorRemoveDimensionFilter(index: number) {
|
||||
this.target.azureMonitor.dimensionFilters.splice(index, 1);
|
||||
this.refresh();
|
||||
console.log('Remove dimension', index, this.target.azureMonitor);
|
||||
}
|
||||
|
||||
/* Azure Log Analytics */
|
||||
|
||||
getWorkspaces = () => {
|
||||
@ -695,3 +693,20 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Modifies the actual query object
|
||||
export function migrateMetricsDimensionFilters(item: AzureMetricQuery) {
|
||||
if (!item.dimensionFilters) {
|
||||
item.dimensionFilters = [];
|
||||
}
|
||||
const oldDimension = (item as any).dimension;
|
||||
if (oldDimension && oldDimension !== 'None') {
|
||||
item.dimensionFilters.push({
|
||||
dimension: oldDimension,
|
||||
operator: 'eq',
|
||||
filter: (item as any).dimensionFilter,
|
||||
});
|
||||
delete (item as any).dimension;
|
||||
delete (item as any).dimensionFilter;
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,12 @@ export interface AzureDataSourceSecureJsonData {
|
||||
appInsightsApiKey?: string;
|
||||
}
|
||||
|
||||
export interface AzureMetricDimension {
|
||||
dimension: string;
|
||||
operator: 'eq'; // future proof
|
||||
filter?: string; // *
|
||||
}
|
||||
|
||||
export interface AzureMetricQuery {
|
||||
resourceGroup: string;
|
||||
resourceName: string;
|
||||
@ -55,8 +61,7 @@ export interface AzureMetricQuery {
|
||||
timeGrain: string;
|
||||
allowedTimeGrainsMs: number[];
|
||||
aggregation: string;
|
||||
dimension: string;
|
||||
dimensionFilter: string;
|
||||
dimensionFilters: AzureMetricDimension[];
|
||||
alias: string;
|
||||
top: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user