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