annotation_query.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package cloudwatch
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. "github.com/aws/aws-sdk-go/aws"
  7. "github.com/aws/aws-sdk-go/service/cloudwatch"
  8. "github.com/grafana/grafana/pkg/components/simplejson"
  9. "github.com/grafana/grafana/pkg/tsdb"
  10. )
  11. func (e *CloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
  12. result := &tsdb.Response{
  13. Results: make(map[string]*tsdb.QueryResult),
  14. }
  15. firstQuery := queryContext.Queries[0]
  16. queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: firstQuery.RefId}
  17. parameters := firstQuery.Model
  18. usePrefixMatch := parameters.Get("prefixMatching").MustBool(false)
  19. region := parameters.Get("region").MustString("")
  20. namespace := parameters.Get("namespace").MustString("")
  21. metricName := parameters.Get("metricName").MustString("")
  22. dimensions := parameters.Get("dimensions").MustMap()
  23. statistics, extendedStatistics, err := parseStatistics(parameters)
  24. if err != nil {
  25. return nil, err
  26. }
  27. period := int64(parameters.Get("period").MustInt(0))
  28. if period == 0 && !usePrefixMatch {
  29. period = 300
  30. }
  31. actionPrefix := parameters.Get("actionPrefix").MustString("")
  32. alarmNamePrefix := parameters.Get("alarmNamePrefix").MustString("")
  33. svc, err := e.getClient(region)
  34. if err != nil {
  35. return nil, err
  36. }
  37. var alarmNames []*string
  38. if usePrefixMatch {
  39. params := &cloudwatch.DescribeAlarmsInput{
  40. MaxRecords: aws.Int64(100),
  41. ActionPrefix: aws.String(actionPrefix),
  42. AlarmNamePrefix: aws.String(alarmNamePrefix),
  43. }
  44. resp, err := svc.DescribeAlarms(params)
  45. if err != nil {
  46. return nil, errors.New("Failed to call cloudwatch:DescribeAlarms")
  47. }
  48. alarmNames = filterAlarms(resp, namespace, metricName, dimensions, statistics, extendedStatistics, period)
  49. } else {
  50. if region == "" || namespace == "" || metricName == "" || len(statistics) == 0 {
  51. return result, nil
  52. }
  53. var qd []*cloudwatch.Dimension
  54. for k, v := range dimensions {
  55. if vv, ok := v.(string); ok {
  56. qd = append(qd, &cloudwatch.Dimension{
  57. Name: aws.String(k),
  58. Value: aws.String(vv),
  59. })
  60. }
  61. }
  62. for _, s := range statistics {
  63. params := &cloudwatch.DescribeAlarmsForMetricInput{
  64. Namespace: aws.String(namespace),
  65. MetricName: aws.String(metricName),
  66. Dimensions: qd,
  67. Statistic: aws.String(s),
  68. Period: aws.Int64(int64(period)),
  69. }
  70. resp, err := svc.DescribeAlarmsForMetric(params)
  71. if err != nil {
  72. return nil, errors.New("Failed to call cloudwatch:DescribeAlarmsForMetric")
  73. }
  74. for _, alarm := range resp.MetricAlarms {
  75. alarmNames = append(alarmNames, alarm.AlarmName)
  76. }
  77. }
  78. for _, s := range extendedStatistics {
  79. params := &cloudwatch.DescribeAlarmsForMetricInput{
  80. Namespace: aws.String(namespace),
  81. MetricName: aws.String(metricName),
  82. Dimensions: qd,
  83. ExtendedStatistic: aws.String(s),
  84. Period: aws.Int64(int64(period)),
  85. }
  86. resp, err := svc.DescribeAlarmsForMetric(params)
  87. if err != nil {
  88. return nil, errors.New("Failed to call cloudwatch:DescribeAlarmsForMetric")
  89. }
  90. for _, alarm := range resp.MetricAlarms {
  91. alarmNames = append(alarmNames, alarm.AlarmName)
  92. }
  93. }
  94. }
  95. startTime, err := queryContext.TimeRange.ParseFrom()
  96. if err != nil {
  97. return nil, err
  98. }
  99. endTime, err := queryContext.TimeRange.ParseTo()
  100. if err != nil {
  101. return nil, err
  102. }
  103. annotations := make([]map[string]string, 0)
  104. for _, alarmName := range alarmNames {
  105. params := &cloudwatch.DescribeAlarmHistoryInput{
  106. AlarmName: alarmName,
  107. StartDate: aws.Time(startTime),
  108. EndDate: aws.Time(endTime),
  109. MaxRecords: aws.Int64(100),
  110. }
  111. resp, err := svc.DescribeAlarmHistory(params)
  112. if err != nil {
  113. return nil, errors.New("Failed to call cloudwatch:DescribeAlarmHistory")
  114. }
  115. for _, history := range resp.AlarmHistoryItems {
  116. annotation := make(map[string]string)
  117. annotation["time"] = history.Timestamp.UTC().Format(time.RFC3339)
  118. annotation["title"] = *history.AlarmName
  119. annotation["tags"] = *history.HistoryItemType
  120. annotation["text"] = *history.HistorySummary
  121. annotations = append(annotations, annotation)
  122. }
  123. }
  124. transformAnnotationToTable(annotations, queryResult)
  125. result.Results[firstQuery.RefId] = queryResult
  126. return result, err
  127. }
  128. func transformAnnotationToTable(data []map[string]string, result *tsdb.QueryResult) {
  129. table := &tsdb.Table{
  130. Columns: make([]tsdb.TableColumn, 4),
  131. Rows: make([]tsdb.RowValues, 0),
  132. }
  133. table.Columns[0].Text = "time"
  134. table.Columns[1].Text = "title"
  135. table.Columns[2].Text = "tags"
  136. table.Columns[3].Text = "text"
  137. for _, r := range data {
  138. values := make([]interface{}, 4)
  139. values[0] = r["time"]
  140. values[1] = r["title"]
  141. values[2] = r["tags"]
  142. values[3] = r["text"]
  143. table.Rows = append(table.Rows, values)
  144. }
  145. result.Tables = append(result.Tables, table)
  146. result.Meta.Set("rowCount", len(data))
  147. }
  148. func filterAlarms(alarms *cloudwatch.DescribeAlarmsOutput, namespace string, metricName string, dimensions map[string]interface{}, statistics []string, extendedStatistics []string, period int64) []*string {
  149. alarmNames := make([]*string, 0)
  150. for _, alarm := range alarms.MetricAlarms {
  151. if namespace != "" && *alarm.Namespace != namespace {
  152. continue
  153. }
  154. if metricName != "" && *alarm.MetricName != metricName {
  155. continue
  156. }
  157. match := true
  158. if len(dimensions) == 0 {
  159. // all match
  160. } else if len(alarm.Dimensions) != len(dimensions) {
  161. match = false
  162. } else {
  163. for _, d := range alarm.Dimensions {
  164. if _, ok := dimensions[*d.Name]; !ok {
  165. match = false
  166. }
  167. }
  168. }
  169. if !match {
  170. continue
  171. }
  172. if len(statistics) != 0 {
  173. found := false
  174. for _, s := range statistics {
  175. if *alarm.Statistic == s {
  176. found = true
  177. }
  178. }
  179. if !found {
  180. continue
  181. }
  182. }
  183. if len(extendedStatistics) != 0 {
  184. found := false
  185. for _, s := range extendedStatistics {
  186. if *alarm.Statistic == s {
  187. found = true
  188. }
  189. }
  190. if !found {
  191. continue
  192. }
  193. }
  194. if period != 0 && *alarm.Period != period {
  195. continue
  196. }
  197. alarmNames = append(alarmNames, alarm.AlarmName)
  198. }
  199. return alarmNames
  200. }