sensu-plugins-mysql 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,130 @@
1
+ # !/usr/bin/env ruby
2
+ #
3
+ # MySQL InnoDB Lock Check Plugin
4
+ # ===
5
+ #
6
+ # This plugin checks InnoDB locks.
7
+ #
8
+ # Copyright 2014 Hiroaki Sano <hiroaki.sano.9stories@gmail.com>
9
+ #
10
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
11
+ # for details.
12
+
13
+ require 'sensu-plugin/check/cli'
14
+ require 'mysql'
15
+
16
+ class CheckMySQLInnoDBLock < Sensu::Plugin::Check::CLI
17
+ option :user,
18
+ description: 'MySQL User',
19
+ short: '-u USER',
20
+ long: '--user USER',
21
+ default: 'root'
22
+
23
+ option :password,
24
+ description: 'MySQL Password',
25
+ short: '-p PASS',
26
+ long: '--password PASS',
27
+ required: true
28
+
29
+ option :hostname,
30
+ description: 'Hostname to login to',
31
+ short: '-h HOST',
32
+ long: '--hostname HOST',
33
+ default: 'localhost'
34
+
35
+ option :port,
36
+ description: 'Port to connect to',
37
+ short: '-P PORT',
38
+ long: '--port PORT',
39
+ default: '3306'
40
+
41
+ option :socket,
42
+ description: 'Socket to use',
43
+ short: '-s SOCKET',
44
+ long: '--socket SOCKET'
45
+
46
+ option :warn,
47
+ description: 'Warning threshold',
48
+ short: '-w SECONDS',
49
+ long: '--warning SECONDS',
50
+ default: 5
51
+
52
+ option :crit,
53
+ description: 'Critical threshold',
54
+ short: '-c SECONDS',
55
+ long: '--critical SECONDS',
56
+ default: 10
57
+
58
+ def run
59
+ db = Mysql.new(config[:hostname], config[:user], config[:password], config[:database], config[:port].to_i, config[:socket])
60
+
61
+ warn = config[:warn].to_i
62
+ crit = config[:crit].to_i
63
+
64
+ res = db.query <<-EQSQL
65
+ select
66
+ t_b.trx_mysql_thread_id blocking_id,
67
+ t_w.trx_mysql_thread_id requesting_id,
68
+ p_b.HOST blocking_host,
69
+ p_w.HOST requesting_host,
70
+ l.lock_table lock_table,
71
+ l.lock_index lock_index,
72
+ l.lock_mode lock_mode,
73
+ p_w.TIME seconds,
74
+ p_b.INFO blocking_info,
75
+ p_w.INFO requesting_info
76
+ from
77
+ information_schema.INNODB_LOCK_WAITS w,
78
+ information_schema.INNODB_LOCKS l,
79
+ information_schema.INNODB_TRX t_b,
80
+ information_schema.INNODB_TRX t_w,
81
+ information_schema.PROCESSLIST p_b,
82
+ information_schema.PROCESSLIST p_w
83
+ where
84
+ w.blocking_lock_id = l.lock_id
85
+ and
86
+ w.blocking_trx_id = t_b.trx_id
87
+ and
88
+ w.requesting_trx_id = t_w.trx_id
89
+ and
90
+ t_b.trx_mysql_thread_id = p_b.ID
91
+ and
92
+ t_w.trx_mysql_thread_id = p_w.ID
93
+ and
94
+ p_w.TIME > #{warn}
95
+ order by
96
+ requesting_id,blocking_id
97
+ EQSQL
98
+
99
+ lock_info = []
100
+ is_crit = false
101
+ res.each_hash do |row|
102
+ h = {}
103
+ is_crit = true if row['seconds'].to_i > crit
104
+ h['blocking_id'] = row['blocking_id']
105
+ h['requesting_id'] = row['requesting_id']
106
+ h['blocking_host'] = row['blocking_host']
107
+ h['requesting_host'] = row['requesting_host']
108
+ h['lock_table'] = row['lock_table']
109
+ h['lock_index'] = row['lock_index']
110
+ h['lock_mode'] = row['lock_mode']
111
+ h['seconds'] = row['seconds']
112
+ h['blocking_info'] = row['blocking_info']
113
+ h['requesting_info'] = row['requesting_info']
114
+ lock_info.push(h)
115
+ end
116
+
117
+ if lock_info.length == 0
118
+ ok
119
+ elsif is_crit == false
120
+ warning "Detected Locks #{lock_info}"
121
+ else
122
+ critical "Detected Locks #{lock_info}"
123
+ end
124
+
125
+ rescue Mysql::Error => e
126
+ critical "MySQL check failed: #{e.error}"
127
+ ensure
128
+ db.close if db
129
+ end
130
+ end
@@ -0,0 +1,158 @@
1
+ # !/usr/bin/env ruby
2
+ #
3
+ # MySQL Replication Status (modded from disk)
4
+ # ===
5
+ #
6
+ # Copyright 2011 Sonian, Inc <chefs@sonian.net>
7
+ # Updated by Oluwaseun Obajobi 2014 to accept ini argument
8
+ #
9
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
10
+ # for details.
11
+ #
12
+ # USING INI ARGUMENT
13
+ # This was implemented to load mysql credentials without parsing the username/password.
14
+ # The ini file should be readable by the sensu user/group.
15
+ # Ref: http://eric.lubow.org/2009/ruby/parsing-ini-files-with-ruby/
16
+ #
17
+ # EXAMPLE
18
+ # mysql-alive.rb -h db01 --ini '/etc/sensu/my.cnf'
19
+ #
20
+ # MY.CNF INI FORMAT
21
+ # [client]
22
+ # user=sensu
23
+ # password="abcd1234"
24
+ #
25
+
26
+ require 'sensu-plugin/check/cli'
27
+ require 'mysql'
28
+ require 'inifile'
29
+
30
+ class CheckMysqlReplicationStatus < Sensu::Plugin::Check::CLI
31
+ option :host,
32
+ short: '-h',
33
+ long: '--host=VALUE',
34
+ description: 'Database host'
35
+
36
+ option :port,
37
+ short: '-P',
38
+ long: '--port=VALUE',
39
+ description: 'Database port',
40
+ default: 3306,
41
+ # #YELLOW
42
+ proc: lambda { |s| s.to_i } # rubocop:disable Lambda
43
+
44
+ option :socket,
45
+ short: '-s SOCKET',
46
+ long: '--socket SOCKET',
47
+ description: 'Socket to use'
48
+
49
+ option :user,
50
+ short: '-u',
51
+ long: '--username=VALUE',
52
+ description: 'Database username'
53
+
54
+ option :pass,
55
+ short: '-p',
56
+ long: '--password=VALUE',
57
+ description: 'Database password'
58
+
59
+ option :ini,
60
+ short: '-i',
61
+ long: '--ini VALUE',
62
+ description: 'My.cnf ini file'
63
+
64
+ option :warn,
65
+ short: '-w',
66
+ long: '--warning=VALUE',
67
+ description: 'Warning threshold for replication lag',
68
+ default: 900,
69
+ # #YELLOW
70
+ proc: lambda { |s| s.to_i } # rubocop:disable Lambda
71
+
72
+ option :crit,
73
+ short: '-c',
74
+ long: '--critical=VALUE',
75
+ description: 'Critical threshold for replication lag',
76
+ default: 1800,
77
+ # #YELLOW
78
+ proc: lambda { |s| s.to_i } # rubocop:disable Lambda
79
+
80
+ option :help,
81
+ short: '-h',
82
+ long: '--help',
83
+ description: 'Check MySQL replication status',
84
+ on: :tail,
85
+ boolean: true,
86
+ show_options: true,
87
+ exit: 0
88
+
89
+ def run
90
+ if config[:ini]
91
+ ini = IniFile.load(config[:ini])
92
+ section = ini['client']
93
+ db_user = section['user']
94
+ db_pass = section['password']
95
+ else
96
+ db_user = config[:user]
97
+ db_pass = config[:pass]
98
+ end
99
+ db_host = config[:host]
100
+
101
+ if [db_host, db_user, db_pass].any?(&:nil?)
102
+ unknown 'Must specify host, user, password'
103
+ end
104
+
105
+ begin
106
+ db = Mysql.new(db_host, db_user, db_pass, nil, config[:port], config[:socket])
107
+ results = db.query 'show slave status'
108
+
109
+ unless results.nil?
110
+ results.each_hash do |row|
111
+ # #YELLOW
112
+ # rubocop:disable all
113
+ warn "couldn't detect replication status" unless
114
+ %w(Slave_IO_State Slave_IO_Running Slave_SQL_Running Last_IO_Error Last_SQL_Error Seconds_Behind_Master).all? do |key|
115
+ row.key? key
116
+ end
117
+
118
+ # rubocop: enable all
119
+ slave_running = %w(Slave_IO_Running Slave_SQL_Running).all? do |key|
120
+ row[key] =~ /Yes/
121
+ end
122
+
123
+ output = 'Slave not running!'
124
+ output += ' STATES:'
125
+ output += " Slave_IO_Running=#{row['Slave_IO_Running']}"
126
+ output += ", Slave_SQL_Running=#{row['Slave_SQL_Running']}"
127
+ output += ", LAST ERROR: #{row['Last_SQL_Error']}"
128
+
129
+ critical output unless slave_running
130
+
131
+ replication_delay = row['Seconds_Behind_Master'].to_i
132
+
133
+ message = "replication delayed by #{replication_delay}"
134
+
135
+ if replication_delay > config[:warn] &&
136
+ replication_delay <= config[:crit]
137
+ warning message
138
+ elsif replication_delay >= config[:crit]
139
+ critical message
140
+ else
141
+ ok "slave running: #{slave_running}, #{message}"
142
+ end
143
+ end
144
+ ok 'show slave status was nil. This server is not a slave.'
145
+ end
146
+
147
+ rescue Mysql::Error => e
148
+ errstr = "Error code: #{e.errno} Error message: #{e.error}"
149
+ critical "#{errstr} SQLSTATE: #{e.sqlstate}" if e.respond_to?('sqlstate')
150
+
151
+ rescue => e
152
+ critical e
153
+
154
+ ensure
155
+ db.close if db
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,261 @@
1
+ # !/usr/bin/env ruby
2
+ #
3
+ # Push mysql stats into graphite
4
+ # ===
5
+ #
6
+ # NOTE: This plugin will attempt to get replication stats but the user
7
+ # must have SUPER or REPLICATION CLIENT privileges to run 'SHOW SLAVE
8
+ # STATUS'. It will silently ignore and continue if 'SHOW SLAVE STATUS'
9
+ # fails for any reason. The key 'slaveLag' will not be present in the
10
+ # output.
11
+ #
12
+ # Copyright 2012 Pete Shima <me@peteshima.com>
13
+ # Additional hacks by Joe Miller - https://github.com/joemiller
14
+ # Updated by Oluwaseun Obajobi 2014 to accept ini argument
15
+ #
16
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
17
+ # for details.
18
+ #
19
+ # USING INI ARGUMENT
20
+ # This was implemented to load mysql credentials without parsing the username/password.
21
+ # The ini file should be readable by the sensu user/group.
22
+ # Ref: http://eric.lubow.org/2009/ruby/parsing-ini-files-with-ruby/
23
+ #
24
+ # EXAMPLE
25
+ # mysql-alive.rb -h db01 --ini '/etc/sensu/my.cnf'
26
+ #
27
+ # MY.CNF INI FORMAT
28
+ # [client]
29
+ # user=sensu
30
+ # password="abcd1234"
31
+ #
32
+
33
+ require 'sensu-plugin/metric/cli'
34
+ require 'mysql2'
35
+ require 'socket'
36
+ require 'inifile'
37
+
38
+ class Mysql2Graphite < Sensu::Plugin::Metric::CLI::Graphite
39
+ option :host,
40
+ short: '-h HOST',
41
+ long: '--host HOST',
42
+ description: 'Mysql Host to connect to',
43
+ required: true
44
+
45
+ option :port,
46
+ short: '-P PORT',
47
+ long: '--port PORT',
48
+ description: 'Mysql Port to connect to',
49
+ proc: proc(&:to_i),
50
+ default: 3306
51
+
52
+ option :username,
53
+ short: '-u USERNAME',
54
+ long: '--user USERNAME',
55
+ description: 'Mysql Username'
56
+
57
+ option :password,
58
+ short: '-p PASSWORD',
59
+ long: '--pass PASSWORD',
60
+ description: 'Mysql password',
61
+ default: ''
62
+
63
+ option :ini,
64
+ short: '-i',
65
+ long: '--ini VALUE',
66
+ description: 'My.cnf ini file'
67
+
68
+ option :scheme,
69
+ description: 'Metric naming scheme, text to prepend to metric',
70
+ short: '-s SCHEME',
71
+ long: '--scheme SCHEME',
72
+ default: "#{Socket.gethostname}.mysql"
73
+
74
+ option :socket,
75
+ short: '-S SOCKET',
76
+ long: '--socket SOCKET'
77
+
78
+ option :verbose,
79
+ short: '-v',
80
+ long: '--verbose',
81
+ boolean: true
82
+
83
+ def run
84
+ # props to https://github.com/coredump/hoardd/blob/master/scripts-available/mysql.coffee
85
+
86
+ metrics = {
87
+ 'general' => {
88
+ 'Bytes_received' => 'rxBytes',
89
+ 'Bytes_sent' => 'txBytes',
90
+ 'Key_read_requests' => 'keyRead_requests',
91
+ 'Key_reads' => 'keyReads',
92
+ 'Key_write_requests' => 'keyWrite_requests',
93
+ 'Key_writes' => 'keyWrites',
94
+ 'Binlog_cache_use' => 'binlogCacheUse',
95
+ 'Binlog_cache_disk_use' => 'binlogCacheDiskUse',
96
+ 'Max_used_connections' => 'maxUsedConnections',
97
+ 'Aborted_clients' => 'abortedClients',
98
+ 'Aborted_connects' => 'abortedConnects',
99
+ 'Threads_connected' => 'threadsConnected',
100
+ 'Open_files' => 'openFiles',
101
+ 'Open_tables' => 'openTables',
102
+ 'Opened_tables' => 'openedTables',
103
+ 'Prepared_stmt_count' => 'preparedStmtCount',
104
+ 'Seconds_Behind_Master' => 'slaveLag',
105
+ 'Select_full_join' => 'fullJoins',
106
+ 'Select_full_range_join' => 'fullRangeJoins',
107
+ 'Select_range' => 'selectRange',
108
+ 'Select_range_check' => 'selectRange_check',
109
+ 'Select_scan' => 'selectScan',
110
+ 'Slow_queries' => 'slowQueries'
111
+ },
112
+ 'querycache' => {
113
+ 'Qcache_queries_in_cache' => 'queriesInCache',
114
+ 'Qcache_hits' => 'cacheHits',
115
+ 'Qcache_inserts' => 'inserts',
116
+ 'Qcache_not_cached' => 'notCached',
117
+ 'Qcache_lowmem_prunes' => 'lowMemPrunes'
118
+ },
119
+ 'commands' => {
120
+ 'Com_admin_commands' => 'admin_commands',
121
+ 'Com_begin' => 'begin',
122
+ 'Com_change_db' => 'change_db',
123
+ 'Com_commit' => 'commit',
124
+ 'Com_create_table' => 'create_table',
125
+ 'Com_drop_table' => 'drop_table',
126
+ 'Com_show_keys' => 'show_keys',
127
+ 'Com_delete' => 'delete',
128
+ 'Com_create_db' => 'create_db',
129
+ 'Com_grant' => 'grant',
130
+ 'Com_show_processlist' => 'show_processlist',
131
+ 'Com_flush' => 'flush',
132
+ 'Com_insert' => 'insert',
133
+ 'Com_purge' => 'purge',
134
+ 'Com_replace' => 'replace',
135
+ 'Com_rollback' => 'rollback',
136
+ 'Com_select' => 'select',
137
+ 'Com_set_option' => 'set_option',
138
+ 'Com_show_binlogs' => 'show_binlogs',
139
+ 'Com_show_databases' => 'show_databases',
140
+ 'Com_show_fields' => 'show_fields',
141
+ 'Com_show_status' => 'show_status',
142
+ 'Com_show_tables' => 'show_tables',
143
+ 'Com_show_variables' => 'show_variables',
144
+ 'Com_update' => 'update',
145
+ 'Com_drop_db' => 'drop_db',
146
+ 'Com_revoke' => 'revoke',
147
+ 'Com_drop_user' => 'drop_user',
148
+ 'Com_show_grants' => 'show_grants',
149
+ 'Com_lock_tables' => 'lock_tables',
150
+ 'Com_show_create_table' => 'show_create_table',
151
+ 'Com_unlock_tables' => 'unlock_tables',
152
+ 'Com_alter_table' => 'alter_table'
153
+ },
154
+ 'counters' => {
155
+ 'Handler_write' => 'handlerWrite',
156
+ 'Handler_update' => 'handlerUpdate',
157
+ 'Handler_delete' => 'handlerDelete',
158
+ 'Handler_read_first' => 'handlerRead_first',
159
+ 'Handler_read_key' => 'handlerRead_key',
160
+ 'Handler_read_next' => 'handlerRead_next',
161
+ 'Handler_read_prev' => 'handlerRead_prev',
162
+ 'Handler_read_rnd' => 'handlerRead_rnd',
163
+ 'Handler_read_rnd_next' => 'handlerRead_rnd_next',
164
+ 'Handler_commit' => 'handlerCommit',
165
+ 'Handler_rollback' => 'handlerRollback',
166
+ 'Handler_savepoint' => 'handlerSavepoint',
167
+ 'Handler_savepoint_rollback' => 'handlerSavepointRollback'
168
+ },
169
+ 'innodb' => {
170
+ 'Innodb_buffer_pool_pages_total' => 'bufferTotal_pages',
171
+ 'Innodb_buffer_pool_pages_free' => 'bufferFree_pages',
172
+ 'Innodb_buffer_pool_pages_dirty' => 'bufferDirty_pages',
173
+ 'Innodb_buffer_pool_pages_data' => 'bufferUsed_pages',
174
+ 'Innodb_page_size' => 'pageSize',
175
+ 'Innodb_pages_created' => 'pagesCreated',
176
+ 'Innodb_pages_read' => 'pagesRead',
177
+ 'Innodb_pages_written' => 'pagesWritten',
178
+ 'Innodb_row_lock_current_waits' => 'currentLockWaits',
179
+ 'Innodb_row_lock_waits' => 'lockWaitTimes',
180
+ 'Innodb_row_lock_time' => 'rowLockTime',
181
+ 'Innodb_data_reads' => 'fileReads',
182
+ 'Innodb_data_writes' => 'fileWrites',
183
+ 'Innodb_data_fsyncs' => 'fileFsyncs',
184
+ 'Innodb_log_writes' => 'logWrites',
185
+ 'Innodb_rows_updated' => 'rowsUpdated',
186
+ 'Innodb_rows_read' => 'rowsRead',
187
+ 'Innodb_rows_deleted' => 'rowsDeleted',
188
+ 'Innodb_rows_inserted' => 'rowsInserted'
189
+ },
190
+ 'configuration' => {
191
+ 'Max_prepared_stmt_count' => 'MaxPreparedStmtCount'
192
+ }
193
+ }
194
+
195
+ config[:host].split(' ').each do |mysql_host|
196
+ mysql_shorthostname = mysql_host.split('.')[0]
197
+ if config[:ini]
198
+ ini = IniFile.load(config[:ini])
199
+ section = ini['client']
200
+ db_user = section['user']
201
+ db_pass = section['password']
202
+ else
203
+ db_user = config[:username]
204
+ db_pass = config[:password]
205
+ end
206
+ begin
207
+ mysql = Mysql2::Client.new(
208
+ host: mysql_host,
209
+ port: config[:port],
210
+ username: db_user,
211
+ password: db_pass,
212
+ socket: config[:socket]
213
+ )
214
+
215
+ results = mysql.query('SHOW GLOBAL STATUS')
216
+ rescue => e
217
+ puts e.message
218
+ end
219
+
220
+ results.each do |row|
221
+ metrics.each do |category, var_mapping|
222
+ if var_mapping.key?(row['Variable_name'])
223
+ output "#{config[:scheme]}.#{mysql_shorthostname}.#{category}.#{var_mapping[row['Variable_name']]}", row['Value']
224
+ end
225
+ end
226
+ end
227
+
228
+ begin
229
+ slave_results = mysql.query('SHOW SLAVE STATUS')
230
+ # should return a single element array containing one hash
231
+ # #YELLOW
232
+ slave_results.first.each do |key, value| # rubocop:disable Style/Next
233
+ if metrics['general'].include?(key)
234
+ # Replication lag being null is bad, very bad, so negativate it here
235
+ value = -1 if key == 'Seconds_Behind_Master' && value.nil?
236
+ output "#{config[:scheme]}.#{mysql_shorthostname}.general.#{metrics['general'][key]}", value
237
+ end
238
+ end
239
+ rescue => e
240
+ puts "Error querying slave status: #{e}" if config[:verbose]
241
+ end
242
+
243
+ begin
244
+ variables_results = mysql.query('SHOW GLOBAL VARIABLES')
245
+
246
+ category = 'configuration'
247
+ variables_results.each do |row|
248
+ metrics[category].each do |metric, desc|
249
+ if metric.casecmp(row['Variable_name']) == 0
250
+ output "#{config[:scheme]}.#{mysql_shorthostname}.#{category}.#{desc}", row['Value']
251
+ end
252
+ end
253
+ end
254
+ rescue => e
255
+ puts e.message
256
+ end
257
+ end
258
+
259
+ ok
260
+ end
261
+ end