master
go 407 lines 12.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package redis
4
5 import (
6 "context"
7 "errors"
8 "os"
9 "strings"
10 "testing"
11
12 "github.com/redis/go-redis/v9"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15
16 "github.com/netdata/netdata/go/plugins/pkg/tlscfg"
17 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
18 )
19
20 var (
21 dataConfigJSON, _ = os.ReadFile("testdata/config.json")
22 dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
23
24 dataPikaInfoAll, _ = os.ReadFile("testdata/pika/info_all.txt")
25 dataVer609InfoAll, _ = os.ReadFile("testdata/v6.0.9/info_all.txt")
26 )
27
28 func Test_testDataIsValid(t *testing.T) {
29 for name, data := range map[string][]byte{
30 "dataConfigJSON": dataConfigJSON,
31 "dataConfigYAML": dataConfigYAML,
32 "dataPikaInfoAll": dataPikaInfoAll,
33 "dataVer609InfoAll": dataVer609InfoAll,
34 } {
35 require.NotNil(t, data, name)
36 }
37 }
38
39 func TestCollector_ConfigurationSerialize(t *testing.T) {
40 collecttest.TestConfigurationSerialize(t, &Collector{}, dataConfigJSON, dataConfigYAML)
41 }
42
43 func TestCollector_Init(t *testing.T) {
44 tests := map[string]struct {
45 config Config
46 wantFail bool
47 }{
48 "success on default config": {
49 config: New().Config,
50 },
51 "fails on unset 'address'": {
52 wantFail: true,
53 config: Config{Address: ""},
54 },
55 "fails on invalid 'address' format": {
56 wantFail: true,
57 config: Config{Address: "127.0.0.1:6379"},
58 },
59 "fails on invalid TLSCA": {
60 wantFail: true,
61 config: Config{
62 Address: "redis://127.0.0.1:6379",
63 TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"},
64 },
65 },
66 }
67
68 for name, test := range tests {
69 t.Run(name, func(t *testing.T) {
70 collr := New()
71 collr.Config = test.config
72
73 if test.wantFail {
74 assert.Error(t, collr.Init(context.Background()))
75 } else {
76 assert.NoError(t, collr.Init(context.Background()))
77 }
78 })
79 }
80 }
81
82 func TestCollector_Check(t *testing.T) {
83 tests := map[string]struct {
84 prepare func(t *testing.T) *Collector
85 wantFail bool
86 }{
87 "success on valid response v6.0.9": {
88 prepare: prepareRedisV609,
89 },
90 "fails on error on Info": {
91 wantFail: true,
92 prepare: prepareRedisErrorOnInfo,
93 },
94 "fails on response from not Redis instance": {
95 wantFail: true,
96 prepare: prepareRedisWithPikaMetrics,
97 },
98 }
99
100 for name, test := range tests {
101 t.Run(name, func(t *testing.T) {
102 collr := test.prepare(t)
103
104 if test.wantFail {
105 assert.Error(t, collr.Check(context.Background()))
106 } else {
107 assert.NoError(t, collr.Check(context.Background()))
108 }
109 })
110 }
111 }
112
113 func TestCollector_Charts(t *testing.T) {
114 collr := New()
115 require.NoError(t, collr.Init(context.Background()))
116
117 assert.NotNil(t, collr.Charts())
118 }
119
120 func TestCollector_Cleanup(t *testing.T) {
121 collr := New()
122 assert.NotPanics(t, func() { collr.Cleanup(context.Background()) })
123
124 require.NoError(t, collr.Init(context.Background()))
125 m := &mockRedisClient{}
126 collr.rdb = m
127
128 collr.Cleanup(context.Background())
129
130 assert.True(t, m.calledClose)
131 }
132
133 func TestCollector_Collect(t *testing.T) {
134 tests := map[string]struct {
135 prepare func(t *testing.T) *Collector
136 wantCollected map[string]int64
137 }{
138 "success on valid response v6.0.9": {
139 prepare: prepareRedisV609,
140 wantCollected: map[string]int64{
141 "active_defrag_hits": 0,
142 "active_defrag_key_hits": 0,
143 "active_defrag_key_misses": 0,
144 "active_defrag_misses": 0,
145 "active_defrag_running": 0,
146 "allocator_active": 1208320,
147 "allocator_allocated": 903408,
148 "allocator_frag_bytes": 304912,
149 "allocator_frag_ratio": 1340,
150 "allocator_resident": 3723264,
151 "allocator_rss_bytes": 2514944,
152 "allocator_rss_ratio": 3080,
153 "aof_base_size": 116,
154 "aof_buffer_length": 0,
155 "aof_current_rewrite_time_sec": -1,
156 "aof_current_size": 294,
157 "aof_delayed_fsync": 0,
158 "aof_enabled": 0,
159 "aof_last_cow_size": 0,
160 "aof_last_rewrite_time_sec": -1,
161 "aof_pending_bio_fsync": 0,
162 "aof_pending_rewrite": 0,
163 "aof_rewrite_buffer_length": 0,
164 "aof_rewrite_in_progress": 0,
165 "aof_rewrite_scheduled": 0,
166 "arch_bits": 64,
167 "blocked_clients": 0,
168 "client_recent_max_input_buffer": 8,
169 "client_recent_max_output_buffer": 0,
170 "clients_in_timeout_table": 0,
171 "cluster_enabled": 0,
172 "cmd_command_calls": 2,
173 "cmd_command_usec": 2182,
174 "cmd_command_usec_per_call": 1091000,
175 "cmd_get_calls": 2,
176 "cmd_get_usec": 29,
177 "cmd_get_usec_per_call": 14500,
178 "cmd_hello_calls": 1,
179 "cmd_hello_usec": 15,
180 "cmd_hello_usec_per_call": 15000,
181 "cmd_hmset_calls": 2,
182 "cmd_hmset_usec": 408,
183 "cmd_hmset_usec_per_call": 204000,
184 "cmd_info_calls": 132,
185 "cmd_info_usec": 37296,
186 "cmd_info_usec_per_call": 282550,
187 "cmd_ping_calls": 19,
188 "cmd_ping_usec": 286,
189 "cmd_ping_usec_per_call": 15050,
190 "cmd_set_calls": 3,
191 "cmd_set_usec": 140,
192 "cmd_set_usec_per_call": 46670,
193 "configured_hz": 10,
194 "connected_clients": 1,
195 "connected_slaves": 0,
196 "db0_expires_keys": 0,
197 "db0_keys": 4,
198 "evicted_keys": 0,
199 "expire_cycle_cpu_milliseconds": 28362,
200 "expired_keys": 0,
201 "expired_stale_perc": 0,
202 "expired_time_cap_reached_count": 0,
203 "hz": 10,
204 "instantaneous_input_kbps": 0,
205 "instantaneous_ops_per_sec": 0,
206 "instantaneous_output_kbps": 0,
207 "io_threaded_reads_processed": 0,
208 "io_threaded_writes_processed": 0,
209 "io_threads_active": 0,
210 "keyspace_hit_rate": 100000,
211 "keyspace_hits": 2,
212 "keyspace_misses": 0,
213 "latest_fork_usec": 810,
214 "lazyfree_pending_objects": 0,
215 "loading": 0,
216 "lru_clock": 13181377,
217 "master_repl_offset": 0,
218 "master_replid2": 0,
219 "maxmemory": 0,
220 "mem_aof_buffer": 0,
221 "mem_clients_normal": 0,
222 "mem_clients_slaves": 0,
223 "mem_fragmentation_bytes": 3185848,
224 "mem_fragmentation_ratio": 4960,
225 "mem_not_counted_for_evict": 0,
226 "mem_replication_backlog": 0,
227 "migrate_cached_sockets": 0,
228 "module_fork_in_progress": 0,
229 "module_fork_last_cow_size": 0,
230 "number_of_cached_scripts": 0,
231 "ping_latency_avg": 0,
232 "ping_latency_count": 5,
233 "ping_latency_max": 0,
234 "ping_latency_min": 0,
235 "ping_latency_sum": 0,
236 "process_id": 1,
237 "pubsub_channels": 0,
238 "pubsub_patterns": 0,
239 "rdb_bgsave_in_progress": 0,
240 "rdb_changes_since_last_save": 0,
241 "rdb_current_bgsave_time_sec": 0,
242 "rdb_last_bgsave_status": 0,
243 "rdb_last_bgsave_time_sec": 0,
244 "rdb_last_cow_size": 290816,
245 "rdb_last_save_time": 125697993,
246 "redis_git_dirty": 0,
247 "redis_git_sha1": 0,
248 "rejected_connections": 0,
249 "repl_backlog_active": 0,
250 "repl_backlog_first_byte_offset": 0,
251 "repl_backlog_histlen": 0,
252 "repl_backlog_size": 1048576,
253 "rss_overhead_bytes": 266240,
254 "rss_overhead_ratio": 1070,
255 "second_repl_offset": -1,
256 "slave_expires_tracked_keys": 0,
257 "sync_full": 0,
258 "sync_partial_err": 0,
259 "sync_partial_ok": 0,
260 "tcp_port": 6379,
261 "total_commands_processed": 161,
262 "total_connections_received": 87,
263 "total_net_input_bytes": 2301,
264 "total_net_output_bytes": 507187,
265 "total_reads_processed": 250,
266 "total_system_memory": 2084032512,
267 "total_writes_processed": 163,
268 "tracking_clients": 0,
269 "tracking_total_items": 0,
270 "tracking_total_keys": 0,
271 "tracking_total_prefixes": 0,
272 "unexpected_error_replies": 0,
273 "uptime_in_days": 2,
274 "uptime_in_seconds": 252812,
275 "used_cpu_sys": 630829,
276 "used_cpu_sys_children": 20,
277 "used_cpu_user": 188394,
278 "used_cpu_user_children": 2,
279 "used_memory": 867160,
280 "used_memory_dataset": 63816,
281 "used_memory_lua": 37888,
282 "used_memory_overhead": 803344,
283 "used_memory_peak": 923360,
284 "used_memory_rss": 3989504,
285 "used_memory_scripts": 0,
286 "used_memory_startup": 803152,
287 },
288 },
289 "fails on error on Info": {
290 prepare: prepareRedisErrorOnInfo,
291 },
292 "fails on response from not Redis instance": {
293 prepare: prepareRedisWithPikaMetrics,
294 },
295 }
296
297 for name, test := range tests {
298 t.Run(name, func(t *testing.T) {
299 collr := test.prepare(t)
300
301 mx := collr.Collect(context.Background())
302
303 copyTimeRelatedMetrics(mx, test.wantCollected)
304
305 assert.Equal(t, test.wantCollected, mx)
306 if len(test.wantCollected) > 0 {
307 collecttest.TestMetricsHasAllChartsDims(t, collr.Charts(), mx)
308 ensureCollectedCommandsAddedToCharts(t, collr)
309 ensureCollectedDbsAddedToCharts(t, collr)
310 }
311 })
312 }
313 }
314
315 func prepareRedisV609(t *testing.T) *Collector {
316 collr := New()
317 require.NoError(t, collr.Init(context.Background()))
318 collr.rdb = &mockRedisClient{
319 result: dataVer609InfoAll,
320 }
321 return collr
322 }
323
324 func prepareRedisErrorOnInfo(t *testing.T) *Collector {
325 collr := New()
326 require.NoError(t, collr.Init(context.Background()))
327 collr.rdb = &mockRedisClient{
328 errOnInfo: true,
329 }
330 return collr
331 }
332
333 func prepareRedisWithPikaMetrics(t *testing.T) *Collector {
334 collr := New()
335 require.NoError(t, collr.Init(context.Background()))
336 collr.rdb = &mockRedisClient{
337 result: dataPikaInfoAll,
338 }
339 return collr
340 }
341 func ensureCollectedCommandsAddedToCharts(t *testing.T, collr *Collector) {
342 for _, id := range []string{
343 chartCommandsCalls.ID,
344 chartCommandsUsec.ID,
345 chartCommandsUsecPerSec.ID,
346 } {
347 chart := collr.Charts().Get(id)
348 require.NotNilf(t, chart, "'%s' chart is not in charts", id)
349 assert.Lenf(t, chart.Dims, len(collr.collectedCommands),
350 "'%s' chart unexpected number of dimensions", id)
351 }
352 }
353
354 func ensureCollectedDbsAddedToCharts(t *testing.T, collr *Collector) {
355 for _, id := range []string{
356 chartKeys.ID,
357 chartExpiresKeys.ID,
358 } {
359 chart := collr.Charts().Get(id)
360 require.NotNilf(t, chart, "'%s' chart is not in charts", id)
361 assert.Lenf(t, chart.Dims, len(collr.collectedDbs),
362 "'%s' chart unexpected number of dimensions", id)
363 }
364 }
365
366 func copyTimeRelatedMetrics(dst, src map[string]int64) {
367 for k, v := range src {
368 switch {
369 case k == "rdb_last_save_time",
370 strings.HasPrefix(k, "ping_latency"):
371
372 if _, ok := dst[k]; ok {
373 dst[k] = v
374 }
375 }
376 }
377 }
378
379 type mockRedisClient struct {
380 errOnInfo bool
381 result []byte
382 calledClose bool
383 }
384
385 func (m *mockRedisClient) Info(_ context.Context, _ ...string) (cmd *redis.StringCmd) {
386 if m.errOnInfo {
387 cmd = redis.NewStringResult("", errors.New("error on Info"))
388 } else {
389 cmd = redis.NewStringResult(string(m.result), nil)
390 }
391 return cmd
392 }
393
394 func (m *mockRedisClient) Ping(_ context.Context) (cmd *redis.StatusCmd) {
395 return redis.NewStatusResult("PONG", nil)
396 }
397
398 func (m *mockRedisClient) SlowLogGet(ctx context.Context, num int64) *redis.SlowLogCmd {
399 cmd := redis.NewSlowLogCmd(ctx, "slowlog", "get", num)
400 cmd.SetVal([]redis.SlowLog{})
401 return cmd
402 }
403
404 func (m *mockRedisClient) Close() error {
405 m.calledClose = true
406 return nil
407 }