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:]
}
|