github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-memcached/lib/memcached.go (about) 1 package mpmemcached 2 3 import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io" 8 "log" 9 "net" 10 "strconv" 11 "strings" 12 13 mp "github.com/mackerelio/go-mackerel-plugin" 14 "golang.org/x/text/cases" 15 "golang.org/x/text/language" 16 ) 17 18 // MemcachedPlugin mackerel plugin for memchached 19 type MemcachedPlugin struct { 20 Target string 21 Socket string 22 Tempfile string 23 Prefix string 24 } 25 26 // MetricKeyPrefix interface for PluginWithPrefix 27 func (m MemcachedPlugin) MetricKeyPrefix() string { 28 if m.Prefix == "" { 29 m.Prefix = "memcached" 30 } 31 return m.Prefix 32 } 33 34 // FetchMetrics interface for mackerelplugin 35 func (m MemcachedPlugin) FetchMetrics() (map[string]float64, error) { 36 network := "tcp" 37 target := m.Target 38 if m.Socket != "" { 39 network = "unix" 40 target = m.Socket 41 } 42 conn, err := net.Dial(network, target) 43 if err != nil { 44 return nil, err 45 } 46 defer conn.Close() 47 48 fmt.Fprintln(conn, "stats") 49 50 ret, err := m.parseStats(conn) 51 if err != nil { 52 return nil, err 53 } 54 ret2, err := m.parseStatsItems(conn) 55 if err != nil { 56 log.Printf("failed to get stats items: %s", err.Error()) 57 } else { 58 for k, v := range ret2 { 59 ret[k] = v 60 } 61 } 62 return ret, nil 63 } 64 65 func (m MemcachedPlugin) parseStatsItems(conn io.ReadWriter) (map[string]float64, error) { 66 ret := make(map[string]float64) 67 fmt.Fprint(conn, "stats items\r\n") 68 scr := bufio.NewScanner(bufio.NewReader(conn)) 69 for scr.Scan() { 70 // ex. STAT items:1:number 1 71 line := scr.Text() 72 if line == "END" { 73 break 74 } 75 fields := strings.Fields(line) 76 if len(fields) != 3 { 77 return nil, fmt.Errorf("result of `stats items` is strange: %s", line) 78 } 79 fields2 := strings.Split(fields[1], ":") 80 if len(fields2) != 3 { 81 return nil, fmt.Errorf("result of `stats items` is strange: %s", line) 82 } 83 if fields2[2] == "evicted_nonzero" { 84 value, err := strconv.ParseFloat(fields[2], 64) 85 if err == nil { 86 ret["nonzero_evictions"] += value 87 } 88 } 89 } 90 return ret, scr.Err() 91 } 92 93 func (m MemcachedPlugin) parseStats(conn io.Reader) (map[string]float64, error) { 94 scanner := bufio.NewScanner(conn) 95 stat := make(map[string]float64) 96 97 for scanner.Scan() { 98 line := scanner.Text() 99 s := string(line) 100 if s == "END" { 101 stat["new_items"] = stat["total_items"] 102 return stat, nil 103 } 104 105 res := strings.Split(s, " ") 106 if res[0] == "STAT" { 107 f, err := strconv.ParseFloat(res[2], 64) 108 if err == nil { 109 stat[res[1]] = f 110 } 111 } 112 } 113 if err := scanner.Err(); err != nil { 114 return stat, err 115 } 116 return nil, nil 117 } 118 119 // GraphDefinition interface for mackerelplugin 120 func (m MemcachedPlugin) GraphDefinition() map[string]mp.Graphs { 121 labelPrefix := cases.Title(language.Und, cases.NoLower).String(m.Prefix) 122 123 // https://github.com/memcached/memcached/blob/master/doc/protocol.txt 124 var graphdef = map[string]mp.Graphs{ 125 "connections": { 126 Label: (labelPrefix + " Connections"), 127 Unit: mp.UnitInteger, 128 Metrics: []mp.Metrics{ 129 {Name: "curr_connections", Label: "Connections"}, 130 }, 131 }, 132 "cmd": { 133 Label: (labelPrefix + " Command"), 134 Unit: mp.UnitInteger, 135 Metrics: []mp.Metrics{ 136 {Name: "cmd_get", Label: "Get", Diff: true}, 137 {Name: "cmd_set", Label: "Set", Diff: true}, 138 {Name: "cmd_flush", Label: "Flush", Diff: true}, 139 {Name: "cmd_touch", Label: "Touch", Diff: true}, 140 }, 141 }, 142 "hitmiss": { 143 Label: (labelPrefix + " Hits/Misses"), 144 Unit: mp.UnitInteger, 145 Metrics: []mp.Metrics{ 146 {Name: "get_hits", Label: "Get Hits", Diff: true}, 147 {Name: "get_misses", Label: "Get Misses", Diff: true}, 148 {Name: "delete_hits", Label: "Delete Hits", Diff: true}, 149 {Name: "delete_misses", Label: "Delete Misses", Diff: true}, 150 {Name: "incr_hits", Label: "Incr Hits", Diff: true}, 151 {Name: "incr_misses", Label: "Incr Misses", Diff: true}, 152 {Name: "cas_hits", Label: "Cas Hits", Diff: true}, 153 {Name: "cas_misses", Label: "Cas Misses", Diff: true}, 154 {Name: "touch_hits", Label: "Touch Hits", Diff: true}, 155 {Name: "touch_misses", Label: "Touch Misses", Diff: true}, 156 }, 157 }, 158 "evictions": { 159 Label: (labelPrefix + " Evictions"), 160 Unit: mp.UnitInteger, 161 Metrics: []mp.Metrics{ 162 {Name: "evictions", Label: "Evictions", Diff: true}, 163 {Name: "nonzero_evictions", Label: "Nonzero Evictions", Diff: true}, 164 {Name: "reclaimed", Label: "Reclaimed", Diff: true}, 165 }, 166 }, 167 "unfetched": { 168 Label: (labelPrefix + " Unfetched"), 169 Unit: mp.UnitInteger, 170 Metrics: []mp.Metrics{ 171 {Name: "expired_unfetched", Label: "Expired unfetched", Diff: true}, 172 {Name: "evicted_unfetched", Label: "Evicted unfetched", Diff: true}, 173 }, 174 }, 175 "rusage": { 176 Label: (labelPrefix + " Resouce Usage"), 177 Unit: mp.UnitFloat, 178 Metrics: []mp.Metrics{ 179 {Name: "rusage_user", Label: "User", Diff: true}, 180 {Name: "rusage_system", Label: "System", Diff: true}, 181 }, 182 }, 183 "bytes": { 184 Label: (labelPrefix + " Traffics"), 185 Unit: mp.UnitBytes, 186 Metrics: []mp.Metrics{ 187 {Name: "bytes_read", Label: "Read", Diff: true}, 188 {Name: "bytes_written", Label: "Write", Diff: true}, 189 }, 190 }, 191 "cachesize": { 192 Label: (labelPrefix + " Cache Size"), 193 Unit: mp.UnitBytes, 194 Metrics: []mp.Metrics{ 195 {Name: "limit_maxbytes", Label: "Total"}, 196 {Name: "bytes", Label: "Used"}, 197 }, 198 }, 199 "items": { 200 Label: (labelPrefix + " Items"), 201 Unit: mp.UnitInteger, 202 Metrics: []mp.Metrics{ 203 {Name: "curr_items", Label: "Current Items"}, 204 {Name: "new_items", Label: "New Items", Diff: true}, 205 }, 206 }, 207 } 208 return graphdef 209 } 210 211 // Do the plugin 212 func Do() { 213 optHost := flag.String("host", "localhost", "Hostname") 214 optPort := flag.String("port", "11211", "Port") 215 optSocket := flag.String("socket", "", "Server socket (overrides hosts and port)") 216 optPrefix := flag.String("metric-key-prefix", "memcached", "Metric key prefix") 217 optTempfile := flag.String("tempfile", "", "Temp file name") 218 flag.Parse() 219 220 var memcached MemcachedPlugin 221 222 memcached.Prefix = *optPrefix 223 224 if *optSocket != "" { 225 memcached.Socket = *optSocket 226 } else { 227 memcached.Target = fmt.Sprintf("%s:%s", *optHost, *optPort) 228 } 229 helper := mp.NewMackerelPlugin(memcached) 230 helper.Tempfile = *optTempfile 231 helper.Run() 232 }