mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: remove duplicate query logic on the frontend (#17198)
* feat: AzureMonitor implements legend key on backend To be able to remove the duplicated query logic on the frontend, the backend code needs to implement alias patterns for legend keys as well as allowing the default list of allowed time grains to be overridden. Some metrics do not support all the time grains and the auto timegrain calculation can be incorrect if the list is not overridden. * feat: AzureMonitor - removes duplicate query logic on frontend * AzureMonitor small refactoring Extracted method and tidied up the auto time grain code. * azuremonitor: support for auto time grains for alerting Converts allowed timegrains into ms and saves in dashboard json. This makes queries for alerting with an auto time grain work in the same way as the frontend. * chore: typings -> implicitAny count down to 3413 * azuremonitor: add more typings
This commit is contained in:
parent
55b63905ea
commit
7e95ded164
@ -32,7 +32,7 @@ type AzureMonitorDatasource struct {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// 1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d in milliseconds
|
// 1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d in milliseconds
|
||||||
allowedIntervalsMS = []int64{60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000}
|
defaultAllowedIntervalsMS = []int64{60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000}
|
||||||
)
|
)
|
||||||
|
|
||||||
// executeTimeSeriesQuery does the following:
|
// executeTimeSeriesQuery does the following:
|
||||||
@ -99,13 +99,15 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
|
|||||||
}
|
}
|
||||||
azureURL := ub.Build()
|
azureURL := ub.Build()
|
||||||
|
|
||||||
alias := fmt.Sprintf("%v", azureMonitorTarget["alias"])
|
alias := ""
|
||||||
|
if val, ok := azureMonitorTarget["alias"]; ok {
|
||||||
|
alias = fmt.Sprintf("%v", val)
|
||||||
|
}
|
||||||
|
|
||||||
timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])
|
timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])
|
||||||
|
timeGrains := azureMonitorTarget["allowedTimeGrainsMs"]
|
||||||
if timeGrain == "auto" {
|
if timeGrain == "auto" {
|
||||||
autoInterval := e.findClosestAllowedIntervalMS(query.IntervalMs)
|
timeGrain, err = e.setAutoTimeGrain(query.IntervalMs, timeGrains)
|
||||||
tg := &TimeGrain{}
|
|
||||||
timeGrain, err = tg.createISO8601DurationFromIntervalMS(autoInterval)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -120,7 +122,7 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
|
|||||||
|
|
||||||
dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimension"]))
|
dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimension"]))
|
||||||
dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"]))
|
dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"]))
|
||||||
if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 {
|
if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 && dimension != "None" {
|
||||||
params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
|
params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +145,35 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
|
|||||||
return azureMonitorQueries, nil
|
return azureMonitorQueries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setAutoTimeGrain tries to find the closest interval to the query's intervalMs value
|
||||||
|
// if the metric has a limited set of possible intervals/time grains then use those
|
||||||
|
// instead of the default list of intervals
|
||||||
|
func (e *AzureMonitorDatasource) setAutoTimeGrain(intervalMs int64, timeGrains interface{}) (string, error) {
|
||||||
|
// parses array of numbers from the timeGrains json field
|
||||||
|
allowedTimeGrains := []int64{}
|
||||||
|
tgs, ok := timeGrains.([]interface{})
|
||||||
|
if ok {
|
||||||
|
for _, v := range tgs {
|
||||||
|
jsonNumber, ok := v.(json.Number)
|
||||||
|
if ok {
|
||||||
|
tg, err := jsonNumber.Int64()
|
||||||
|
if err == nil {
|
||||||
|
allowedTimeGrains = append(allowedTimeGrains, tg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
autoInterval := e.findClosestAllowedIntervalMS(intervalMs, allowedTimeGrains)
|
||||||
|
tg := &TimeGrain{}
|
||||||
|
autoTimeGrain, err := tg.createISO8601DurationFromIntervalMS(autoInterval)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoTimeGrain, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureMonitorQuery, queries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.QueryResult, AzureMonitorResponse, error) {
|
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{Meta: simplejson.New(), RefId: query.RefID}
|
||||||
|
|
||||||
@ -257,7 +288,7 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data
|
|||||||
metadataName = series.Metadatavalues[0].Name.LocalizedValue
|
metadataName = series.Metadatavalues[0].Name.LocalizedValue
|
||||||
metadataValue = series.Metadatavalues[0].Value
|
metadataValue = series.Metadatavalues[0].Value
|
||||||
}
|
}
|
||||||
defaultMetricName := formatLegendKey(query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue)
|
metricName := formatLegendKey(query.Alias, query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue, data.Namespace, data.Value[0].ID)
|
||||||
|
|
||||||
for _, point := range series.Data {
|
for _, point := range series.Data {
|
||||||
var value float64
|
var value float64
|
||||||
@ -279,10 +310,11 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
||||||
Name: defaultMetricName,
|
Name: metricName,
|
||||||
Points: points,
|
Points: points,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
queryRes.Meta.Set("unit", data.Value[0].Unit)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -290,13 +322,21 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data
|
|||||||
// findClosestAllowedIntervalMs is used for the auto time grain setting.
|
// findClosestAllowedIntervalMs is used for the auto time grain setting.
|
||||||
// It finds the closest time grain from the list of allowed time grains for Azure Monitor
|
// It finds the closest time grain from the list of allowed time grains for Azure Monitor
|
||||||
// using the Grafana interval in milliseconds
|
// using the Grafana interval in milliseconds
|
||||||
func (e *AzureMonitorDatasource) findClosestAllowedIntervalMS(intervalMs int64) int64 {
|
// Some metrics only allow a limited list of time grains. The allowedTimeGrains parameter
|
||||||
closest := allowedIntervalsMS[0]
|
// allows overriding the default list of allowed time grains.
|
||||||
|
func (e *AzureMonitorDatasource) findClosestAllowedIntervalMS(intervalMs int64, allowedTimeGrains []int64) int64 {
|
||||||
|
allowedIntervals := defaultAllowedIntervalsMS
|
||||||
|
|
||||||
for i, allowed := range allowedIntervalsMS {
|
if len(allowedTimeGrains) > 0 {
|
||||||
|
allowedIntervals = allowedTimeGrains
|
||||||
|
}
|
||||||
|
|
||||||
|
closest := allowedIntervals[0]
|
||||||
|
|
||||||
|
for i, allowed := range allowedIntervals {
|
||||||
if intervalMs > allowed {
|
if intervalMs > allowed {
|
||||||
if i+1 < len(allowedIntervalsMS) {
|
if i+1 < len(allowedIntervals) {
|
||||||
closest = allowedIntervalsMS[i+1]
|
closest = allowedIntervals[i+1]
|
||||||
} else {
|
} else {
|
||||||
closest = allowed
|
closest = allowed
|
||||||
}
|
}
|
||||||
@ -306,9 +346,50 @@ func (e *AzureMonitorDatasource) findClosestAllowedIntervalMS(intervalMs int64)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// formatLegendKey builds the legend key or timeseries name
|
// formatLegendKey builds the legend key or timeseries name
|
||||||
func formatLegendKey(resourceName string, metricName string, metadataName string, metadataValue string) string {
|
// Alias patterns like {{resourcename}} are replaced with the appropriate data values.
|
||||||
|
func formatLegendKey(alias string, resourceName string, metricName string, metadataName string, metadataValue string, namespace string, seriesID string) string {
|
||||||
|
if alias == "" {
|
||||||
if len(metadataName) > 0 {
|
if len(metadataName) > 0 {
|
||||||
return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName)
|
return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s.%s", resourceName, metricName)
|
return fmt.Sprintf("%s.%s", resourceName, metricName)
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex := strings.Index(seriesID, "/resourceGroups/") + 16
|
||||||
|
endIndex := strings.Index(seriesID, "/providers")
|
||||||
|
resourceGroup := seriesID[startIndex:endIndex]
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
if metaPartName == "resourcegroup" {
|
||||||
|
return []byte(resourceGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaPartName == "namespace" {
|
||||||
|
return []byte(namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaPartName == "resourcename" {
|
||||||
|
return []byte(resourceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaPartName == "metric" {
|
||||||
|
return []byte(metricName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaPartName == "dimensionname" {
|
||||||
|
return []byte(metadataName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaPartName == "dimensionvalue" {
|
||||||
|
return []byte(metadataValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return in
|
||||||
|
})
|
||||||
|
|
||||||
|
return string(result)
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,49 @@ func TestAzureMonitorDatasource(t *testing.T) {
|
|||||||
So(queries[0].Alias, ShouldEqual, "testalias")
|
So(queries[0].Alias, ShouldEqual, "testalias")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("and has a time grain set to auto", func() {
|
||||||
|
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"azureMonitor": map[string]interface{}{
|
||||||
|
"timeGrain": "auto",
|
||||||
|
"aggregation": "Average",
|
||||||
|
"resourceGroup": "grafanastaging",
|
||||||
|
"resourceName": "grafana",
|
||||||
|
"metricDefinition": "Microsoft.Compute/virtualMachines",
|
||||||
|
"metricName": "Percentage CPU",
|
||||||
|
"alias": "testalias",
|
||||||
|
"queryType": "Azure Monitor",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tsdbQuery.Queries[0].IntervalMs = 400000
|
||||||
|
|
||||||
|
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(queries[0].Params["interval"][0], ShouldEqual, "PT15M")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("and has a time grain set to auto and the metric has a limited list of allowed time grains", func() {
|
||||||
|
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"azureMonitor": map[string]interface{}{
|
||||||
|
"timeGrain": "auto",
|
||||||
|
"aggregation": "Average",
|
||||||
|
"resourceGroup": "grafanastaging",
|
||||||
|
"resourceName": "grafana",
|
||||||
|
"metricDefinition": "Microsoft.Compute/virtualMachines",
|
||||||
|
"metricName": "Percentage CPU",
|
||||||
|
"alias": "testalias",
|
||||||
|
"queryType": "Azure Monitor",
|
||||||
|
"allowedTimeGrainsMs": []interface{}{"auto", json.Number("60000"), json.Number("300000")},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tsdbQuery.Queries[0].IntervalMs = 400000
|
||||||
|
|
||||||
|
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(queries[0].Params["interval"][0], ShouldEqual, "PT5M")
|
||||||
|
})
|
||||||
|
|
||||||
Convey("and has a dimension filter", func() {
|
Convey("and has a dimension filter", func() {
|
||||||
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
|
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
|
||||||
"azureMonitor": map[string]interface{}{
|
"azureMonitor": map[string]interface{}{
|
||||||
@ -89,6 +132,29 @@ func TestAzureMonitorDatasource(t *testing.T) {
|
|||||||
So(queries[0].Target, ShouldEqual, "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
|
So(queries[0].Target, ShouldEqual, "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("and has a dimension filter set to None", func() {
|
||||||
|
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"azureMonitor": map[string]interface{}{
|
||||||
|
"timeGrain": "PT1M",
|
||||||
|
"aggregation": "Average",
|
||||||
|
"resourceGroup": "grafanastaging",
|
||||||
|
"resourceName": "grafana",
|
||||||
|
"metricDefinition": "Microsoft.Compute/virtualMachines",
|
||||||
|
"metricName": "Percentage CPU",
|
||||||
|
"alias": "testalias",
|
||||||
|
"queryType": "Azure Monitor",
|
||||||
|
"dimension": "None",
|
||||||
|
"dimensionFilter": "*",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
|
||||||
|
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Parse AzureMonitor API response in the time series format", func() {
|
Convey("Parse AzureMonitor API response in the time series format", func() {
|
||||||
@ -235,6 +301,48 @@ func TestAzureMonitorDatasource(t *testing.T) {
|
|||||||
So(res.Series[2].Name, ShouldEqual, "grafana{blobtype=Azure Data Lake Storage}.Blob Count")
|
So(res.Series[2].Name, ShouldEqual, "grafana{blobtype=Azure Data Lake Storage}.Blob Count")
|
||||||
So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0)
|
So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("when data from query has alias patterns", func() {
|
||||||
|
data, err := loadTestFile("./test-data/2-azure-monitor-response-total.json")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
|
||||||
|
query := &AzureMonitorQuery{
|
||||||
|
Alias: "custom {{resourcegroup}} {{namespace}} {{resourceName}} {{metric}}",
|
||||||
|
UrlComponents: map[string]string{
|
||||||
|
"resourceName": "grafana",
|
||||||
|
},
|
||||||
|
Params: url.Values{
|
||||||
|
"aggregation": {"Total"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = datasource.parseResponse(res, data, query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(res.Series[0].Name, ShouldEqual, "custom grafanastaging Microsoft.Compute/virtualMachines grafana Percentage CPU")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("when data has dimension filters and alias patterns", 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{
|
||||||
|
Alias: "{{dimensionname}}={{DimensionValue}}",
|
||||||
|
UrlComponents: map[string]string{
|
||||||
|
"resourceName": "grafana",
|
||||||
|
},
|
||||||
|
Params: url.Values{
|
||||||
|
"aggregation": {"Average"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = datasource.parseResponse(res, data, query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(res.Series[0].Name, ShouldEqual, "blobtype=PageBlob")
|
||||||
|
So(res.Series[1].Name, ShouldEqual, "blobtype=BlockBlob")
|
||||||
|
So(res.Series[2].Name, ShouldEqual, "blobtype=Azure Data Lake Storage")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Find closest allowed interval for auto time grain", func() {
|
Convey("Find closest allowed interval for auto time grain", func() {
|
||||||
@ -247,13 +355,16 @@ func TestAzureMonitorDatasource(t *testing.T) {
|
|||||||
"2d": 172800000,
|
"2d": 172800000,
|
||||||
}
|
}
|
||||||
|
|
||||||
closest := datasource.findClosestAllowedIntervalMS(intervals["3m"])
|
closest := datasource.findClosestAllowedIntervalMS(intervals["3m"], []int64{})
|
||||||
So(closest, ShouldEqual, intervals["5m"])
|
So(closest, ShouldEqual, intervals["5m"])
|
||||||
|
|
||||||
closest = datasource.findClosestAllowedIntervalMS(intervals["10m"])
|
closest = datasource.findClosestAllowedIntervalMS(intervals["10m"], []int64{})
|
||||||
So(closest, ShouldEqual, intervals["15m"])
|
So(closest, ShouldEqual, intervals["15m"])
|
||||||
|
|
||||||
closest = datasource.findClosestAllowedIntervalMS(intervals["2d"])
|
closest = datasource.findClosestAllowedIntervalMS(intervals["2d"], []int64{})
|
||||||
|
So(closest, ShouldEqual, intervals["1d"])
|
||||||
|
|
||||||
|
closest = datasource.findClosestAllowedIntervalMS(intervals["3m"], []int64{intervals["1d"]})
|
||||||
So(closest, ShouldEqual, intervals["1d"])
|
So(closest, ShouldEqual, intervals["1d"])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
azlog log.Logger
|
azlog log.Logger
|
||||||
|
legendKeyFormat *regexp.Regexp
|
||||||
)
|
)
|
||||||
|
|
||||||
// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services
|
// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services
|
||||||
@ -36,6 +38,7 @@ func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint,
|
|||||||
func init() {
|
func init() {
|
||||||
azlog = log.New("tsdb.azuremonitor")
|
azlog = log.New("tsdb.azuremonitor")
|
||||||
tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor)
|
tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor)
|
||||||
|
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query takes in the frontend queries, parses them into the query format
|
// Query takes in the frontend queries, parses them into the query format
|
||||||
|
@ -93,52 +93,36 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
metricDefinition: 'Microsoft.Compute/virtualMachines',
|
metricDefinition: 'Microsoft.Compute/virtualMachines',
|
||||||
metricName: 'Percentage CPU',
|
metricName: 'Percentage CPU',
|
||||||
timeGrain: 'PT1H',
|
timeGrain: 'PT1H',
|
||||||
alias: '',
|
alias: '{{metric}}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('and data field is average', () => {
|
|
||||||
const response = {
|
const response = {
|
||||||
value: [
|
results: {
|
||||||
{
|
A: {
|
||||||
timeseries: [
|
refId: 'A',
|
||||||
{
|
meta: {
|
||||||
data: [
|
rawQuery:
|
||||||
{
|
'aggregation=Average&api-version=2018-01-01&interval=PT1M' +
|
||||||
timeStamp: '2017-08-22T21:00:00Z',
|
'&metricnames=Percentage+CPU×pan=2019-05-19T15%3A11%3A37Z%2F2019-05-19T21%3A11%3A37Z',
|
||||||
average: 1.0503333333333331,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T22:00:00Z',
|
|
||||||
average: 1.045083333333333,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T23:00:00Z',
|
|
||||||
average: 1.0457499999999995,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id:
|
|
||||||
'/subscriptions/xxx/resourceGroups/testRG/providers/Microsoft.Compute/virtualMachines' +
|
|
||||||
'/testRN/providers/Microsoft.Insights/metrics/Percentage CPU',
|
|
||||||
name: {
|
|
||||||
value: 'Percentage CPU',
|
|
||||||
localizedValue: 'Percentage CPU',
|
|
||||||
},
|
|
||||||
type: 'Microsoft.Insights/metrics',
|
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
},
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Percentage CPU',
|
||||||
|
points: [[2.2075, 1558278660000], [2.29, 1558278720000]],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
tables: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
||||||
expect(options.url).toContain(
|
expect(options.url).toContain('/api/tsdb/query');
|
||||||
'/testRG/providers/Microsoft.Compute/virtualMachines/testRN/providers/microsoft.insights/metrics'
|
|
||||||
);
|
|
||||||
return ctx.$q.when({ data: response, status: 200 });
|
return ctx.$q.when({ data: response, status: 200 });
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -146,163 +130,11 @@ describe('AzureMonitorDatasource', () => {
|
|||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
return ctx.ds.query(options).then(results => {
|
return ctx.ds.query(options).then(results => {
|
||||||
expect(results.data.length).toBe(1);
|
expect(results.data.length).toBe(1);
|
||||||
expect(results.data[0].target).toEqual('testRN.Percentage CPU');
|
expect(results.data[0].name).toEqual('Percentage CPU');
|
||||||
expect(results.data[0].datapoints[0][1]).toEqual(1503435600000);
|
expect(results.data[0].rows[0][1]).toEqual(1558278660000);
|
||||||
expect(results.data[0].datapoints[0][0]).toEqual(1.0503333333333331);
|
expect(results.data[0].rows[0][0]).toEqual(2.2075);
|
||||||
expect(results.data[0].datapoints[2][1]).toEqual(1503442800000);
|
expect(results.data[0].rows[1][1]).toEqual(1558278720000);
|
||||||
expect(results.data[0].datapoints[2][0]).toEqual(1.0457499999999995);
|
expect(results.data[0].rows[1][0]).toEqual(2.29);
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and data field is total', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
timeseries: [
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T21:00:00Z',
|
|
||||||
total: 1.0503333333333331,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T22:00:00Z',
|
|
||||||
total: 1.045083333333333,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T23:00:00Z',
|
|
||||||
total: 1.0457499999999995,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id:
|
|
||||||
'/subscriptions/xxx/resourceGroups/testRG/providers/Microsoft.Compute/virtualMachines' +
|
|
||||||
'/testRN/providers/Microsoft.Insights/metrics/Percentage CPU',
|
|
||||||
name: {
|
|
||||||
value: 'Percentage CPU',
|
|
||||||
localizedValue: 'Percentage CPU',
|
|
||||||
},
|
|
||||||
type: 'Microsoft.Insights/metrics',
|
|
||||||
unit: 'Percent',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
|
||||||
expect(options.url).toContain(
|
|
||||||
'/testRG/providers/Microsoft.Compute/virtualMachines/testRN/providers/microsoft.insights/metrics'
|
|
||||||
);
|
|
||||||
return ctx.$q.when({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
|
||||||
return ctx.ds.query(options).then(results => {
|
|
||||||
expect(results.data.length).toBe(1);
|
|
||||||
expect(results.data[0].target).toEqual('testRN.Percentage CPU');
|
|
||||||
expect(results.data[0].datapoints[0][1]).toEqual(1503435600000);
|
|
||||||
expect(results.data[0].datapoints[0][0]).toEqual(1.0503333333333331);
|
|
||||||
expect(results.data[0].datapoints[2][1]).toEqual(1503442800000);
|
|
||||||
expect(results.data[0].datapoints[2][0]).toEqual(1.0457499999999995);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and data has a dimension filter', () => {
|
|
||||||
const response = {
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
timeseries: [
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T21:00:00Z',
|
|
||||||
total: 1.0503333333333331,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T22:00:00Z',
|
|
||||||
total: 1.045083333333333,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeStamp: '2017-08-22T23:00:00Z',
|
|
||||||
total: 1.0457499999999995,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
metadatavalues: [
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
value: 'blobtype',
|
|
||||||
localizedValue: 'blobtype',
|
|
||||||
},
|
|
||||||
value: 'BlockBlob',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id:
|
|
||||||
'/subscriptions/xxx/resourceGroups/testRG/providers/Microsoft.Compute/virtualMachines' +
|
|
||||||
'/testRN/providers/Microsoft.Insights/metrics/Percentage CPU',
|
|
||||||
name: {
|
|
||||||
value: 'Percentage CPU',
|
|
||||||
localizedValue: 'Percentage CPU',
|
|
||||||
},
|
|
||||||
type: 'Microsoft.Insights/metrics',
|
|
||||||
unit: 'Percent',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('and with no alias specified', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
|
||||||
const expected =
|
|
||||||
'/testRG/providers/Microsoft.Compute/virtualMachines/testRN/providers/microsoft.insights/metrics';
|
|
||||||
expect(options.url).toContain(expected);
|
|
||||||
return ctx.$q.when({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
|
||||||
return ctx.ds.query(options).then(results => {
|
|
||||||
expect(results.data.length).toBe(1);
|
|
||||||
expect(results.data[0].target).toEqual('testRN{blobtype=BlockBlob}.Percentage CPU');
|
|
||||||
expect(results.data[0].datapoints[0][1]).toEqual(1503435600000);
|
|
||||||
expect(results.data[0].datapoints[0][0]).toEqual(1.0503333333333331);
|
|
||||||
expect(results.data[0].datapoints[2][1]).toEqual(1503442800000);
|
|
||||||
expect(results.data[0].datapoints[2][0]).toEqual(1.0457499999999995);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with an alias specified', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
options.targets[0].azureMonitor.alias =
|
|
||||||
'{{resourcegroup}} + {{namespace}} + {{resourcename}} + ' +
|
|
||||||
'{{metric}} + {{dimensionname}} + {{dimensionvalue}}';
|
|
||||||
|
|
||||||
ctx.backendSrv.datasourceRequest = (options: { url: string }) => {
|
|
||||||
const expected =
|
|
||||||
'/testRG/providers/Microsoft.Compute/virtualMachines/testRN/providers/microsoft.insights/metrics';
|
|
||||||
expect(options.url).toContain(expected);
|
|
||||||
return ctx.$q.when({ data: response, status: 200 });
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
|
||||||
return ctx.ds.query(options).then(results => {
|
|
||||||
expect(results.data.length).toBe(1);
|
|
||||||
const expected =
|
|
||||||
'testRG + Microsoft.Compute/virtualMachines + testRN + Percentage CPU + blobtype + BlockBlob';
|
|
||||||
expect(results.data[0].target).toEqual(expected);
|
|
||||||
expect(results.data[0].datapoints[0][1]).toEqual(1503435600000);
|
|
||||||
expect(results.data[0].datapoints[0][0]).toEqual(1.0503333333333331);
|
|
||||||
expect(results.data[0].datapoints[2][1]).toEqual(1503442800000);
|
|
||||||
expect(results.data[0].datapoints[2][0]).toEqual(1.0457499999999995);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import AzureMonitorFilterBuilder from './azure_monitor_filter_builder';
|
|
||||||
import UrlBuilder from './url_builder';
|
import UrlBuilder from './url_builder';
|
||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import SupportedNamespaces from './supported_namespaces';
|
import SupportedNamespaces from './supported_namespaces';
|
||||||
import TimegrainConverter from '../time_grain_converter';
|
import TimegrainConverter from '../time_grain_converter';
|
||||||
import { AzureMonitorQuery, AzureDataSourceJsonData } from '../types';
|
import {
|
||||||
import { DataQueryRequest, DataSourceInstanceSettings } from '@grafana/ui/src/types';
|
AzureMonitorQuery,
|
||||||
|
AzureDataSourceJsonData,
|
||||||
|
AzureMonitorMetricDefinitionsResponse,
|
||||||
|
AzureMonitorResourceGroupsResponse,
|
||||||
|
} from '../types';
|
||||||
|
import {
|
||||||
|
DataQueryRequest,
|
||||||
|
DataQueryResponseData,
|
||||||
|
DataSourceInstanceSettings,
|
||||||
|
TimeSeries,
|
||||||
|
toDataFrame,
|
||||||
|
} from '@grafana/ui';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
@ -19,7 +29,7 @@ export default class AzureMonitorDatasource {
|
|||||||
url: string;
|
url: string;
|
||||||
defaultDropdownValue = 'select';
|
defaultDropdownValue = 'select';
|
||||||
cloudName: string;
|
cloudName: string;
|
||||||
supportedMetricNamespaces: any[] = [];
|
supportedMetricNamespaces: string[] = [];
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
@ -40,7 +50,7 @@ export default class AzureMonitorDatasource {
|
|||||||
return !!this.subscriptionId && this.subscriptionId.length > 0;
|
return !!this.subscriptionId && this.subscriptionId.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(options: DataQueryRequest<AzureMonitorQuery>) {
|
async query(options: DataQueryRequest<AzureMonitorQuery>): Promise<DataQueryResponseData[]> {
|
||||||
const queries = _.filter(options.targets, item => {
|
const queries = _.filter(options.targets, item => {
|
||||||
return (
|
return (
|
||||||
item.hide !== true &&
|
item.hide !== true &&
|
||||||
@ -56,6 +66,7 @@ export default class AzureMonitorDatasource {
|
|||||||
}).map(target => {
|
}).map(target => {
|
||||||
const item = target.azureMonitor;
|
const item = target.azureMonitor;
|
||||||
|
|
||||||
|
// fix for timeGrainUnit which is a deprecated/removed field name
|
||||||
if (item.timeGrainUnit && item.timeGrain !== 'auto') {
|
if (item.timeGrainUnit && item.timeGrain !== 'auto') {
|
||||||
item.timeGrain = TimegrainConverter.createISO8601Duration(item.timeGrain, item.timeGrainUnit);
|
item.timeGrain = TimegrainConverter.createISO8601Duration(item.timeGrain, item.timeGrainUnit);
|
||||||
}
|
}
|
||||||
@ -65,78 +76,66 @@ export default class AzureMonitorDatasource {
|
|||||||
const resourceName = this.templateSrv.replace(item.resourceName, options.scopedVars);
|
const resourceName = this.templateSrv.replace(item.resourceName, options.scopedVars);
|
||||||
const metricDefinition = this.templateSrv.replace(item.metricDefinition, options.scopedVars);
|
const metricDefinition = this.templateSrv.replace(item.metricDefinition, options.scopedVars);
|
||||||
const timeGrain = this.templateSrv.replace((item.timeGrain || '').toString(), options.scopedVars);
|
const timeGrain = this.templateSrv.replace((item.timeGrain || '').toString(), options.scopedVars);
|
||||||
|
const aggregation = this.templateSrv.replace(item.aggregation, options.scopedVars);
|
||||||
const filterBuilder = new AzureMonitorFilterBuilder(
|
|
||||||
item.metricName,
|
|
||||||
options.range.from,
|
|
||||||
options.range.to,
|
|
||||||
timeGrain,
|
|
||||||
options.interval
|
|
||||||
);
|
|
||||||
|
|
||||||
if (item.timeGrains) {
|
|
||||||
filterBuilder.setAllowedTimeGrains(item.timeGrains);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.aggregation) {
|
|
||||||
filterBuilder.setAggregation(item.aggregation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.dimension && item.dimension !== 'None') {
|
|
||||||
filterBuilder.setDimensionFilter(item.dimension, item.dimensionFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = this.templateSrv.replace(filterBuilder.generateFilter(), options.scopedVars);
|
|
||||||
|
|
||||||
const url = UrlBuilder.buildAzureMonitorQueryUrl(
|
|
||||||
this.baseUrl,
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
metricDefinition,
|
|
||||||
resourceName,
|
|
||||||
this.apiVersion,
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
refId: target.refId,
|
refId: target.refId,
|
||||||
intervalMs: options.intervalMs,
|
intervalMs: options.intervalMs,
|
||||||
maxDataPoints: options.maxDataPoints,
|
|
||||||
datasourceId: this.id,
|
datasourceId: this.id,
|
||||||
url: url,
|
subscription: subscriptionId,
|
||||||
format: target.format,
|
queryType: 'Azure Monitor',
|
||||||
alias: item.alias,
|
type: 'timeSeriesQuery',
|
||||||
raw: false,
|
raw: false,
|
||||||
|
azureMonitor: {
|
||||||
|
resourceGroup: resourceGroup,
|
||||||
|
resourceName: resourceName,
|
||||||
|
metricDefinition: metricDefinition,
|
||||||
|
timeGrain: timeGrain,
|
||||||
|
allowedTimeGrainsMs: item.allowedTimeGrainsMs,
|
||||||
|
metricName: this.templateSrv.replace(item.metricName, options.scopedVars),
|
||||||
|
aggregation: aggregation,
|
||||||
|
dimension: this.templateSrv.replace(item.dimension, options.scopedVars),
|
||||||
|
dimensionFilter: this.templateSrv.replace(item.dimensionFilter, options.scopedVars),
|
||||||
|
alias: item.alias,
|
||||||
|
format: target.format,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!queries || queries.length === 0) {
|
if (!queries || queries.length === 0) {
|
||||||
return [];
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = this.doQueries(queries);
|
const { data } = await this.backendSrv.datasourceRequest({
|
||||||
|
url: '/api/tsdb/query',
|
||||||
return Promise.all(promises).then(results => {
|
method: 'POST',
|
||||||
return new ResponseParser(results).parseQueryResult();
|
data: {
|
||||||
|
from: options.range.from.valueOf().toString(),
|
||||||
|
to: options.range.to.valueOf().toString(),
|
||||||
|
queries,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const result: DataQueryResponseData[] = [];
|
||||||
|
if (data.results) {
|
||||||
|
Object['values'](data.results).forEach((queryRes: any) => {
|
||||||
|
if (!queryRes.series) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queryRes.series.forEach((series: any) => {
|
||||||
|
const timeSerie: TimeSeries = {
|
||||||
|
target: series.name,
|
||||||
|
datapoints: series.points,
|
||||||
|
refId: queryRes.refId,
|
||||||
|
meta: queryRes.meta,
|
||||||
|
};
|
||||||
|
result.push(toDataFrame(timeSerie));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
doQueries(queries) {
|
return Promise.resolve([]);
|
||||||
return _.map(queries, query => {
|
|
||||||
return this.doRequest(query.url)
|
|
||||||
.then(result => {
|
|
||||||
return {
|
|
||||||
result: result,
|
|
||||||
query: query,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
throw {
|
|
||||||
error: err,
|
|
||||||
query: query,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationQuery(options) {}
|
annotationQuery(options) {}
|
||||||
@ -217,14 +216,14 @@ export default class AzureMonitorDatasource {
|
|||||||
|
|
||||||
getSubscriptions(route?: string) {
|
getSubscriptions(route?: string) {
|
||||||
const url = `/${route || this.cloudName}/subscriptions?api-version=2019-03-01`;
|
const url = `/${route || this.cloudName}/subscriptions?api-version=2019-03-01`;
|
||||||
return this.doRequest(url).then(result => {
|
return this.doRequest(url).then((result: any) => {
|
||||||
return ResponseParser.parseSubscriptions(result);
|
return ResponseParser.parseSubscriptions(result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getResourceGroups(subscriptionId: string) {
|
getResourceGroups(subscriptionId: string) {
|
||||||
const url = `${this.baseUrl}/${subscriptionId}/resourceGroups?api-version=${this.apiVersion}`;
|
const url = `${this.baseUrl}/${subscriptionId}/resourceGroups?api-version=${this.apiVersion}`;
|
||||||
return this.doRequest(url).then(result => {
|
return this.doRequest(url).then((result: AzureMonitorResourceGroupsResponse) => {
|
||||||
return ResponseParser.parseResponseValues(result, 'name', 'name');
|
return ResponseParser.parseResponseValues(result, 'name', 'name');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -234,7 +233,7 @@ export default class AzureMonitorDatasource {
|
|||||||
this.apiVersion
|
this.apiVersion
|
||||||
}`;
|
}`;
|
||||||
return this.doRequest(url)
|
return this.doRequest(url)
|
||||||
.then(result => {
|
.then((result: AzureMonitorMetricDefinitionsResponse) => {
|
||||||
return ResponseParser.parseResponseValues(result, 'type', 'type');
|
return ResponseParser.parseResponseValues(result, 'type', 'type');
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
jest.mock('app/core/utils/kbn', () => {
|
|
||||||
return {
|
|
||||||
interval_to_ms: interval => {
|
|
||||||
if (interval.substring(interval.length - 1) === 's') {
|
|
||||||
return interval.substring(0, interval.length - 1) * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interval.substring(interval.length - 1) === 'm') {
|
|
||||||
return interval.substring(0, interval.length - 1) * 1000 * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interval.substring(interval.length - 1) === 'd') {
|
|
||||||
return interval.substring(0, interval.length - 1) * 1000 * 60 * 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
import AzureMonitorFilterBuilder from './azure_monitor_filter_builder';
|
|
||||||
import { toUtc } from '@grafana/ui/src/utils/moment_wrapper';
|
|
||||||
|
|
||||||
describe('AzureMonitorFilterBuilder', () => {
|
|
||||||
let builder: AzureMonitorFilterBuilder;
|
|
||||||
|
|
||||||
const timefilter = 'timespan=2017-08-22T06:00:00Z/2017-08-22T07:00:00Z';
|
|
||||||
const metricFilter = 'metricnames=Percentage CPU';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
builder = new AzureMonitorFilterBuilder(
|
|
||||||
'Percentage CPU',
|
|
||||||
toUtc('2017-08-22 06:00'),
|
|
||||||
toUtc('2017-08-22 07:00'),
|
|
||||||
'PT1H',
|
|
||||||
'3m'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and auto time grain of 3 minutes', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.timeGrain = 'auto';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should always add datetime filtering and a time grain rounded to the closest allowed value to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=PT5M&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and auto time grain of 30 seconds', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.timeGrain = 'auto';
|
|
||||||
builder.grafanaInterval = '30s';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should always add datetime filtering and a time grain in ISO_8601 format to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=PT1M&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and auto time grain of 10 minutes', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.timeGrain = 'auto';
|
|
||||||
builder.grafanaInterval = '10m';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should always add datetime filtering and a time grain rounded to the closest allowed value to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=PT15M&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and auto time grain of 2 day', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.timeGrain = 'auto';
|
|
||||||
builder.grafanaInterval = '2d';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should always add datetime filtering and a time grain rounded to the closest allowed value to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=P1D&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and 1 hour time grain', () => {
|
|
||||||
it('should always add datetime filtering and a time grain in ISO_8601 format to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=PT1H&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and 1 minute time grain', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.timeGrain = 'PT1M';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should always add datetime filtering and a time grain in ISO_8601 format to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=PT1M&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and 1 day time grain and an aggregation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.timeGrain = 'P1D';
|
|
||||||
builder.setAggregation('Maximum');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add time grain to the filter in ISO_8601 format', () => {
|
|
||||||
const filter = timefilter + '&interval=P1D&aggregation=Maximum&' + metricFilter;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a metric name and 1 day time grain and an aggregation and a dimension', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
builder.setDimensionFilter('aDimension', 'aFilterValue');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add dimension to the filter', () => {
|
|
||||||
const filter = timefilter + '&interval=PT1H&' + metricFilter + `&$filter=aDimension eq 'aFilterValue'`;
|
|
||||||
expect(builder.generateFilter()).toEqual(filter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,72 +0,0 @@
|
|||||||
import TimegrainConverter from '../time_grain_converter';
|
|
||||||
|
|
||||||
export default class AzureMonitorFilterBuilder {
|
|
||||||
aggregation: string;
|
|
||||||
timeGrainInterval = '';
|
|
||||||
dimension: string;
|
|
||||||
dimensionFilter: string;
|
|
||||||
allowedTimeGrains = ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private metricName: string,
|
|
||||||
private from,
|
|
||||||
private to,
|
|
||||||
public timeGrain: string,
|
|
||||||
public grafanaInterval: string
|
|
||||||
) {}
|
|
||||||
|
|
||||||
setAllowedTimeGrains(timeGrains) {
|
|
||||||
this.allowedTimeGrains = [];
|
|
||||||
timeGrains.forEach(tg => {
|
|
||||||
if (tg.value === 'auto') {
|
|
||||||
this.allowedTimeGrains.push(tg.value);
|
|
||||||
} else {
|
|
||||||
this.allowedTimeGrains.push(TimegrainConverter.createKbnUnitFromISO8601Duration(tg.value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setAggregation(agg) {
|
|
||||||
this.aggregation = agg;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDimensionFilter(dimension, dimensionFilter) {
|
|
||||||
this.dimension = dimension;
|
|
||||||
this.dimensionFilter = dimensionFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
generateFilter() {
|
|
||||||
let filter = this.createDatetimeAndTimeGrainConditions();
|
|
||||||
|
|
||||||
if (this.aggregation) {
|
|
||||||
filter += `&aggregation=${this.aggregation}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.metricName && this.metricName.trim().length > 0) {
|
|
||||||
filter += `&metricnames=${this.metricName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dimension && this.dimensionFilter && this.dimensionFilter.trim().length > 0) {
|
|
||||||
filter += `&$filter=${this.dimension} eq '${this.dimensionFilter}'`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDatetimeAndTimeGrainConditions() {
|
|
||||||
const dateTimeCondition = `timespan=${this.from.utc().format()}/${this.to.utc().format()}`;
|
|
||||||
|
|
||||||
if (this.timeGrain === 'auto') {
|
|
||||||
this.timeGrain = this.calculateAutoTimeGrain();
|
|
||||||
}
|
|
||||||
const timeGrainCondition = `&interval=${this.timeGrain}`;
|
|
||||||
|
|
||||||
return dateTimeCondition + timeGrainCondition;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateAutoTimeGrain() {
|
|
||||||
const roundedInterval = TimegrainConverter.findClosestTimeGrain(this.grafanaInterval, this.allowedTimeGrains);
|
|
||||||
|
|
||||||
return TimegrainConverter.createISO8601DurationFromInterval(roundedInterval);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +1,17 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import TimeGrainConverter from '../time_grain_converter';
|
import TimeGrainConverter from '../time_grain_converter';
|
||||||
import { dateTime } from '@grafana/ui/src/utils/moment_wrapper';
|
|
||||||
|
|
||||||
export default class ResponseParser {
|
export default class ResponseParser {
|
||||||
constructor(private results) {}
|
static parseResponseValues(
|
||||||
|
result: any,
|
||||||
|
textFieldName: string,
|
||||||
|
valueFieldName: string
|
||||||
|
): Array<{ text: string; value: string }> {
|
||||||
|
const list: Array<{ text: string; value: string }> = [];
|
||||||
|
|
||||||
parseQueryResult() {
|
if (!result) {
|
||||||
const data: any[] = [];
|
return list;
|
||||||
for (let i = 0; i < this.results.length; i++) {
|
|
||||||
for (let j = 0; j < this.results[i].result.data.value.length; j++) {
|
|
||||||
for (let k = 0; k < this.results[i].result.data.value[j].timeseries.length; k++) {
|
|
||||||
const alias = this.results[i].query.alias;
|
|
||||||
data.push({
|
|
||||||
target: ResponseParser.createTarget(
|
|
||||||
this.results[i].result.data.value[j],
|
|
||||||
this.results[i].result.data.value[j].timeseries[k].metadatavalues,
|
|
||||||
alias
|
|
||||||
),
|
|
||||||
datapoints: ResponseParser.convertDataToPoints(this.results[i].result.data.value[j].timeseries[k].data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static createTarget(data, metadatavalues, alias: string) {
|
|
||||||
const resourceGroup = ResponseParser.parseResourceGroupFromId(data.id);
|
|
||||||
const resourceName = ResponseParser.parseResourceNameFromId(data.id);
|
|
||||||
const namespace = ResponseParser.parseNamespaceFromId(data.id, resourceName);
|
|
||||||
if (alias) {
|
|
||||||
const regex = /\{\{([\s\S]+?)\}\}/g;
|
|
||||||
return alias.replace(regex, (match, g1, g2) => {
|
|
||||||
const group = g1 || g2;
|
|
||||||
|
|
||||||
if (group === 'resourcegroup') {
|
|
||||||
return resourceGroup;
|
|
||||||
} else if (group === 'namespace') {
|
|
||||||
return namespace;
|
|
||||||
} else if (group === 'resourcename') {
|
|
||||||
return resourceName;
|
|
||||||
} else if (group === 'metric') {
|
|
||||||
return data.name.value;
|
|
||||||
} else if (group === 'dimensionname') {
|
|
||||||
return metadatavalues && metadatavalues.length > 0 ? metadatavalues[0].name.value : '';
|
|
||||||
} else if (group === 'dimensionvalue') {
|
|
||||||
return metadatavalues && metadatavalues.length > 0 ? metadatavalues[0].value : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadatavalues && metadatavalues.length > 0) {
|
|
||||||
return `${resourceName}{${metadatavalues[0].name.value}=${metadatavalues[0].value}}.${data.name.value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${resourceName}.${data.name.value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseResourceGroupFromId(id: string) {
|
|
||||||
const startIndex = id.indexOf('/resourceGroups/') + 16;
|
|
||||||
const endIndex = id.indexOf('/providers');
|
|
||||||
|
|
||||||
return id.substring(startIndex, endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseNamespaceFromId(id: string, resourceName: string) {
|
|
||||||
const startIndex = id.indexOf('/providers/') + 11;
|
|
||||||
const endIndex = id.indexOf('/' + resourceName);
|
|
||||||
|
|
||||||
return id.substring(startIndex, endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseResourceNameFromId(id: string) {
|
|
||||||
const endIndex = id.lastIndexOf('/providers');
|
|
||||||
const startIndex = id.slice(0, endIndex).lastIndexOf('/') + 1;
|
|
||||||
|
|
||||||
return id.substring(startIndex, endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static convertDataToPoints(timeDataFrame) {
|
|
||||||
const dataPoints: any[] = [];
|
|
||||||
|
|
||||||
for (let k = 0; k < timeDataFrame.length; k++) {
|
|
||||||
const epoch = ResponseParser.dateTimeToEpoch(timeDataFrame[k].timeStamp);
|
|
||||||
const aggKey = ResponseParser.getKeyForAggregationField(timeDataFrame[k]);
|
|
||||||
|
|
||||||
if (aggKey) {
|
|
||||||
dataPoints.push([timeDataFrame[k][aggKey], epoch]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
static dateTimeToEpoch(dateTimeValue) {
|
|
||||||
return dateTime(dateTimeValue).valueOf();
|
|
||||||
}
|
|
||||||
|
|
||||||
static getKeyForAggregationField(dataObj): string {
|
|
||||||
const keys = _.keys(dataObj);
|
|
||||||
if (keys.length < 2) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.intersection(keys, ['total', 'average', 'maximum', 'minimum', 'count'])[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseResponseValues(result: any, textFieldName: string, valueFieldName: string) {
|
|
||||||
const list: any[] = [];
|
|
||||||
for (let i = 0; i < result.data.value.length; i++) {
|
for (let i = 0; i < result.data.value.length; i++) {
|
||||||
if (!_.find(list, ['value', _.get(result.data.value[i], valueFieldName)])) {
|
if (!_.find(list, ['value', _.get(result.data.value[i], valueFieldName)])) {
|
||||||
list.push({
|
list.push({
|
||||||
@ -121,8 +23,13 @@ export default class ResponseParser {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseResourceNames(result: any, metricDefinition: string) {
|
static parseResourceNames(result: any, metricDefinition: string): Array<{ text: string; value: string }> {
|
||||||
const list: any[] = [];
|
const list: Array<{ text: string; value: string }> = [];
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < result.data.value.length; i++) {
|
for (let i = 0; i < result.data.value.length; i++) {
|
||||||
if (result.data.value[i].type === metricDefinition) {
|
if (result.data.value[i].type === metricDefinition) {
|
||||||
list.push({
|
list.push({
|
||||||
@ -136,12 +43,21 @@ export default class ResponseParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static parseMetadata(result: any, metricName: string) {
|
static parseMetadata(result: any, metricName: string) {
|
||||||
|
const defaultAggTypes = ['None', 'Average', 'Minimum', 'Maximum', 'Total', 'Count'];
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return {
|
||||||
|
primaryAggType: '',
|
||||||
|
supportedAggTypes: defaultAggTypes,
|
||||||
|
supportedTimeGrains: [],
|
||||||
|
dimensions: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const metricData: any = _.find(result.data.value, o => {
|
const metricData: any = _.find(result.data.value, o => {
|
||||||
return _.get(o, 'name.value') === metricName;
|
return _.get(o, 'name.value') === metricName;
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultAggTypes = ['None', 'Average', 'Minimum', 'Maximum', 'Total', 'Count'];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
primaryAggType: metricData.primaryAggregationType,
|
primaryAggType: metricData.primaryAggregationType,
|
||||||
supportedAggTypes: metricData.supportedAggregationTypes || defaultAggTypes,
|
supportedAggTypes: metricData.supportedAggregationTypes || defaultAggTypes,
|
||||||
@ -150,8 +66,12 @@ export default class ResponseParser {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseTimeGrains(metricAvailabilities) {
|
static parseTimeGrains(metricAvailabilities: any[]): Array<{ text: string; value: string }> {
|
||||||
const timeGrains: any[] = [];
|
const timeGrains: any[] = [];
|
||||||
|
if (!metricAvailabilities) {
|
||||||
|
return timeGrains;
|
||||||
|
}
|
||||||
|
|
||||||
metricAvailabilities.forEach(avail => {
|
metricAvailabilities.forEach(avail => {
|
||||||
if (avail.timeGrain) {
|
if (avail.timeGrain) {
|
||||||
timeGrains.push({
|
timeGrains.push({
|
||||||
@ -163,8 +83,8 @@ export default class ResponseParser {
|
|||||||
return timeGrains;
|
return timeGrains;
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseDimensions(metricData: any) {
|
static parseDimensions(metricData: any): Array<{ text: string; value: string }> {
|
||||||
const dimensions: any[] = [];
|
const dimensions: Array<{ text: string; value: string }> = [];
|
||||||
if (!metricData.dimensions || metricData.dimensions.length === 0) {
|
if (!metricData.dimensions || metricData.dimensions.length === 0) {
|
||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
@ -182,10 +102,15 @@ export default class ResponseParser {
|
|||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseSubscriptions(result: any) {
|
static parseSubscriptions(result: any): Array<{ text: string; value: string }> {
|
||||||
|
const list: Array<{ text: string; value: string }> = [];
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
const valueFieldName = 'subscriptionId';
|
const valueFieldName = 'subscriptionId';
|
||||||
const textFieldName = 'displayName';
|
const textFieldName = 'displayName';
|
||||||
const list: Array<{ text: string; value: string }> = [];
|
|
||||||
for (let i = 0; i < result.data.value.length; i++) {
|
for (let i = 0; i < result.data.value.length; i++) {
|
||||||
if (!_.find(list, ['value', _.get(result.data.value[i], valueFieldName)])) {
|
if (!_.find(list, ['value', _.get(result.data.value[i], valueFieldName)])) {
|
||||||
list.push({
|
list.push({
|
||||||
|
@ -235,7 +235,7 @@ export default class SupportedNamespaces {
|
|||||||
|
|
||||||
constructor(private cloudName: string) {}
|
constructor(private cloudName: string) {}
|
||||||
|
|
||||||
get() {
|
get(): string[] {
|
||||||
return this.supportedMetricNamespaces[this.cloudName];
|
return this.supportedMetricNamespaces[this.cloudName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,24 +52,6 @@ describe('AzureMonitorUrlBuilder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/blobServices', () => {
|
|
||||||
it('should build the query url in the longer format', () => {
|
|
||||||
const url = UrlBuilder.buildAzureMonitorQueryUrl(
|
|
||||||
'',
|
|
||||||
'sub1',
|
|
||||||
'rg',
|
|
||||||
'Microsoft.Storage/storageAccounts/blobServices',
|
|
||||||
'rn1/default',
|
|
||||||
'2017-05-01-preview',
|
|
||||||
'metricnames=aMetric'
|
|
||||||
);
|
|
||||||
expect(url).toBe(
|
|
||||||
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/' +
|
|
||||||
'providers/microsoft.insights/metrics?api-version=2017-05-01-preview&metricnames=aMetric'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/fileServices', () => {
|
describe('when metric definition is Microsoft.Storage/storageAccounts/fileServices', () => {
|
||||||
it('should build the getMetricNames url in the longer format', () => {
|
it('should build the getMetricNames url in the longer format', () => {
|
||||||
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
|
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
|
||||||
@ -87,24 +69,6 @@ describe('AzureMonitorUrlBuilder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/fileServices', () => {
|
|
||||||
it('should build the query url in the longer format', () => {
|
|
||||||
const url = UrlBuilder.buildAzureMonitorQueryUrl(
|
|
||||||
'',
|
|
||||||
'sub1',
|
|
||||||
'rg',
|
|
||||||
'Microsoft.Storage/storageAccounts/fileServices',
|
|
||||||
'rn1/default',
|
|
||||||
'2017-05-01-preview',
|
|
||||||
'metricnames=aMetric'
|
|
||||||
);
|
|
||||||
expect(url).toBe(
|
|
||||||
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/' +
|
|
||||||
'providers/microsoft.insights/metrics?api-version=2017-05-01-preview&metricnames=aMetric'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/tableServices', () => {
|
describe('when metric definition is Microsoft.Storage/storageAccounts/tableServices', () => {
|
||||||
it('should build the getMetricNames url in the longer format', () => {
|
it('should build the getMetricNames url in the longer format', () => {
|
||||||
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
|
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
|
||||||
@ -122,24 +86,6 @@ describe('AzureMonitorUrlBuilder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/tableServices', () => {
|
|
||||||
it('should build the query url in the longer format', () => {
|
|
||||||
const url = UrlBuilder.buildAzureMonitorQueryUrl(
|
|
||||||
'',
|
|
||||||
'sub1',
|
|
||||||
'rg',
|
|
||||||
'Microsoft.Storage/storageAccounts/tableServices',
|
|
||||||
'rn1/default',
|
|
||||||
'2017-05-01-preview',
|
|
||||||
'metricnames=aMetric'
|
|
||||||
);
|
|
||||||
expect(url).toBe(
|
|
||||||
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/tableServices/default/' +
|
|
||||||
'providers/microsoft.insights/metrics?api-version=2017-05-01-preview&metricnames=aMetric'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/queueServices', () => {
|
describe('when metric definition is Microsoft.Storage/storageAccounts/queueServices', () => {
|
||||||
it('should build the getMetricNames url in the longer format', () => {
|
it('should build the getMetricNames url in the longer format', () => {
|
||||||
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
|
const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl(
|
||||||
@ -156,22 +102,4 @@ describe('AzureMonitorUrlBuilder', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when metric definition is Microsoft.Storage/storageAccounts/queueServices', () => {
|
|
||||||
it('should build the query url in the longer format', () => {
|
|
||||||
const url = UrlBuilder.buildAzureMonitorQueryUrl(
|
|
||||||
'',
|
|
||||||
'sub1',
|
|
||||||
'rg',
|
|
||||||
'Microsoft.Storage/storageAccounts/queueServices',
|
|
||||||
'rn1/default',
|
|
||||||
'2017-05-01-preview',
|
|
||||||
'metricnames=aMetric'
|
|
||||||
);
|
|
||||||
expect(url).toBe(
|
|
||||||
'/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' +
|
|
||||||
'providers/microsoft.insights/metrics?api-version=2017-05-01-preview&metricnames=aMetric'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,29 +1,4 @@
|
|||||||
export default class UrlBuilder {
|
export default class UrlBuilder {
|
||||||
static buildAzureMonitorQueryUrl(
|
|
||||||
baseUrl: string,
|
|
||||||
subscriptionId: string,
|
|
||||||
resourceGroup: string,
|
|
||||||
metricDefinition: string,
|
|
||||||
resourceName: string,
|
|
||||||
apiVersion: string,
|
|
||||||
filter: string
|
|
||||||
) {
|
|
||||||
if ((metricDefinition.match(/\//g) || []).length > 1) {
|
|
||||||
const rn = resourceName.split('/');
|
|
||||||
const service = metricDefinition.substring(metricDefinition.lastIndexOf('/') + 1);
|
|
||||||
const md = metricDefinition.substring(0, metricDefinition.lastIndexOf('/'));
|
|
||||||
return (
|
|
||||||
`${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${md}/${rn[0]}/${service}/${rn[1]}` +
|
|
||||||
`/providers/microsoft.insights/metrics?api-version=${apiVersion}&${filter}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
`${baseUrl}/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${metricDefinition}/${resourceName}` +
|
|
||||||
`/providers/microsoft.insights/metrics?api-version=${apiVersion}&${filter}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static buildAzureMonitorGetMetricNamesUrl(
|
static buildAzureMonitorGetMetricNamesUrl(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
|
@ -3,6 +3,7 @@ import { QueryCtrl } from 'app/plugins/sdk';
|
|||||||
// import './css/query_editor.css';
|
// import './css/query_editor.css';
|
||||||
import TimegrainConverter from './time_grain_converter';
|
import TimegrainConverter from './time_grain_converter';
|
||||||
import './editor/editor_component';
|
import './editor/editor_component';
|
||||||
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { auto } from 'angular';
|
import { auto } from 'angular';
|
||||||
@ -30,7 +31,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
dimensionFilter: string;
|
dimensionFilter: string;
|
||||||
timeGrain: string;
|
timeGrain: string;
|
||||||
timeGrainUnit: string;
|
timeGrainUnit: string;
|
||||||
timeGrains: any[];
|
timeGrains: Array<{ text: string; value: string }>;
|
||||||
|
allowedTimeGrainsMs: number[];
|
||||||
dimensions: any[];
|
dimensions: any[];
|
||||||
dimension: any;
|
dimension: any;
|
||||||
aggregation: string;
|
aggregation: string;
|
||||||
@ -175,6 +177,14 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
delete this.target.azureMonitor.timeGrainUnit;
|
delete this.target.azureMonitor.timeGrainUnit;
|
||||||
this.onMetricNameChange();
|
this.onMetricNameChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.target.azureMonitor.timeGrains &&
|
||||||
|
this.target.azureMonitor.timeGrains.length > 0 &&
|
||||||
|
(!this.target.azureMonitor.allowedTimeGrainsMs || this.target.azureMonitor.allowedTimeGrainsMs.length === 0)
|
||||||
|
) {
|
||||||
|
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(this.target.azureMonitor.timeGrains);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
migrateToFromTimes() {
|
migrateToFromTimes() {
|
||||||
@ -312,6 +322,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
this.target.azureMonitor.timeGrain = '';
|
this.target.azureMonitor.timeGrain = '';
|
||||||
this.target.azureMonitor.dimensions = [];
|
this.target.azureMonitor.dimensions = [];
|
||||||
this.target.azureMonitor.dimension = '';
|
this.target.azureMonitor.dimension = '';
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMetricDefinitionChange() {
|
onMetricDefinitionChange() {
|
||||||
@ -331,6 +342,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
this.target.azureMonitor.timeGrain = '';
|
this.target.azureMonitor.timeGrain = '';
|
||||||
this.target.azureMonitor.dimensions = [];
|
this.target.azureMonitor.dimensions = [];
|
||||||
this.target.azureMonitor.dimension = '';
|
this.target.azureMonitor.dimension = '';
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMetricNameChange() {
|
onMetricNameChange() {
|
||||||
@ -352,6 +364,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
|
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
|
||||||
this.target.azureMonitor.timeGrain = 'auto';
|
this.target.azureMonitor.timeGrain = 'auto';
|
||||||
|
|
||||||
|
this.target.azureMonitor.allowedTimeGrainsMs = this.convertTimeGrainsToMs(metadata.supportedTimeGrains || []);
|
||||||
|
|
||||||
this.target.azureMonitor.dimensions = metadata.dimensions;
|
this.target.azureMonitor.dimensions = metadata.dimensions;
|
||||||
if (metadata.dimensions.length > 0) {
|
if (metadata.dimensions.length > 0) {
|
||||||
this.target.azureMonitor.dimension = metadata.dimensions[0].value;
|
this.target.azureMonitor.dimension = metadata.dimensions[0].value;
|
||||||
@ -361,6 +375,16 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
.catch(this.handleQueryCtrlError.bind(this));
|
.catch(this.handleQueryCtrlError.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertTimeGrainsToMs(timeGrains: Array<{ text: string; value: string }>) {
|
||||||
|
const allowedTimeGrainsMs: number[] = [];
|
||||||
|
timeGrains.forEach((tg: any) => {
|
||||||
|
if (tg.value !== 'auto') {
|
||||||
|
allowedTimeGrainsMs.push(kbn.interval_to_ms(TimegrainConverter.createKbnUnitFromISO8601Duration(tg.value)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return allowedTimeGrainsMs;
|
||||||
|
}
|
||||||
|
|
||||||
getAutoInterval() {
|
getAutoInterval() {
|
||||||
if (this.target.azureMonitor.timeGrain === 'auto') {
|
if (this.target.azureMonitor.timeGrain === 'auto') {
|
||||||
return TimegrainConverter.findClosestTimeGrain(
|
return TimegrainConverter.findClosestTimeGrain(
|
||||||
|
@ -35,6 +35,7 @@ export interface AzureMetricQuery {
|
|||||||
timeGrainUnit: string;
|
timeGrainUnit: string;
|
||||||
timeGrain: string;
|
timeGrain: string;
|
||||||
timeGrains: string[];
|
timeGrains: string[];
|
||||||
|
allowedTimeGrainsMs: number[];
|
||||||
aggregation: string;
|
aggregation: string;
|
||||||
dimension: string;
|
dimension: string;
|
||||||
dimensionFilter: string;
|
dimensionFilter: string;
|
||||||
@ -47,6 +48,24 @@ export interface AzureLogsQuery {
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Azure Monitor API Types
|
||||||
|
|
||||||
|
export interface AzureMonitorMetricDefinitionsResponse {
|
||||||
|
data: {
|
||||||
|
value: Array<{ name: string; type: string; location?: string }>;
|
||||||
|
};
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureMonitorResourceGroupsResponse {
|
||||||
|
data: {
|
||||||
|
value: Array<{ name: string }>;
|
||||||
|
};
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Azure Log Analytics types
|
// Azure Log Analytics types
|
||||||
export interface KustoSchema {
|
export interface KustoSchema {
|
||||||
Databases: { [key: string]: KustoDatabase };
|
Databases: { [key: string]: KustoDatabase };
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
echo -e "Collecting code stats (typescript errors & more)"
|
echo -e "Collecting code stats (typescript errors & more)"
|
||||||
|
|
||||||
|
|
||||||
ERROR_COUNT_LIMIT=3000
|
ERROR_COUNT_LIMIT=2945
|
||||||
DIRECTIVES_LIMIT=172
|
DIRECTIVES_LIMIT=172
|
||||||
CONTROLLERS_LIMIT=139
|
CONTROLLERS_LIMIT=139
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user