From 4cf47dcd0f525bd295011a7ea2bf865c3ea49dfc Mon Sep 17 00:00:00 2001 From: Cameron Sparr Date: Thu, 3 Dec 2015 13:53:37 -0700 Subject: [PATCH] memcached plugin. Break out metric parsing into it's own func And unit test it using a sample response string. This will make it easier to see what other metrics are available to the plugin for adding future metrics. --- plugins/memcached/memcached.go | 51 +++++++----- plugins/memcached/memcached_test.go | 125 ++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 22 deletions(-) diff --git a/plugins/memcached/memcached.go b/plugins/memcached/memcached.go index 1b89238fc6f4d..9919b0c24979d 100644 --- a/plugins/memcached/memcached.go +++ b/plugins/memcached/memcached.go @@ -28,8 +28,8 @@ var sampleConfig = ` var defaultTimeout = 5 * time.Second -// The list of metrics tha should be calculated -var sendAsIs = []string{ +// The list of metrics that should be sent +var sendMetrics = []string{ "get_hits", "get_misses", "evictions", @@ -128,14 +128,36 @@ func (m *Memcached) gatherServer( return err } - // Read response + values, err := parseResponse(rw.Reader) + if err != nil { + return err + } + + // Add server address as a tag + tags := map[string]string{"server": address} + + // Process values + for _, key := range sendMetrics { + if value, ok := values[key]; ok { + // Mostly it is the number + if iValue, errParse := strconv.ParseInt(value, 10, 64); errParse != nil { + acc.Add(key, value, tags) + } else { + acc.Add(key, iValue, tags) + } + } + } + return nil +} + +func parseResponse(r *bufio.Reader) (map[string]string, error) { values := make(map[string]string) for { // Read line - line, _, errRead := rw.Reader.ReadLine() + line, _, errRead := r.ReadLine() if errRead != nil { - return errRead + return values, errRead } // Done if bytes.Equal(line, []byte("END")) { @@ -144,28 +166,13 @@ func (m *Memcached) gatherServer( // Read values s := bytes.SplitN(line, []byte(" "), 3) if len(s) != 3 || !bytes.Equal(s[0], []byte("STAT")) { - return fmt.Errorf("unexpected line in stats response: %q", line) + return values, fmt.Errorf("unexpected line in stats response: %q", line) } // Save values values[string(s[1])] = string(s[2]) } - - // Add server address as a tag - tags := map[string]string{"server": address} - - // Process values - for _, key := range sendAsIs { - if value, ok := values[key]; ok { - // Mostly it is the number - if iValue, errParse := strconv.ParseInt(value, 10, 64); errParse != nil { - acc.Add(key, value, tags) - } else { - acc.Add(key, iValue, tags) - } - } - } - return nil + return values, nil } func init() { diff --git a/plugins/memcached/memcached_test.go b/plugins/memcached/memcached_test.go index 0c0fdfbe169a4..05ff669b38d90 100644 --- a/plugins/memcached/memcached_test.go +++ b/plugins/memcached/memcached_test.go @@ -1,6 +1,8 @@ package memcached import ( + "bufio" + "strings" "testing" "github.com/influxdb/telegraf/testutil" @@ -33,3 +35,126 @@ func TestMemcachedGeneratesMetrics(t *testing.T) { assert.True(t, acc.HasIntValue(metric), metric) } } + +func TestMemcachedParseMetrics(t *testing.T) { + r := bufio.NewReader(strings.NewReader(memcachedStats)) + values, err := parseResponse(r) + require.NoError(t, err, "Error parsing memcached response") + + tests := []struct { + key string + value string + }{ + {"pid", "23235"}, + {"uptime", "194"}, + {"time", "1449174679"}, + {"version", "1.4.14 (Ubuntu)"}, + {"libevent", "2.0.21-stable"}, + {"pointer_size", "64"}, + {"rusage_user", "0.000000"}, + {"rusage_system", "0.007566"}, + {"curr_connections", "5"}, + {"total_connections", "6"}, + {"connection_structures", "6"}, + {"reserved_fds", "20"}, + {"cmd_get", "0"}, + {"cmd_set", "0"}, + {"cmd_flush", "0"}, + {"cmd_touch", "0"}, + {"get_hits", "0"}, + {"get_misses", "0"}, + {"delete_misses", "0"}, + {"delete_hits", "0"}, + {"incr_misses", "0"}, + {"incr_hits", "0"}, + {"decr_misses", "0"}, + {"decr_hits", "0"}, + {"cas_misses", "0"}, + {"cas_hits", "0"}, + {"cas_badval", "0"}, + {"touch_hits", "0"}, + {"touch_misses", "0"}, + {"auth_cmds", "0"}, + {"auth_errors", "0"}, + {"bytes_read", "7"}, + {"bytes_written", "0"}, + {"limit_maxbytes", "67108864"}, + {"accepting_conns", "1"}, + {"listen_disabled_num", "0"}, + {"threads", "4"}, + {"conn_yields", "0"}, + {"hash_power_level", "16"}, + {"hash_bytes", "524288"}, + {"hash_is_expanding", "0"}, + {"expired_unfetched", "0"}, + {"evicted_unfetched", "0"}, + {"bytes", "0"}, + {"curr_items", "0"}, + {"total_items", "0"}, + {"evictions", "0"}, + {"reclaimed", "0"}, + } + + for _, test := range tests { + value, ok := values[test.key] + if !ok { + t.Errorf("Did not find key for metric %s in values", test.key) + continue + } + if value != test.value { + t.Errorf("Metric: %s, Expected: %s, actual: %s", + test.key, test.value, value) + } + } +} + +var memcachedStats = `STAT pid 23235 +STAT uptime 194 +STAT time 1449174679 +STAT version 1.4.14 (Ubuntu) +STAT libevent 2.0.21-stable +STAT pointer_size 64 +STAT rusage_user 0.000000 +STAT rusage_system 0.007566 +STAT curr_connections 5 +STAT total_connections 6 +STAT connection_structures 6 +STAT reserved_fds 20 +STAT cmd_get 0 +STAT cmd_set 0 +STAT cmd_flush 0 +STAT cmd_touch 0 +STAT get_hits 0 +STAT get_misses 0 +STAT delete_misses 0 +STAT delete_hits 0 +STAT incr_misses 0 +STAT incr_hits 0 +STAT decr_misses 0 +STAT decr_hits 0 +STAT cas_misses 0 +STAT cas_hits 0 +STAT cas_badval 0 +STAT touch_hits 0 +STAT touch_misses 0 +STAT auth_cmds 0 +STAT auth_errors 0 +STAT bytes_read 7 +STAT bytes_written 0 +STAT limit_maxbytes 67108864 +STAT accepting_conns 1 +STAT listen_disabled_num 0 +STAT threads 4 +STAT conn_yields 0 +STAT hash_power_level 16 +STAT hash_bytes 524288 +STAT hash_is_expanding 0 +STAT expired_unfetched 0 +STAT evicted_unfetched 0 +STAT bytes 0 +STAT curr_items 0 +STAT total_items 0 +STAT evictions 0 +STAT reclaimed 0 +END +`