sensu-plugins-mysql 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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