Commit 60327953 by Daniel Lee

azuremonitor: handles timegrain set to auto on backend

parent 452c4f5b
......@@ -30,6 +30,11 @@ type AzureMonitorDatasource struct {
dsInfo *models.DataSource
}
var (
// 1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d in milliseconds
allowedIntervalsMS = []int64{60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000}
)
// executeTimeSeriesQuery does the following:
// 1. build the AzureMonitor url and querystring for each query
// 2. executes each query by calling the Azure Monitor API
......@@ -49,7 +54,7 @@ func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, ori
if err != nil {
return nil, err
}
azlog.Debug("AzureMonitor", "Response", resp)
// azlog.Debug("AzureMonitor", "Response", resp)
err = e.parseResponse(queryRes, resp, query)
if err != nil {
......@@ -93,10 +98,20 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
alias := fmt.Sprintf("%v", azureMonitorTarget["alias"])
timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])
if timeGrain == "auto" {
autoInSeconds := e.findClosestAllowedIntervalMs(query.IntervalMs) / 1000
tg := &TimeGrain{}
timeGrain, err = tg.createISO8601DurationFromInterval(fmt.Sprintf("%vs", autoInSeconds))
if err != nil {
return nil, err
}
}
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", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"]))
params.Add("interval", timeGrain)
params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"]))
params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"]))
......@@ -269,3 +284,18 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data
return nil
}
func (e *AzureMonitorDatasource) findClosestAllowedIntervalMs(intervalMs int64) int64 {
closest := allowedIntervalsMS[0]
for i, allowed := range allowedIntervalsMS {
if intervalMs > allowed {
if i+1 < len(allowedIntervalsMS) {
closest = allowedIntervalsMS[i+1]
} else {
closest = allowed
}
}
}
return closest
}
......@@ -229,6 +229,26 @@ func TestAzureMonitorDatasource(t *testing.T) {
So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0)
})
})
Convey("Find closest allowed interval for auto time grain", func() {
intervals := map[string]int64{
"3m": 180000,
"5m": 300000,
"10m": 600000,
"15m": 900000,
"1d": 86400000,
"2d": 172800000,
}
closest := datasource.findClosestAllowedIntervalMs(intervals["3m"])
So(closest, ShouldEqual, intervals["5m"])
closest = datasource.findClosestAllowedIntervalMs(intervals["10m"])
So(closest, ShouldEqual, intervals["15m"])
closest = datasource.findClosestAllowedIntervalMs(intervals["2d"])
So(closest, ShouldEqual, intervals["1d"])
})
})
}
......
package azuremonitor
import (
"fmt"
"strconv"
"strings"
)
// TimeGrain handles convertions between
// the ISO 8601 Duration format (PT1H), Kbn units (1h) and Time Grains (1 hour)
// Also handles using the automatic Grafana interval to calculate a ISO 8601 Duration.
type TimeGrain struct{}
var (
smallTimeUnits = []string{"hour", "minute", "h", "m"}
)
func (tg *TimeGrain) createISO8601DurationFromInterval(interval string) (string, error) {
if strings.Contains(interval, "ms") {
return "PT1M", nil
}
timeValueString := interval[0 : len(interval)-1]
timeValue, err := strconv.Atoi(timeValueString)
if err != nil {
return "", fmt.Errorf("Could not parse interval %v to an ISO 8061 duration", interval)
}
unit := interval[len(interval)-1:]
if unit == "s" {
toMinutes := (timeValue * 60) % 60
// mimumum interval is 1m for Azure Monitor
if toMinutes < 1 {
toMinutes = 1
}
return tg.createISO8601Duration(toMinutes, "m"), nil
}
return tg.createISO8601Duration(timeValue, unit), nil
}
func (tg *TimeGrain) createISO8601Duration(timeValue int, timeUnit string) string {
for _, smallTimeUnit := range smallTimeUnits {
if timeUnit == smallTimeUnit {
return fmt.Sprintf("PT%v%v", timeValue, strings.ToUpper(timeUnit[0:1]))
}
}
return fmt.Sprintf("P%v%v", timeValue, strings.ToUpper(timeUnit[0:1]))
}
package azuremonitor
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestTimeGrain(t *testing.T) {
Convey("TimeGrain", t, func() {
tgc := &TimeGrain{}
Convey("create ISO 8601 Duration", func() {
Convey("when given a time unit smaller than a day", func() {
minuteKbnDuration := tgc.createISO8601Duration(1, "m")
hourKbnDuration := tgc.createISO8601Duration(2, "h")
minuteDuration := tgc.createISO8601Duration(1, "minute")
hourDuration := tgc.createISO8601Duration(2, "hour")
Convey("should convert it to a time duration", func() {
So(minuteKbnDuration, ShouldEqual, "PT1M")
So(hourKbnDuration, ShouldEqual, "PT2H")
So(minuteDuration, ShouldEqual, "PT1M")
So(hourDuration, ShouldEqual, "PT2H")
})
})
Convey("when given the day time unit", func() {
kbnDuration := tgc.createISO8601Duration(1, "d")
duration := tgc.createISO8601Duration(2, "day")
Convey("should convert it to a date duration", func() {
So(kbnDuration, ShouldEqual, "P1D")
So(duration, ShouldEqual, "P2D")
})
})
})
Convey("create ISO 8601 Duration from Grafana interval", func() {
Convey("and interval is less than a minute", func() {
durationMS, _ := tgc.createISO8601DurationFromInterval("100ms")
durationS, _ := tgc.createISO8601DurationFromInterval("59s")
Convey("should be rounded up to a minute as is the minimum interval for Azure Monitor", func() {
So(durationMS, ShouldEqual, "PT1M")
So(durationS, ShouldEqual, "PT1M")
})
})
Convey("and interval is more than a minute", func() {
durationM, _ := tgc.createISO8601DurationFromInterval("10m")
durationD, _ := tgc.createISO8601DurationFromInterval("2d")
Convey("should be rounded up to a minute as is the minimum interval for Azure Monitor", func() {
So(durationM, ShouldEqual, "PT10M")
So(durationD, ShouldEqual, "P2D")
})
})
})
})
}
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