2022-05-24 21:49:47 +08:00
|
|
|
//go:generate ../../../tools/readme_config_includer/generator
|
2016-03-26 06:16:23 +08:00
|
|
|
package cloudwatch
|
|
|
|
|
|
|
|
|
|
import (
|
2021-10-22 05:32:10 +08:00
|
|
|
"context"
|
2022-05-24 21:49:47 +08:00
|
|
|
_ "embed"
|
2016-03-26 06:16:23 +08:00
|
|
|
"fmt"
|
2019-10-22 05:18:55 +08:00
|
|
|
"net"
|
|
|
|
|
"net/http"
|
2024-05-16 00:11:55 +08:00
|
|
|
"regexp"
|
2019-04-23 08:36:46 +08:00
|
|
|
"strconv"
|
2016-03-26 06:16:23 +08:00
|
|
|
"strings"
|
2016-06-02 19:34:03 +08:00
|
|
|
"sync"
|
2016-03-26 06:16:23 +08:00
|
|
|
"time"
|
|
|
|
|
|
2021-10-22 05:32:10 +08:00
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
2024-10-02 02:49:53 +08:00
|
|
|
"github.com/aws/aws-sdk-go-v2/service/cloudwatch"
|
2021-10-22 05:32:10 +08:00
|
|
|
"github.com/aws/aws-sdk-go-v2/service/cloudwatch/types"
|
2021-04-28 09:41:52 +08:00
|
|
|
|
2016-03-26 06:16:23 +08:00
|
|
|
"github.com/influxdata/telegraf"
|
2020-08-07 22:12:14 +08:00
|
|
|
"github.com/influxdata/telegraf/config"
|
2019-04-23 08:36:46 +08:00
|
|
|
"github.com/influxdata/telegraf/filter"
|
2016-03-26 06:16:23 +08:00
|
|
|
"github.com/influxdata/telegraf/internal"
|
2016-05-24 21:50:01 +08:00
|
|
|
"github.com/influxdata/telegraf/internal/limiter"
|
2024-10-02 02:49:53 +08:00
|
|
|
"github.com/influxdata/telegraf/metric"
|
|
|
|
|
common_aws "github.com/influxdata/telegraf/plugins/common/aws"
|
|
|
|
|
"github.com/influxdata/telegraf/plugins/common/proxy"
|
2016-03-26 06:16:23 +08:00
|
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
|
|
|
)
|
|
|
|
|
|
2022-05-24 21:49:47 +08:00
|
|
|
//go:embed sample.conf
|
|
|
|
|
var sampleConfig string
|
|
|
|
|
|
2020-08-07 22:12:14 +08:00
|
|
|
// CloudWatch contains the configuration and cache for the cloudwatch plugin.
|
|
|
|
|
type CloudWatch struct {
|
|
|
|
|
StatisticExclude []string `toml:"statistic_exclude"`
|
|
|
|
|
StatisticInclude []string `toml:"statistic_include"`
|
|
|
|
|
Timeout config.Duration `toml:"timeout"`
|
|
|
|
|
|
2024-10-02 02:49:53 +08:00
|
|
|
proxy.HTTPProxy
|
2021-02-27 02:58:28 +08:00
|
|
|
|
2024-10-15 19:06:55 +08:00
|
|
|
Period config.Duration `toml:"period"`
|
|
|
|
|
Delay config.Duration `toml:"delay"`
|
|
|
|
|
Namespace string `toml:"namespace" deprecated:"1.25.0;1.35.0;use 'namespaces' instead"`
|
|
|
|
|
Namespaces []string `toml:"namespaces"`
|
|
|
|
|
Metrics []*cloudwatchMetric `toml:"metrics"`
|
|
|
|
|
CacheTTL config.Duration `toml:"cache_ttl"`
|
|
|
|
|
RateLimit int `toml:"ratelimit"`
|
|
|
|
|
RecentlyActive string `toml:"recently_active"`
|
|
|
|
|
BatchSize int `toml:"batch_size"`
|
|
|
|
|
IncludeLinkedAccounts bool `toml:"include_linked_accounts"`
|
|
|
|
|
MetricFormat string `toml:"metric_format"`
|
|
|
|
|
Log telegraf.Logger `toml:"-"`
|
2020-08-07 22:12:14 +08:00
|
|
|
|
|
|
|
|
client cloudwatchClient
|
|
|
|
|
statFilter filter.Filter
|
|
|
|
|
metricCache *metricCache
|
|
|
|
|
queryDimensions map[string]*map[string]string
|
|
|
|
|
windowStart time.Time
|
|
|
|
|
windowEnd time.Time
|
2021-08-04 05:29:26 +08:00
|
|
|
|
2024-10-02 02:49:53 +08:00
|
|
|
common_aws.CredentialConfig
|
2020-08-07 22:12:14 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2024-10-15 19:06:55 +08:00
|
|
|
// cloudwatchMetric defines a simplified Cloudwatch metric.
|
|
|
|
|
type cloudwatchMetric struct {
|
2020-08-07 22:12:14 +08:00
|
|
|
StatisticExclude *[]string `toml:"statistic_exclude"`
|
|
|
|
|
StatisticInclude *[]string `toml:"statistic_include"`
|
|
|
|
|
MetricNames []string `toml:"names"`
|
2024-10-15 19:06:55 +08:00
|
|
|
Dimensions []*dimension `toml:"dimensions"`
|
2020-08-07 22:12:14 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2024-10-15 19:06:55 +08:00
|
|
|
// dimension defines a simplified Cloudwatch dimension (provides metric filtering).
|
|
|
|
|
type dimension struct {
|
2021-04-21 05:29:58 +08:00
|
|
|
Name string `toml:"name"`
|
|
|
|
|
Value string `toml:"value"`
|
|
|
|
|
valueMatcher filter.Filter
|
2020-08-07 22:12:14 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2020-08-07 22:12:14 +08:00
|
|
|
// metricCache caches metrics, their filters, and generated queries.
|
|
|
|
|
type metricCache struct {
|
|
|
|
|
ttl time.Duration
|
|
|
|
|
built time.Time
|
|
|
|
|
metrics []filteredMetric
|
2021-10-22 05:32:10 +08:00
|
|
|
queries map[string][]types.MetricDataQuery
|
2020-08-07 22:12:14 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2020-08-07 22:12:14 +08:00
|
|
|
type cloudwatchClient interface {
|
2024-10-02 02:49:53 +08:00
|
|
|
ListMetrics(context.Context, *cloudwatch.ListMetricsInput, ...func(*cloudwatch.Options)) (*cloudwatch.ListMetricsOutput, error)
|
|
|
|
|
GetMetricData(context.Context, *cloudwatch.GetMetricDataInput, ...func(*cloudwatch.Options)) (*cloudwatch.GetMetricDataOutput, error)
|
2020-08-07 22:12:14 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2022-05-24 21:49:47 +08:00
|
|
|
func (*CloudWatch) SampleConfig() string {
|
|
|
|
|
return sampleConfig
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
func (c *CloudWatch) Init() error {
|
|
|
|
|
if len(c.Namespace) != 0 {
|
|
|
|
|
c.Namespaces = append(c.Namespaces, c.Namespace)
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
|
|
|
|
|
2024-05-16 00:11:55 +08:00
|
|
|
switch c.MetricFormat {
|
|
|
|
|
case "":
|
|
|
|
|
c.MetricFormat = "sparse"
|
|
|
|
|
case "dense", "sparse":
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("invalid metric_format: %s", c.MetricFormat)
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
err := c.initializeCloudWatch()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
|
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
// Set config level filter (won't change throughout life of plugin).
|
|
|
|
|
c.statFilter, err = filter.NewIncludeExcludeFilter(c.StatisticInclude, c.StatisticExclude)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *CloudWatch) Gather(acc telegraf.Accumulator) error {
|
2019-04-23 08:36:46 +08:00
|
|
|
filteredMetrics, err := getFilteredMetrics(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2019-10-22 05:18:55 +08:00
|
|
|
c.updateWindow(time.Now())
|
2019-04-23 08:36:46 +08:00
|
|
|
|
|
|
|
|
// Get all of the possible queries so we can send groups of 100.
|
2021-03-23 01:21:36 +08:00
|
|
|
queries := c.getDataQueries(filteredMetrics)
|
2019-11-16 10:52:55 +08:00
|
|
|
if len(queries) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
// Limit concurrency or we can easily exhaust user connection limit.
|
|
|
|
|
// See cloudwatch API request limits:
|
|
|
|
|
// http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_limits.html
|
|
|
|
|
lmtr := limiter.NewRateLimiter(c.RateLimit, time.Second)
|
|
|
|
|
defer lmtr.Stop()
|
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
|
rLock := sync.Mutex{}
|
|
|
|
|
|
2021-10-22 05:32:10 +08:00
|
|
|
results := map[string][]types.MetricDataResult{}
|
2019-04-23 08:36:46 +08:00
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
for namespace, namespacedQueries := range queries {
|
2021-10-22 05:32:10 +08:00
|
|
|
var batches [][]types.MetricDataQuery
|
2019-04-23 08:36:46 +08:00
|
|
|
|
2022-08-02 03:09:25 +08:00
|
|
|
for c.BatchSize < len(namespacedQueries) {
|
|
|
|
|
namespacedQueries, batches = namespacedQueries[c.BatchSize:], append(batches, namespacedQueries[0:c.BatchSize:c.BatchSize])
|
2021-08-11 05:47:23 +08:00
|
|
|
}
|
|
|
|
|
batches = append(batches, namespacedQueries)
|
|
|
|
|
|
|
|
|
|
for i := range batches {
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
<-lmtr.C
|
2021-10-22 05:32:10 +08:00
|
|
|
go func(n string, inm []types.MetricDataQuery) {
|
2021-08-11 05:47:23 +08:00
|
|
|
defer wg.Done()
|
|
|
|
|
result, err := c.gatherMetrics(c.getDataInputs(inm))
|
|
|
|
|
if err != nil {
|
|
|
|
|
acc.AddError(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2019-04-23 08:36:46 +08:00
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
rLock.Lock()
|
|
|
|
|
results[n] = append(results[n], result...)
|
|
|
|
|
rLock.Unlock()
|
|
|
|
|
}(namespace, batches[i])
|
|
|
|
|
}
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wg.Wait()
|
2024-06-11 16:38:08 +08:00
|
|
|
c.aggregateMetrics(acc, results)
|
|
|
|
|
return nil
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
|
|
|
|
|
2021-02-27 02:58:28 +08:00
|
|
|
func (c *CloudWatch) initializeCloudWatch() error {
|
2024-10-02 02:49:53 +08:00
|
|
|
proxyFunc, err := c.HTTPProxy.Proxy()
|
2021-02-27 02:58:28 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-11 02:15:30 +08:00
|
|
|
awsCreds, err := c.CredentialConfig.Credentials()
|
2021-10-22 05:32:10 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-02 02:49:53 +08:00
|
|
|
c.client = cloudwatch.NewFromConfig(awsCreds, func(options *cloudwatch.Options) {
|
2023-12-13 04:31:48 +08:00
|
|
|
if c.CredentialConfig.EndpointURL != "" && c.CredentialConfig.Region != "" {
|
|
|
|
|
options.BaseEndpoint = &c.CredentialConfig.EndpointURL
|
2023-02-16 04:00:58 +08:00
|
|
|
}
|
|
|
|
|
|
2023-02-11 02:15:30 +08:00
|
|
|
options.ClientLogMode = 0
|
2021-10-22 05:32:10 +08:00
|
|
|
options.HTTPClient = &http.Client{
|
2019-10-22 05:18:55 +08:00
|
|
|
// use values from DefaultTransport
|
|
|
|
|
Transport: &http.Transport{
|
2024-10-02 02:49:53 +08:00
|
|
|
Proxy: proxyFunc,
|
2019-10-22 05:18:55 +08:00
|
|
|
DialContext: (&net.Dialer{
|
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
|
KeepAlive: 30 * time.Second,
|
|
|
|
|
DualStack: true,
|
|
|
|
|
}).DialContext,
|
|
|
|
|
MaxIdleConns: 100,
|
|
|
|
|
IdleConnTimeout: 90 * time.Second,
|
|
|
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
|
|
|
ExpectContinueTimeout: 1 * time.Second,
|
|
|
|
|
},
|
2020-08-07 22:12:14 +08:00
|
|
|
Timeout: time.Duration(c.Timeout),
|
2021-10-22 05:32:10 +08:00
|
|
|
}
|
|
|
|
|
})
|
2021-02-27 02:58:28 +08:00
|
|
|
|
2024-10-15 19:06:55 +08:00
|
|
|
// Initialize regex matchers for each dimension value.
|
2021-04-21 05:29:58 +08:00
|
|
|
for _, m := range c.Metrics {
|
|
|
|
|
for _, dimension := range m.Dimensions {
|
|
|
|
|
matcher, err := filter.NewIncludeExcludeFilter([]string{dimension.Value}, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dimension.valueMatcher = matcher
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-27 02:58:28 +08:00
|
|
|
return nil
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type filteredMetric struct {
|
2021-10-22 05:32:10 +08:00
|
|
|
metrics []types.Metric
|
2023-05-24 15:42:30 +08:00
|
|
|
accounts []string
|
2019-04-23 08:36:46 +08:00
|
|
|
statFilter filter.Filter
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getFilteredMetrics returns metrics specified in the config file or metrics listed from Cloudwatch.
|
|
|
|
|
func getFilteredMetrics(c *CloudWatch) ([]filteredMetric, error) {
|
|
|
|
|
if c.metricCache != nil && c.metricCache.isValid() {
|
|
|
|
|
return c.metricCache.metrics, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fMetrics := []filteredMetric{}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
|
|
|
|
// check for provided metric filter
|
|
|
|
|
if c.Metrics != nil {
|
|
|
|
|
for _, m := range c.Metrics {
|
2021-10-22 05:32:10 +08:00
|
|
|
metrics := []types.Metric{}
|
2023-05-24 15:42:30 +08:00
|
|
|
var accounts []string
|
2020-05-16 06:43:32 +08:00
|
|
|
if !hasWildcard(m.Dimensions) {
|
2022-12-09 23:38:37 +08:00
|
|
|
dimensions := make([]types.Dimension, 0, len(m.Dimensions))
|
|
|
|
|
for _, d := range m.Dimensions {
|
|
|
|
|
dimensions = append(dimensions, types.Dimension{
|
2016-05-25 19:30:39 +08:00
|
|
|
Name: aws.String(d.Name),
|
|
|
|
|
Value: aws.String(d.Value),
|
2022-12-09 23:38:37 +08:00
|
|
|
})
|
2016-05-25 19:30:39 +08:00
|
|
|
}
|
|
|
|
|
for _, name := range m.MetricNames {
|
2021-08-11 05:47:23 +08:00
|
|
|
for _, namespace := range c.Namespaces {
|
2021-10-22 05:32:10 +08:00
|
|
|
metrics = append(metrics, types.Metric{
|
2021-08-11 05:47:23 +08:00
|
|
|
Namespace: aws.String(namespace),
|
|
|
|
|
MetricName: aws.String(name),
|
|
|
|
|
Dimensions: dimensions,
|
|
|
|
|
})
|
|
|
|
|
}
|
2016-05-25 19:30:39 +08:00
|
|
|
}
|
2024-06-04 03:52:10 +08:00
|
|
|
if c.IncludeLinkedAccounts {
|
|
|
|
|
_, allAccounts := c.fetchNamespaceMetrics()
|
|
|
|
|
accounts = append(accounts, allAccounts...)
|
|
|
|
|
}
|
2016-05-25 19:30:39 +08:00
|
|
|
} else {
|
2023-05-24 15:42:30 +08:00
|
|
|
allMetrics, allAccounts := c.fetchNamespaceMetrics()
|
|
|
|
|
|
2016-05-25 19:30:39 +08:00
|
|
|
for _, name := range m.MetricNames {
|
2024-10-02 02:49:53 +08:00
|
|
|
for i, singleMetric := range allMetrics {
|
|
|
|
|
if isSelected(name, singleMetric, m.Dimensions) {
|
2021-08-11 05:47:23 +08:00
|
|
|
for _, namespace := range c.Namespaces {
|
2021-10-22 05:32:10 +08:00
|
|
|
metrics = append(metrics, types.Metric{
|
2021-08-11 05:47:23 +08:00
|
|
|
Namespace: aws.String(namespace),
|
|
|
|
|
MetricName: aws.String(name),
|
2024-10-02 02:49:53 +08:00
|
|
|
Dimensions: singleMetric.Dimensions,
|
2021-08-11 05:47:23 +08:00
|
|
|
})
|
|
|
|
|
}
|
2023-05-24 15:42:30 +08:00
|
|
|
if c.IncludeLinkedAccounts {
|
|
|
|
|
accounts = append(accounts, allAccounts[i])
|
|
|
|
|
}
|
2016-05-25 19:30:39 +08:00
|
|
|
}
|
|
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-23 08:36:46 +08:00
|
|
|
|
|
|
|
|
if m.StatisticExclude == nil {
|
|
|
|
|
m.StatisticExclude = &c.StatisticExclude
|
|
|
|
|
}
|
|
|
|
|
if m.StatisticInclude == nil {
|
|
|
|
|
m.StatisticInclude = &c.StatisticInclude
|
|
|
|
|
}
|
|
|
|
|
statFilter, err := filter.NewIncludeExcludeFilter(*m.StatisticInclude, *m.StatisticExclude)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
fMetrics = append(fMetrics, filteredMetric{
|
|
|
|
|
metrics: metrics,
|
|
|
|
|
statFilter: statFilter,
|
2023-05-24 15:42:30 +08:00
|
|
|
accounts: accounts,
|
2019-04-23 08:36:46 +08:00
|
|
|
})
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-05-24 15:42:30 +08:00
|
|
|
metrics, accounts := c.fetchNamespaceMetrics()
|
2022-04-08 06:01:21 +08:00
|
|
|
fMetrics = []filteredMetric{
|
|
|
|
|
{
|
|
|
|
|
metrics: metrics,
|
|
|
|
|
statFilter: c.statFilter,
|
2023-05-24 15:42:30 +08:00
|
|
|
accounts: accounts,
|
2022-04-08 06:01:21 +08:00
|
|
|
},
|
|
|
|
|
}
|
2016-12-13 22:13:53 +08:00
|
|
|
}
|
2019-04-23 08:36:46 +08:00
|
|
|
c.metricCache = &metricCache{
|
|
|
|
|
metrics: fMetrics,
|
|
|
|
|
built: time.Now(),
|
2020-08-07 22:12:14 +08:00
|
|
|
ttl: time.Duration(c.CacheTTL),
|
2016-12-13 22:13:53 +08:00
|
|
|
}
|
2019-04-23 08:36:46 +08:00
|
|
|
return fMetrics, nil
|
|
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
// fetchNamespaceMetrics retrieves available metrics for a given CloudWatch namespace.
|
2023-05-24 15:42:30 +08:00
|
|
|
func (c *CloudWatch) fetchNamespaceMetrics() ([]types.Metric, []string) {
|
2021-10-22 05:32:10 +08:00
|
|
|
metrics := []types.Metric{}
|
2023-05-24 15:42:30 +08:00
|
|
|
var accounts []string
|
2021-08-11 05:47:23 +08:00
|
|
|
for _, namespace := range c.Namespaces {
|
2024-10-02 02:49:53 +08:00
|
|
|
params := &cloudwatch.ListMetricsInput{
|
2023-05-24 15:42:30 +08:00
|
|
|
Dimensions: []types.DimensionFilter{},
|
|
|
|
|
Namespace: aws.String(namespace),
|
2023-12-13 04:31:48 +08:00
|
|
|
IncludeLinkedAccounts: &c.IncludeLinkedAccounts,
|
2022-11-07 22:43:20 +08:00
|
|
|
}
|
|
|
|
|
if c.RecentlyActive == "PT3H" {
|
|
|
|
|
params.RecentlyActive = types.RecentlyActivePt3h
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
for {
|
2021-10-22 05:32:10 +08:00
|
|
|
resp, err := c.client.ListMetrics(context.Background(), params)
|
2021-08-11 05:47:23 +08:00
|
|
|
if err != nil {
|
2022-05-17 03:27:00 +08:00
|
|
|
c.Log.Errorf("failed to list metrics with namespace %s: %v", namespace, err)
|
|
|
|
|
// skip problem namespace on error and continue to next namespace
|
|
|
|
|
break
|
2021-08-11 05:47:23 +08:00
|
|
|
}
|
|
|
|
|
metrics = append(metrics, resp.Metrics...)
|
2023-05-24 15:42:30 +08:00
|
|
|
accounts = append(accounts, resp.OwningAccounts...)
|
2022-11-07 22:43:20 +08:00
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
if resp.NextToken == nil {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
params.NextToken = resp.NextToken
|
|
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
2023-05-24 15:42:30 +08:00
|
|
|
return metrics, accounts
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
2019-10-22 05:18:55 +08:00
|
|
|
func (c *CloudWatch) updateWindow(relativeTo time.Time) {
|
2020-08-07 22:12:14 +08:00
|
|
|
windowEnd := relativeTo.Add(-time.Duration(c.Delay))
|
2018-09-12 05:59:39 +08:00
|
|
|
|
|
|
|
|
if c.windowEnd.IsZero() {
|
|
|
|
|
// this is the first run, no window info, so just get a single period
|
2020-08-07 22:12:14 +08:00
|
|
|
c.windowStart = windowEnd.Add(-time.Duration(c.Period))
|
2018-09-12 05:59:39 +08:00
|
|
|
} else {
|
|
|
|
|
// subsequent window, start where last window left off
|
|
|
|
|
c.windowStart = c.windowEnd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.windowEnd = windowEnd
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
// getDataQueries gets all of the possible queries so we can maximize the request payload.
|
2021-10-22 05:32:10 +08:00
|
|
|
func (c *CloudWatch) getDataQueries(filteredMetrics []filteredMetric) map[string][]types.MetricDataQuery {
|
2019-04-23 08:36:46 +08:00
|
|
|
if c.metricCache != nil && c.metricCache.queries != nil && c.metricCache.isValid() {
|
2021-03-23 01:21:36 +08:00
|
|
|
return c.metricCache.queries
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
c.queryDimensions = map[string]*map[string]string{}
|
|
|
|
|
|
2021-10-22 05:32:10 +08:00
|
|
|
dataQueries := map[string][]types.MetricDataQuery{}
|
2019-04-23 08:36:46 +08:00
|
|
|
for i, filtered := range filteredMetrics {
|
2024-10-02 02:49:53 +08:00
|
|
|
for j, singleMetric := range filtered.metrics {
|
2019-04-23 08:36:46 +08:00
|
|
|
id := strconv.Itoa(j) + "_" + strconv.Itoa(i)
|
2024-10-02 02:49:53 +08:00
|
|
|
dimension := ctod(singleMetric.Dimensions)
|
2023-05-24 15:42:30 +08:00
|
|
|
var accountID *string
|
2024-05-31 16:34:31 +08:00
|
|
|
if c.IncludeLinkedAccounts && len(filtered.accounts) > j {
|
2023-05-24 15:42:30 +08:00
|
|
|
accountID = aws.String(filtered.accounts[j])
|
|
|
|
|
(*dimension)["account"] = filtered.accounts[j]
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
2023-05-24 15:42:30 +08:00
|
|
|
|
|
|
|
|
statisticTypes := map[string]string{
|
|
|
|
|
"average": "Average",
|
|
|
|
|
"maximum": "Maximum",
|
|
|
|
|
"minimum": "Minimum",
|
|
|
|
|
"sum": "Sum",
|
|
|
|
|
"sample_count": "SampleCount",
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
2023-05-24 15:42:30 +08:00
|
|
|
|
|
|
|
|
for statisticType, statistic := range statisticTypes {
|
|
|
|
|
if !filtered.statFilter.Match(statisticType) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
queryID := statisticType + "_" + id
|
|
|
|
|
c.queryDimensions[queryID] = dimension
|
2024-10-02 02:49:53 +08:00
|
|
|
dataQueries[*singleMetric.Namespace] = append(dataQueries[*singleMetric.Namespace], types.MetricDataQuery{
|
2023-05-24 15:42:30 +08:00
|
|
|
Id: aws.String(queryID),
|
|
|
|
|
AccountId: accountID,
|
2024-10-02 02:49:53 +08:00
|
|
|
Label: aws.String(snakeCase(*singleMetric.MetricName + "_" + statisticType)),
|
2021-10-22 05:32:10 +08:00
|
|
|
MetricStat: &types.MetricStat{
|
2021-12-02 00:49:59 +08:00
|
|
|
Metric: &filtered.metrics[j],
|
2021-10-22 05:32:10 +08:00
|
|
|
Period: aws.Int32(int32(time.Duration(c.Period).Seconds())),
|
2023-05-24 15:42:30 +08:00
|
|
|
Stat: aws.String(statistic),
|
2019-04-23 08:36:46 +08:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-24 02:19:04 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
if len(dataQueries) == 0 {
|
2019-11-16 10:52:55 +08:00
|
|
|
c.Log.Debug("no metrics found to collect")
|
2021-03-23 01:21:36 +08:00
|
|
|
return nil
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
if c.metricCache == nil {
|
|
|
|
|
c.metricCache = &metricCache{
|
|
|
|
|
queries: dataQueries,
|
|
|
|
|
built: time.Now(),
|
2020-08-07 22:12:14 +08:00
|
|
|
ttl: time.Duration(c.CacheTTL),
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
c.metricCache.queries = dataQueries
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
2021-03-23 01:21:36 +08:00
|
|
|
return dataQueries
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
// gatherMetrics gets metric data from Cloudwatch.
|
|
|
|
|
func (c *CloudWatch) gatherMetrics(
|
2024-10-02 02:49:53 +08:00
|
|
|
params *cloudwatch.GetMetricDataInput,
|
2021-10-22 05:32:10 +08:00
|
|
|
) ([]types.MetricDataResult, error) {
|
|
|
|
|
results := []types.MetricDataResult{}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
for {
|
2021-10-22 05:32:10 +08:00
|
|
|
resp, err := c.client.GetMetricData(context.Background(), params)
|
2016-03-26 06:16:23 +08:00
|
|
|
if err != nil {
|
2023-02-23 04:38:06 +08:00
|
|
|
return nil, fmt.Errorf("failed to get metric data: %w", err)
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
results = append(results, resp.MetricDataResults...)
|
|
|
|
|
if resp.NextToken == nil {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
params.NextToken = resp.NextToken
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
return results, nil
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-11 16:38:08 +08:00
|
|
|
func (c *CloudWatch) aggregateMetrics(acc telegraf.Accumulator, metricDataResults map[string][]types.MetricDataResult) {
|
2024-10-02 02:49:53 +08:00
|
|
|
grouper := metric.NewSeriesGrouper()
|
2021-08-11 05:47:23 +08:00
|
|
|
for namespace, results := range metricDataResults {
|
|
|
|
|
namespace = sanitizeMeasurement(namespace)
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
for _, result := range results {
|
|
|
|
|
tags := map[string]string{}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2021-08-11 05:47:23 +08:00
|
|
|
if dimensions, ok := c.queryDimensions[*result.Id]; ok {
|
|
|
|
|
tags = *dimensions
|
|
|
|
|
}
|
|
|
|
|
tags["region"] = c.Region
|
|
|
|
|
|
|
|
|
|
for i := range result.Values {
|
2024-05-16 00:11:55 +08:00
|
|
|
if c.MetricFormat == "dense" {
|
|
|
|
|
// Remove the IDs from the result ID to get the statistic type
|
|
|
|
|
// e.g. "average" from "average_0_0"
|
|
|
|
|
re := regexp.MustCompile(`_\d+_\d+$`)
|
|
|
|
|
statisticType := re.ReplaceAllString(*result.Id, "")
|
|
|
|
|
|
|
|
|
|
// Remove the statistic type from the label to get the AWS Metric name
|
|
|
|
|
// e.g. "CPUUtilization" from "CPUUtilization_average"
|
|
|
|
|
re = regexp.MustCompile(`_?` + regexp.QuoteMeta(statisticType) + `$`)
|
|
|
|
|
tags["metric_name"] = re.ReplaceAllString(*result.Label, "")
|
|
|
|
|
|
|
|
|
|
grouper.Add(namespace, tags, result.Timestamps[i], statisticType, result.Values[i])
|
|
|
|
|
} else {
|
|
|
|
|
grouper.Add(namespace, tags, result.Timestamps[i], *result.Label, result.Values[i])
|
|
|
|
|
}
|
2021-04-23 05:08:03 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
2019-04-23 08:36:46 +08:00
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
|
2024-10-02 02:49:53 +08:00
|
|
|
for _, singleMetric := range grouper.Metrics() {
|
|
|
|
|
acc.AddMetric(singleMetric)
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
func sanitizeMeasurement(namespace string) string {
|
2022-05-11 23:53:34 +08:00
|
|
|
namespace = strings.ReplaceAll(namespace, "/", "_")
|
2016-03-26 06:16:23 +08:00
|
|
|
namespace = snakeCase(namespace)
|
2019-04-23 08:36:46 +08:00
|
|
|
return "cloudwatch_" + namespace
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func snakeCase(s string) string {
|
|
|
|
|
s = internal.SnakeCase(s)
|
2022-05-11 23:53:34 +08:00
|
|
|
s = strings.ReplaceAll(s, " ", "_")
|
|
|
|
|
s = strings.ReplaceAll(s, "__", "_")
|
2016-03-26 06:16:23 +08:00
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
// ctod converts cloudwatch dimensions to regular dimensions.
|
2021-10-22 05:32:10 +08:00
|
|
|
func ctod(cDimensions []types.Dimension) *map[string]string {
|
2019-04-23 08:36:46 +08:00
|
|
|
dimensions := map[string]string{}
|
|
|
|
|
for i := range cDimensions {
|
|
|
|
|
dimensions[snakeCase(*cDimensions[i].Name)] = *cDimensions[i].Value
|
|
|
|
|
}
|
|
|
|
|
return &dimensions
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-02 02:49:53 +08:00
|
|
|
func (c *CloudWatch) getDataInputs(dataQueries []types.MetricDataQuery) *cloudwatch.GetMetricDataInput {
|
|
|
|
|
return &cloudwatch.GetMetricDataInput{
|
2019-04-23 08:36:46 +08:00
|
|
|
StartTime: aws.Time(c.windowStart),
|
|
|
|
|
EndTime: aws.Time(c.windowEnd),
|
|
|
|
|
MetricDataQueries: dataQueries,
|
|
|
|
|
}
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
|
|
|
|
|
2019-04-23 08:36:46 +08:00
|
|
|
// isValid checks the validity of the metric cache.
|
|
|
|
|
func (f *metricCache) isValid() bool {
|
|
|
|
|
return f.metrics != nil && time.Since(f.built) < f.ttl
|
2016-03-26 06:16:23 +08:00
|
|
|
}
|
2016-05-25 19:30:39 +08:00
|
|
|
|
2024-10-15 19:06:55 +08:00
|
|
|
func hasWildcard(dimensions []*dimension) bool {
|
2016-05-25 19:30:39 +08:00
|
|
|
for _, d := range dimensions {
|
2021-04-21 05:29:58 +08:00
|
|
|
if d.Value == "" || strings.ContainsAny(d.Value, "*?[") {
|
2016-05-25 19:30:39 +08:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 19:06:55 +08:00
|
|
|
func isSelected(name string, cloudwatchMetric types.Metric, dimensions []*dimension) bool {
|
2024-10-02 02:49:53 +08:00
|
|
|
if name != *cloudwatchMetric.MetricName {
|
2016-12-13 22:13:53 +08:00
|
|
|
return false
|
|
|
|
|
}
|
2024-10-02 02:49:53 +08:00
|
|
|
if len(cloudwatchMetric.Dimensions) != len(dimensions) {
|
2016-05-25 19:30:39 +08:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, d := range dimensions {
|
|
|
|
|
selected := false
|
2024-10-02 02:49:53 +08:00
|
|
|
for _, d2 := range cloudwatchMetric.Dimensions {
|
2016-05-25 19:30:39 +08:00
|
|
|
if d.Name == *d2.Name {
|
2021-04-21 05:29:58 +08:00
|
|
|
if d.Value == "" || d.valueMatcher.Match(*d2.Value) {
|
2016-05-25 19:30:39 +08:00
|
|
|
selected = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !selected {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
2024-10-15 19:06:55 +08:00
|
|
|
|
|
|
|
|
func newCloudWatch() *CloudWatch {
|
|
|
|
|
return &CloudWatch{
|
|
|
|
|
CacheTTL: config.Duration(time.Hour),
|
|
|
|
|
RateLimit: 25,
|
|
|
|
|
Timeout: config.Duration(time.Second * 5),
|
|
|
|
|
BatchSize: 500,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
inputs.Add("cloudwatch", func() telegraf.Input {
|
|
|
|
|
return newCloudWatch()
|
|
|
|
|
})
|
|
|
|
|
}
|