MySql.onstop()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
1
# -*- coding: utf-8 -*-
2
"""Basic MySQL reader plugin."""
3
4
import re
5
from collections import deque, namedtuple
6
7
import pymysql
0 ignored issues
show
Configuration introduced by
The import pymysql could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8
9
import plumd
10
from plumd.util import Filter
0 ignored issues
show
Unused Code introduced by
Unused Filter imported from plumd.util
Loading history...
11
12
__author__ = 'Kenny Freeman'
13
__email__ = '[email protected]'
14
__license__ = "ISCL"
15
__docformat__ = 'reStructuredText'
16
17
18
TableStatus = namedtuple('TableStatus',
19
                         ['name', 'engine', 'version', 'row_format', 'rows',
20
                          'avg_row_length', 'data_length', 'max_data_length',
21
                          'index_length', 'data_free', 'auto_increment',
22
                          'create_time', 'update_time', 'check_time',
23
                          'collation', 'checksum', 'create_options',
24
                          'comment'])
25
26
27
class MySql(plumd.Reader):
28
    """Plugin to measure various MySQL metrics."""
29
30
    defaults = {
31
        'poll.interval': 10,
32
        'host': '127.0.0.1',
33
        'port': 3306,
34
        'user': 'monitor',
35
        'pass': 'monitor',
36
        'db': 'mysql',
37
        'table_status_re': ".*",
38
        'record_table_status': False,
39
        'status_record': {
40
            'errs': [
41
                "Aborted_clients",
42
                "Aborted_connects",
43
                "Access_denied_errors"
44
            ],
45
            'binlog': [
46
                "Binlog_commits",
47
                "Binlog_group_commits",
48
                "Binlog_group_commit_trigger_count",
49
                "Binlog_group_commit_trigger_lock_wait",
50
                "Binlog_group_commit_trigger_timeout",
51
                "Binlog_snapshot_position",
52
                "Binlog_bytes_written",
53
                "Binlog_cache_disk_use",
54
                "Binlog_cache_use",
55
                "Binlog_stmt_cache_disk_use",
56
                "Binlog_stmt_cache_use"
57
            ],
58
            'com': [
59
                "Com_admin_commands",
60
                "Com_begin",
61
                "Com_commit",
62
                "Com_delete",
63
                "Com_delete_multi",
64
                "Com_empty_query",
65
                "Com_execute_sql",
66
                "Com_insert",
67
                "Com_insert_select",
68
                "Com_kill",
69
                "Com_prepare_sql",
70
                "Com_replace",
71
                "Com_replace_select",
72
                "Com_reset",
73
                "Com_rollback",
74
                "Com_select",
75
                "Com_stmt_close",
76
                "Com_stmt_execute",
77
                "Com_stmt_fetch",
78
                "Com_stmt_prepare",
79
                "Com_stmt_reprepare",
80
                "Com_stmt_reset",
81
                "Com_stmt_send_long_data",
82
                "Com_truncate",
83
                "Com_update",
84
                "Com_update_multi"
85
            ],
86
            'conn': [
87
                "Connection_errors_accept",
88
                "Connection_errors_internal",
89
                "Connection_errors_max_connections",
90
                "Connection_errors_peer_address",
91
                "Connection_errors_select",
92
                "Connection_errors_tcpwrap",
93
                "Connections",
94
                "Max_used_connections"
95
            ],
96
            'tmp': [
97
                "Created_tmp_disk_tables",
98
                "Created_tmp_files",
99
                "Created_tmp_tables"
100
            ],
101
            'handler': [
102
                "Handler_commit",
103
                "Handler_delete",
104
                "Handler_discover",
105
                "Handler_external_lock",
106
                "Handler_icp_attempts",
107
                "Handler_icp_match",
108
                "Handler_mrr_init",
109
                "Handler_mrr_key_refills",
110
                "Handler_mrr_rowid_refills",
111
                "Handler_prepare",
112
                "Handler_read_first",
113
                "Handler_read_key",
114
                "Handler_read_last",
115
                "Handler_read_next",
116
                "Handler_read_prev",
117
                "Handler_read_rnd",
118
                "Handler_read_rnd_deleted",
119
                "Handler_read_rnd_next",
120
                "Handler_rollback",
121
                "Handler_savepoint",
122
                "Handler_savepoint_rollback",
123
                "Handler_tmp_update",
124
                "Handler_tmp_write",
125
                "Handler_update",
126
                "Handler_write"
127
            ],
128
            'inno': [
129
                "Innodb_buffer_pool_bytes_data",
130
                "Innodb_buffer_pool_bytes_dirty",
131
                "Innodb_buffer_pool_pages_data",
132
                "Innodb_buffer_pool_pages_dirty",
133
                "Innodb_buffer_pool_pages_flushed",
134
                "Innodb_buffer_pool_pages_free",
135
                "Innodb_buffer_pool_pages_lru_flushed",
136
                "Innodb_buffer_pool_pages_made_not_young",
137
                "Innodb_buffer_pool_pages_made_young",
138
                "Innodb_buffer_pool_pages_misc",
139
                "Innodb_buffer_pool_pages_old",
140
                "Innodb_buffer_pool_pages_total",
141
                "Innodb_buffer_pool_read_ahead",
142
                "Innodb_buffer_pool_read_ahead_evicted",
143
                "Innodb_buffer_pool_read_ahead_rnd",
144
                "Innodb_buffer_pool_read_requests",
145
                "Innodb_buffer_pool_reads",
146
                "Innodb_buffer_pool_wait_free",
147
                "Innodb_buffer_pool_write_requests",
148
                "Innodb_checkpoint_age",
149
                "Innodb_checkpoint_max_age",
150
                "Innodb_data_fsyncs",
151
                "Innodb_data_pending_fsyncs",
152
                "Innodb_data_pending_reads",
153
                "Innodb_data_pending_writes",
154
                "Innodb_data_read",
155
                "Innodb_data_reads",
156
                "Innodb_data_writes",
157
                "Innodb_data_written",
158
                "Innodb_dblwr_pages_written",
159
                "Innodb_dblwr_writes",
160
                "Innodb_deadlocks",
161
                "Innodb_history_list_length",
162
                "Innodb_ibuf_discarded_delete_marks",
163
                "Innodb_ibuf_discarded_deletes",
164
                "Innodb_ibuf_discarded_inserts",
165
                "Innodb_ibuf_free_list",
166
                "Innodb_ibuf_merged_delete_marks",
167
                "Innodb_ibuf_merged_deletes",
168
                "Innodb_ibuf_merged_inserts",
169
                "Innodb_ibuf_merges",
170
                "Innodb_ibuf_segment_size",
171
                "Innodb_ibuf_size",
172
                "Innodb_log_waits",
173
                "Innodb_log_write_requests",
174
                "Innodb_log_writes",
175
                "Innodb_lsn_current",
176
                "Innodb_lsn_flushed",
177
                "Innodb_lsn_last_checkpoint",
178
                "Innodb_master_thread_active_loops",
179
                "Innodb_master_thread_idle_loops",
180
                "Innodb_mem_adaptive_hash",
181
                "Innodb_mem_dictionary",
182
                "Innodb_mem_total",
183
                "Innodb_mutex_os_waits",
184
                "Innodb_mutex_spin_rounds",
185
                "Innodb_mutex_spin_waits",
186
                "Innodb_os_log_fsyncs",
187
                "Innodb_os_log_pending_fsyncs",
188
                "Innodb_os_log_pending_writes",
189
                "Innodb_os_log_written",
190
                "Innodb_pages_created",
191
                "Innodb_pages_read",
192
                "Innodb_pages_written",
193
                "Innodb_purge_trx_id",
194
                "Innodb_purge_undo_no",
195
                "Innodb_read_views_memory",
196
                "Innodb_row_lock_current_waits",
197
                "Innodb_row_lock_time",
198
                "Innodb_row_lock_time_avg",
199
                "Innodb_row_lock_time_max",
200
                "Innodb_row_lock_waits",
201
                "Innodb_rows_deleted",
202
                "Innodb_rows_inserted",
203
                "Innodb_rows_read",
204
                "Innodb_rows_updated",
205
                "Innodb_system_rows_deleted",
206
                "Innodb_system_rows_inserted",
207
                "Innodb_system_rows_read",
208
                "Innodb_system_rows_updated",
209
                "Innodb_s_lock_os_waits",
210
                "Innodb_s_lock_spin_rounds",
211
                "Innodb_s_lock_spin_waits",
212
                "Innodb_x_lock_os_waits",
213
                "Innodb_x_lock_spin_rounds",
214
                "Innodb_x_lock_spin_waits",
215
                "Innodb_num_index_pages_written",
216
                "Innodb_num_non_index_pages_written",
217
                "Innodb_secondary_index_triggered_cluster_reads",
218
                "Innodb_secondary_index_triggered_cluster_reads_avoided"
219
            ],
220
            'key': [
221
                "Key_blocks_not_flushed",
222
                "Key_blocks_unused",
223
                "Key_blocks_used",
224
                "Key_blocks_warm",
225
                "Key_read_requests",
226
                "Key_reads",
227
                "Key_write_requests",
228
                "Key_writes"
229
            ],
230
            'open': [
231
                "Open_files",
232
                "Open_streams",
233
                "Open_table_definitions",
234
                "Open_tables",
235
                "Opened_files",
236
                "Opened_plugin_libraries",
237
                "Opened_table_definitions",
238
                "Opened_tables",
239
                "Opened_views"
240
            ],
241
            'qps': [
242
                "Prepared_stmt_count",
243
                "Queries",
244
                "Questions",
245
                "Rows_read",
246
                "Rows_sent",
247
                "Rows_tmp_read",
248
                "Select_full_join",
249
                "Select_full_range_join",
250
                "Select_range",
251
                "Select_range_check",
252
                "Select_scan",
253
            ],
254
            'sort': [
255
                "Sort_merge_passes",
256
                "Sort_priority_queue_sorts",
257
                "Sort_range",
258
                "Sort_rows",
259
                "Sort_scan"
260
            ],
261
            'misc': [
262
                "Bytes_received",
263
                "Bytes_sent",
264
                "Cpu_time",
265
                "Slow_queries",
266
                "Busy_time",
267
                "Empty_queries",
268
                "Executed_triggers",
269
                "Max_statement_time_exceeded",
270
                "Memory_used",
271
                "Uptime",
272
                "Syncs",
273
                "Table_locks_immediate",
274
                "Table_locks_waited"
275
            ],
276
            'threads': [
277
                "Threadpool_idle_threads",
278
                "Threadpool_threads",
279
                "Threads_cached",
280
                "Threads_connected",
281
                "Threads_created",
282
                "Threads_running"
283
            ]
284
        }
285
    }
