summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/redis/redis_test.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/collectors/go.d.plugin/modules/redis/redis_test.go418
1 files changed, 418 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/redis/redis_test.go b/src/go/collectors/go.d.plugin/modules/redis/redis_test.go
new file mode 100644
index 000000000..c96232c34
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/redis/redis_test.go
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package redis
+
+import (
+ "context"
+ "errors"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+
+ "github.com/go-redis/redis/v8"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+
+ dataPikaInfoAll, _ = os.ReadFile("testdata/pika/info_all.txt")
+ dataVer609InfoAll, _ = os.ReadFile("testdata/v6.0.9/info_all.txt")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ "dataPikaInfoAll": dataPikaInfoAll,
+ "dataVer609InfoAll": dataVer609InfoAll,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestRedis_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &Redis{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestRedis_Init(t *testing.T) {
+ tests := map[string]struct {
+ config Config
+ wantFail bool
+ }{
+ "success on default config": {
+ config: New().Config,
+ },
+ "fails on unset 'address'": {
+ wantFail: true,
+ config: Config{Address: ""},
+ },
+ "fails on invalid 'address' format": {
+ wantFail: true,
+ config: Config{Address: "127.0.0.1:6379"},
+ },
+ "fails on invalid TLSCA": {
+ wantFail: true,
+ config: Config{
+ Address: "redis://127.0.0.1:6379",
+ TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"},
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ rdb := New()
+ rdb.Config = test.config
+
+ if test.wantFail {
+ assert.Error(t, rdb.Init())
+ } else {
+ assert.NoError(t, rdb.Init())
+ }
+ })
+ }
+}
+
+func TestRedis_Check(t *testing.T) {
+ tests := map[string]struct {
+ prepare func(t *testing.T) *Redis
+ wantFail bool
+ }{
+ "success on valid response v6.0.9": {
+ prepare: prepareRedisV609,
+ },
+ "fails on error on Info": {
+ wantFail: true,
+ prepare: prepareRedisErrorOnInfo,
+ },
+ "fails on response from not Redis instance": {
+ wantFail: true,
+ prepare: prepareRedisWithPikaMetrics,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ rdb := test.prepare(t)
+
+ if test.wantFail {
+ assert.Error(t, rdb.Check())
+ } else {
+ assert.NoError(t, rdb.Check())
+ }
+ })
+ }
+}
+
+func TestRedis_Charts(t *testing.T) {
+ rdb := New()
+ require.NoError(t, rdb.Init())
+
+ assert.NotNil(t, rdb.Charts())
+}
+
+func TestRedis_Cleanup(t *testing.T) {
+ rdb := New()
+ assert.NotPanics(t, rdb.Cleanup)
+
+ require.NoError(t, rdb.Init())
+ m := &mockRedisClient{}
+ rdb.rdb = m
+
+ rdb.Cleanup()
+
+ assert.True(t, m.calledClose)
+}
+
+func TestRedis_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepare func(t *testing.T) *Redis
+ wantCollected map[string]int64
+ }{
+ "success on valid response v6.0.9": {
+ prepare: prepareRedisV609,
+ wantCollected: map[string]int64{
+ "active_defrag_hits": 0,
+ "active_defrag_key_hits": 0,
+ "active_defrag_key_misses": 0,
+ "active_defrag_misses": 0,
+ "active_defrag_running": 0,
+ "allocator_active": 1208320,
+ "allocator_allocated": 903408,
+ "allocator_frag_bytes": 304912,
+ "allocator_frag_ratio": 1340,
+ "allocator_resident": 3723264,
+ "allocator_rss_bytes": 2514944,
+ "allocator_rss_ratio": 3080,
+ "aof_base_size": 116,
+ "aof_buffer_length": 0,
+ "aof_current_rewrite_time_sec": -1,
+ "aof_current_size": 294,
+ "aof_delayed_fsync": 0,
+ "aof_enabled": 0,
+ "aof_last_cow_size": 0,
+ "aof_last_rewrite_time_sec": -1,
+ "aof_pending_bio_fsync": 0,
+ "aof_pending_rewrite": 0,
+ "aof_rewrite_buffer_length": 0,
+ "aof_rewrite_in_progress": 0,
+ "aof_rewrite_scheduled": 0,
+ "arch_bits": 64,
+ "blocked_clients": 0,
+ "client_recent_max_input_buffer": 8,
+ "client_recent_max_output_buffer": 0,
+ "clients_in_timeout_table": 0,
+ "cluster_enabled": 0,
+ "cmd_command_calls": 2,
+ "cmd_command_usec": 2182,
+ "cmd_command_usec_per_call": 1091000,
+ "cmd_get_calls": 2,
+ "cmd_get_usec": 29,
+ "cmd_get_usec_per_call": 14500,
+ "cmd_hello_calls": 1,
+ "cmd_hello_usec": 15,
+ "cmd_hello_usec_per_call": 15000,
+ "cmd_hmset_calls": 2,
+ "cmd_hmset_usec": 408,
+ "cmd_hmset_usec_per_call": 204000,
+ "cmd_info_calls": 132,
+ "cmd_info_usec": 37296,
+ "cmd_info_usec_per_call": 282550,
+ "cmd_ping_calls": 19,
+ "cmd_ping_usec": 286,
+ "cmd_ping_usec_per_call": 15050,
+ "cmd_set_calls": 3,
+ "cmd_set_usec": 140,
+ "cmd_set_usec_per_call": 46670,
+ "configured_hz": 10,
+ "connected_clients": 1,
+ "connected_slaves": 0,
+ "db0_expires_keys": 0,
+ "db0_keys": 4,
+ "evicted_keys": 0,
+ "expire_cycle_cpu_milliseconds": 28362,
+ "expired_keys": 0,
+ "expired_stale_perc": 0,
+ "expired_time_cap_reached_count": 0,
+ "hz": 10,
+ "instantaneous_input_kbps": 0,
+ "instantaneous_ops_per_sec": 0,
+ "instantaneous_output_kbps": 0,
+ "io_threaded_reads_processed": 0,
+ "io_threaded_writes_processed": 0,
+ "io_threads_active": 0,
+ "keyspace_hit_rate": 100000,
+ "keyspace_hits": 2,
+ "keyspace_misses": 0,
+ "latest_fork_usec": 810,
+ "lazyfree_pending_objects": 0,
+ "loading": 0,
+ "lru_clock": 13181377,
+ "master_repl_offset": 0,
+ "master_replid2": 0,
+ "maxmemory": 0,
+ "mem_aof_buffer": 0,
+ "mem_clients_normal": 0,
+ "mem_clients_slaves": 0,
+ "mem_fragmentation_bytes": 3185848,
+ "mem_fragmentation_ratio": 4960,
+ "mem_not_counted_for_evict": 0,
+ "mem_replication_backlog": 0,
+ "migrate_cached_sockets": 0,
+ "module_fork_in_progress": 0,
+ "module_fork_last_cow_size": 0,
+ "number_of_cached_scripts": 0,
+ "ping_latency_avg": 0,
+ "ping_latency_count": 5,
+ "ping_latency_max": 0,
+ "ping_latency_min": 0,
+ "ping_latency_sum": 0,
+ "process_id": 1,
+ "pubsub_channels": 0,
+ "pubsub_patterns": 0,
+ "rdb_bgsave_in_progress": 0,
+ "rdb_changes_since_last_save": 0,
+ "rdb_current_bgsave_time_sec": 0,
+ "rdb_last_bgsave_status": 0,
+ "rdb_last_bgsave_time_sec": 0,
+ "rdb_last_cow_size": 290816,
+ "rdb_last_save_time": 56978305,
+ "redis_git_dirty": 0,
+ "redis_git_sha1": 0,
+ "rejected_connections": 0,
+ "repl_backlog_active": 0,
+ "repl_backlog_first_byte_offset": 0,
+ "repl_backlog_histlen": 0,
+ "repl_backlog_size": 1048576,
+ "rss_overhead_bytes": 266240,
+ "rss_overhead_ratio": 1070,
+ "second_repl_offset": -1,
+ "slave_expires_tracked_keys": 0,
+ "sync_full": 0,
+ "sync_partial_err": 0,
+ "sync_partial_ok": 0,
+ "tcp_port": 6379,
+ "total_commands_processed": 161,
+ "total_connections_received": 87,
+ "total_net_input_bytes": 2301,
+ "total_net_output_bytes": 507187,
+ "total_reads_processed": 250,
+ "total_system_memory": 2084032512,
+ "total_writes_processed": 163,
+ "tracking_clients": 0,
+ "tracking_total_items": 0,
+ "tracking_total_keys": 0,
+ "tracking_total_prefixes": 0,
+ "unexpected_error_replies": 0,
+ "uptime_in_days": 2,
+ "uptime_in_seconds": 252812,
+ "used_cpu_sys": 630829,
+ "used_cpu_sys_children": 20,
+ "used_cpu_user": 188394,
+ "used_cpu_user_children": 2,
+ "used_memory": 867160,
+ "used_memory_dataset": 63816,
+ "used_memory_lua": 37888,
+ "used_memory_overhead": 803344,
+ "used_memory_peak": 923360,
+ "used_memory_rss": 3989504,
+ "used_memory_scripts": 0,
+ "used_memory_startup": 803152,
+ },
+ },
+ "fails on error on Info": {
+ prepare: prepareRedisErrorOnInfo,
+ },
+ "fails on response from not Redis instance": {
+ prepare: prepareRedisWithPikaMetrics,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ rdb := test.prepare(t)
+
+ ms := rdb.Collect()
+
+ copyTimeRelatedMetrics(ms, test.wantCollected)
+
+ assert.Equal(t, test.wantCollected, ms)
+ if len(test.wantCollected) > 0 {
+ ensureCollectedHasAllChartsDimsVarsIDs(t, rdb, ms)
+ ensureCollectedCommandsAddedToCharts(t, rdb)
+ ensureCollectedDbsAddedToCharts(t, rdb)
+ }
+ })
+ }
+}
+
+func prepareRedisV609(t *testing.T) *Redis {
+ rdb := New()
+ require.NoError(t, rdb.Init())
+ rdb.rdb = &mockRedisClient{
+ result: dataVer609InfoAll,
+ }
+ return rdb
+}
+
+func prepareRedisErrorOnInfo(t *testing.T) *Redis {
+ rdb := New()
+ require.NoError(t, rdb.Init())
+ rdb.rdb = &mockRedisClient{
+ errOnInfo: true,
+ }
+ return rdb
+}
+
+func prepareRedisWithPikaMetrics(t *testing.T) *Redis {
+ rdb := New()
+ require.NoError(t, rdb.Init())
+ rdb.rdb = &mockRedisClient{
+ result: dataPikaInfoAll,
+ }
+ return rdb
+}
+
+func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, rdb *Redis, ms map[string]int64) {
+ for _, chart := range *rdb.Charts() {
+ if chart.Obsolete {
+ continue
+ }
+ for _, dim := range chart.Dims {
+ _, ok := ms[dim.ID]
+ assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID)
+ }
+ for _, v := range chart.Vars {
+ _, ok := ms[v.ID]
+ assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID)
+ }
+ }
+}
+
+func ensureCollectedCommandsAddedToCharts(t *testing.T, rdb *Redis) {
+ for _, id := range []string{
+ chartCommandsCalls.ID,
+ chartCommandsUsec.ID,
+ chartCommandsUsecPerSec.ID,
+ } {
+ chart := rdb.Charts().Get(id)
+ require.NotNilf(t, chart, "'%s' chart is not in charts", id)
+ assert.Lenf(t, chart.Dims, len(rdb.collectedCommands),
+ "'%s' chart unexpected number of dimensions", id)
+ }
+}
+
+func ensureCollectedDbsAddedToCharts(t *testing.T, rdb *Redis) {
+ for _, id := range []string{
+ chartKeys.ID,
+ chartExpiresKeys.ID,
+ } {
+ chart := rdb.Charts().Get(id)
+ require.NotNilf(t, chart, "'%s' chart is not in charts", id)
+ assert.Lenf(t, chart.Dims, len(rdb.collectedDbs),
+ "'%s' chart unexpected number of dimensions", id)
+ }
+}
+
+func copyTimeRelatedMetrics(dst, src map[string]int64) {
+ for k, v := range src {
+ switch {
+ case k == "rdb_last_save_time",
+ strings.HasPrefix(k, "ping_latency"):
+
+ if _, ok := dst[k]; ok {
+ dst[k] = v
+ }
+ }
+ }
+}
+
+type mockRedisClient struct {
+ errOnInfo bool
+ result []byte
+ calledClose bool
+}
+
+func (m *mockRedisClient) Info(_ context.Context, _ ...string) (cmd *redis.StringCmd) {
+ if m.errOnInfo {
+ cmd = redis.NewStringResult("", errors.New("error on Info"))
+ } else {
+ cmd = redis.NewStringResult(string(m.result), nil)
+ }
+ return cmd
+}
+
+func (m *mockRedisClient) Ping(_ context.Context) (cmd *redis.StatusCmd) {
+ return redis.NewStatusResult("PONG", nil)
+}
+
+func (m *mockRedisClient) Close() error {
+ m.calledClose = true
+ return nil
+}