master
go 192 lines 4.62 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package redis
4
5 import (
6 "context"
7 _ "embed"
8 "errors"
9 "fmt"
10 "sync"
11 "time"
12
13 "github.com/blang/semver/v4"
14 "github.com/redis/go-redis/v9"
15
16 "github.com/netdata/netdata/go/plugins/pkg/confopt"
17 "github.com/netdata/netdata/go/plugins/pkg/tlscfg"
18 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
19 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/oldmetrix"
20 )
21
22 //go:embed "config_schema.json"
23 var configSchema string
24
25 type noopLogger struct{}
26
27 func (noopLogger) Printf(context.Context, string, ...any) {}
28
29 func init() {
30 redis.SetLogger(noopLogger{})
31
32 collectorapi.Register("redis", collectorapi.Creator{
33 JobConfigSchema: configSchema,
34 Create: func() collectorapi.CollectorV1 { return New() },
35 Config: func() any { return &Config{} },
36 Methods: redisMethods,
37 MethodHandler: redisFunctionHandler,
38 })
39 }
40
41 func New() *Collector {
42 c := &Collector{
43 Config: Config{
44 Address: "redis://@localhost:6379",
45 Timeout: confopt.Duration(time.Second),
46 PingSamples: 5,
47 Functions: FunctionsConfig{
48 TopQueries: TopQueriesConfig{
49 Limit: 500,
50 },
51 },
52 },
53
54 addAOFChartsOnce: &sync.Once{},
55 addReplSlaveChartsOnce: &sync.Once{},
56 pingSummary: oldmetrix.NewSummary(),
57 collectedCommands: make(map[string]bool),
58 collectedDbs: make(map[string]bool),
59 }
60 c.funcRouter = newFuncRouter(c)
61 return c
62 }
63
64 type Config struct {
65 Vnode string `yaml:"vnode,omitempty" json:"vnode"`
66 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
67 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
68 Address string `yaml:"address" json:"address"`
69 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
70 Username string `yaml:"username,omitempty" json:"username"`
71 Password string `yaml:"password,omitempty" json:"password"`
72 tlscfg.TLSConfig `yaml:",inline" json:""`
73 PingSamples int `yaml:"ping_samples" json:"ping_samples"`
74 Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
75 }
76
77 type FunctionsConfig struct {
78 TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
79 }
80
81 type TopQueriesConfig struct {
82 Disabled bool `yaml:"disabled" json:"disabled"`
83 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
84 Limit int `yaml:"limit,omitempty" json:"limit"`
85 }
86
87 func (c Config) topQueriesTimeout() time.Duration {
88 if c.Functions.TopQueries.Timeout == 0 {
89 return c.Timeout.Duration()
90 }
91 return c.Functions.TopQueries.Timeout.Duration()
92 }
93
94 func (c Config) topQueriesLimit() int {
95 if c.Functions.TopQueries.Limit <= 0 {
96 return 500
97 }
98 return c.Functions.TopQueries.Limit
99 }
100
101 type (
102 Collector struct {
103 collectorapi.Base
104 Config `yaml:",inline" json:""`
105
106 charts *collectorapi.Charts
107 addAOFChartsOnce *sync.Once
108 addReplSlaveChartsOnce *sync.Once
109
110 rdb redisClient
111
112 funcRouter *funcRouter
113
114 server string
115 version *semver.Version
116 pingSummary oldmetrix.Summary
117 collectedCommands map[string]bool
118 collectedDbs map[string]bool
119 }
120 redisClient interface {
121 Info(ctx context.Context, section ...string) *redis.StringCmd
122 Ping(context.Context) *redis.StatusCmd
123 SlowLogGet(ctx context.Context, num int64) *redis.SlowLogCmd
124 Close() error
125 }
126 )
127
128 func (c *Collector) Configuration() any {
129 return c.Config
130 }
131
132 func (c *Collector) Init(context.Context) error {
133 err := c.validateConfig()
134 if err != nil {
135 return fmt.Errorf("config validation: %v", err)
136 }
137
138 rdb, err := c.initRedisClient()
139 if err != nil {
140 return fmt.Errorf("init redis client: %v", err)
141 }
142 c.rdb = rdb
143
144 charts, err := c.initCharts()
145 if err != nil {
146 return fmt.Errorf("init charts: %v", err)
147 }
148 c.charts = charts
149
150 return nil
151 }
152
153 func (c *Collector) Check(context.Context) error {
154 mx, err := c.collect()
155 if err != nil {
156 return err
157 }
158 if len(mx) == 0 {
159 return errors.New("no metrics collected")
160 }
161 return nil
162 }
163
164 func (c *Collector) Charts() *collectorapi.Charts {
165 return c.charts
166 }
167
168 func (c *Collector) Collect(context.Context) map[string]int64 {
169 ms, err := c.collect()
170 if err != nil {
171 c.Error(err)
172 }
173
174 if len(ms) == 0 {
175 return nil
176 }
177 return ms
178 }
179
180 func (c *Collector) Cleanup(ctx context.Context) {
181 if c.funcRouter != nil {
182 c.funcRouter.Cleanup(ctx)
183 }
184 if c.rdb == nil {
185 return
186 }
187 err := c.rdb.Close()
188 if err != nil {
189 c.Warningf("cleanup: error on closing redis client [%s]: %v", c.Address, err)
190 }
191 c.rdb = nil
192 }