286
287
    def __init__(self, log, config):
288
        """Plugin to measure various MySQL metrics.
289
290
        :param log: A logger
291
        :type log: logging.RootLogger
292
        :param config: a plumd.config.Conf configuration helper instance.
293
        :type config: plumd.config.Conf
294
        """
295
        super(MySql, self).__init__(log, config)
296
        self.config.defaults(MySql.defaults)
297
        self.db = self.config.get('db')
298
        self.dbconn = None
299
        self.dbcur = None
300
        self.record = self.config.get('status_record')
301
        self.record_table_status = self.config.get('record_table_status')
302
        self.table_status_re = re.compile(self.config.get('table_status_re'))
303
304
    def onstart(self):
305
        """Connect to our db - use a persistent connection - ensures
306
        metrics even when all connections are used.
307
        """
308
        self.connect()
309
310
    def onstop(self):
311
        """Disconnect from our db."""
312
        self.dbcur.close()
313
        self.dbconn.close()
314
315
    def disconnect(self):
316
        """Disconnect from the database, if connected."""
317
        if self.dbcur:
318
            try:
319
                self.dbcur.close()
320
            except Exception:
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
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...
321
                pass
322
        if self.dbconn:
323
            try:
324
                self.dbconn.close()
325
            except Exception:
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
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...
326
                pass
