scout-gear 5.2.0 → 6.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c39189bc9fe8816a3c0494c78966486f28383e8e15bf38ab33fe4dcde6739f3c
4
- data.tar.gz: 5f2753e6810a68be44589f9392de1eff2f8592dd2704b05a0ccdf866f07f6a1b
3
+ metadata.gz: 919678993bd756d6a67712023120e562c01a69942faf8ad23eb088f29559b1fa
4
+ data.tar.gz: a233c808dc9af64381196bce97c74727c9064c9b1a4c2a129b364a5d776b01e1
5
5
  SHA512:
6
- metadata.gz: 820d17306f468e7b206672b717489f78eb6fbfa37c843441202ce124c54cb7b84b89062a951c14a2db65ada47faf073dbc6a1b863294388ce66a8c1f7dd36e9f
7
- data.tar.gz: c0fe23608355128c0e709db78c5598c5ece26e885eaa8788872878b7d55e29fc50bcb8215d3b4080f9e8d89456630e2739b087ee7d8b4b57498b299df3dc89d2
6
+ metadata.gz: 842323af61a5c66c7bf4433cbc0d23e6484e562c88601c64ac7a47ea2c1e217e6f7b590bd7d613d62614ebb2ab228246a2ac150d4d263f9425d9c1a2b4333e65
7
+ data.tar.gz: 2a4a7d00279d0cbc3153d7b116405fb5d02f8656249b4e0bdf417d01599749686242fb30db183b2baff856e92dcd5c83317161e73bb5bb1dbc861ce10d0086de
data/.vimproject CHANGED
@@ -103,6 +103,12 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
103
103
  usage.rb
104
104
  util.rb
105
105
  }
106
+ semaphore.rb
107
+ work_queue.rb
108
+ work_queue=work_queue{
109
+ socket.rb
110
+ worker.rb
111
+ }
106
112
  }
107
113
  }
108
114
  test=test {
@@ -224,16 +230,16 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
224
230
  concurrency=concurrency{
225
231
  processes.rb
226
232
  threads.rb
227
- processes=processes{
228
- socket.rb
229
- worker.rb
230
- }
231
- }
233
+ processes=processes{
234
+ socket.rb
235
+ worker.rb
236
+ }
237
+ }
232
238
 
233
- procpath.rb
239
+ procpath.rb
234
240
 
235
- migrate.rb
236
- }
241
+ migrate.rb
242
+ }
237
243
 
238
244
  monitor.rb
239
245
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.2.0
1
+ 6.0.0
@@ -71,8 +71,20 @@ end
71
71
 
72
72
  class LockInterrupted < TryAgain; end
73
73
 
74
- #
75
- #class ClosedStream < StandardError; end
74
+ class ClosedStream < StandardError; end
75
+
76
+ class DoneProcessing < Exception
77
+ attr_accessor :pid
78
+ def initialize(pid = Process.pid)
79
+ @pid = pid
80
+ end
81
+
82
+ def message
83
+ "Done processing pid #{pid}"
84
+ end
85
+ end
86
+
87
+
76
88
  #class OpenGzipError < StandardError; end
77
89
  #
78
90
  #
@@ -2,6 +2,7 @@ require_relative 'color_class'
2
2
  require_relative '../indiferent_hash'
3
3
 
4
4
  require 'term/ansicolor'
5
+ require 'colorist'
5
6
 
6
7
  module Colorize
7
8
  def self.colors=(colors)
@@ -9,7 +10,13 @@ module Colorize
9
10
  end
10
11
 
11
12
  def self.colors
12
- @colors ||= IndiferentHash.setup({green: "#00cd00" , red: "#cd0000" , yellow: "#ffd700" })
13
+ @colors ||= IndiferentHash.setup(Hash[<<-EOF.split("\n").collect{|l| l.split(" ")}])
14
+ green #00cd00
15
+ red #cd0000
16
+ yellow #ffd700
17
+ blue #0000cd
18
+ path blue
19
+ EOF
13
20
  end
14
21
 
15
22
  def self.diverging_colors=(colors)
@@ -33,7 +40,6 @@ module Colorize
33
40
  EOF
34
41
  end
35
42
 
36
-
37
43
  def self.from_name(color)
38
44
  return color if color =~ /^#?[0-9A-F]+$/i
39
45
  return colors[color.to_s] if colors.include?(color.to_s)
@@ -52,7 +58,7 @@ module Colorize
52
58
  when 'blue'
53
59
  colors["RoyalBlue"]
54
60
  else
55
- colors[color.to_s] || color
61
+ colors[color.to_s] || color.to_s
56
62
  end
