From 57eb7112b9b815fea79125168e6d11030d4db7eb Mon Sep 17 00:00:00 2001 From: Yury Nikitin Date: Thu, 18 Jan 2024 16:22:47 +0300 Subject: [PATCH] Add Cloudwatch graph to grabana --- go.mod | 2 + go.sum | 10 +++++ graph/graph.go | 20 +++++++++ target/cloudwatch/cloudwatch.go | 65 ++++++++++++++++++++++++++++ target/cloudwatch/cloudwatch_test.go | 52 ++++++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 target/cloudwatch/cloudwatch.go create mode 100644 target/cloudwatch/cloudwatch_test.go diff --git a/go.mod b/go.mod index e51f0527..5acf900f 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,9 @@ require ( github.com/ulikunitz/xz v0.5.11 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.17.0 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/tools v0.17.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 // indirect ) diff --git a/go.sum b/go.sum index efc89b18..ae98c1e8 100644 --- a/go.sum +++ b/go.sum @@ -66,10 +66,14 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -79,6 +83,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= @@ -88,6 +93,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -104,8 +110,12 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/graph/graph.go b/graph/graph.go index 8bc3f1f4..41a06b5d 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -8,6 +8,7 @@ import ( "github.com/K-Phoen/grabana/errors" "github.com/K-Phoen/grabana/graph/series" "github.com/K-Phoen/grabana/links" + "github.com/K-Phoen/grabana/target/cloudwatch" "github.com/K-Phoen/grabana/target/graphite" "github.com/K-Phoen/grabana/target/influxdb" "github.com/K-Phoen/grabana/target/prometheus" @@ -186,6 +187,25 @@ func WithStackdriverTarget(target *stackdriver.Stackdriver) Option { } } +// WithCloudwatchTarget adds a cloudwatch metric to the graph. +func WithCloudwatchTarget(metric string, namespace string, options ...cloudwatch.Option) Option { + target := cloudwatch.New(metric, namespace, options...) + + return func(graph *Graph) error { + graph.Builder.AddTarget(&sdk.Target{ + RefID: target.Ref, + Namespace: target.Namespace, + MetricName: target.MetricName, + Statistics: target.Statistics, + Dimensions: target.Dimensions, + Region: target.Region, + LegendFormat: target.LegendFormat, + }) + + return nil + } +} + // DataSource sets the data source to be used by the graph. func DataSource(source string) Option { return func(graph *Graph) error { diff --git a/target/cloudwatch/cloudwatch.go b/target/cloudwatch/cloudwatch.go new file mode 100644 index 00000000..9b8be04d --- /dev/null +++ b/target/cloudwatch/cloudwatch.go @@ -0,0 +1,65 @@ +package cloudwatch + +// Option represents an option that can be used to configure a cloudwatch metric. +type Option func(target *Cloudwatch) + +// Cloudwatch represents a cloudwatch metric. +type Cloudwatch struct { + Ref string + Namespace string + MetricName string + Region string + Statistics []string + Dimensions map[string]string + Period string + LegendFormat string +} + +// New creates a new cloudwatch query. +func New(metric string, namespace string, options ...Option) *Cloudwatch { + cloudwatch := &Cloudwatch{ + MetricName: metric, + Namespace: namespace, + } + + for _, opt := range options { + opt(cloudwatch) + } + + return cloudwatch +} + +// Legend sets the legend format. +func Legend(legend string) Option { + return func(cloudwatch *Cloudwatch) { + cloudwatch.LegendFormat = legend + } +} + +// Region sets the region. +func Region(region string) Option { + return func(cloudwatch *Cloudwatch) { + cloudwatch.Region = region + } +} + +// Statistic sets the statistic. +func Statistic(statistics []string) Option { + return func(cloudwatch *Cloudwatch) { + cloudwatch.Statistics = statistics + } +} + +// Dimensions sets the dimensions. +func Dimensions(dimensions map[string]string) Option { + return func(cloudwatch *Cloudwatch) { + cloudwatch.Dimensions = dimensions + } +} + +// Ref sets the reference ID for this query. +func Ref(ref string) Option { + return func(cloudwatch *Cloudwatch) { + cloudwatch.Ref = ref + } +} diff --git a/target/cloudwatch/cloudwatch_test.go b/target/cloudwatch/cloudwatch_test.go new file mode 100644 index 00000000..bcc48158 --- /dev/null +++ b/target/cloudwatch/cloudwatch_test.go @@ -0,0 +1,52 @@ +package cloudwatch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewCloudwatchTargetCanBeCreated(t *testing.T) { + req := require.New(t) + metricName := "Cloudwatch" + namespace := "Test" + + target := New(metricName, namespace) + + req.Equal(metricName, target.MetricName) + req.Equal(namespace, target.Namespace) +} + +func TestLegendCanBeConfigured(t *testing.T) { + req := require.New(t) + legend := "lala" + + target := New("", "", Legend(legend)) + + req.Equal(legend, target.LegendFormat) +} + +func TestRefCanBeConfigured(t *testing.T) { + req := require.New(t) + + target := New("", "", Ref("A")) + + req.Equal("A", target.Ref) +} + +func TestRegionCanBeSet(t *testing.T) { + req := require.New(t) + + target := New("", "", Region("C")) + + req.Equal("C", target.Region) +} + +func TestStatisticCanBeSet(t *testing.T) { + req := require.New(t) + statistics := []string{"A", "B", "C"} + + target := New("", "", Statistic(statistics)) + + req.Equal(statistics, target.Statistics) +}