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  }