Memcache.poll()   B
last analyzed

Complexity

Conditions 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 25
rs 8.0894
cc 5
1
# -*- coding: utf-8 -*-
2
"""Memcache metrics."""
3
import sys
4
import time
5
import socket
6
import os.path
7
8
import plumd
9
from plumd.util import Differential
10
11
PY3 = sys.version_info > (3,)
12
13
14
__author__ = 'Kenny Freeman'
15
__email__ = '[email protected]'
16
__license__ = "ISCL"
17
__docformat__ = 'reStructuredText'
18
19
20
class Memcache(plumd.Reader):
21
    """Plugin to record memcache stats."""
22
23
    STAT_CMD = "stats\n"
24
    RECV_SIZE = 4096
25
26
    # default config values
27
    defaults = {
28
        'poll.interval': 10,
29
        'gauges': [
30
            "auth_cmds",
31
            "auth_errors",
32
            "bytes",
33
            "bytes_read",
34
            "bytes_written",
35
            "cas_badval",
36
            "cas_hits",
37
            "cas_misses",
38
            "cmd_flush",
39
            "cmd_get",
40
            "cmd_set",
41
            "cmd_touch",
42
            "connection_structures",
43
            "conn_yields",
44
            "curr_connections",
45
            "curr_items",
46
            "decr_hits",
47
            "decr_misses",
48
            "delete_hits",
49
            "delete_misses",
50
            "evicted_unfetched",
51
            "evictions",
52
            "expired_unfetched",
53
            "get_hits",
54
            "get_misses",
55
            "hash_bytes",
56
            "hash_is_expanding",
57
            "hash_power_level",
58
            "incr_hits",
59
            "incr_misses",
60
            "limit_maxbytes",
61
            "listen_disabled_num",
62
            "reclaimed",
63
            "reserved_fds",
64
            "rusage_system",
65
            "rusage_user",
66
            "threads",
67
            "total_connections",
68
            "total_items",
69
            "touch_hits",
70
            "touch_misses",
71
            "uptime"
72
        ],
73
        'rates': [],
74
        'host': '127.0.0.1',  # memcache server hostname/ip
75
        'port': 11211,       # memcache server port
76
        'socket': None,      # use tcp or unix domain socket
77
        'timeout': 10        # connection timeouts
78
    }
79
80
    def __init__(self, log, config):
81
        """Plugin to record memcache stats.
82
83
        :param log: A logger
84
        :type log: logging.RootLogger
85
        :param config: a plumd.config.Conf configuration helper instance.
86
        :type config: plumd.config.Conf
87
        """
88
        super(Memcache, self).__init__(log, config)
89
        self.config.defaults(Memcache.defaults)
90
91
        # metrics to record
92
        self.gauges = self.config.get('gauges')
93
        self.rates = self.config.get('rates')
94
95
        # memcached connection - either unix socket or tcp
96
        spath = self.config.get('socket')
97
        if spath is None or not os.path.exists(spath):
98
            host = self.config.get('host')
99
            port = self.config.get('port')
100
            self.addr = (host, port)
101
            self.sfamily = socket.AF_INET
102
        else:
103
            self.addr = spath
104
            self.sfamily = socket.AF_UNIX
105
106
        self.socket = None
107
        self.timeout = config.get('timeout')
108
        self.calc = Differential()
109
110
    def poll(self):
111
        """Query memcache for stats.
112
113
        :rtype: ResultSet
114
        """
115
        result = plumd.Result("memcache")
116
117
        name = self.name
118
        stats = self.get_stats()
119
        times = time.time()
120
121
        # record gauges
122
        for stat in self.gauges:
123
            if stat in stats:
124
                mname = "{0}.{1}".format(name, stat)
125
                result.add(plumd.Float(mname, stats[stat]))
126
127
        # record rates
128
        for stat in self.rates:
129
            if stat in stats:
130
                mname = "{0}.{1}".format(name, stat)
131
                mval = self.calc.per_second(mname, float(stats[stat]), times)
132
                result.add(plumd.Float(mname, mval))
133
134
        return plumd.ResultSet([result])
135
136
    def get_stats(self):
137
        """Request and read stats from memcache socket."""
138
        stats = {}
139
140
        if not self.socket and not self.connect():
141
            return {}
142
143
        try:
144
            if PY3:
145
                self.socket.sendall(bytes(Memcache.STAT_CMD, 'utf8'))
146
            else:
147
                self.socket.sendall(Memcache.STAT_CMD)
148
149
            st_str = self.socket.recv(Memcache.RECV_SIZE)
150
151
            for line in st_str.split("\n"):
152
                vals = line.split()
153
                if len(vals) != 3:
154
                    continue
155
                stype, sname, sval = vals
156
                if stype == "STAT":
157
                    msg = "Memcached: {0} = {1}"
158
                    stats[sname] = sval
159
160
        except Exception as exc:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
161
            msg = "Memcached: {0}: poll: exception: {1}"
162
            self.log.error(msg.format(self.addr, exc))
163
            self.disconnect()
164
165
        return stats
166
167
    def connect(self):
168
        """Connect to memcached, returns True if sucessful, False otherwise.
169
170
        :rtype: bool
171
        """
172
        self.log.debug("Memcached: connecting: {0}".format(self.addr))
173
        if self.socket:
174
            self.disconnect()
175
        try:
176
            # create the socket
177
            self.socket = socket.socket(self.sfamily,
178
                                        socket.SOCK_STREAM)
179
            # set timeout for socket operations
180
            self.socket.settimeout(self.timeout)
181
            # connect
182
            self.socket.connect(self.addr)
183
            # log and return
184
            msg = "Memcached: connected: {0}"
185
            self.log.info(msg.format(self.addr))
186
            return True
187
        except Exception as exc:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
188
            # log exception, ensure cleanup is done (disconnect)
189
            msg = "Memcached: {0}: connect: excception: {1}"
190
            self.log.error(msg.format(self.addr, exc))
191
            return False
192
        return False
193
194
    def disconnect(self):
195
        """Severe the memcached connection."""
196
        if self.socket:
197
            try:
198
                self.socket.close()
199
                self.socket = None
200
            except Exception as exc:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
201
                msg = "Memcached: dicsonnect: exception {0}".format(exc)
202
                self.log.error(msg)
203
                self.socket = None
204