57
63
  end
58
64
 
@@ -134,8 +140,21 @@ module Log
134
140
  WHITE, DARK, GREEN, YELLOW, RED = Color::SOLARIZED.values_at :base0, :base00, :green, :yellow, :magenta
135
141
 
136
142
  SEVERITY_COLOR = [reset, cyan, green, magenta, blue, yellow, red] #.collect{|e| "\033[#{e}"}
143
+ CONCEPT_COLORS = IndiferentHash.setup({
144
+ :title => magenta,
145
+ :path => blue,
146
+ :input => blue,
147
+ :value => green,
148
+ :integer => green,
149
+ :negative => red,
150
+ :float => green,
151
+ :waiting => yellow,
152
+ :started => blue,
153
+ :done => green,
154
+ :error => red,
155
+ })
137
156
  HIGHLIGHT = "\033[1m"
138
-
157
+
139
158
  def self.uncolor(str)
140
159
  "" << Term::ANSIColor.uncolor(str)
141
160
  end
@@ -144,15 +163,20 @@ module Log
144
163
  reset
145
164
  end
146
165
 
147
- def self.color(severity, str = nil, reset = false)
166
+ def self.color(color, str = nil, reset = false)
148
167
  return str.dup || "" if nocolor
149
- color = reset ? Term::ANSIColor.reset : ""
150
- color << SEVERITY_COLOR[severity] if Integer === severity
151
- color << Term::ANSIColor.send(severity) if Symbol === severity and Term::ANSIColor.respond_to? severity
168
+
169
+ color = SEVERITY_COLOR[color] if Integer === color
170
+ color = CONCEPT_COLORS[color] if CONCEPT_COLORS.include?(color)
171
+ color = Term::ANSIColor.send(color) if Symbol === color and Term::ANSIColor.respond_to?(color)
172
+
173
+ return str if Symbol === color
174
+ color_str = reset ? Term::ANSIColor.reset : ""
175
+ color_str << color
152
176
  if str.nil?
153
- color
177
+ color_str
154
178
  else
155
- color + str.to_s + self.color(0)
179
+ color_str + str.to_s + Term::ANSIColor.reset
156
180
  end
157
181
  end
158
182
 
@@ -195,15 +195,16 @@ module Log
195
195
  def done(io = STDERR)
196
196
  done_msg = Log.color(:magenta, "· ") << Log.color(:green, "done")
197
197
  if @start
198
- ellapsed = (Time.now - @start).to_i
198
+ ellapsed = (Time.now - @start)
199
199
  else
200
200
  ellapsed = 0
201
201
  end
202
- ellapsed = [ellapsed/3600, ellapsed/60 % 60, ellapsed % 60].map{|t| "%02i" % t }.join(':')
203
- done_msg << " " << Log.color(:blue, (@ticks).to_s) << " #{bytes ? 'bytes' : 'items'} in " << Log.color(:green, ellapsed)
202
+ ellapsed_str = [ellapsed/3600, ellapsed/60 % 60, ellapsed % 60].map{|t| "%02i" % t }.join(':')
203
+ done_msg << " " << Log.color(:blue, (@ticks).to_s) << " #{bytes ? 'bytes' : 'items'} in " << Log.color(:green, ellapsed_str)
204
204
  @last_count = 0
205
205
  @last_time = @start
206
- done_msg << " - " << thr_msg
206
+ thr = ellapsed > 0 ? (@ticks / ellapsed).to_i.to_s : 0
207
+ done_msg << " - " << Log.color(:blue, thr) << " per second"
207
208
  done_msg << Log.color(:magenta, " · " << desc)
208
209
  print(io, Log.up_lines(@depth) << done_msg << Log.down_lines(@depth))
209
210
 
@@ -20,4 +20,22 @@ module Misc
20
20
  end
21
21
  res
22
22
  end
23
+
24
+ def self.profile(options = {})
25
+ require 'ruby-prof'
26
+ profiler = RubyProf::Profile.new
27
+ profiler.start
28
+ begin
29
+ res = yield
30
+ rescue Exception
31
+ puts "Profiling aborted"
32
+ raise $!
33
+ ensure
34
+ result = profiler.stop
35
+ printer = RubyProf::FlatPrinter.new(result)
36
+ printer.print(STDOUT, options)
37
+ end
38
+
39
+ res
40
+ end
23
41
  end
@@ -370,4 +370,34 @@ module Open
370
370
  def self.tee_stream(stream)
371
371
  tee_stream_thread(stream)
372
372
  end
