annotation_query.go 6.3 KB

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