Total Complexity | 33 |
Total Lines | 248 |
Duplicated Lines | 45.56 % |
Changes | 1 | ||
Bugs | 1 | Features | 1 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | # -*- coding: utf-8 -*- |
||
38 | class Redis(plumd.plugins.Reader): |
||
39 | """Plugin to record redis metrics.""" |
||
40 | |||
41 | INFO_CMD = "info\r\n" |
||
42 | RECV_SIZE = 4096 |
||
43 | |||
44 | # default config values |
||
45 | defaults = { |
||
46 | 'poll.interval': 10, |
||
47 | 'gauges': [ |
||
48 | "aof_current_rewrite_time_sec", |
||
49 | "aof_enabled", |
||
50 | "aof_last_rewrite_time_sec", |
||
51 | "aof_rewrite_in_progress", |
||
52 | "aof_rewrite_scheduled", |
||
53 | "blocked_clients", |
||
54 | "client_biggest_input_buf", |
||
55 | "client_longest_output_list", |
||
56 | "connected_clients", |
||
57 | "connected_slaves", |
||
58 | "evicted_keys", |
||
59 | "expired_keys", |
||
60 | "instantaneous_input_kbps", |
||
61 | "instantaneous_ops_per_sec", |
||
62 | "instantaneous_output_kbps", |
||
63 | "keyspace_hits", |
||
64 | "keyspace_misses", |
||
65 | "latest_fork_usec", |
||
66 | "loading", |
||
67 | "master_repl_offset", |
||
68 | "mem_fragmentation_ratio", |
||
69 | "pubsub_channels", |
||
70 | "pubsub_patterns", |
||
71 | "rdb_bgsave_in_progress", |
||
72 | "rdb_changes_since_last_save", |
||
73 | "rdb_current_bgsave_time_sec", |
||
74 | "rdb_last_bgsave_time_sec", |
||
75 | "rdb_last_save_time", |
||
76 | "rejected_connections", |
||
77 | "repl_backlog_active", |
||
78 | "repl_backlog_first_byte_offset", |
||
79 | "repl_backlog_histlen", |
||
80 | "repl_backlog_size", |
||
81 | "sync_full", |
||
82 | "sync_partial_err", |
||
83 | "sync_partial_ok", |
||
84 | "total_commands_processed", |
||
85 | "total_connections_received", |
||
86 | "total_net_input_bytes", |
||
87 | "total_net_output_bytes", |
||
88 | "uptime_in_days", |
||
89 | "uptime_in_seconds", |
||
90 | "used_cpu_sys", |
||
91 | "used_cpu_sys_children", |
||
92 | "used_cpu_user", |
||
93 | "used_cpu_user_children", |
||
94 | "used_memory", |
||
95 | "used_memory_lua", |
||
96 | "used_memory_peak", |
||
97 | "used_memory_rss", |
||
98 | "master_last_io_seconds_ago", |
||
99 | "master_sync_in_progress", |
||
100 | "slave_repl_offset", |
||
101 | "slave_priority", |
||
102 | "slave_read_only", |
||
103 | "connected_slaves", |
||
104 | "master_repl_offset", |
||
105 | "repl_backlog_active", |
||
106 | "repl_backlog_size", |
||
107 | "repl_backlog_first_byte_offset", |
||
108 | "repl_backlog_histlen" |
||
109 | "connected_slaves" ], |
||
110 | 'rates': [], |
||
111 | 'host': '127.0.0.1', # Redis server hostname/ip |
||
112 | 'port': 6379, # Redis server port |
||
113 | 'timeout': 10, # connection timeouts |
||
114 | 'retry_time': 30, # pause n seconds between reconnects |
||
115 | 'retry_cnt': 3 # retry connection n times |
||
116 | } |
||
117 | |||
118 | View Code Duplication | def __init__(self, log, config): |
|
1 ignored issue
–
show
|
|||
119 | """Plugin to record redis metrics. |
||
120 | |||
121 | :param log: A logger |
||
122 | :type log: logging.RootLogger |
||
123 | :param config: a plumd.config.Conf configuration helper instance. |
||
124 | :type config: plumd.config.Conf |
||
125 | """ |
||
126 | super(Redis, self).__init__(log, config) |
||
127 | self.config.defaults(Redis.defaults) |
||
128 | |||
129 | # metrics to record |
||
130 | self.gauges = self.config.get('gauges') |
||
131 | self.rates = self.config.get('rates') |
||
132 | |||
133 | # Redis connection |
||
134 | host = self.config.get('host') |
||
135 | port = self.config.get('port') |
||
136 | self.addr = (host, port) |
||
137 | self.socket = None |
||
138 | self.timeout = config.get('timeout') |
||
139 | self.retry_time = config.get('retry_time') |
||
140 | self.retry_cnt = config.get('retry_cnt') |
||
141 | self.calc = Differential() |
||
142 | |||
143 | |||
144 | View Code Duplication | def poll(self): |
|
1 ignored issue
–
show
|
|||
145 | """Query Redis for metrics. |
||
146 | |||
147 | :rtype: ResultSet |
||
148 | """ |
||
149 | result = plumd.Result("Redis") |
||
150 | |||
151 | stats = self.get_metrics() |
||
152 | ts = time.time() |
||
153 | name = self.name |
||
154 | |||
155 | # record gauges |
||
156 | for stat in self.gauges: |
||
157 | if stat in stats: |
||
158 | mname = "{0}.{1}".format(name, stat) |
||
159 | result.add(plumd.Float(mname, stats[stat])) |
||
160 | |||
161 | # record rates |
||
162 | for stat in self.rates: |
||
163 | if stat in stats: |
||
164 | mname = "{0}.{1}".format(name, stat) |
||
165 | mval = self.calc.per_second(mname, float(stats[stat]), ts) |
||
166 | result.add(plumd.Float(mname, mval)) |
||
167 | |||
168 | # replication |
||
169 | if "slave0" in stats: |
||
170 | self.record_slave(stats, result) |
||
171 | |||
172 | return plumd.ResultSet([result]) |
||
173 | |||
174 | |||
175 | def record_slave(self, stats, result): |
||
176 | #slave0:ip=127.0.0.1,port=6399,state=online,offset=239,lag=1 |
||
177 | name = self.name |
||
178 | slave_str = "slave{0}" |
||
179 | moffstr = 'master_repl_offset' |
||
180 | moffset = 0 if moffstr not in stats else int(stats[moffstr]) |
||
181 | |||
182 | for i in xrange(0, len(stats.keys())): |
||
183 | sname = slave_str.format(i) |
||
184 | if sname not in stats: |
||
185 | break |
||
186 | vals = stats[sname].split(",") |
||
187 | |||
188 | smetrics = dict((k,v) for k,v in (v.split('=') for v in vals)) |
||
189 | required = ['ip', 'port', 'state', 'offset', 'lag'] |
||
190 | missing = [n for n in required if n not in smetrics] |
||
191 | |||
192 | if missing: |
||
193 | self.log.error("Redis: invalid slave: {0}".format(stats[sname])) |
||
194 | continue |
||
195 | |||
196 | sip = smetrics['ip'].replace(".", "_") |
||
197 | smname = "{0}_{1}".format(sip, smetrics['port']) |
||
198 | |||
199 | # record offset and lag |
||
200 | mname = "{0}.slave.{1}.offset".format(name, sname) |
||
201 | soffset = moffset - int(smetrics['offset']) |
||
202 | result.add(plumd.Float(mname, soffset)) |
||
203 | mname = "{0}.slave.{1}.lag".format(name, sname) |
||
204 | result.add(plumd.Float(mname, smetrics['lag'])) |
||
205 | |||
206 | # if slave is online set online = 1, otherwise 0 |
||
207 | sonline = 1 if smetrics['state'] == "online" else 0 |
||
208 | mname = "{0}.slave.{1}.online".format(name, sname) |
||
209 | result.add(plumd.Int(mname, sonline)) |
||
210 | |||
211 | |||
212 | View Code Duplication | def get_metrics(self): |
|
1 ignored issue
–
show
|
|||
213 | """Request and read stats from Redis socket.""" |
||
214 | stats = {} |
||
215 | |||
216 | if not self.socket and not self.connect(): |
||
217 | return {} |
||
218 | |||
219 | try: |
||
220 | if PY3: |
||
221 | self.socket.sendall(bytes(Redis.INFO_CMD, 'utf8')) |
||
222 | else: |
||
223 | self.socket.sendall(Redis.INFO_CMD) |
||
224 | |||
225 | st_str = self.socket.recv(Redis.RECV_SIZE) |
||
226 | |||
227 | for line in st_str.split("\n"): |
||
228 | if line.startswith("#") or not line: |
||
229 | continue |
||
230 | vals = line.split(":") |
||
231 | if len(vals) != 2: |
||
232 | continue |
||
233 | sname, sval = vals |
||
234 | msg = "Redis: {0} = {1}" |
||
235 | self.log.debug(msg.format(sname, sval)) |
||
236 | stats[sname] = sval |
||
237 | except Exception as e: |
||
238 | msg = "Redis: {0}: poll: exception: {1}" |
||
239 | self.log.error(msg.format(self.addr, e)) |
||
240 | self.disconnect() |
||
241 | |||
242 | return stats |
||
243 | |||
244 | |||
245 | View Code Duplication | def connect(self): |
|
1 ignored issue
–
show
|
|||
246 | """Connect to Redis, returns True if sucessful, False otherwise. |
||
247 | |||
248 | :rtype: bool |
||
249 | """ |
||
250 | self.log.debug("Redis: connecting: {0}".format(self.addr)) |
||
251 | for i in xrange(0, self.retry_cnt): |
||
252 | if self.socket: |
||
253 | self.disconnect() |
||
254 | try: |
||
255 | # create the socket |
||
256 | self.socket = socket.socket(socket.AF_INET, |
||
257 | socket.SOCK_STREAM) |
||
258 | # set timeout for socket operations |
||
259 | self.socket.settimeout(self.timeout) |
||
260 | # connect |
||
261 | self.socket.connect(self.addr) |
||
262 | # log and return |
||
263 | msg = "Redis: connected: {0}" |
||
264 | self.log.info(msg.format(self.addr)) |
||
265 | return True |
||
266 | except Exception as e: |
||
267 | # log exception, ensure cleanup is done (disconnect) |
||
268 | msg = "Redis: {0}: connect: excception: {1}" |
||
269 | self.log.error(msg.format(self.addr, e)) |
||
270 | # pause before reconnecting |
||
271 | time.sleep(self.retry_time) |
||
272 | self.disconnect() |
||
273 | return False |
||
274 | |||
275 | |||
276 | def disconnect(self): |
||
277 | """Severe the Redis connection.""" |
||
278 | if self.socket: |
||
279 | try: |
||
280 | self.socket.close() |
||
281 | self.socket = None |
||
282 | except Exception as e: |
||
283 | msg = "Redis: dicsonnect: exception {0}".format(e) |
||
284 | self.log.error(msg) |
||
285 | self.socket = None |
||
286 |