db_sucker 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +193 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/db_sucker +12 -0
- data/bin/db_sucker.sh +14 -0
- data/db_sucker.gemspec +29 -0
- data/doc/config_example.rb +53 -0
- data/doc/container_example.yml +150 -0
- data/lib/db_sucker/adapters/mysql2.rb +103 -0
- data/lib/db_sucker/application/colorize.rb +28 -0
- data/lib/db_sucker/application/container/accessors.rb +60 -0
- data/lib/db_sucker/application/container/ssh.rb +225 -0
- data/lib/db_sucker/application/container/validations.rb +53 -0
- data/lib/db_sucker/application/container/variation/accessors.rb +45 -0
- data/lib/db_sucker/application/container/variation/helpers.rb +21 -0
- data/lib/db_sucker/application/container/variation/worker_api.rb +65 -0
- data/lib/db_sucker/application/container/variation.rb +60 -0
- data/lib/db_sucker/application/container.rb +70 -0
- data/lib/db_sucker/application/container_collection.rb +47 -0
- data/lib/db_sucker/application/core.rb +222 -0
- data/lib/db_sucker/application/dispatch.rb +364 -0
- data/lib/db_sucker/application/evented_resultset.rb +149 -0
- data/lib/db_sucker/application/fake_channel.rb +22 -0
- data/lib/db_sucker/application/output_helper.rb +197 -0
- data/lib/db_sucker/application/sklaven_treiber/log_spool.rb +57 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/accessors.rb +105 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/core.rb +168 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/helpers.rb +144 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/base.rb +240 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_copy.rb +81 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_gunzip.rb +58 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_import_sql.rb +80 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_shasum.rb +49 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/pv_wrapper.rb +73 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/sftp_download.rb +57 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/throughput.rb +219 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/routines.rb +313 -0
- data/lib/db_sucker/application/sklaven_treiber/worker.rb +48 -0
- data/lib/db_sucker/application/sklaven_treiber.rb +281 -0
- data/lib/db_sucker/application/slot_pool.rb +137 -0
- data/lib/db_sucker/application/tie.rb +25 -0
- data/lib/db_sucker/application/window/core.rb +185 -0
- data/lib/db_sucker/application/window/dialog.rb +142 -0
- data/lib/db_sucker/application/window/keypad/core.rb +85 -0
- data/lib/db_sucker/application/window/keypad.rb +174 -0
- data/lib/db_sucker/application/window/prompt.rb +124 -0
- data/lib/db_sucker/application/window.rb +329 -0
- data/lib/db_sucker/application.rb +168 -0
- data/lib/db_sucker/patches/beta-warning.rb +374 -0
- data/lib/db_sucker/patches/developer.rb +29 -0
- data/lib/db_sucker/patches/net-sftp.rb +20 -0
- data/lib/db_sucker/patches/thread-count.rb +30 -0
- data/lib/db_sucker/version.rb +4 -0
- data/lib/db_sucker.rb +81 -0
- metadata +217 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class EventedResultset
|
4
|
+
SetAlreadyClosedError = Class.new(::RuntimeError)
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@store = []
|
9
|
+
@monitor = Monitor.new
|
10
|
+
@closed = false
|
11
|
+
@close_signal = @monitor.new_cond
|
12
|
+
@value_signal = @monitor.new_cond
|
13
|
+
end
|
14
|
+
|
15
|
+
def sync &block
|
16
|
+
@monitor.synchronize(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def enq data, group = nil
|
20
|
+
sync do
|
21
|
+
raise SetAlreadyClosedError, "failed to enqueue data: resultset is already closed" if closed?
|
22
|
+
@store << [group.try(:to_sym), data]
|
23
|
+
@value_signal.broadcast
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def push *args
|
28
|
+
sync do
|
29
|
+
args.each {|a| enq(a) }
|
30
|
+
@store
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :<<, :push
|
34
|
+
|
35
|
+
def close!
|
36
|
+
sync do
|
37
|
+
@closed = true
|
38
|
+
@value_signal.broadcast
|
39
|
+
@close_signal.broadcast
|
40
|
+
end
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def closed?
|
45
|
+
sync { @closed }
|
46
|
+
end
|
47
|
+
|
48
|
+
def empty?
|
49
|
+
sync { @store.empty? }
|
50
|
+
end
|
51
|
+
|
52
|
+
def wait
|
53
|
+
sync do
|
54
|
+
return if closed?
|
55
|
+
@close_signal.wait
|
56
|
+
end
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def for_group group
|
61
|
+
@store.map{|grp, data| grp == group.try(:to_sym) ? data : nil }.compact
|
62
|
+
end
|
63
|
+
|
64
|
+
def each &block
|
65
|
+
wait
|
66
|
+
if block
|
67
|
+
@store.each do |group, data|
|
68
|
+
block.call(data)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
@store.map(&:second)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def join *a
|
76
|
+
wait
|
77
|
+
each.join(*a)
|
78
|
+
end
|
79
|
+
|
80
|
+
def [] which
|
81
|
+
wait
|
82
|
+
@store[which].try(:last)
|
83
|
+
end
|
84
|
+
|
85
|
+
def eachx &block
|
86
|
+
wait
|
87
|
+
@store.each(&block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def each_line &block
|
91
|
+
Thread.current[self.object_id.to_s] = nil
|
92
|
+
loop do
|
93
|
+
data = gets
|
94
|
+
if !data
|
95
|
+
break if closed?
|
96
|
+
next
|
97
|
+
end
|
98
|
+
block.call(data)
|
99
|
+
end
|
100
|
+
ensure
|
101
|
+
Thread.current[self.object_id.to_s] = nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def each_linex &block
|
105
|
+
Thread.current[self.object_id.to_s] = nil
|
106
|
+
loop do
|
107
|
+
group, entry = getx
|
108
|
+
unless entry
|
109
|
+
break if closed?
|
110
|
+
next
|
111
|
+
end
|
112
|
+
block.call(group, entry)
|
113
|
+
end
|
114
|
+
ensure
|
115
|
+
Thread.current[self.object_id.to_s] = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def gets
|
119
|
+
sync do
|
120
|
+
Thread.current[self.object_id.to_s] ||= -1
|
121
|
+
if !closed? && !@store[Thread.current[self.object_id.to_s]+1]
|
122
|
+
@value_signal.wait
|
123
|
+
end
|
124
|
+
if @store[Thread.current[self.object_id.to_s]+1]
|
125
|
+
Thread.current[self.object_id.to_s] += 1
|
126
|
+
@store[Thread.current[self.object_id.to_s]].try(:last)
|
127
|
+
else
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def getx
|
134
|
+
sync do
|
135
|
+
Thread.current[self.object_id.to_s] ||= -1
|
136
|
+
if !closed? && !@store[Thread.current[self.object_id.to_s]+1]
|
137
|
+
@value_signal.wait
|
138
|
+
end
|
139
|
+
if @store[Thread.current[self.object_id.to_s]+1]
|
140
|
+
Thread.current[self.object_id.to_s] += 1
|
141
|
+
@store[Thread.current[self.object_id.to_s]]
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class FakeChannel
|
4
|
+
def initialize &block
|
5
|
+
@storage = {}
|
6
|
+
@termination = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def [] k
|
10
|
+
@storage[k]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []= k, v
|
14
|
+
@storage[k] = v
|
15
|
+
end
|
16
|
+
|
17
|
+
def alive?
|
18
|
+
!@termination.try(:call, self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
module OutputHelper
|
4
|
+
def self.hook ctx
|
5
|
+
[:puts, :print, :warn, :debug, :log, :warning, :error, :abort, :decolorize, :db_table_listing, :print_db_table_list, :decolorize, :render_table, :f_percentage, :human_bytes, :human_number, :human_percentage, :human_seconds, :human_seconds2, :rll, :c, :strbool].each do |meth|
|
6
|
+
ctx.__send__(:define_method, meth) do |*a|
|
7
|
+
Thread.main[:app].__send__(meth, *a)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def puts *a
|
13
|
+
outsync { @opts[:stdout].send(:puts, *a) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def print *a
|
17
|
+
outsync { @opts[:stdout].send(:print, *a) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn *a
|
21
|
+
outsync { @opts[:stdout].send(:warn, *a) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug msg, lvl = 1
|
25
|
+
puts c("[DEBUG] #{msg}", :black) if @opts[:debug] && @opts[:debug] >= lvl
|
26
|
+
end
|
27
|
+
|
28
|
+
def log msg
|
29
|
+
puts c("#{msg}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def warning msg
|
33
|
+
warn c("[WARN] #{msg}", :red)
|
34
|
+
end
|
35
|
+
|
36
|
+
def error msg
|
37
|
+
warn c("[ERROR] #{msg}", :red)
|
38
|
+
end
|
39
|
+
|
40
|
+
def abort msg, exit_code = 1
|
41
|
+
warn c("[ABORT] #{msg}", :red)
|
42
|
+
exit(exit_code) if exit_code
|
43
|
+
end
|
44
|
+
|
45
|
+
def rll
|
46
|
+
print "\033[A"
|
47
|
+
print "\033[2K\r"
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_table table, headers = []
|
51
|
+
[].tap do |r|
|
52
|
+
col_sizes = table.map{|col| col.map{|i| decolorize(i.to_s) }.map(&:length).max }
|
53
|
+
headers.map{|i| decolorize(i.to_s) }.map(&:length).each_with_index do |length, header|
|
54
|
+
col_sizes[header] = [col_sizes[header] || 0, length || 0].max
|
55
|
+
end
|
56
|
+
|
57
|
+
# header
|
58
|
+
if headers.any?
|
59
|
+
r << [].tap do |line|
|
60
|
+
col_sizes.count.times do |col|
|
61
|
+
line << headers[col].ljust(col_sizes[col] + (headers[col].length - decolorize(headers[col]).length))
|
62
|
+
end
|
63
|
+
end.join(" | ")
|
64
|
+
r << "".ljust(col_sizes.inject(&:+) + ((col_sizes.count - 1) * 3), "-")
|
65
|
+
end
|
66
|
+
|
67
|
+
# records
|
68
|
+
table[0].count.times do |row|
|
69
|
+
r << [].tap do |line|
|
70
|
+
col_sizes.count.times do |col|
|
71
|
+
line << "#{table[col][row]}".ljust(col_sizes[col] + (table[col][row].to_s.length - decolorize(table[col][row]).length))
|
72
|
+
end
|
73
|
+
end.join(" | ")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def db_table_listing col
|
79
|
+
col.each do |id, ccfg|
|
80
|
+
log ""
|
81
|
+
log "====================="
|
82
|
+
log "=== #{c id, :magenta}"
|
83
|
+
log "====================="
|
84
|
+
a, b = [], []
|
85
|
+
ccfg.variations.map do |name, vd|
|
86
|
+
a << c(name, :blue)
|
87
|
+
b << (vd.label.present? ? c(vd.label) : c("no label", :black))
|
88
|
+
end
|
89
|
+
render_table([a, b], [c("variation"), c("label")]).each{|l| log("#{l}") }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def print_db_table_list host, dbs
|
94
|
+
log ""
|
95
|
+
log c(host, :red)
|
96
|
+
|
97
|
+
dbs.each_with_index do |db, i|
|
98
|
+
if db.is_a?(Array)
|
99
|
+
d = c(db[0], :magenta) << c(" (#{db[1].length})", :black)
|
100
|
+
if i == dbs.count - 1
|
101
|
+
log("#{db[1].any? ? "├──" : "└──"} #{d}")
|
102
|
+
else
|
103
|
+
log("├── #{d}")
|
104
|
+
end
|
105
|
+
|
106
|
+
table = render_table([db[1].map{|r| c(r[1], :cyan) }, db[1].map{|r| c(r[0], :green) }])
|
107
|
+
table.each_with_index do |l, i2|
|
108
|
+
if i2 == table.count - 1
|
109
|
+
log("│ └── #{l}")
|
110
|
+
else
|
111
|
+
log("│ ├── #{l}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
log " #{c db, :blue}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def strbool v
|
121
|
+
v = true if ["true", "t", "1", "y", "yes", "on"].include?(v)
|
122
|
+
v = false if ["false", "f", "0", "n", "no", "off"].include?(v)
|
123
|
+
v
|
124
|
+
end
|
125
|
+
|
126
|
+
def f_percentage have, total, nn = 2
|
127
|
+
human_percentage(total == 0 ? 100 : have == 0 ? 0 : (have.to_d / total.to_d * 100.to_d), nn)
|
128
|
+
end
|
129
|
+
|
130
|
+
def human_bytes bytes
|
131
|
+
return false unless bytes
|
132
|
+
{
|
133
|
+
'B' => 1024,
|
134
|
+
'KB' => 1024 * 1024,
|
135
|
+
'MB' => 1024 * 1024 * 1024,
|
136
|
+
'GB' => 1024 * 1024 * 1024 * 1024,
|
137
|
+
'TB' => 1024 * 1024 * 1024 * 1024 * 1024
|
138
|
+
}.each_pair { |e, s| return "#{"%.2f" % (bytes.to_d / (s / 1024)).round(2)} #{e}" if bytes < s }
|
139
|
+
end
|
140
|
+
|
141
|
+
def human_number(n)
|
142
|
+
n.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
|
143
|
+
end
|
144
|
+
|
145
|
+
def human_percentage(n, nn = 2)
|
146
|
+
"%.#{nn}f%%" % n
|
147
|
+
end
|
148
|
+
|
149
|
+
def human_seconds secs, sprec = 0
|
150
|
+
t_minute = 60
|
151
|
+
t_hour = t_minute * 60
|
152
|
+
t_day = t_hour * 24
|
153
|
+
t_week = t_day * 7
|
154
|
+
t_month = t_day * 30
|
155
|
+
t_year = t_month * 12
|
156
|
+
"".tap do |r|
|
157
|
+
if secs >= t_year
|
158
|
+
r << "#{"%i" % (secs / t_year)}y "
|
159
|
+
secs = secs % t_year
|
160
|
+
end
|
161
|
+
|
162
|
+
if secs >= t_month
|
163
|
+
r << "#{"%i" % (secs / t_month)}m "
|
164
|
+
secs = secs % t_month
|
165
|
+
end
|
166
|
+
|
167
|
+
if secs >= t_week
|
168
|
+
r << "#{"%i" % (secs / t_week)}w "
|
169
|
+
secs = secs % t_week
|
170
|
+
end
|
171
|
+
|
172
|
+
if secs >= t_day || !r.blank?
|
173
|
+
r << "#{"%i" % (secs / t_day)}d "
|
174
|
+
secs = secs % t_day
|
175
|
+
end
|
176
|
+
|
177
|
+
if secs >= t_hour || !r.blank?
|
178
|
+
r << "#{"%i" % (secs / t_hour)}h "
|
179
|
+
secs = secs % t_hour
|
180
|
+
end
|
181
|
+
|
182
|
+
if secs >= t_minute || !r.blank?
|
183
|
+
r << "#{"%i" % (secs / t_minute)}m "
|
184
|
+
secs = secs % t_minute
|
185
|
+
end
|
186
|
+
|
187
|
+
r << "#{"%.#{sprec}f" % secs.round(sprec)}s" unless r.include?("d")
|
188
|
+
end.strip
|
189
|
+
end
|
190
|
+
|
191
|
+
def human_seconds2 secs
|
192
|
+
return "?:¿?:¿?" if secs.try(:infinite?)
|
193
|
+
Time.at(secs.round).utc.strftime("%k:%M:%S").strip
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class SklavenTreiber
|
4
|
+
class LogSpool
|
5
|
+
attr_reader :original, :spool
|
6
|
+
|
7
|
+
def initialize original
|
8
|
+
@original = original
|
9
|
+
@enabled = true
|
10
|
+
@spool = []
|
11
|
+
@monitor = Monitor.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def sync
|
15
|
+
@monitor.synchronize { yield }
|
16
|
+
end
|
17
|
+
|
18
|
+
def enable
|
19
|
+
sync do
|
20
|
+
@enabled = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
sync { @spool.clear }
|
26
|
+
end
|
27
|
+
|
28
|
+
def disable void_spool = false, &block
|
29
|
+
sync do
|
30
|
+
@enabled = false
|
31
|
+
(void_spool && clear) || (block && spooldown(&block))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def spooldown
|
36
|
+
sync do
|
37
|
+
while e = @spool.shift
|
38
|
+
yield(e + [original])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def puts *args
|
44
|
+
sync { @enabled ? (@spool << [:puts, args, Time.current]) : @original.send(:puts, *args) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def print *args
|
48
|
+
sync { @enabled ? (@spool << [:print, args, Time.current]) : @original.send(:print, *args) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def warn *args
|
52
|
+
sync { @enabled ? (@spool << [:warn, args, Time.current]) : @original.send(:warn, *args) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class SklavenTreiber
|
4
|
+
class Worker
|
5
|
+
module Accessors
|
6
|
+
def pending?
|
7
|
+
@state == :pending
|
8
|
+
end
|
9
|
+
|
10
|
+
def done?
|
11
|
+
succeeded? || failed? || canceled?
|
12
|
+
end
|
13
|
+
|
14
|
+
def sshing?
|
15
|
+
@sshing
|
16
|
+
end
|
17
|
+
|
18
|
+
def failed?
|
19
|
+
@state == :failed
|
20
|
+
end
|
21
|
+
|
22
|
+
def succeeded?
|
23
|
+
@state == :done
|
24
|
+
end
|
25
|
+
|
26
|
+
def canceled?
|
27
|
+
@state == :canceled
|
28
|
+
end
|
29
|
+
|
30
|
+
def running?
|
31
|
+
@state == :running
|
32
|
+
end
|
33
|
+
|
34
|
+
def paused?
|
35
|
+
@state == :paused
|
36
|
+
end
|
37
|
+
|
38
|
+
def pausing?
|
39
|
+
@state == :pausing
|
40
|
+
end
|
41
|
+
|
42
|
+
def deferred?
|
43
|
+
@deferred
|
44
|
+
end
|
45
|
+
|
46
|
+
def status
|
47
|
+
@status
|
48
|
+
end
|
49
|
+
|
50
|
+
def state
|
51
|
+
@state
|
52
|
+
end
|
53
|
+
|
54
|
+
def trxid
|
55
|
+
sklaventreiber.trxid
|
56
|
+
end
|
57
|
+
|
58
|
+
def app
|
59
|
+
sklaventreiber.app
|
60
|
+
end
|
61
|
+
|
62
|
+
def descriptive
|
63
|
+
"#{ctn.source["database"]}-#{table}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def identifier
|
67
|
+
"#{trxid}_table"
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
"#<#{self.class}:#{self.object_id}-#{descriptive}(#{@state})>"
|
72
|
+
end
|
73
|
+
|
74
|
+
def tmp_filename tmp_suffix = false
|
75
|
+
"#{ctn.tmp_path}/#{trxid}_#{ctn.source["database"]}_#{table}.dbsc#{".tmp" if tmp_suffix}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def local_tmp_path
|
79
|
+
sklaventreiber.app.core_tmp_path
|
80
|
+
end
|
81
|
+
|
82
|
+
def local_tmp_file file
|
83
|
+
"#{local_tmp_path}/#{file}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def spinner_frame
|
87
|
+
@spinner_frames.unshift(@spinner_frames.pop)[0]
|
88
|
+
end
|
89
|
+
|
90
|
+
def copy_file_destination dstfile
|
91
|
+
d, dt = Time.current.strftime("%Y-%m-%d"), Time.current.strftime("%H-%M-%S")
|
92
|
+
|
93
|
+
File.expand_path dstfile.dup
|
94
|
+
.gsub!(":combined", ":datetime_-_:table")
|
95
|
+
.gsub!(":datetime", "#{d}_#{dt}")
|
96
|
+
.gsub!(":date", d)
|
97
|
+
.gsub!(":time", dt)
|
98
|
+
.gsub!(":table", table)
|
99
|
+
.gsub!(":id", sklaventreiber.trxid)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
class SklavenTreiber
|
4
|
+
class Worker
|
5
|
+
module Core
|
6
|
+
def sync
|
7
|
+
@monitor.synchronize { yield }
|
8
|
+
end
|
9
|
+
|
10
|
+
def aquire thread
|
11
|
+
@thread = thread
|
12
|
+
thread[:current_task] = descriptive
|
13
|
+
thread[:current_worker] = self
|
14
|
+
if m = thread[:managed_worker]
|
15
|
+
debug "Consumer thread ##{m} aquired worker #{descriptive}"
|
16
|
+
else
|
17
|
+
debug "Main thread aquired worker #{descriptive}"
|
18
|
+
end
|
19
|
+
@status = ["initializing...", "gray"]
|
20
|
+
@state = :aquired
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def pause wait = false
|
25
|
+
sync do
|
26
|
+
return if done?
|
27
|
+
return if @state == :pausing || @state == :paused
|
28
|
+
@pause_data = { state_was: @state, signal: @monitor.new_cond }
|
29
|
+
if @state == :pending
|
30
|
+
@state = :paused
|
31
|
+
else
|
32
|
+
@state = :pausing
|
33
|
+
@pause_data[:signal].wait if wait
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def _pausepoint
|
39
|
+
sync do
|
40
|
+
return if !(@state == :pausing || @state == :paused)
|
41
|
+
return unless @pause_data
|
42
|
+
return unless @thread == Thread.current
|
43
|
+
@state = :paused
|
44
|
+
@pause_data[:signal].broadcast
|
45
|
+
end
|
46
|
+
@thread[:paused] = true
|
47
|
+
Thread.stop
|
48
|
+
@thread[:paused] = false
|
49
|
+
_cancelpoint
|
50
|
+
end
|
51
|
+
|
52
|
+
def unpause
|
53
|
+
sync do
|
54
|
+
return if !(@state == :pausing || @state == :paused)
|
55
|
+
return unless @pause_data
|
56
|
+
@state = @pause_data[:state_was]
|
57
|
+
@pause_data = false
|
58
|
+
@thread.wakeup if @thread
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def cancel! reason = nil, now = false
|
63
|
+
return if done?
|
64
|
+
@should_cancel = reason || true
|
65
|
+
unpause
|
66
|
+
sync { _cancelpoint(reason) if pending? || now }
|
67
|
+
end
|
68
|
+
|
69
|
+
def fail! reason, now = false
|
70
|
+
@status = ["FAILED(#{@current_perform}) #{reason}", "red"]
|
71
|
+
@state = :failed
|
72
|
+
throw :abort_execution, true if now
|
73
|
+
end
|
74
|
+
|
75
|
+
def _cancelpoint reason = nil
|
76
|
+
if @should_cancel
|
77
|
+
reason ||= @should_cancel if @should_cancel.is_a?(String)
|
78
|
+
reason ||= @status[0]
|
79
|
+
@should_cancel = false
|
80
|
+
@state = :canceled
|
81
|
+
@status = ["CANCELED#{" (was #{reason.to_s.gsub("[CLOSING] ", "")})" if reason}", "red"]
|
82
|
+
throw :abort_execution, true
|
83
|
+
true
|
84
|
+
elsif @state == :failed
|
85
|
+
throw :abort_execution, true
|
86
|
+
true
|
87
|
+
end
|
88
|
+
_pausepoint
|
89
|
+
end
|
90
|
+
|
91
|
+
def priority
|
92
|
+
100 - ({
|
93
|
+
running: 50,
|
94
|
+
aquired: 50,
|
95
|
+
pausing: 50,
|
96
|
+
paused: 30,
|
97
|
+
canceled: 35,
|
98
|
+
pending: 30,
|
99
|
+
failed: 20,
|
100
|
+
done: 10,
|
101
|
+
}[@state] || 0)
|
102
|
+
end
|
103
|
+
|
104
|
+
def run
|
105
|
+
@state = :running
|
106
|
+
@sshing = true
|
107
|
+
@started = Time.current
|
108
|
+
@download_state = { state: :idle, offset: 0 }
|
109
|
+
@remote_files_to_remove = []
|
110
|
+
@local_files_to_remove = []
|
111
|
+
@current_perform = nil
|
112
|
+
|
113
|
+
app.fire(:worker_routine_before_all, self)
|
114
|
+
catch :abort_execution do
|
115
|
+
perform.each_with_index do |m, i|
|
116
|
+
@current_perform = m
|
117
|
+
_cancelpoint
|
118
|
+
@step = i + 1
|
119
|
+
r = catch(:abort_execution) {
|
120
|
+
aquire_slots(*app.opts[:routine_pools][m.to_sym]) do
|
121
|
+
begin
|
122
|
+
r0 = Time.current
|
123
|
+
app.fire(:worker_routine_before, self, @current_perform)
|
124
|
+
send(:"_#{m}")
|
125
|
+
ensure
|
126
|
+
app.fire(:worker_routine_after, self, @current_perform)
|
127
|
+
@timings[m] = Time.current - r0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
nil
|
131
|
+
}
|
132
|
+
throw :abort_execution if r
|
133
|
+
_cancelpoint
|
134
|
+
end
|
135
|
+
@status = ["DONE (#{runtime})", "green"]
|
136
|
+
end
|
137
|
+
rescue StandardError => ex
|
138
|
+
@exception = ex
|
139
|
+
fail! "#{ex.class}: #{ex.message}"
|
140
|
+
Thread.main[:app].notify_exception("SklavenTreiber::Worker encountered an error in `#{@current_perform}' (ctn: #{ctn.name}, var: #{var.name}, db: #{ctn.source["database"]}, table: #{table})", ex)
|
141
|
+
rescue Interrupt => ex
|
142
|
+
@state = :failed
|
143
|
+
ensure
|
144
|
+
# cleanup temp files
|
145
|
+
ctn.sftp_start do |sftp|
|
146
|
+
@remote_files_to_remove.each do |file|
|
147
|
+
sftp.remove!(file) rescue false
|
148
|
+
end
|
149
|
+
end if @remote_files_to_remove.any?
|
150
|
+
|
151
|
+
# cleanup local temp files
|
152
|
+
@local_files_to_remove.each do |file|
|
153
|
+
File.unlink(file) rescue false
|
154
|
+
end
|
155
|
+
|
156
|
+
app.fire(:worker_routine_after_all, self)
|
157
|
+
@state = :done if !canceled? && !failed?
|
158
|
+
@ended = Time.current
|
159
|
+
@sshing = false
|
160
|
+
|
161
|
+
# debug timings
|
162
|
+
debug "[Timings(#{table})] all: #{human_seconds(@timings.values.sum, 3)}, #{@timings.map{|a,t| "#{a}: #{human_seconds(t, 3)}" } * ", "}", 50
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|