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  }