فهرست منبع

azuremonitor: handles timegrain set to auto on backend

Daniel Lee 6 سال پیش
والد
کامیت
60327953a2

+ 32 - 2
pkg/tsdb/azuremonitor/azuremonitor-datasource.go

@@ -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
+}

+ 20 - 0
pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go

@@ -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"])
+		})
 	})
 }
 

+ 53 - 0
pkg/tsdb/azuremonitor/time-grain.go

@@ -0,0 +1,53 @@
+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]))
+}

+ 60 - 0
pkg/tsdb/azuremonitor/time-grain_test.go

@@ -0,0 +1,60 @@
+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")
+				})
+			})
+		})
+	})
+}