|
62 | 62 |
|
63 | 63 | // ErrNoServers is returned when no servers are configured or available. |
64 | 64 | ErrNoServers = errors.New("memcache: no servers configured or available") |
| 65 | + |
| 66 | + // ErrBadStatsLine means that the response line was expected to be |
| 67 | + // a line with server statistics information, but the parser was not |
| 68 | + // able to parse it. |
| 69 | + ErrBadStatsLine = errors.New("memcache: bad stats line") |
65 | 70 | ) |
66 | 71 |
|
67 | 72 | const ( |
@@ -113,6 +118,12 @@ var ( |
113 | 118 | resultTouched = []byte("TOUCHED\r\n") |
114 | 119 |
|
115 | 120 | resultClientErrorPrefix = []byte("CLIENT_ERROR ") |
| 121 | + |
| 122 | + statsGetHits = []byte("STAT get_hits ") |
| 123 | + statsGetMisses = []byte("STAT get_misses ") |
| 124 | + statsBytesWritten = []byte("STAT bytes_written ") |
| 125 | + statsItems = []byte("STAT curr_items ") |
| 126 | + statsBytes = []byte("STAT bytes ") |
116 | 127 | ) |
117 | 128 |
|
118 | 129 | // New returns a memcache client using the provided server(s) |
@@ -179,6 +190,24 @@ type conn struct { |
179 | 190 | c *Client |
180 | 191 | } |
181 | 192 |
|
| 193 | +// Statistics is a record of a single memcached server usage stats. |
| 194 | +type Statistics struct { |
| 195 | + // Hits is a counter of cache hits. |
| 196 | + Hits uint64 |
| 197 | + |
| 198 | + // Misses is a counter of cache misses. |
| 199 | + Misses uint64 |
| 200 | + |
| 201 | + // ByteHits is amount of bytes transferred for gets. |
| 202 | + ByteHits uint64 |
| 203 | + |
| 204 | + // Items is amount of keys currently in the cache. |
| 205 | + Items uint64 |
| 206 | + |
| 207 | + // Bytes is a size of all items currently in the cache. |
| 208 | + Bytes uint64 |
| 209 | +} |
| 210 | + |
182 | 211 | // release returns this connection back to the client's free pool |
183 | 212 | func (cn *conn) release() { |
184 | 213 | cn.c.putFreeConn(cn.addr, cn) |
@@ -682,3 +711,98 @@ func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) { |
682 | 711 | }) |
683 | 712 | return val, err |
684 | 713 | } |
| 714 | + |
| 715 | +func parseStatsLine(s []byte, expectedPrefix []byte) (uint64, error) { |
| 716 | + if !bytes.HasPrefix(s, expectedPrefix) { |
| 717 | + return 0, ErrBadStatsLine |
| 718 | + } |
| 719 | + return strconv.ParseUint( |
| 720 | + string(s[len(expectedPrefix):len(s)-2]), 10, 64) |
| 721 | +} |
| 722 | + |
| 723 | +func parseStatsResponse(r *bufio.Reader, stats *Statistics) error { |
| 724 | + for { |
| 725 | + line, err := r.ReadSlice('\n') |
| 726 | + if err != nil { |
| 727 | + return err |
| 728 | + } |
| 729 | + if bytes.Equal(line, resultEnd) { |
| 730 | + return nil |
| 731 | + } |
| 732 | + if hits, err := parseStatsLine(line, statsGetHits); err == nil { |
| 733 | + stats.Hits = hits |
| 734 | + continue |
| 735 | + } |
| 736 | + if misses, err := parseStatsLine(line, statsGetMisses); err == nil { |
| 737 | + stats.Misses = misses |
| 738 | + continue |
| 739 | + } |
| 740 | + if bytesWritten, err := parseStatsLine(line, statsBytesWritten); err == nil { |
| 741 | + stats.ByteHits = bytesWritten |
| 742 | + continue |
| 743 | + } |
| 744 | + if items, err := parseStatsLine(line, statsItems); err == nil { |
| 745 | + stats.Items = items |
| 746 | + continue |
| 747 | + } |
| 748 | + if bytes_total, err := parseStatsLine(line, statsBytes); err == nil { |
| 749 | + stats.Bytes = bytes_total |
| 750 | + continue |
| 751 | + } |
| 752 | + } |
| 753 | +} |
| 754 | + |
| 755 | +// Stats returns memcached statistics for each server in the server list. |
| 756 | +func (c *Client) Stats() (map[string]*Statistics, error) { |
| 757 | + var mlk sync.Mutex |
| 758 | + m := make(map[string]*Statistics) |
| 759 | + addItemToMap := func(server string, s *Statistics) { |
| 760 | + mlk.Lock() |
| 761 | + defer mlk.Unlock() |
| 762 | + m[server] = s |
| 763 | + } |
| 764 | + |
| 765 | + // Eliminate duplicates. |
| 766 | + serversMap := make(map[net.Addr]string) |
| 767 | + c.selector.Each(func(a net.Addr) error { |
| 768 | + serversMap[a] = a.String() |
| 769 | + return nil |
| 770 | + }) |
| 771 | + |
| 772 | + // Query servers. |
| 773 | + ch := make(chan error) |
| 774 | + for addr, _ := range serversMap { |
| 775 | + go func(addr net.Addr) { |
| 776 | + cn, err := c.getConn(addr) |
| 777 | + if err != nil { |
| 778 | + ch <- err |
| 779 | + return |
| 780 | + } |
| 781 | + defer cn.condRelease(&err) |
| 782 | + |
| 783 | + if _, err := fmt.Fprintf(cn.rw, "stats\r\n"); err != nil { |
| 784 | + ch <- err |
| 785 | + return |
| 786 | + } |
| 787 | + if err := cn.rw.Flush(); err != nil { |
| 788 | + ch <- err |
| 789 | + return |
| 790 | + } |
| 791 | + stats := new(Statistics) |
| 792 | + if err := parseStatsResponse(cn.rw.Reader, stats); err != nil { |
| 793 | + ch <- err |
| 794 | + return |
| 795 | + } |
| 796 | + addItemToMap(cn.addr.String(), stats) |
| 797 | + ch <- nil |
| 798 | + }(addr) |
| 799 | + } |
| 800 | + |
| 801 | + var err error |
| 802 | + for _ = range serversMap { |
| 803 | + if ge := <-ch; ge != nil { |
| 804 | + err = ge |
| 805 | + } |
| 806 | + } |
| 807 | + return m, err |
| 808 | +} |
0 commit comments