373
+
374
+ def self.read_stream(stream, size)
375
+ str = nil
376
+ Thread.pass while IO.select([stream],nil,nil,1).nil?
377
+ while not str = stream.read(size)
378
+ IO.select([stream],nil,nil,1)
379
+ Thread.pass
380
+ raise ClosedStream if stream.eof?
381
+ end
382
+
383
+ while str.length < size
384
+ raise ClosedStream if stream.eof?
385
+ IO.select([stream],nil,nil,1)
386
+ if new = stream.read(size-str.length)
387
+ str << new
388
+ end
389
+ end
390
+ str
391
+ end
392
+
393
+ def self.read_stream(stream, size)
394
+ str = ""
395
+ while str.length < size
396
+ missing = size - str.length
397
+ more = stream.read(missing)
398
+ str << more
399
+ end
400
+ str
401
+ end
402
+
373
403
  end
@@ -0,0 +1,148 @@
1
+ begin
2
+ require 'inline'
3
+ continue = true
4
+ rescue Exception
5
+ Log.warn "The RubyInline gem could not be loaded: semaphore synchronization will not work"
6
+ continue = false
7
+ end
8
+
9
+ if continue
10
+ module ScoutSemaphore
11
+ inline(:C) do |builder|
12
+ builder.prefix <<-EOF
13
+ #include <unistd.h>
14
+ #include <stdio.h>
15
+ #include <stdlib.h>
16
+ #include <semaphore.h>
17
+ #include <time.h>
18
+ #include <assert.h>
19
+ #include <errno.h>
20
+ #include <signal.h>
21
+ #include <fcntl.h>
22
+ EOF
23
+
24
+ builder.c_singleton <<-EOF
25
+ void create_semaphore(char* name, int value){
26
+ sem_open(name, O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO, value);
27
+ }
28
+ EOF
29
+ builder.c_singleton <<-EOF
30
+ void delete_semaphore(char* name){
31
+ sem_unlink(name);
32
+ }
33
+ EOF
34
+
35
+ builder.c_singleton <<-EOF
36
+ int wait_semaphore(char* name){
37
+ int ret;
38
+ sem_t* sem;
39
+ sem = sem_open(name, 0);
40
+ ret = sem_wait(sem);
41
+ sem_close(sem);
42
+ return(ret);
43
+ }
44
+ EOF
45
+
46
+ builder.c_singleton <<-EOF
47
+ void post_semaphore(char* name){
48
+ sem_t* sem;
49
+ sem = sem_open(name, 0);
50
+ sem_post(sem);
51
+ sem_close(sem);
52
+ }
53
+ EOF
54
+ end
55
+
56
+ SEM_MUTEX = Mutex.new
57
+ def self.synchronize(sem)
58
+ ret = ScoutSemaphore.wait_semaphore(sem)
59
+ raise SemaphoreInterrupted if ret == -1
60
+ begin
61
+ yield
62
+ ensure
63
+ ScoutSemaphore.post_semaphore(sem)
64
+ end
65
+ end
66
+
67
+ def self.with_semaphore(size, file = nil)
68
+ if file.nil?
69
+ file = "/" << Misc.digest(rand(1000000000000).to_s) if file.nil?
70
+ else
71
+ file = file.gsub('/', '_') if file
72
+ end
73
+
74
+ begin
75
+ Log.debug "Creating semaphore (#{ size }): #{file}"
76
+ ScoutSemaphore.create_semaphore(file, size)
77
+ yield file
78
+ ensure
79
+ Log.debug "Removing semaphore #{ file }"
80
+ ScoutSemaphore.delete_semaphore(file)
81
+ end
82
+ end
83
+
84
+ def self.fork_each_on_semaphore(elems, size, file = nil)
85
+
86
+ TSV.traverse elems, :cpus => size, :bar => "Fork each on semaphore: #{ Misc.fingerprint elems }", :into => Set.new do |elem|
87
+ elems.annotate elem if elems.respond_to? :annotate
88
+ begin
89
+ yield elem
90
+ rescue Interrupt
91
+ Log.warn "Process #{Process.pid} was aborted"
92
+ end
93
+ nil
94
+ end
95
+ nil
96
+ end
97
+
98
+ def self.thread_each_on_semaphore(elems, size)
99
+ mutex = Mutex.new
100
+ count = 0
101
+ cv = ConditionVariable.new
102
+ wait_mutex = Mutex.new
103
+
104
+ begin
105
+
106
+ threads = []
107
+ wait_mutex.synchronize do
108
+ threads = elems.collect do |elem|
109
+ Thread.new(elem) do |elem|
110
+
111
+ continue = false
112
+ mutex.synchronize do
113
+ while not continue do
114
+ if count < size
115
+ continue = true
116
+ count += 1
117
+ end
118
+ mutex.sleep 1 unless continue
119
+ end
120
+ end
121
+
122
+ begin
123
+ yield elem
124
+ rescue Interrupt
125
+ Log.error "Thread was aborted while processing: #{Misc.fingerprint elem}"
126
+ raise $!
127
+ ensure
128
+ mutex.synchronize do
129
+ count -= 1
130
+ cv.signal if mutex.locked?
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ threads.each do |thread|
138
+ thread.join
139
+ end
140
+ rescue Exception
141
+ Log.exception $!
142
+ Log.info "Ensuring threads are dead: #{threads.length}"
143
+ threads.each do |thread| thread.kill end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
@@ -0,0 +1,119 @@
1
+ require 'scout/open'
2
+ require 'scout/semaphore'
3
+ require 'scout/exceptions'
4
+ class WorkQueue
5
+ class Socket
6
+ attr_accessor :sread, :swrite, :write_sem, :read_sem, :cleaned
7
+ def initialize(serializer = nil)
8
+ @sread, @swrite = Open.pipe
9
+
10
+ @serializer = serializer || Marshal
11
+
12
+ @key = "/" << rand(1000000000).to_s << '.' << Process.pid.to_s;
13
+ @write_sem = @key + '.in'
14
+ @read_sem = @key + '.out'
15
+ Log.debug "Creating socket semaphores: #{@key}"
16
+ ScoutSemaphore.create_semaphore(@write_sem,1)
17
+ ScoutSemaphore.create_semaphore(@read_sem,1)
18
+ end
19
+
20
+ def clean
21
+ @cleaned = true
22
+ @sread.close unless @sread.closed?
23
+ @swrite.close unless @swrite.closed?
24
+ Log.low "Destroying socket semaphores: #{[@key] * ", "}"
25
+ ScoutSemaphore.delete_semaphore(@write_sem)
26
+ ScoutSemaphore.delete_semaphore(@read_sem)
27
+ end
28
+
29
+
30
+ def dump(obj)
31
+ stream = @swrite
32
+ obj.concurrent_stream = nil if obj.respond_to?(:concurrent_stream)
33
+ case obj
34
+ when Integer
35
+ size_head = [obj,"I"].pack 'La'
36
+ str = size_head
37
+ when nil
38
+ size_head = [0,"N"].pack 'La'
39
+ str = size_head
40
+ when String
41
+ payload = obj
42
+ size_head = [payload.bytesize,"C"].pack 'La'
43
+ str = size_head << payload
44
+ else
45
+ payload = @serializer.dump(obj)
46
+ size_head = [payload.bytesize,"S"].pack 'La'
47
+ str = size_head << payload
48
+ end
49
+
50
+ write_length = str.length
51
+ wrote = stream.write(str)
52
+ while wrote < write_length
53
+ wrote += stream.write(str[wrote..-1])
54
+ end
55
+ end
56
+
57
+ def load
58
+ stream = @sread
59
+ size_head = Open.read_stream stream, 5
60
+
61
+ size, type = size_head.unpack('La')
62
+
63
+ return nil if type == "N"
64
+ return size.to_i if type == "I"
65
+ begin
66
+ payload = Open.read_stream stream, size
67
+ case type
68
+ when "S"
69
+ begin
70
+ @serializer.load(payload)
71
+ rescue Exception
72
+ Log.exception $!
73
+ raise $!
74
+ end
75
+ when "C"
76
+ payload
77
+ end
78
+ rescue TryAgain
79
+ retry
80
+ end
81
+ end
82
+
83
+ def closed_read?
84
+ @sread.closed?
85
+ end
86
+
87
+ def closed_write?
88
+ @swrite.closed?
89
+ end
90
+
91
+ def close_write
92
+ self.dump ClosedStream.new
93
+ @swrite.close unless closed_write?
94
+ end
95
+
96
+ def close_read
97
+ @sread.close unless closed_read?
98
+ end
99
+
100
+ #{{{ ACCESSOR
101
+ def push(obj)
102
+ ScoutSemaphore.synchronize(@write_sem) do
103
+ self.dump(obj)
104
+ end
105
+ end
106
+
107
+ def pop
108
+ ScoutSemaphore.synchronize(@read_sem) do
109
+ res = self.load
110
+ raise res if ClosedStream === res
111
+ res
112
+ end
113
+ end
114
+
115
+ alias write push
116
+
117
+ alias read pop
118
+ end
119
+ end
@@ -0,0 +1,54 @@
1
+ class WorkQueue
2
+ class Worker
3
+ attr_accessor :pid, :ignore_ouput
4
+ def initialize
5
+ end
6
+
7
+ def run
8
+ @pid = Process.fork do
9
+ yield
10
+ end
11
+ end
12
+
13
+ def process(input, output, &block)
14
+ run do
15
+ begin
16
+ while obj = input.read
17
+ if DoneProcessing === obj
18
+ output.write DoneProcessing.new
19
+ raise obj
20
+ end
21
+ res = block.call obj
22
+ output.write res unless ignore_ouput || res == :ignore
23
+ end
24
+ rescue DoneProcessing
25
+ Log.log "Worker #{Process.pid} done"
26
+ rescue Exception
27
+ Log.exception $!
28
+ exit -1
29
+ end
30
+ end
31
+ end
32
+
33
+ def join
34
+ Log.log "Joining worker #{@pid}"
35
+ Process.waitpid @pid
36
+ end
37
+
38
+ def exit(status)
39
+ Log.log "Worker #{@pid} exited with status #{Log.color(:green, status)}"
40
+ end
41
+
42
+ def self.join(workers)
43
+ workers = [workers] unless Array === workers
44
+ begin
45
+ while pid = Process.wait
46
+ status = $?
47
+ worker = workers.select{|w| w.pid == pid }.first
48
+ worker.exit status.exitstatus if worker
49
+ end
50
+ rescue Errno::ECHILD
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,86 @@
1
+ require_relative 'work_queue/socket'
2
+ require_relative 'work_queue/worker'
3
+
4
+ class WorkQueue
5
+ attr_accessor :workers, :worker_proc, :callback
6
+
7
+ def initialize(workers = 0, &block)
8
+ @input = WorkQueue::Socket.new
9
+ @output = WorkQueue::Socket.new
10
+ @workers = workers.times.collect{ Worker.new }
11
+ @worker_proc = block
12
+ @worker_mutex = Mutex.new
13
+ @removed_workers = []
14
+ end
15
+
16
+ def add_worker(&block)
17
+ worker = Worker.new
18
+ @worker_mutex.synchronize do
19
+ @workers.push(worker)
20
+ if block_given?
21
+ worker.process @input, @output, &block
22
+ else
23
+ worker.process @input, @output, &@worker_proc
24
+ end
25
+ end
26
+ worker
27
+ end
28
+
29
+ def ignore_ouput
30
+ @workers.each{|w| w.ignore_ouput = true }
31
+ end
32
+
33
+ def remove_one_worker
34
+ @input.write DoneProcessing.new
35
+ end
36
+
37
+ def remove_worker(pid)
38
+ worker = @worker_mutex.synchronize do
39
+ Log.debug "Remove #{pid}"
40
+ @removed_workers.concat(@workers.delete_if{|w| w.pid == pid })
41
+ end
42
+ end
43
+
44
+ def process(&callback)
45
+ @workers.each do |w|
46
+ w.process @input, @output, &@worker_proc
47
+ end
48
+ @reader = Thread.new do
49
+ begin
50
+ while true
51
+ obj = @output.read
52
+ if DoneProcessing === obj
53
+ remove_worker obj.pid if obj.pid
54
+ else
55
+ callback.call obj if callback
56
+ end
57
+ end
58
+ rescue Aborted
59
+ end
60
+ end if @output
61
+ end
62
+
63
+ def write(obj)
64
+ @input.write obj
65
+ end
66
+
67
+ def close
68
+ while @worker_mutex.synchronize{ @workers.length } > 0
69
+ begin
70
+ @input.write DoneProcessing.new
71
+ pid = Process.wait
72
+ status = $?
73
+ worker = @worker_mutex.synchronize{ @removed_workers.delete_if{|w| w.pid == pid }.first }
74
+ worker.exit $?.exitstatus if worker
75
+ rescue Errno::ECHILD
76
+ Thread.pass until @workers.length == 0
77
+ break
78
+ end
79
+ end
80
+ @reader.raise Aborted if @reader
81
+ end
82
+
83
+ def join
84
+ @reader.join if @reader
85
+ end
86
+ end
@@ -55,9 +55,9 @@ class Step
55
55
 
56
56
  def report_status(status, message = nil)
57
57
  if message.nil?
58
- Log.info Log.color(:green, status.to_s) + " " + Log.color(:blue, path)
58
+ Log.info Log.color(status, status.to_s) + " " + Log.color(:path, path)
59
59
  else
60
- Log.info Log.color(:green, status.to_s) + " " + Log.color(:blue, path) + " " + message
60
+ Log.info Log.color(status, status.to_s) + " " + Log.color(:path, path) + " " + message
61
61
  end
62
62
  end
63
63