summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/modules/memcached/collect.go
blob: 9ead8f47b2600dc1a87bc890638909493f8e1a31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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:]
}