diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/memcached/collect.go')
-rw-r--r-- | src/go/plugin/go.d/modules/memcached/collect.go | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/memcached/collect.go b/src/go/plugin/go.d/modules/memcached/collect.go new file mode 100644 index 000000000..9ead8f47b --- /dev/null +++ b/src/go/plugin/go.d/modules/memcached/collect.go @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package memcached + +import ( + "bufio" + "bytes" + "errors" + "strconv" + "strings" +) + +// https://github.com/memcached/memcached/blob/b1aefcdf8a265f8a5126e8aa107a50988fa1ec35/doc/protocol.txt#L1267 +var statsMetrics = map[string]bool{ + "limit_maxbytes": true, + "bytes": true, + "bytes_read": true, + "bytes_written": true, + "cas_badval": true, + "cas_hits": true, + "cas_misses": true, + "cmd_get": true, + "cmd_set": true, + "cmd_touch": true, + "curr_connections": true, + "curr_items": true, + "decr_hits": true, + "decr_misses": true, + "delete_hits": true, + "delete_misses": true, + "evictions": true, + "get_hits": true, + "get_misses": true, + "incr_hits": true, + "incr_misses": true, + "reclaimed": true, + "rejected_connections": true, + "total_connections": true, + "total_items": true, + "touch_hits": true, + "touch_misses": true, +} + +func (m *Memcached) collect() (map[string]int64, error) { + if m.conn == nil { + conn, err := m.establishConn() + if err != nil { + return nil, err + } + m.conn = conn + } + + stats, err := m.conn.queryStats() + if err != nil { + m.conn.disconnect() + m.conn = nil + return nil, err + } + + mx := make(map[string]int64) + + if err := m.collectStats(mx, stats); err != nil { + return nil, err + } + + return mx, nil +} + +func (m *Memcached) collectStats(mx map[string]int64, stats []byte) error { + if len(stats) == 0 { + return errors.New("empty stats response") + } + + var n int + sc := bufio.NewScanner(bytes.NewReader(stats)) + + for sc.Scan() { + line := strings.TrimSpace(sc.Text()) + + switch { + case strings.HasPrefix(line, "STAT"): + key, value := getStatKeyValue(line) + if !statsMetrics[key] { + continue + } + if v, err := strconv.ParseInt(value, 10, 64); err == nil { + mx[key] = v + n++ + } + case strings.HasPrefix(line, "ERROR"): + return errors.New("received ERROR response") + } + } + + if n == 0 { + return errors.New("unexpected memcached response") + } + + mx["avail"] = mx["limit_maxbytes"] - mx["bytes"] + + return nil +} + +func (m *Memcached) establishConn() (memcachedConn, error) { + conn := m.newMemcachedConn(m.Config) + + if err := conn.connect(); err != nil { + return nil, err + } + + return conn, nil +} + +func getStatKeyValue(line string) (string, string) { + line = strings.TrimPrefix(line, "STAT ") + i := strings.IndexByte(line, ' ') + if i < 0 { + return "", "" + } + return line[:i], line[i+1:] +} |