ppool 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 440a4893e77aede60e337fa03a33a04efd842adf
4
+ data.tar.gz: fdc7ddf70a85f7634e3ae92c2c932071b4fab269
5
+ SHA512:
6
+ metadata.gz: fac3e95edd1a6199030f6847805936c88e5a2c395489de0bb68d58ff23e888cff3057c84e137991174b549058e5eee1b57f9413807785e90f48b5baaf18351bd
7
+ data.tar.gz: cd1182ccd48acd747d4c225bd9cd8ad2b9036c9df5cd7ad11d7e3a3257de1963894a7f1da84bf1d534773876e97043c7b6a1f9b4c29145f96cccf1a15ba2733d
data/bin/ppool ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'ppool'
5
+ require 'optparse'
6
+
7
+
8
+ options = {
9
+ :logdir => "./logs",
10
+ :size => 1,
11
+ :basic => false,
12
+ }
13
+
14
+ $USAGE = "Usage: ppool [options] COMMAND ARGS..."
15
+
16
+
17
+ OptionParser.new do |opts|
18
+ opts.banner = $USAGE
19
+
20
+ opts.on('-l', '--logs DIR', 'Log directory') { |v| options[:logdir] = v }
21
+ opts.on('-s', '--size SIZE', 'Initial pool size') { |v| options[:size] = v }
22
+ opts.on('-b', '--basic', 'Basic (non curses) verion') { |v| options[:basic] = true}
23
+
24
+ opts.on("-h", "--help", "Show this message") {
25
+ puts opts
26
+ exit
27
+ }
28
+
29
+ end.parse!
30
+
31
+ command = ARGV.join(' ')
32
+ if command == ''
33
+ puts $USAGE
34
+ exit(1)
35
+ end
36
+
37
+ logdir = options[:logdir]
38
+ if ! Dir.exist?(logdir)
39
+ Dir.mkdir(logdir)
40
+ end
41
+
42
+ size = options[:size].to_i
43
+
44
+ if options[:basic]
45
+ controller = TerminalProcessController.new(size, command, logdir)
46
+ else
47
+ controller = CursesProcessController.new(size, command, logdir)
48
+ end
49
+
50
+ ProcessPool.new(controller).run
51
+
@@ -0,0 +1,51 @@
1
+ class BasicProcessController
2
+
3
+ def initialize
4
+ @time_started = Time.new.to_i
5
+ end
6
+
7
+ def running?
8
+ return true
9
+ end
10
+
11
+ def num_processes
12
+ return 1
13
+ end
14
+
15
+ def process_started(pid, num_processes)
16
+ puts "> process started #{pid}; num_processes #{num_processes}"
17
+ end
18
+
19
+ def run_process
20
+ info "#{Process.pid} running"
21
+ exit 0
22
+ end
23
+
24
+ def process_ended(pid, status)
25
+ puts "> process ended - pid #{pid}, status #{status}"
26
+ end
27
+
28
+ def progress(stats)
29
+ puts "> active #{stats[:active_processes]} started #{stats[:processes_started]} ended #{stats[:processes_ended]} errors #{stats[:errors]}"
30
+ end
31
+
32
+ def delay
33
+ return 0.1
34
+ end
35
+
36
+ def info(m)
37
+ puts "+ #{m}"
38
+ end
39
+
40
+ def time_running
41
+ secs = Time.new.to_i - @time_started
42
+ hours = (secs / (60 * 60)) % 24
43
+ mins = (secs / 60) % 60
44
+ secs = secs % 60
45
+
46
+ return "%.2d:%.2d:%.2d" % [hours, mins,secs]
47
+ end
48
+
49
+
50
+ end
51
+
@@ -0,0 +1,157 @@
1
+
2
+ require 'curses'
3
+
4
+ class CursesProcessController < ShellProcessController
5
+
6
+ def initialize(size, script, logdir)
7
+ super(script, logdir)
8
+ @finishing = false
9
+ @finished = false
10
+ @size = size
11
+ @msg = ""
12
+ @last_stats = {}
13
+ init_window
14
+ end
15
+
16
+ def running?
17
+ return !@finished
18
+ end
19
+
20
+ def num_processes
21
+ return @size
22
+ end
23
+
24
+
25
+ def process_started(pid, num_processes)
26
+ end
27
+
28
+ def process_ended(pid, status)
29
+ #draw_window
30
+ end
31
+
32
+ def progress(stats)
33
+
34
+ @last_stats = stats
35
+
36
+ draw_window
37
+
38
+ @win.attron(Curses.color_pair(3)|Curses::A_BOLD) {
39
+ @win.setpos(2 , 5)
40
+ @win.addstr("Time #{time_running}")
41
+ @win.setpos(3 , 5)
42
+ @win.addstr("Size #{@size}")
43
+ @win.setpos(4 , 5)
44
+ @win.addstr("Running #{stats[:active_processes]}")
45
+ @win.setpos(5 , 5)
46
+ @win.addstr("Started #{stats[:processes_started]}")
47
+ @win.setpos(6 , 5)
48
+ @win.addstr("Ended #{stats[:processes_ended]}")
49
+ @win.setpos(7 , 5)
50
+ @win.addstr("Errors #{stats[:errors]}")
51
+ @win.setpos(8 , 5)
52
+ @win.addstr("Logs #{@logdir}")
53
+ @win.setpos(9 , 5)
54
+ @win.addstr(" #{@msg}")
55
+ }
56
+
57
+ process_keys
58
+
59
+ if @finishing
60
+ info "finishing #{stats[:active_processes]}"
61
+ if stats[:active_processes] == 0
62
+ @finished = true
63
+ end
64
+ end
65
+ @win.refresh
66
+
67
+ end
68
+
69
+ def delay
70
+ return 0.1
71
+ end
72
+
73
+
74
+ def init_window
75
+
76
+ info "init_window"
77
+
78
+ Curses.init_screen
79
+ Curses.start_color
80
+ Curses.init_pair(1, Curses::COLOR_BLUE, Curses::COLOR_BLACK)
81
+ Curses.init_pair(2, Curses::COLOR_RED, Curses::COLOR_BLACK)
82
+ Curses.init_pair(3, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
83
+
84
+ #Curses.noecho
85
+ #Curses.cbreak
86
+ #Curses.nonl
87
+ #Curses.curs_set(0)
88
+
89
+ lines = Curses.lines
90
+ cols = Curses.cols
91
+
92
+ height = 12
93
+ width = 70
94
+
95
+ @win = Curses::Window.new(height, width, 2, 2)
96
+ #@win.keypad = true
97
+ #@win.nodelay = true
98
+
99
+ draw_window end
100
+
101
+ def draw_window
102
+ @win.clear
103
+ @win.attron(Curses.color_pair(1)|Curses::A_NORMAL) {
104
+ @win.box('|', '-')
105
+ }
106
+
107
+ end
108
+
109
+ def finished
110
+ info "finished"
111
+ Curses.close_screen
112
+
113
+ puts ""
114
+ puts "Time #{time_running}"
115
+ puts "Size #{@size}"
116
+ puts "Running #{@last_stats[:active_processes]}"
117
+ puts "Started #{@last_stats[:processes_started]}"
118
+ puts "Ended #{@last_stats[:processes_ended]}"
119
+ puts "Errors #{@last_stats[:errors]}"
120
+ puts "Logs #{@logdir}"
121
+ puts " #{@msg}"
122
+ puts ""
123
+
124
+ exit(0)
125
+ end
126
+
127
+
128
+ def process_keys
129
+
130
+ # Need to reset keyboard handling after a process
131
+ # has been forked.
132
+ Curses.noecho
133
+ Curses.cbreak
134
+ Curses.nonl
135
+ Curses.curs_set(0)
136
+ @win.keypad = true
137
+ @win.nodelay = true
138
+
139
+ case @win.getch
140
+ when '+', Curses::KEY_UP
141
+ @size = @size + 1
142
+ when '-', Curses::KEY_DOWN
143
+ @size = @size - 1
144
+ if @size < 0
145
+ @size = 0
146
+ end
147
+ when 'q', 'Q'
148
+ @size = 0
149
+ @finishing = true
150
+ when 'x', 'X'
151
+ finished
152
+ end
153
+ end
154
+
155
+
156
+ end
157
+
data/lib/ppool.rb ADDED
@@ -0,0 +1,6 @@
1
+
2
+ require "process_pool.rb"
3
+ require "basic_process_controller.rb"
4
+ require "shell_process_controller.rb"
5
+ require "terminal_process_controller.rb"
6
+ require "curses_process_controller.rb"
@@ -0,0 +1,77 @@
1
+
2
+ class ProcessPool
3
+
4
+ def initialize(controller)
5
+ @controller = controller
6
+ @active_processes = 0
7
+ @started_count = 0
8
+ @ended_count = 0
9
+ @errors = 0
10
+ end
11
+
12
+
13
+ def run
14
+
15
+ progress
16
+
17
+ while @controller.running?
18
+
19
+ num_processes = @controller.num_processes
20
+
21
+ # Create processes
22
+ while @active_processes < num_processes
23
+
24
+ @controller.info "num_proceses #{num_processes}, active_processes #{@active_processes}; forking"
25
+
26
+ pid = Process.fork do
27
+ @controller.run_process
28
+ end
29
+
30
+
31
+ @active_processes = @active_processes + 1
32
+ @controller.process_started(pid, @active_processes)
33
+ @started_count = @started_count + 1
34
+
35
+ progress
36
+ end
37
+
38
+ doneWaiting = false
39
+ while ! doneWaiting
40
+ begin
41
+ pidStatus = Process.wait2(-1, Process::WNOHANG)
42
+ if pidStatus != nil
43
+ @controller.process_ended(pidStatus[0], pidStatus[1].exitstatus)
44
+ @active_processes = @active_processes - 1
45
+ @ended_count = @ended_count + 1
46
+ if pidStatus[1].exitstatus != 0
47
+ @errors = @errors + 1
48
+ end
49
+ else
50
+ doneWaiting = true
51
+ end
52
+ rescue => e
53
+ doneWaiting = true
54
+ #@controller.info "Exception #{e}"
55
+ end
56
+ end
57
+
58
+ progress
59
+
60
+ sleep @controller.delay
61
+
62
+ end
63
+ @controller.finished
64
+
65
+ end
66
+
67
+ def progress
68
+
69
+ @controller.progress({
70
+ :active_processes => @active_processes,
71
+ :processes_started => @started_count,
72
+ :processes_ended => @ended_count,
73
+ :errors => @errors
74
+ })
75
+ end
76
+
77
+ end
@@ -0,0 +1,30 @@
1
+ class ShellProcessController < BasicProcessController
2
+
3
+ def initialize(script, logdir)
4
+ super()
5
+ @script = script
6
+ @logdir = logdir
7
+ @log = File.open("#{logdir}/ppool.log", 'w')
8
+
9
+ end
10
+
11
+ def run_process
12
+
13
+ timestamp = Time.now.strftime('%Y%m%d%H%M%S')
14
+ pid = Process.pid
15
+
16
+ stdout = "#{@logdir}/process_#{pid}_#{timestamp}.stdout"
17
+ stderr = "#{@logdir}/process_#{pid}_#{timestamp}.stderr"
18
+ stdin = "/dev/null"
19
+
20
+ info "running #{@script} output to #{stdout}"
21
+ exec("#{@script} > #{stdout} 2> #{stderr} < #{stdin}")
22
+
23
+ end
24
+
25
+ def info(m)
26
+ @log.write("#{m}\n")
27
+ @log.flush
28
+ end
29
+
30
+ end
@@ -0,0 +1,116 @@
1
+
2
+ require 'io/console'
3
+ require 'io/wait'
4
+
5
+ class TerminalProcessController < ShellProcessController
6
+
7
+ def initialize(size, script, logdir)
8
+ super(script, logdir)
9
+ @finishing = false
10
+ @finished = false
11
+ @size = size
12
+ @msg = ""
13
+ @last_stats = {}
14
+ @count = 0
15
+
16
+ Signal.trap('INT') do
17
+ finished
18
+ end
19
+
20
+ end
21
+
22
+ def running?
23
+ return !@finished
24
+ end
25
+
26
+ def num_processes
27
+ return @size
28
+ end
29
+
30
+ def process_started(pid, num_processes)
31
+ end
32
+
33
+ def process_ended(pid, status)
34
+ end
35
+
36
+ def progress(stats)
37
+
38
+ if stats != @last_stats
39
+ if @count % 20 == 0
40
+
41
+ puts "----------------------------------------------"
42
+ puts " Time | Size Active Started Ended Errors"
43
+ puts "=============================================="
44
+
45
+ end
46
+ puts(" %s | %4d %4d %4d %4d %4d\n" % [time_running, @size, stats[:active_processes], stats[:processes_started], stats[:processes_ended], stats[:errors]])
47
+ @last_stats = stats
48
+ @count = @count + 1
49
+ end
50
+
51
+ process_keys
52
+
53
+ if @finishing
54
+ info "finishing #{stats[:active_processes]}"
55
+ if stats[:active_processes] == 0
56
+ @finished = true
57
+ end
58
+ end
59
+
60
+
61
+ end
62
+
63
+ def delay
64
+ return 0.1
65
+ end
66
+
67
+
68
+ def process_keys
69
+
70
+ case read_ch
71
+ when '+'
72
+ @size = @size + 1
73
+ @last_stats = {}
74
+ puts ""
75
+ when '-'
76
+ @size = @size - 1
77
+ if @size < 0
78
+ @size = 0
79
+ end
80
+ @last_stats = {}
81
+ puts ""
82
+ when 'q', 'Q'
83
+ @size = 0
84
+ @finishing = true
85
+ @last_stats = {}
86
+ puts ""
87
+ when 'x', 'X'
88
+ finished
89
+ end
90
+
91
+ end
92
+
93
+ def read_ch
94
+ begin
95
+ system("stty raw")
96
+ if $stdin.ready?
97
+ c = $stdin.getc
98
+ return c.chr
99
+ end
100
+ ensure
101
+ system("stty -raw")
102
+ end
103
+ return nil
104
+ end
105
+
106
+
107
+ def finished
108
+ system("stty -raw")
109
+ puts ""
110
+ exit(0)
111
+ end
112
+
113
+
114
+
115
+ end
116
+
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ppool
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description: Run a pool of processes
28
+ email: pftylr@gmail.com
29
+ executables:
30
+ - ppool
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/ppool
35
+ - lib/basic_process_controller.rb
36
+ - lib/curses_process_controller.rb
37
+ - lib/ppool.rb
38
+ - lib/process_pool.rb
39
+ - lib/shell_process_controller.rb
40
+ - lib/terminal_process_controller.rb
41
+ homepage: http://rubygems.org/gems/ppool
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.5.2
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Process pool
65
+ test_files: []