forked from soundcloud/lhm
/
lhm-kill-queue
executable file
·172 lines (140 loc) · 3.75 KB
/
lhm-kill-queue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/env ruby
require 'active_record'
require 'lhm/sql_helper'
require 'optparse'
module Lhm
class KillQueue
def initialize
@port = 3306
@grace = 10
@tiny = 0.1
@marker = "%#{ SqlHelper.annotation }%"
OptionParser.new do |opts|
opts.on("-h", "--hostname HOSTNAME") { |v| @hostname = v }
opts.on("-u", "--username USERNAME") { |v| @username = v }
opts.on("-p", "--password PASSWORD") { |v| @password = v }
opts.on("-d", "--database DATABASE") { |v| @database = v }
opts.on("-m", "--mode MODE") { |v| @mode = v.to_sym }
opts.on("-y", "--confirm") { |v| @confirm = true }
end.parse!
unless(@hostname && @username && @password && @database)
abort usage
end
unless([:kill, :master, :slave].include?(@mode))
abort "specify -m kill OR -m master OR -m slave"
end
connect
end
def usage
<<-desc.gsub(/^ /, '')
kills queries on the given server after detecting 'lock table#{ @marker }'.
usage:
lhm-kill-queue -h hostname -u username -p password -d database \\
(-m kill | -m master | -m slave) [--confirm]
desc
end
def run
case @mode
when :kill then kill
when :master then master
when :slave then slave
end
end
def kill
lock = trip
kill_process(lock)
end
def master
lock = trip
puts "starting to kill non lhm processes in #{ @grace } seconds"
sleep(@grace + @tiny)
[list_non_lhm].flatten.each do |process|
kill_process(process)
sleep(@tiny)
end
end
def slave
lock = trip
puts "starting to kill non lhm SELECT processes in #{ @grace } seconds"
sleep(@grace + @tiny)
[list_non_lhm].flatten.each do |process|
if(select?(process))
kill_process(process)
sleep(@tiny)
end
end
end
private
def connect
ActiveRecord::Base.establish_connection({
:adapter => 'mysql',
:host => @hostname,
:port => @port,
:username => @username,
:password => @password,
:database => @database
})
end
def connection
ActiveRecord::Base.connection
end
def list_non_lhm
select_processes %Q(
info not like '#{ @marker }' and time > #{ @grace } and command = 'Query'
)
end
def trip
until res = select_processes("info like 'lock table#{ @marker }'").first
sleep @tiny
print '.'
end
res
end
def kill_process(process_id)
puts "killing #{ select_statement(process_id) }"
if(@confirm)
print "confirm ('y' to confirm): "
if(gets.strip != 'y')
puts "skipped."
return
end
end
connection.execute("kill #{ process_id }")
puts "killed #{ process_id }"
end
def select?(process)
if statement = select_statement(process)
case statement
when /delete/i then false
when /update/i then false
when /insert/i then false
else
!!statement.match(/select/i)
end
end
end
def select_statement(process)
if process
value %Q(
select info from information_schema.processlist where id = #{ process }
)
end
end
def select_processes(predicate)
values %Q(
select id from information_schema.processlist
where db = '#{ @database }'
and user = '#{ @username }'
and #{ predicate }
)
end
def value(statement)
connection.select_value(statement)
end
def values(statement)
connection.select_values(statement)
end
end
end
killer = Lhm::KillQueue.new
killer.run