master
go 121 lines 2.56 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package memcached
4
5 import (
6 "bufio"
7 "bytes"
8 "errors"
9 "strconv"
10 "strings"
11 )
12
13 // https://github.com/memcached/memcached/blob/b1aefcdf8a265f8a5126e8aa107a50988fa1ec35/doc/protocol.txt#L1267
14 var statsMetrics = map[string]bool{
15 "limit_maxbytes": true,
16 "bytes": true,
17 "bytes_read": true,
18 "bytes_written": true,
19 "cas_badval": true,
20 "cas_hits": true,
21 "cas_misses": true,
22 "cmd_get": true,
23 "cmd_set": true,
24 "cmd_touch": true,
25 "curr_connections": true,
26 "curr_items": true,
27 "decr_hits": true,
28 "decr_misses": true,
29 "delete_hits": true,
30 "delete_misses": true,
31 "evictions": true,
32 "get_hits": true,
33 "get_misses": true,
34 "incr_hits": true,
35 "incr_misses": true,
36 "reclaimed": true,
37 "rejected_connections": true,
38 "total_connections": true,
39 "total_items": true,
40 "touch_hits": true,
41 "touch_misses": true,
42 }
43
44 func (c *Collector) collect() (map[string]int64, error) {
45 if c.conn == nil {
46 conn, err := c.establishConn()
47 if err != nil {
48 return nil, err
49 }
50 c.conn = conn
51 }
52
53 stats, err := c.conn.queryStats()
54 if err != nil {
55 c.conn.disconnect()
56 c.conn = nil
57 return nil, err
58 }
59
60 mx := make(map[string]int64)
61
62 if err := c.collectStats(mx, stats); err != nil {
63 return nil, err
64 }
65
66 return mx, nil
67 }
68
69 func (c *Collector) collectStats(mx map[string]int64, stats []byte) error {
70 if len(stats) == 0 {
71 return errors.New("empty stats response")
72 }
73
74 var n int
75 sc := bufio.NewScanner(bytes.NewReader(stats))
76
77 for sc.Scan() {
78 line := strings.TrimSpace(sc.Text())
79
80 switch {
81 case strings.HasPrefix(line, "STAT"):
82 key, value := getStatKeyValue(line)
83 if !statsMetrics[key] {
84 continue
85 }
86 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
87 mx[key] = v
88 n++
89 }
90 case strings.HasPrefix(line, "ERROR"):
91 return errors.New("received ERROR response")
92 }
93 }
94
95 if n == 0 {
96 return errors.New("unexpected memcached response")
97 }
98
99 mx["avail"] = mx["limit_maxbytes"] - mx["bytes"]
100
101 return nil
102 }
103
104 func (c *Collector) establishConn() (memcachedConn, error) {
105 conn := c.newMemcachedConn(c.Config)
106
107 if err := conn.connect(); err != nil {
108 return nil, err
109 }
110
111 return conn, nil
112 }
113
114 func getStatKeyValue(line string) (string, string) {
115 line = strings.TrimPrefix(line, "STAT ")
116 before, after, ok := strings.Cut(line, " ")
117 if !ok {
118 return "", ""
119 }
120 return before, after
121 }