Commit b816f35c by Daniel Lee

azuremonitor: handle multi-dimensions on backend

parent a5e5db20
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strings"
"time" "time"
"github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/api/pluginproxy"
...@@ -24,7 +25,7 @@ import ( ...@@ -24,7 +25,7 @@ import (
) )
var ( var (
slog log.Logger azlog log.Logger
) )
// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services // AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services
...@@ -47,7 +48,7 @@ func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, ...@@ -47,7 +48,7 @@ func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint,
} }
func init() { func init() {
slog = log.New("tsdb.azuremonitor") azlog = log.New("tsdb.azuremonitor")
tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor) tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor)
} }
...@@ -61,7 +62,6 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou ...@@ -61,7 +62,6 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou
queryType := tsdbQuery.Queries[0].Model.Get("queryType").MustString("") queryType := tsdbQuery.Queries[0].Model.Get("queryType").MustString("")
switch queryType { switch queryType {
case "azureMonitorTimeSeriesQuery":
case "Azure Monitor": case "Azure Monitor":
fallthrough fallthrough
default: default:
...@@ -112,26 +112,39 @@ func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Azure ...@@ -112,26 +112,39 @@ func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Azure
var target string var target string
azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() azureMonitorTarget := query.Model.Get("azureMonitor").MustMap()
azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
urlComponents := make(map[string]string) urlComponents := make(map[string]string)
urlComponents["resourceGroup"] = azureMonitorTarget["resourceGroup"].(string) urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
urlComponents["metricDefinition"] = azureMonitorTarget["metricDefinition"].(string) urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
urlComponents["resourceName"] = azureMonitorTarget["resourceName"].(string) urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"])
azureURL := fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", urlComponents["resourceGroup"], urlComponents["metricDefinition"], urlComponents["resourceName"]) ub := URLBuilder{
ResourceGroup: urlComponents["resourceGroup"],
MetricDefinition: urlComponents["metricDefinition"],
ResourceName: urlComponents["resourceName"],
}
azureURL := ub.Build()
alias := azureMonitorTarget["alias"].(string) alias := fmt.Sprintf("%v", azureMonitorTarget["alias"])
params := url.Values{} params := url.Values{}
params.Add("api-version", "2018-01-01") params.Add("api-version", "2018-01-01")
params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339)))
params.Add("interval", azureMonitorTarget["timeGrain"].(string)) params.Add("interval", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"]))
params.Add("aggregation", azureMonitorTarget["aggregation"].(string)) params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"]))
params.Add("metricnames", azureMonitorTarget["metricName"].(string)) params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"]))
dimension := fmt.Sprintf("%v", azureMonitorTarget["dimension"])
dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"]))
if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && dimensionFilter != "" {
params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
}
target = params.Encode() target = params.Encode()
if setting.Env == setting.DEV { if setting.Env == setting.DEV {
slog.Debug("Azuremonitor request", "params", params) azlog.Debug("Azuremonitor request", "params", params)
} }
azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{ azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{
...@@ -174,6 +187,7 @@ func (e *AzureMonitorExecutor) executeQuery(ctx context.Context, query *AzureMon ...@@ -174,6 +187,7 @@ func (e *AzureMonitorExecutor) executeQuery(ctx context.Context, query *AzureMon
opentracing.HTTPHeaders, opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(req.Header)) opentracing.HTTPHeadersCarrier(req.Header))
azlog.Debug("AzureMonitor", "Request URL", req.URL.String())
res, err := ctxhttp.Do(ctx, e.httpClient, req) res, err := ctxhttp.Do(ctx, e.httpClient, req)
if err != nil { if err != nil {
queryResult.Error = err queryResult.Error = err
...@@ -213,7 +227,7 @@ func (e *AzureMonitorExecutor) createRequest(ctx context.Context, dsInfo *models ...@@ -213,7 +227,7 @@ func (e *AzureMonitorExecutor) createRequest(ctx context.Context, dsInfo *models
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
slog.Error("Failed to create request", "error", err) azlog.Error("Failed to create request", "error", err)
return nil, fmt.Errorf("Failed to create request. error: %v", err) return nil, fmt.Errorf("Failed to create request. error: %v", err)
} }
...@@ -233,14 +247,14 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit ...@@ -233,14 +247,14 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit
} }
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
slog.Error("Request failed", "status", res.Status, "body", string(body)) azlog.Error("Request failed", "status", res.Status, "body", string(body))
return AzureMonitorResponse{}, fmt.Errorf(string(body)) return AzureMonitorResponse{}, fmt.Errorf(string(body))
} }
var data AzureMonitorResponse var data AzureMonitorResponse
err = json.Unmarshal(body, &data) err = json.Unmarshal(body, &data)
if err != nil { if err != nil {
slog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body)) azlog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body))
return AzureMonitorResponse{}, err return AzureMonitorResponse{}, err
} }
...@@ -248,14 +262,24 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit ...@@ -248,14 +262,24 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit
} }
func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error { func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error {
slog.Info("AzureMonitor", "Response", data) azlog.Debug("AzureMonitor", "Response", data)
for _, series := range data.Value { if len(data.Value) == 0 {
return nil
}
for _, series := range data.Value[0].Timeseries {
points := make([]tsdb.TimePoint, 0) points := make([]tsdb.TimePoint, 0)
defaultMetricName := fmt.Sprintf("%s.%s", query.UrlComponents["resourceName"], series.Name.LocalizedValue) metadataName := ""
metadataValue := ""
if len(series.Metadatavalues) > 0 {
metadataName = series.Metadatavalues[0].Name.LocalizedValue
metadataValue = series.Metadatavalues[0].Value
}
defaultMetricName := formatLegendKey(query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue)
for _, point := range series.Timeseries[0].Data { for _, point := range series.Data {
var value float64 var value float64
switch query.Params.Get("aggregation") { switch query.Params.Get("aggregation") {
case "Average": case "Average":
......
...@@ -178,6 +178,33 @@ func TestAzureMonitor(t *testing.T) { ...@@ -178,6 +178,33 @@ func TestAzureMonitor(t *testing.T) {
So(res.Series[0].Points[0][0].Float64, ShouldEqual, 4) So(res.Series[0].Points[0][0].Float64, ShouldEqual, 4)
So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549723440000) So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549723440000)
}) })
Convey("when data from query aggregated as total and has dimension filter", func() {
data, err := loadTestFile("./test-data/6-azure-monitor-response-multi-dimension.json")
So(err, ShouldBeNil)
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
query := &AzureMonitorQuery{
UrlComponents: map[string]string{
"resourceName": "grafana",
},
Params: url.Values{
"aggregation": {"Average"},
},
}
err = executor.parseResponse(res, data, query)
So(err, ShouldBeNil)
So(len(res.Series), ShouldEqual, 3)
So(res.Series[0].Name, ShouldEqual, "grafana{blobtype=PageBlob}.Blob Count")
So(res.Series[0].Points[0][0].Float64, ShouldEqual, 3)
So(res.Series[1].Name, ShouldEqual, "grafana{blobtype=BlockBlob}.Blob Count")
So(res.Series[1].Points[0][0].Float64, ShouldEqual, 1)
So(res.Series[2].Name, ShouldEqual, "grafana{blobtype=Azure Data Lake Storage}.Blob Count")
So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0)
})
}) })
}) })
} }
......
package azuremonitor
import "fmt"
// formatLegendKey builds the legend key or timeseries name
func formatLegendKey(resourceName string, metricName string, metadataName string, metadataValue string) string {
if len(metadataName) > 0 {
return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName)
}
return fmt.Sprintf("%s.%s", resourceName, metricName)
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"interval": "PT1M", "interval": "PT1M",
"value": [ "value": [
{ {
"id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU",
"type": "Microsoft.Insights\/metrics", "type": "Microsoft.Insights\/metrics",
"name": { "name": {
"value": "Percentage CPU", "value": "Percentage CPU",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"interval": "PT1M", "interval": "PT1M",
"value": [ "value": [
{ {
"id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU",
"type": "Microsoft.Insights\/metrics", "type": "Microsoft.Insights\/metrics",
"name": { "name": {
"value": "Percentage CPU", "value": "Percentage CPU",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"interval": "PT1M", "interval": "PT1M",
"value": [ "value": [
{ {
"id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU",
"type": "Microsoft.Insights\/metrics", "type": "Microsoft.Insights\/metrics",
"name": { "name": {
"value": "Percentage CPU", "value": "Percentage CPU",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"interval": "PT1M", "interval": "PT1M",
"value": [ "value": [
{ {
"id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU",
"type": "Microsoft.Insights\/metrics", "type": "Microsoft.Insights\/metrics",
"name": { "name": {
"value": "Percentage CPU", "value": "Percentage CPU",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"interval": "PT1M", "interval": "PT1M",
"value": [ "value": [
{ {
"id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU",
"type": "Microsoft.Insights\/metrics", "type": "Microsoft.Insights\/metrics",
"name": { "name": {
"value": "Percentage CPU", "value": "Percentage CPU",
......
{
"cost": 0,
"timespan": "2019-02-09T15:21:39Z\/2019-02-09T21:21:39Z",
"interval": "PT1H",
"value": [
{
"id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Storage\/storageAccounts\/grafanastaging\/blobServices\/default\/providers\/Microsoft.Insights\/metrics\/BlobCount",
"type": "Microsoft.Insights\/metrics",
"name": {
"value": "BlobCount",
"localizedValue": "Blob Count"
},
"unit": "Count",
"timeseries": [
{
"metadatavalues": [
{
"name": {
"value": "blobtype",
"localizedValue": "blobtype"
},
"value": "PageBlob"
}
],
"data": [
{
"timeStamp": "2019-02-09T15:21:00Z",
"average": 3
},
{
"timeStamp": "2019-02-09T16:21:00Z",
"average": 3
},
{
"timeStamp": "2019-02-09T17:21:00Z",
"average": 3
},
{
"timeStamp": "2019-02-09T18:21:00Z",
"average": 3
},
{
"timeStamp": "2019-02-09T19:21:00Z",
"average": 3
},
{
"timeStamp": "2019-02-09T20:21:00Z"
}
]
},
{
"metadatavalues": [
{
"name": {
"value": "blobtype",
"localizedValue": "blobtype"
},
"value": "BlockBlob"
}
],
"data": [
{
"timeStamp": "2019-02-09T15:21:00Z",
"average": 1
},
{
"timeStamp": "2019-02-09T16:21:00Z",
"average": 1
},
{
"timeStamp": "2019-02-09T17:21:00Z",
"average": 1
},
{
"timeStamp": "2019-02-09T18:21:00Z",
"average": 1
},
{
"timeStamp": "2019-02-09T19:21:00Z",
"average": 1
},
{
"timeStamp": "2019-02-09T20:21:00Z"
}
]
},
{
"metadatavalues": [
{
"name": {
"value": "blobtype",
"localizedValue": "blobtype"
},
"value": "Azure Data Lake Storage"
}
],
"data": [
{
"timeStamp": "2019-02-09T15:21:00Z",
"average": 0
},
{
"timeStamp": "2019-02-09T16:21:00Z",
"average": 0
},
{
"timeStamp": "2019-02-09T17:21:00Z",
"average": 0
},
{
"timeStamp": "2019-02-09T18:21:00Z",
"average": 0
},
{
"timeStamp": "2019-02-09T19:21:00Z",
"average": 0
},
{
"timeStamp": "2019-02-09T20:21:00Z"
}
]
}
]
}
],
"namespace": "Microsoft.Storage\/storageAccounts\/blobServices",
"resourceregion": "westeurope"
}
package azuremonitor
import (
"fmt"
"strings"
)
// URLBuilder builds the URL for calling the Azure Monitor API
type URLBuilder struct {
ResourceGroup string
MetricDefinition string
ResourceName string
}
// Build checks the metric definition property to see which form of the url
// should be returned
func (ub *URLBuilder) Build() string {
if strings.Count(ub.MetricDefinition, "/") > 1 {
rn := strings.Split(ub.ResourceName, "/")
lastIndex := strings.LastIndex(ub.MetricDefinition, "/")
service := ub.MetricDefinition[lastIndex+1:]
md := ub.MetricDefinition[0:lastIndex]
return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, md, rn[0], service, rn[1])
}
return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, ub.MetricDefinition, ub.ResourceName)
}
package azuremonitor
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestURLBuilder(t *testing.T) {
Convey("AzureMonitor URL Builder", t, func() {
Convey("when metric definition is in the short form", func() {
ub := &URLBuilder{
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Compute/virtualMachines",
ResourceName: "rn",
}
url := ub.Build()
So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics")
})
Convey("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func() {
ub := &URLBuilder{
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices",
ResourceName: "rn1/default",
}
url := ub.Build()
So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics")
})
Convey("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func() {
ub := &URLBuilder{
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices",
ResourceName: "rn1/default",
}
url := ub.Build()
So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics")
})
})
}
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