azuremonitor-datasource.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. package azuremonitor
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "net/url"
  10. "path"
  11. "strings"
  12. "time"
  13. "github.com/grafana/grafana/pkg/api/pluginproxy"
  14. "github.com/grafana/grafana/pkg/models"
  15. "github.com/grafana/grafana/pkg/plugins"
  16. "github.com/grafana/grafana/pkg/setting"
  17. opentracing "github.com/opentracing/opentracing-go"
  18. "golang.org/x/net/context/ctxhttp"
  19. "github.com/grafana/grafana/pkg/components/null"
  20. "github.com/grafana/grafana/pkg/components/simplejson"
  21. "github.com/grafana/grafana/pkg/tsdb"
  22. )
  23. // AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported
  24. type AzureMonitorDatasource struct {
  25. httpClient *http.Client
  26. dsInfo *models.DataSource
  27. }
  28. var (
  29. // 1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d in milliseconds
  30. allowedIntervalsMS = []int64{60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000}
  31. )
  32. // executeTimeSeriesQuery does the following:
  33. // 1. build the AzureMonitor url and querystring for each query
  34. // 2. executes each query by calling the Azure Monitor API
  35. // 3. parses the responses for each query into the timeseries format
  36. func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
  37. result := &tsdb.Response{
  38. Results: map[string]*tsdb.QueryResult{},
  39. }
  40. queries, err := e.buildQueries(originalQueries, timeRange)
  41. if err != nil {
  42. return nil, err
  43. }
  44. for _, query := range queries {
  45. queryRes, resp, err := e.executeQuery(ctx, query, originalQueries, timeRange)
  46. if err != nil {
  47. return nil, err
  48. }
  49. // azlog.Debug("AzureMonitor", "Response", resp)
  50. err = e.parseResponse(queryRes, resp, query)
  51. if err != nil {
  52. queryRes.Error = err
  53. }
  54. result.Results[query.RefID] = queryRes
  55. }
  56. return result, nil
  57. }
  58. func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*AzureMonitorQuery, error) {
  59. azureMonitorQueries := []*AzureMonitorQuery{}
  60. startTime, err := timeRange.ParseFrom()
  61. if err != nil {
  62. return nil, err
  63. }
  64. endTime, err := timeRange.ParseTo()
  65. if err != nil {
  66. return nil, err
  67. }
  68. for _, query := range queries {
  69. var target string
  70. azureMonitorTarget := query.Model.Get("azureMonitor").MustMap()
  71. azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
  72. urlComponents := map[string]string{}
  73. urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
  74. urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
  75. urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"])
  76. ub := urlBuilder{
  77. ResourceGroup: urlComponents["resourceGroup"],
  78. MetricDefinition: urlComponents["metricDefinition"],
  79. ResourceName: urlComponents["resourceName"],
  80. }
  81. azureURL := ub.Build()
  82. alias := fmt.Sprintf("%v", azureMonitorTarget["alias"])
  83. timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])
  84. if timeGrain == "auto" {
  85. autoInterval := e.findClosestAllowedIntervalMS(query.IntervalMs)
  86. tg := &TimeGrain{}
  87. timeGrain, err = tg.createISO8601DurationFromIntervalMS(autoInterval)
  88. if err != nil {
  89. return nil, err
  90. }
  91. }
  92. params := url.Values{}
  93. params.Add("api-version", "2018-01-01")
  94. params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339)))
  95. params.Add("interval", timeGrain)
  96. params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"]))
  97. params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"]))
  98. dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimension"]))
  99. dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"]))
  100. if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 {
  101. params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
  102. }
  103. target = params.Encode()
  104. if setting.Env == setting.DEV {
  105. azlog.Debug("Azuremonitor request", "params", params)
  106. }
  107. azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{
  108. URL: azureURL,
  109. UrlComponents: urlComponents,
  110. Target: target,
  111. Params: params,
  112. RefID: query.RefId,
  113. Alias: alias,
  114. })
  115. }
  116. return azureMonitorQueries, nil
  117. }
  118. func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureMonitorQuery, queries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.QueryResult, AzureMonitorResponse, error) {
  119. queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID}
  120. req, err := e.createRequest(ctx, e.dsInfo)
  121. if err != nil {
  122. queryResult.Error = err
  123. return queryResult, AzureMonitorResponse{}, nil
  124. }
  125. req.URL.Path = path.Join(req.URL.Path, query.URL)
  126. req.URL.RawQuery = query.Params.Encode()
  127. queryResult.Meta.Set("rawQuery", req.URL.RawQuery)
  128. span, ctx := opentracing.StartSpanFromContext(ctx, "azuremonitor query")
  129. span.SetTag("target", query.Target)
  130. span.SetTag("from", timeRange.From)
  131. span.SetTag("until", timeRange.To)
  132. span.SetTag("datasource_id", e.dsInfo.Id)
  133. span.SetTag("org_id", e.dsInfo.OrgId)
  134. defer span.Finish()
  135. opentracing.GlobalTracer().Inject(
  136. span.Context(),
  137. opentracing.HTTPHeaders,
  138. opentracing.HTTPHeadersCarrier(req.Header))
  139. azlog.Debug("AzureMonitor", "Request URL", req.URL.String())
  140. res, err := ctxhttp.Do(ctx, e.httpClient, req)
  141. if err != nil {
  142. queryResult.Error = err
  143. return queryResult, AzureMonitorResponse{}, nil
  144. }
  145. data, err := e.unmarshalResponse(res)
  146. if err != nil {
  147. queryResult.Error = err
  148. return queryResult, AzureMonitorResponse{}, nil
  149. }
  150. return queryResult, data, nil
  151. }
  152. func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
  153. // find plugin
  154. plugin, ok := plugins.DataSources[dsInfo.Type]
  155. if !ok {
  156. return nil, errors.New("Unable to find datasource plugin Azure Monitor")
  157. }
  158. var azureMonitorRoute *plugins.AppPluginRoute
  159. for _, route := range plugin.Routes {
  160. if route.Path == "azuremonitor" {
  161. azureMonitorRoute = route
  162. break
  163. }
  164. }
  165. cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
  166. subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString()
  167. proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID)
  168. u, _ := url.Parse(dsInfo.Url)
  169. u.Path = path.Join(u.Path, "render")
  170. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  171. if err != nil {
  172. azlog.Error("Failed to create request", "error", err)
  173. return nil, fmt.Errorf("Failed to create request. error: %v", err)
  174. }
  175. req.Header.Set("Content-Type", "application/json")
  176. req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
  177. pluginproxy.ApplyRoute(ctx, req, proxyPass, azureMonitorRoute, dsInfo)
  178. return req, nil
  179. }
  180. func (e *AzureMonitorDatasource) unmarshalResponse(res *http.Response) (AzureMonitorResponse, error) {
  181. body, err := ioutil.ReadAll(res.Body)
  182. defer res.Body.Close()
  183. if err != nil {
  184. return AzureMonitorResponse{}, err
  185. }
  186. if res.StatusCode/100 != 2 {
  187. azlog.Error("Request failed", "status", res.Status, "body", string(body))
  188. return AzureMonitorResponse{}, fmt.Errorf(string(body))
  189. }
  190. var data AzureMonitorResponse
  191. err = json.Unmarshal(body, &data)
  192. if err != nil {
  193. azlog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body))
  194. return AzureMonitorResponse{}, err
  195. }
  196. return data, nil
  197. }
  198. func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error {
  199. if len(data.Value) == 0 {
  200. return nil
  201. }
  202. for _, series := range data.Value[0].Timeseries {
  203. points := []tsdb.TimePoint{}
  204. metadataName := ""
  205. metadataValue := ""
  206. if len(series.Metadatavalues) > 0 {
  207. metadataName = series.Metadatavalues[0].Name.LocalizedValue
  208. metadataValue = series.Metadatavalues[0].Value
  209. }
  210. defaultMetricName := formatLegendKey(query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue)
  211. for _, point := range series.Data {
  212. var value float64
  213. switch query.Params.Get("aggregation") {
  214. case "Average":
  215. value = point.Average
  216. case "Total":
  217. value = point.Total
  218. case "Maximum":
  219. value = point.Maximum
  220. case "Minimum":
  221. value = point.Minimum
  222. case "Count":
  223. value = point.Count
  224. default:
  225. value = point.Count
  226. }
  227. points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.TimeStamp).Unix())*1000))
  228. }
  229. queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
  230. Name: defaultMetricName,
  231. Points: points,
  232. })
  233. }
  234. return nil
  235. }
  236. // findClosestAllowedIntervalMs is used for the auto time grain setting.
  237. // It finds the closest time grain from the list of allowed time grains for Azure Monitor
  238. // using the Grafana interval in milliseconds
  239. func (e *AzureMonitorDatasource) findClosestAllowedIntervalMS(intervalMs int64) int64 {
  240. closest := allowedIntervalsMS[0]
  241. for i, allowed := range allowedIntervalsMS {
  242. if intervalMs > allowed {
  243. if i+1 < len(allowedIntervalsMS) {
  244. closest = allowedIntervalsMS[i+1]
  245. } else {
  246. closest = allowed
  247. }
  248. }
  249. }
  250. return closest
  251. }
  252. // formatLegendKey builds the legend key or timeseries name
  253. func formatLegendKey(resourceName string, metricName string, metadataName string, metadataValue string) string {
  254. if len(metadataName) > 0 {
  255. return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName)
  256. }
  257. return fmt.Sprintf("%s.%s", resourceName, metricName)
  258. }