327
328
    def connect(self):
329
        """Connect to the configured database."""
330
        ret = True
331
        self.disconnect()
332
        host = self.config.get('host')
333
        port = int(self.config.get('port'))
334
        user = self.config.get('user')
335
        passwd = self.config.get('passwd')
336
        db_name = self.config.get('db')
337
        try:
338
            self.dbconn = pymysql.connect(host=host, port=port, user=user,
339
                                          passwd=passwd, db=db_name)
340
            self.dbcur = self.dbconn.cursor()
341
        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...
342
            msg = "MySql: exception connecting to {0}:{1}:{2}: {3}"
343
            self.log.error(msg.format(host, port, db_name, exc))
344
            ret = False
345
            self.disconnect()
346
        return ret
347
348
    def poll(self):
349
        """Poll for MySQL metrics.
350
351
        :rtype: ResultSet
352
        """
353
        result = plumd.Result("mysql")
354
        # add db name as prefix to all metrics
355
        db_name = self.config.get('db')
356
        # currently records selected values from status
357
        # future versions to support calculated values + others
358
        try:
359
            self.dbcur.execute("SHOW GLOBAL STATUS")
360
        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...
361
            self.log.error("MySql: exception getting status: {0}".format(exc))
362
            if not self.connect():
363
                return plumd.ResultSet([result])
