mirror of https://github.com/grafana/grafana.git
azuremonitor: handle multi-dimensions on backend
This commit is contained in:
parent
a5e5db20e1
commit
b816f35c41
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
||||
|
|
@ -24,7 +25,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
slog log.Logger
|
||||
azlog log.Logger
|
||||
)
|
||||
|
||||
// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services
|
||||
|
|
@ -47,7 +48,7 @@ func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint,
|
|||
}
|
||||
|
||||
func init() {
|
||||
slog = log.New("tsdb.azuremonitor")
|
||||
azlog = log.New("tsdb.azuremonitor")
|
||||
tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor)
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +62,6 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou
|
|||
queryType := tsdbQuery.Queries[0].Model.Get("queryType").MustString("")
|
||||
|
||||
switch queryType {
|
||||
case "azureMonitorTimeSeriesQuery":
|
||||
case "Azure Monitor":
|
||||
fallthrough
|
||||
default:
|
||||
|
|
@ -112,26 +112,39 @@ func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Azure
|
|||
var target string
|
||||
|
||||
azureMonitorTarget := query.Model.Get("azureMonitor").MustMap()
|
||||
azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
|
||||
|
||||
urlComponents := make(map[string]string)
|
||||
urlComponents["resourceGroup"] = azureMonitorTarget["resourceGroup"].(string)
|
||||
urlComponents["metricDefinition"] = azureMonitorTarget["metricDefinition"].(string)
|
||||
urlComponents["resourceName"] = azureMonitorTarget["resourceName"].(string)
|
||||
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
|
||||
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
|
||||
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.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("interval", azureMonitorTarget["timeGrain"].(string))
|
||||
params.Add("aggregation", azureMonitorTarget["aggregation"].(string))
|
||||
params.Add("metricnames", azureMonitorTarget["metricName"].(string))
|
||||
params.Add("interval", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"]))
|
||||
params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"]))
|
||||
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()
|
||||
|
||||
if setting.Env == setting.DEV {
|
||||
slog.Debug("Azuremonitor request", "params", params)
|
||||
azlog.Debug("Azuremonitor request", "params", params)
|
||||
}
|
||||
|
||||
azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{
|
||||
|
|
@ -174,6 +187,7 @@ func (e *AzureMonitorExecutor) executeQuery(ctx context.Context, query *AzureMon
|
|||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(req.Header))
|
||||
|
||||
azlog.Debug("AzureMonitor", "Request URL", req.URL.String())
|
||||
res, err := ctxhttp.Do(ctx, e.httpClient, req)
|
||||
if err != nil {
|
||||
queryResult.Error = err
|
||||
|
|
@ -213,7 +227,7 @@ func (e *AzureMonitorExecutor) createRequest(ctx context.Context, dsInfo *models
|
|||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), 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)
|
||||
}
|
||||
|
||||
|
|
@ -233,14 +247,14 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit
|
|||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
var data AzureMonitorResponse
|
||||
err = json.Unmarshal(body, &data)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -248,14 +262,24 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
switch query.Params.Get("aggregation") {
|
||||
case "Average":
|
||||
|
|
|
|||
|
|
@ -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][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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
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 @@
|
|||
"interval": "PT1M",
|
||||
"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",
|
||||
"name": {
|
||||
"value": "Percentage CPU",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"interval": "PT1M",
|
||||
"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",
|
||||
"name": {
|
||||
"value": "Percentage CPU",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"interval": "PT1M",
|
||||
"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",
|
||||
"name": {
|
||||
"value": "Percentage CPU",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"interval": "PT1M",
|
||||
"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",
|
||||
"name": {
|
||||
"value": "Percentage CPU",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"interval": "PT1M",
|
||||
"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",
|
||||
"name": {
|
||||
"value": "Percentage CPU",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
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)
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
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")
|
||||
})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue