diff --git a/plugins/inputs/couchbase/couchbase.go b/plugins/inputs/couchbase/couchbase.go index 353ad4be4f79e..2c9f0ac3f8cc4 100644 --- a/plugins/inputs/couchbase/couchbase.go +++ b/plugins/inputs/couchbase/couchbase.go @@ -4,6 +4,7 @@ package couchbase import ( _ "embed" "encoding/json" + "fmt" "net/http" "regexp" "sync" @@ -21,12 +22,11 @@ import ( var sampleConfig string type Couchbase struct { - Servers []string - + Servers []string `toml:"servers"` BucketStatsIncluded []string `toml:"bucket_stats_included"` - - ClusterBucketStats bool `toml:"cluster_bucket_stats"` - NodeBucketStats bool `toml:"node_bucket_stats"` + ClusterBucketStats bool `toml:"cluster_bucket_stats"` + NodeBucketStats bool `toml:"node_bucket_stats"` + AutoFailoverStats bool `toml:"autofailover_stats"` bucketInclude filter.Filter client *http.Client @@ -34,6 +34,13 @@ type Couchbase struct { tls.ClientConfig } +type autoFailover struct { + Count int `json:"count"` + Enabled bool `json:"enabled"` + MaxCount int `json:"maxCount"` + Timeout int `json:"timeout"` +} + var regexpURI = regexp.MustCompile(`(\S+://)?(\S+\:\S+@)`) func (*Couchbase) SampleConfig() string { @@ -87,9 +94,8 @@ func (cb *Couchbase) gatherServer(acc telegraf.Accumulator, addr string) error { acc.AddFields("couchbase_node", fields, tags) } + cluster := regexpURI.ReplaceAllString(addr, "${1}") for name, bucket := range pool.BucketMap { - cluster := regexpURI.ReplaceAllString(addr, "${1}") - if cb.ClusterBucketStats { fields := cb.basicBucketStats(bucket.BasicStats) tags := map[string]string{"cluster": cluster, "bucket": name} @@ -117,9 +123,49 @@ func (cb *Couchbase) gatherServer(acc telegraf.Accumulator, addr string) error { } } + if cb.AutoFailoverStats { + tags := map[string]string{"cluster": cluster} + fields, err := cb.gatherAutoFailoverStats(addr) + if err != nil { + return fmt.Errorf("unable to collect autofailover settings: %w", err) + } + + acc.AddFields("couchbase_node_autofailover", fields, tags) + } + return nil } +func (cb *Couchbase) gatherAutoFailoverStats(server string) (map[string]any, error) { + var fields map[string]any + + url := server + "/settings/autoFailover" + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fields, err + } + + r, err := cb.client.Do(req) + if err != nil { + return fields, err + } + defer r.Body.Close() + + var stats autoFailover + if err := json.NewDecoder(r.Body).Decode(&stats); err != nil { + return fields, err + } + + fields = map[string]any{ + "count": stats.Count, + "enabled": stats.Enabled, + "max_count": stats.MaxCount, + "timeout": stats.Timeout, + } + + return fields, nil +} + // basicBucketStats gets the basic bucket statistics func (cb *Couchbase) basicBucketStats(basicStats map[string]interface{}) map[string]interface{} { fields := make(map[string]interface{}) diff --git a/plugins/inputs/couchbase/couchbase_test.go b/plugins/inputs/couchbase/couchbase_test.go index 40600acb4c23c..044ab25fd4356 100644 --- a/plugins/inputs/couchbase/couchbase_test.go +++ b/plugins/inputs/couchbase/couchbase_test.go @@ -171,6 +171,50 @@ func TestGatherNodeOnly(t *testing.T) { acc.AssertDoesNotContainMeasurement(t, "couchbase_bucket") } +func TestGatherFailover(t *testing.T) { + faker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/pools": + _, _ = w.Write(readJSON(t, "testdata/pools_response.json")) + case "/pools/default": + _, _ = w.Write(readJSON(t, "testdata/pools_default_response.json")) + case "/pools/default/buckets": + _, _ = w.Write(readJSON(t, "testdata/bucket_response.json")) + case "/settings/autoFailover": + _, _ = w.Write(readJSON(t, "testdata/settings_autofailover.json")) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + + cb := Couchbase{ + Servers: []string{faker.URL}, + ClusterBucketStats: false, + NodeBucketStats: false, + AutoFailoverStats: true, + } + require.NoError(t, cb.Init()) + + var acc testutil.Accumulator + require.NoError(t, cb.gatherServer(&acc, faker.URL)) + require.Equal(t, 0, len(acc.Errors)) + require.Equal(t, 8, len(acc.Metrics)) + + var metric *testutil.Metric + for _, m := range acc.Metrics { + if m.Measurement == "couchbase_node_autofailover" { + metric = m + break + } + } + + require.NotNil(t, metric) + require.Equal(t, 1, metric.Fields["count"]) + require.Equal(t, true, metric.Fields["enabled"]) + require.Equal(t, 2, metric.Fields["max_count"]) + require.Equal(t, 72, metric.Fields["timeout"]) +} + func readJSON(t *testing.T, jsonFilePath string) []byte { data, err := os.ReadFile(jsonFilePath) require.NoErrorf(t, err, "could not read from data file %s", jsonFilePath) diff --git a/plugins/inputs/couchbase/sample.conf b/plugins/inputs/couchbase/sample.conf index ccbdd52025377..c32063b3b630d 100644 --- a/plugins/inputs/couchbase/sample.conf +++ b/plugins/inputs/couchbase/sample.conf @@ -25,7 +25,10 @@ ## Whether to collect cluster-wide bucket statistics ## It is recommended to disable this in favor of node_stats ## to get a better view of the cluster. - cluster_bucket_stats = true + # cluster_bucket_stats = true ## Whether to collect bucket stats for each individual node - node_bucket_stats = false + # node_bucket_stats = false + + ## Whether to collect auto-failover settings + # autofailover_stats = false