sensu-plugins-mysql-nagyt 2.6.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +155 -0
- data/LICENSE +22 -0
- data/README.md +156 -0
- data/bin/check-mysql-alive.rb +102 -0
- data/bin/check-mysql-connections.rb +110 -0
- data/bin/check-mysql-disk.rb +140 -0
- data/bin/check-mysql-innodb-lock.rb +149 -0
- data/bin/check-mysql-msr-replication-status.rb +153 -0
- data/bin/check-mysql-query-result-count.rb +113 -0
- data/bin/check-mysql-replication-status.rb +175 -0
- data/bin/check-mysql-select-count.rb +115 -0
- data/bin/check-mysql-status.rb +209 -0
- data/bin/check-mysql-threads.rb +122 -0
- data/bin/metrics-mysql-graphite.rb +268 -0
- data/bin/metrics-mysql-multiple-select-count.rb +112 -0
- data/bin/metrics-mysql-processes.rb +142 -0
- data/bin/metrics-mysql-query-result-count.rb +100 -0
- data/bin/metrics-mysql-raw.rb +396 -0
- data/bin/metrics-mysql-select-count.rb +102 -0
- data/bin/metrics-mysql.rb +58 -0
- data/bin/mysql-metrics.sql +15 -0
- data/lib/sensu-plugins-mysql.rb +1 -0
- data/lib/sensu-plugins-mysql/version.rb +9 -0
- metadata +262 -0
@@ -0,0 +1,175 @@
|
|
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
|
+
# Updated by Nicola Strappazzon 2016 to implement Multi Source Replication
|
9
|
+
#
|
10
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
11
|
+
# for details.
|
12
|
+
#
|
13
|
+
# USING INI ARGUMENT
|
14
|
+
# This was implemented to load mysql credentials without parsing the username/password.
|
15
|
+
# The ini file should be readable by the sensu user/group.
|
16
|
+
# Ref: http://eric.lubow.org/2009/ruby/parsing-ini-files-with-ruby/
|
17
|
+
#
|
18
|
+
# EXAMPLE
|
19
|
+
# mysql-alive.rb -h db01 --ini '/etc/sensu/my.cnf'
|
20
|
+
# mysql-alive.rb -h db01 --ini '/etc/sensu/my.cnf' --ini-section customsection
|
21
|
+
#
|
22
|
+
# MY.CNF INI FORMAT
|
23
|
+
# [client]
|
24
|
+
# user=sensu
|
25
|
+
# password="abcd1234"
|
26
|
+
#
|
27
|
+
# [customsection]
|
28
|
+
# user=user
|
29
|
+
# password="password"
|
30
|
+
#
|
31
|
+
|
32
|
+
require 'sensu-plugin/check/cli'
|
33
|
+
require 'mysql'
|
34
|
+
require 'inifile'
|
35
|
+
|
36
|
+
class CheckMysqlReplicationStatus < Sensu::Plugin::Check::CLI
|
37
|
+
option :host,
|
38
|
+
short: '-h',
|
39
|
+
long: '--host=VALUE',
|
40
|
+
description: 'Database host'
|
41
|
+
|
42
|
+
option :port,
|
43
|
+
short: '-P',
|
44
|
+
long: '--port=VALUE',
|
45
|
+
description: 'Database port',
|
46
|
+
default: 3306,
|
47
|
+
# #YELLOW
|
48
|
+
proc: lambda { |s| s.to_i } # rubocop:disable Lambda
|
49
|
+
|
50
|
+
option :socket,
|
51
|
+
short: '-s SOCKET',
|
52
|
+
long: '--socket SOCKET',
|
53
|
+
description: 'Socket to use'
|
54
|
+
|
55
|
+
option :user,
|
56
|
+
short: '-u',
|
57
|
+
long: '--username=VALUE',
|
58
|
+
description: 'Database username'
|
59
|
+
|
60
|
+
option :pass,
|
61
|
+
short: '-p',
|
62
|
+
long: '--password=VALUE',
|
63
|
+
description: 'Database password'
|
64
|
+
|
65
|
+
option :master_connection,
|
66
|
+
short: '-m',
|
67
|
+
long: '--master-connection=VALUE',
|
68
|
+
description: 'Replication master connection name'
|
69
|
+
|
70
|
+
option :ini,
|
71
|
+
short: '-i',
|
72
|
+
long: '--ini VALUE',
|
73
|
+
description: 'My.cnf ini file'
|
74
|
+
|
75
|
+
option :ini_section,
|
76
|
+
description: 'Section in my.cnf ini file',
|
77
|
+
long: '--ini-section VALUE',
|
78
|
+
default: 'client'
|
79
|
+
|
80
|
+
option :warn,
|
81
|
+
short: '-w',
|
82
|
+
long: '--warning=VALUE',
|
83
|
+
description: 'Warning threshold for replication lag',
|
84
|
+
default: 900,
|
85
|
+
# #YELLOW
|
86
|
+
proc: lambda { |s| s.to_i } # rubocop:disable Lambda
|
87
|
+
|
88
|
+
option :crit,
|
89
|
+
short: '-c',
|
90
|
+
long: '--critical=VALUE',
|
91
|
+
description: 'Critical threshold for replication lag',
|
92
|
+
default: 1800,
|
93
|
+
# #YELLOW
|
94
|
+
proc: lambda { |s| s.to_i } # rubocop:disable Lambda
|
95
|
+
|
96
|
+
def run
|
97
|
+
if config[:ini]
|
98
|
+
ini = IniFile.load(config[:ini])
|
99
|
+
section = ini[config[:ini_section]]
|
100
|
+
db_user = section['user']
|
101
|
+
db_pass = section['password']
|
102
|
+
else
|
103
|
+
db_user = config[:user]
|
104
|
+
db_pass = config[:pass]
|
105
|
+
end
|
106
|
+
db_host = config[:host]
|
107
|
+
db_conn = config[:master_connection]
|
108
|
+
|
109
|
+
if [db_host, db_user, db_pass].any?(&:nil?)
|
110
|
+
unknown 'Must specify host, user, password'
|
111
|
+
end
|
112
|
+
|
113
|
+
begin
|
114
|
+
db = Mysql.new(db_host, db_user, db_pass, nil, config[:port], config[:socket])
|
115
|
+
|
116
|
+
results = if db_conn.nil?
|
117
|
+
db.query 'SHOW SLAVE STATUS'
|
118
|
+
else
|
119
|
+
db.query "SHOW SLAVE '#{db_conn}' STATUS"
|
120
|
+
end
|
121
|
+
|
122
|
+
unless results.nil?
|
123
|
+
results.each_hash do |row|
|
124
|
+
warn "couldn't detect replication status" unless
|
125
|
+
%w(Slave_IO_State Slave_IO_Running Slave_SQL_Running Last_IO_Error Last_SQL_Error Seconds_Behind_Master).all? do |key|
|
126
|
+
row.key? key
|
127
|
+
end
|
128
|
+
|
129
|
+
slave_running = %w(Slave_IO_Running Slave_SQL_Running).all? do |key|
|
130
|
+
row[key] =~ /Yes/
|
131
|
+
end
|
132
|
+
|
133
|
+
output = if db_conn.nil?
|
134
|
+
'Slave not running!'
|
135
|
+
else
|
136
|
+
"Slave on master connection #{db_conn} not running!"
|
137
|
+
end
|
138
|
+
|
139
|
+
output += ' STATES:'
|
140
|
+
output += " Slave_IO_Running=#{row['Slave_IO_Running']}"
|
141
|
+
output += ", Slave_SQL_Running=#{row['Slave_SQL_Running']}"
|
142
|
+
output += ", LAST ERROR: #{row['Last_SQL_Error']}"
|
143
|
+
|
144
|
+
critical output unless slave_running
|
145
|
+
|
146
|
+
replication_delay = row['Seconds_Behind_Master'].to_i
|
147
|
+
|
148
|
+
message = "replication delayed by #{replication_delay}"
|
149
|
+
|
150
|
+
if replication_delay > config[:warn] &&
|
151
|
+
replication_delay <= config[:crit]
|
152
|
+
warning message
|
153
|
+
elsif replication_delay >= config[:crit]
|
154
|
+
critical message
|
155
|
+
elsif db_conn.nil?
|
156
|
+
ok "slave running: #{slave_running}, #{message}"
|
157
|
+
else
|
158
|
+
ok "master connection: #{db_conn}, slave running: #{slave_running}, #{message}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
ok 'show slave status was nil. This server is not a slave.'
|
162
|
+
end
|
163
|
+
|
164
|
+
rescue Mysql::Error => e
|
165
|
+
errstr = "Error code: #{e.errno} Error message: #{e.error}"
|
166
|
+
critical "#{errstr} SQLSTATE: #{e.sqlstate}" if e.respond_to?('sqlstate')
|
167
|
+
|
168
|
+
rescue => e
|
169
|
+
critical e
|
170
|
+
|
171
|
+
ensure
|
172
|
+
db.close if db
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# MySQL Select Count Check
|
4
|
+
#
|
5
|
+
# Checks the length of a result set from a MySQL query.
|
6
|
+
#
|
7
|
+
# Copyright 2017 Andrew Thal <athal7@me.com> to check-mysql-query-result-count.rb
|
8
|
+
# Modified by Mutsutoshi Yoshimoto <negachov@gmail.com> 2018 to select count(*) version
|
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
|
+
require 'inifile'
|
16
|
+
|
17
|
+
class MysqlSelectCountCheck < Sensu::Plugin::Check::CLI
|
18
|
+
option :host,
|
19
|
+
short: '-h HOST',
|
20
|
+
long: '--host HOST',
|
21
|
+
description: 'MySQL Host to connect to',
|
22
|
+
required: true
|
23
|
+
|
24
|
+
option :port,
|
25
|
+
short: '-P PORT',
|
26
|
+
long: '--port PORT',
|
27
|
+
description: 'MySQL Port to connect to',
|
28
|
+
proc: proc(&:to_i),
|
29
|
+
default: 3306
|
30
|
+
|
31
|
+
option :username,
|
32
|
+
short: '-u USERNAME',
|
33
|
+
long: '--user USERNAME',
|
34
|
+
description: 'MySQL Username'
|
35
|
+
|
36
|
+
option :password,
|
37
|
+
short: '-p PASSWORD',
|
38
|
+
long: '--pass PASSWORD',
|
39
|
+
description: 'MySQL password'
|
40
|
+
|
41
|
+
option :database,
|
42
|
+
short: '-d DATABASE',
|
43
|
+
long: '--database DATABASE',
|
44
|
+
description: 'MySQL database',
|
45
|
+
required: true
|
46
|
+
|
47
|
+
option :ini,
|
48
|
+
short: '-i',
|
49
|
+
long: '--ini VALUE',
|
50
|
+
description: 'My.cnf ini file'
|
51
|
+
|
52
|
+
option :ini_section,
|
53
|
+
description: 'Section in my.cnf ini file',
|
54
|
+
long: '--ini-section VALUE',
|
55
|
+
default: 'client'
|
56
|
+
|
57
|
+
option :socket,
|
58
|
+
short: '-S SOCKET',
|
59
|
+
long: '--socket SOCKET',
|
60
|
+
description: 'MySQL Unix socket to connect to'
|
61
|
+
|
62
|
+
option :warn,
|
63
|
+
short: '-w COUNT',
|
64
|
+
long: '--warning COUNT',
|
65
|
+
description: 'Warning when query value exceeds threshold',
|
66
|
+
proc: proc(&:to_i),
|
67
|
+
required: true
|
68
|
+
|
69
|
+
option :crit,
|
70
|
+
short: '-c COUNT',
|
71
|
+
long: '--critical COUNT',
|
72
|
+
description: 'Critical when query value exceeds threshold',
|
73
|
+
proc: proc(&:to_i),
|
74
|
+
required: true
|
75
|
+
|
76
|
+
option :query,
|
77
|
+
short: '-q SELECT_COUNT_QUERY',
|
78
|
+
long: '--query SELECT_COUNT_QUERY',
|
79
|
+
description: 'Query to execute',
|
80
|
+
required: true
|
81
|
+
|
82
|
+
def run
|
83
|
+
if config[:ini]
|
84
|
+
ini = IniFile.load(config[:ini])
|
85
|
+
section = ini[config[:ini_section]]
|
86
|
+
db_user = section['user']
|
87
|
+
db_pass = section['password']
|
88
|
+
else
|
89
|
+
db_user = config[:username]
|
90
|
+
db_pass = config[:password]
|
91
|
+
end
|
92
|
+
raise "invalid query : #{config[:query]}" unless config[:query] =~ /^select\s+count\(\s*\*\s*\)/i
|
93
|
+
|
94
|
+
db = Mysql.real_connect(config[:host], db_user, db_pass, config[:database], config[:port], config[:socket])
|
95
|
+
|
96
|
+
count = db.query(config[:query]).fetch_row[0].to_i
|
97
|
+
if count >= config[:crit]
|
98
|
+
critical "Count is above the CRITICAL limit: #{count} count / #{config[:crit]} limit"
|
99
|
+
elsif count >= config[:warn]
|
100
|
+
warning "Count is above the WARNING limit: #{count} count / #{config[:warn]} limit"
|
101
|
+
else
|
102
|
+
ok "Count is below thresholds : #{count} count"
|
103
|
+
end
|
104
|
+
|
105
|
+
rescue Mysql::Error => e
|
106
|
+
errstr = "Error code: #{e.errno} Error message: #{e.error}"
|
107
|
+
critical "#{errstr} SQLSTATE: #{e.sqlstate}" if e.respond_to?('sqlstate')
|
108
|
+
|
109
|
+
rescue StandardError => e
|
110
|
+
critical "unhandled exception: #{e}"
|
111
|
+
|
112
|
+
ensure
|
113
|
+
db.close if db
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# MySQL Status Plugin
|
4
|
+
# ===
|
5
|
+
#
|
6
|
+
# This plugin attempts to login to mysql with provided credentials.
|
7
|
+
# NO DEPENDENCIES (no mysql-devel and thus no implicit mysql-server restart)
|
8
|
+
# It checks whether MySQL is UP --check status
|
9
|
+
# It checks replication delay --check replication
|
10
|
+
# Author: Magic Online - www.magic.fr
|
11
|
+
# Date: September 2016
|
12
|
+
#
|
13
|
+
# Author: Magic Online - www.magic.fr - September 2016
|
14
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
15
|
+
# for details.
|
16
|
+
#
|
17
|
+
# USING INI ARGUMENT
|
18
|
+
# The ini file should be readable by the sensu user/group.
|
19
|
+
#
|
20
|
+
# EXAMPLE
|
21
|
+
# check-mysql-status.rb -h localhost --ini '/etc/sensu/my.cnf' --check status
|
22
|
+
# check-mysql-status.rb -h localhost --ini '/etc/sensu/my.cnf' --check replication
|
23
|
+
#
|
24
|
+
# MY.CNF INI FORMAT
|
25
|
+
# [client]
|
26
|
+
# user=sensu
|
27
|
+
# password="abcd1234"
|
28
|
+
# socket="/var/lib/mysql/mysql.sock"
|
29
|
+
#
|
30
|
+
|
31
|
+
require 'sensu-plugin/check/cli'
|
32
|
+
require 'inifile'
|
33
|
+
require 'open3'
|
34
|
+
|
35
|
+
# Check MySQL Status
|
36
|
+
class CheckMySQLStatus < Sensu::Plugin::Check::CLI
|
37
|
+
option :user,
|
38
|
+
description: 'MySQL User, you really should use ini to hide credentials instead of using me',
|
39
|
+
short: '-u USER',
|
40
|
+
long: '--user USER',
|
41
|
+
default: 'mosim'
|
42
|
+
|
43
|
+
option :password,
|
44
|
+
description: 'MySQL Password, you really should use ini to hide credentials instead of using me',
|
45
|
+
short: '-p PASS',
|
46
|
+
long: '--password PASS',
|
47
|
+
default: 'mysqlPassWord'
|
48
|
+
|
49
|
+
option :ini,
|
50
|
+
description: 'My.cnf ini file',
|
51
|
+
short: '-i',
|
52
|
+
long: '--ini VALUE'
|
53
|
+
|
54
|
+
option :ini_section,
|
55
|
+
description: 'Section in my.cnf ini file',
|
56
|
+
long: '--ini-section VALUE',
|
57
|
+
default: 'client'
|
58
|
+
|
59
|
+
option :hostname,
|
60
|
+
description: 'Hostname to login to',
|
61
|
+
short: '-h HOST',
|
62
|
+
long: '--hostname HOST',
|
63
|
+
default: 'localhost'
|
64
|
+
|
65
|
+
option :database,
|
66
|
+
description: 'Database schema to connect to',
|
67
|
+
short: '-d DATABASE',
|
68
|
+
long: '--database DATABASE',
|
69
|
+
default: 'test'
|
70
|
+
|
71
|
+
option :port,
|
72
|
+
description: 'Port to connect to',
|
73
|
+
short: '-P PORT',
|
74
|
+
long: '--port PORT',
|
75
|
+
default: '3306'
|
76
|
+
|
77
|
+
option :socket,
|
78
|
+
description: 'Socket to use',
|
79
|
+
short: '-s SOCKET',
|
80
|
+
long: '--socket SOCKET',
|
81
|
+
default: '/var/run/mysqld/mysqld.sock'
|
82
|
+
|
83
|
+
option :binary,
|
84
|
+
description: 'Absolute path to mysql binary',
|
85
|
+
short: '-b BINARY',
|
86
|
+
long: '--binary BINARY',
|
87
|
+
default: 'mysql'
|
88
|
+
|
89
|
+
option :check,
|
90
|
+
description: 'type of check: (status|replication)',
|
91
|
+
short: '-C CHECK',
|
92
|
+
long: '--check CHECK',
|
93
|
+
default: 'status'
|
94
|
+
|
95
|
+
option :warn,
|
96
|
+
description: 'Warning threshold for replication lag',
|
97
|
+
short: '-w',
|
98
|
+
long: '--warning=VALUE',
|
99
|
+
default: 900
|
100
|
+
|
101
|
+
option :crit,
|
102
|
+
description: 'Critical threshold for replication lag',
|
103
|
+
short: '-c',
|
104
|
+
long: '--critical=VALUE',
|
105
|
+
default: 1800
|
106
|
+
|
107
|
+
option :debug,
|
108
|
+
description: 'Print debug info',
|
109
|
+
long: '--debug',
|
110
|
+
default: false
|
111
|
+
|
112
|
+
def credentials
|
113
|
+
if config[:ini]
|
114
|
+
ini = IniFile.load(config[:ini])
|
115
|
+
section = ini[config[:ini_section]]
|
116
|
+
db_user = section['user']
|
117
|
+
db_pass = section['password']
|
118
|
+
db_socket = if config[:socket]
|
119
|
+
config[:socket]
|
120
|
+
else
|
121
|
+
section['socket']
|
122
|
+
end
|
123
|
+
else
|
124
|
+
db_user = config[:user]
|
125
|
+
db_pass = config[:password]
|
126
|
+
db_socket = config[:socket]
|
127
|
+
end
|
128
|
+
[db_user, db_pass, db_socket]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Status check
|
132
|
+
def status_check(db_user, db_pass, db_socket)
|
133
|
+
cmd = "#{config[:binary]} -u #{db_user} -h #{config[:hostname]} --port #{config[:port]} \
|
134
|
+
--socket #{db_socket} -p\"#{db_pass.strip}\" --batch --disable-column-names -e 'show schemas;'"
|
135
|
+
begin
|
136
|
+
stdout, _stderr, status = Open3.capture3(cmd)
|
137
|
+
if status.to_i == 0
|
138
|
+
ok "#{status} | #{stdout.split("\n")}"
|
139
|
+
else
|
140
|
+
critical "Error message: status: #{status}"
|
141
|
+
end
|
142
|
+
rescue => e
|
143
|
+
critical "Error message: status: #{status} | Exception: #{e}"
|
144
|
+
ensure
|
145
|
+
puts ''
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def replication_check(db_user, db_pass, db_socket)
|
150
|
+
table = {}
|
151
|
+
begin
|
152
|
+
cmd = "#{config[:binary]} -u #{db_user} -h #{config[:hostname]} --port #{config[:port]} \
|
153
|
+
--socket #{db_socket} -p\"#{db_pass.strip}\" -e 'SHOW SLAVE STATUS\\G'"
|
154
|
+
stdout, _stderr, status = Open3.capture3(cmd)
|
155
|
+
if status.to_i != 0
|
156
|
+
critical "Error message: status: #{status}"
|
157
|
+
end
|
158
|
+
stdout.split("\n").each do |line|
|
159
|
+
key = line.split(':')[0]
|
160
|
+
value = line.split(':')[1]
|
161
|
+
table[key.strip.to_s] = value.to_s unless key.include? '***'
|
162
|
+
end
|
163
|
+
dict = []
|
164
|
+
table.keys.to_a.each do |k|
|
165
|
+
%w(Slave_IO_State Slave_IO_Running Slave_SQL_Running Last_IO_Error Last_SQL_Error Seconds_Behind_Master).each do |key|
|
166
|
+
dict.push(k.strip.to_s) if key.strip == k.strip
|
167
|
+
end
|
168
|
+
end
|
169
|
+
table.each do |attribute, value|
|
170
|
+
puts "#{attribute} : #{value}" if config[:debug]
|
171
|
+
warn "couldn't detect replication status :#{dict.size}" unless dict.size == 6
|
172
|
+
slave_running = %w(Slave_IO_Running Slave_SQL_Running).all? do |key|
|
173
|
+
table[key].to_s =~ /Yes/
|
174
|
+
end
|
175
|
+
output = 'Slave not running!'
|
176
|
+
output += ' STATES:'
|
177
|
+
output += " Slave_IO_Running=#{table['Slave_IO_Running']}"
|
178
|
+
output += ", Slave_SQL_Running=#{table['Slave_SQL_Running']}"
|
179
|
+
output += ", LAST ERROR: #{table['Last_SQL_Error']}"
|
180
|
+
critical output unless slave_running
|
181
|
+
replication_delay = table['Seconds_Behind_Master'].to_i
|
182
|
+
message = "replication delayed by #{replication_delay}"
|
183
|
+
if replication_delay > config[:warn].to_i && replication_delay <= config[:crit].to_i
|
184
|
+
warning message
|
185
|
+
elsif replication_delay >= config[:crit].to_i
|
186
|
+
critical message
|
187
|
+
else
|
188
|
+
ok "slave running: #{slave_running}, #{message}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
ok 'show slave status was nil. This server is not a slave.'
|
192
|
+
rescue => e
|
193
|
+
critical "Error message: status: #{status} | Exception: #{e}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def run
|
198
|
+
db_user = credentials[0]
|
199
|
+
db_pass = credentials[1]
|
200
|
+
db_socket = credentials[2]
|
201
|
+
if config[:check] == 'status'
|
202
|
+
status_check(db_user, db_pass, db_socket)
|
203
|
+
end
|
204
|
+
if config[:check] == 'replication'
|
205
|
+
replication_check(db_user, db_pass, db_socket)
|
206
|
+
end
|
207
|
+
unknown 'No check type succeeded. Check your options'
|
208
|
+
end
|
209
|
+
end
|