Skip to content

Commit

Permalink
Fetching statistics from memcached server
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin Nikitin committed Feb 19, 2017
1 parent 1952afa commit 0473636
Showing 1 changed file with 124 additions and 0 deletions.
124 changes: 124 additions & 0 deletions memcache/memcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ var (

// ErrNoServers is returned when no servers are configured or available.
ErrNoServers = errors.New("memcache: no servers configured or available")

// ErrBadStatsLine means that the response line was expected to be
// a line with server statistics information, but the parser was not
// able to parse it.
ErrBadStatsLine = errors.New("memcache: bad stats line")
)

const (
Expand Down Expand Up @@ -113,6 +118,12 @@ var (
resultTouched = []byte("TOUCHED\r\n")

resultClientErrorPrefix = []byte("CLIENT_ERROR ")

statsGetHits = []byte("STAT get_hits ")
statsGetMisses = []byte("STAT get_misses ")
statsBytesWritten = []byte("STAT bytes_written ")
statsItems = []byte("STAT curr_items ")
statsBytes = []byte("STAT bytes ")
)

// New returns a memcache client using the provided server(s)
Expand Down Expand Up @@ -179,6 +190,24 @@ type conn struct {
c *Client
}

// Statistics is a record of a single memcached server usage stats.
type Statistics struct {
// Hits is a counter of cache hits.
Hits uint64

// Misses is a counter of cache misses.
Misses uint64

// ByteHits is amount of bytes transferred for gets.
ByteHits uint64

// Items is amount of keys currently in the cache.
Items uint64

// Bytes is a size of all items currently in the cache.
Bytes uint64
}

// release returns this connection back to the client's free pool
func (cn *conn) release() {
cn.c.putFreeConn(cn.addr, cn)
Expand Down Expand Up @@ -682,3 +711,98 @@ func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) {
})
return val, err
}

func parseStatsLine(s []byte, expectedPrefix []byte) (uint64, error) {
if !bytes.HasPrefix(s, expectedPrefix) {
return 0, ErrBadStatsLine
}
return strconv.ParseUint(
string(s[len(expectedPrefix):len(s)-2]), 10, 64)
}

func parseStatsResponse(r *bufio.Reader, stats *Statistics) error {
for {
line, err := r.ReadSlice('\n')
if err != nil {
return err
}
if bytes.Equal(line, resultEnd) {
return nil
}
if hits, err := parseStatsLine(line, statsGetHits); err == nil {
stats.Hits = hits
continue
}
if misses, err := parseStatsLine(line, statsGetMisses); err == nil {
stats.Misses = misses
continue
}
if bytesWritten, err := parseStatsLine(line, statsBytesWritten); err == nil {
stats.ByteHits = bytesWritten
continue
}
if items, err := parseStatsLine(line, statsItems); err == nil {
stats.Items = items
continue
}
if bytes_total, err := parseStatsLine(line, statsBytes); err == nil {
stats.Bytes = bytes_total
continue
}
}
}

// Stats returns memcached statistics for each server in the server list.
func (c *Client) Stats() (map[string]*Statistics, error) {
var mlk sync.Mutex
m := make(map[string]*Statistics)
addItemToMap := func(server string, s *Statistics) {
mlk.Lock()
defer mlk.Unlock()
m[server] = s
}

// Eliminate duplicates.
serversMap := make(map[net.Addr]string)
c.selector.Each(func(a net.Addr) error {
serversMap[a] = a.String()
return nil
})

// Query servers.
ch := make(chan error)
for addr, _ := range serversMap {
go func(addr net.Addr) {
cn, err := c.getConn(addr)
if err != nil {
ch <- err
return
}
defer cn.release()

if _, err := fmt.Fprintf(cn.rw, "stats\r\n"); err != nil {
ch <- err
return
}
if err := cn.rw.Flush(); err != nil {
ch <- err
return
}
stats := new(Statistics)
if err := parseStatsResponse(cn.rw.Reader, stats); err != nil {
ch <- err
return
}
addItemToMap(cn.addr.String(), stats)
ch <- nil
}(addr)
}

var err error
for _ = range serversMap {
if ge := <-ch; ge != nil {
err = ge
}
}
return m, err
}

0 comments on commit 0473636

Please sign in to comment.