364
365
        try:
366
            status = dict(self.dbcur.fetchall())
367
        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...
368
            self.log.error("MySql: exception fetching status: {0}".format(exc))
369
            self.connect()
370
            return plumd.ResultSet([result])
371
372
        # all values recorded as is - todo: gauges and rates
373
        del_metrics = {}
374
        for prefix, metrics in self.record.items():
375
            mfmt_str = "{0}.{1}.{2}"
376
            for metric in metrics:
377
                try:
378
                    val = status[metric]
379
                    mname = mfmt_str.format(db_name, prefix, metric)
380
                    result.add(plumd.Float(mname, val))
381
                except (KeyError, ValueError) as e:
382
                    msg = "MySql: disabling unknown/invalid metric: {0}: {1}"
383
                    self.log.warn(msg.format(metric, e))
384
                    if prefix not in del_metrics:
385
                        del_metrics[prefix] = deque()
386
                    del_metrics[prefix].append(metric)
387
388
        # remove missing/unknown metrics - do outside of above for loop
389
        # as we can't change self.record in the loop
390
        for pfx, metrics in del_metrics.items():
391
            for metric in metrics:
392
                self.record[pfx].remove(metric)
393
394
        if self.record_table_status:
395
            self.table_status(result)
396
397
        return plumd.ResultSet([result])
398
399
    def table_status(self, result):
400
        """Record table metrics from 'show table status'. Note: innodb metrics
401
        for table status can be off by as much as 50%.
402
403
        :param result: A plumd.Result object to add metrics to.
404
        :type result: plumd.Result
405
        """
406
        # currently records selected values from status
407
        # future versions to support calculated values + others
408
        try:
409
            self.dbcur.execute("SHOW TABLE STATUS")
410
        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...
411
            self.log.error(
412
                "MySql: exception getting table status: {0}".format(exc))
413
            if not self.connect():
414
                return
415
416
        try:
417
            tbl_status_res = self.dbcur.fetchall()
418
        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...
419
            msg = "MySql: exception fetching table status: {0}"
420
            self.log.error(msg.format(exc))
421
            self.connect()
422
            return
423
424
        # add db name as prefix to all metrics
425
        db = self.config.get('db')
426
427
        regexp = self.table_status_re
428
        # all values recorded as is - todo: gauges and rates
429
        for tbl_status_vals in tbl_status_res:
430
            tstatus = TableStatus(*list(tbl_status_vals))
431
            if not regexp.match(tstatus.name):
432
                continue
433
            mfmt_str = "{0}.tables.{1}.{2}"
434
            table = tstatus.name
435
            result.add(plumd.Float(mfmt_str.format(db, table, "rows"),
436
                                   tstatus.rows))
437
            result.add(plumd.Float(mfmt_str.format(db, table, "avg_row_length"),
438
                                   tstatus.avg_row_length))
439
            result.add(plumd.Float(mfmt_str.format(db, table, "data_length"),
440
                                   tstatus.data_length))
441
            result.add(plumd.Float(mfmt_str.format(db, table, "max_data_length"),
442
                                   tstatus.max_data_length))
443
            result.add(plumd.Float(mfmt_str.format(db, table, "index_length"),
444
                                   tstatus.index_length))
445
            result.add(plumd.Float(mfmt_str.format(db, table, "data_free"),
446
                                   tstatus.data_free))
447