annotation_query.go 6.0 KB

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