fasten 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/Gemfile.lock +11 -9
- data/exe/fasten +5 -0
- data/fasten.gemspec +4 -2
- data/lib/fasten.rb +86 -31
- data/lib/fasten/{executor.rb → runner.rb} +102 -28
- data/lib/fasten/std_thread_proxy.rb +56 -0
- data/lib/fasten/support/dag.rb +130 -0
- data/lib/fasten/support/fork_worker.rb +107 -0
- data/lib/fasten/support/logger.rb +57 -0
- data/lib/fasten/support/state.rb +32 -0
- data/lib/fasten/support/stats.rb +137 -0
- data/lib/fasten/support/thread_worker.rb +61 -0
- data/lib/fasten/support/ui.rb +22 -0
- data/lib/fasten/support/yaml.rb +54 -0
- data/lib/fasten/task.rb +16 -7
- data/lib/fasten/timeout_queue.rb +36 -0
- data/lib/fasten/ui/console.rb +7 -7
- data/lib/fasten/ui/curses.rb +26 -22
- data/lib/fasten/version.rb +1 -1
- data/lib/fasten/worker.rb +48 -81
- metadata +34 -14
- data/lib/fasten/dag.rb +0 -137
- data/lib/fasten/logger.rb +0 -62
- data/lib/fasten/state.rb +0 -30
- data/lib/fasten/stats.rb +0 -137
- data/lib/fasten/ui.rb +0 -18
- data/lib/fasten/yaml.rb +0 -48
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fasten
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aldrin Martoq
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.17.1
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.17.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: curses
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: pry
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,7 +109,7 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: hirb
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - ">="
|
@@ -109,7 +123,7 @@ dependencies:
|
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
126
|
+
name: os
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - ">="
|
@@ -139,7 +153,8 @@ dependencies:
|
|
139
153
|
description: Fasten your seatbelts! Run jobs in parallel, intelligently.
|
140
154
|
email:
|
141
155
|
- contacto@a0.cl
|
142
|
-
executables:
|
156
|
+
executables:
|
157
|
+
- fasten
|
143
158
|
extensions: []
|
144
159
|
extra_rdoc_files: []
|
145
160
|
files:
|
@@ -157,20 +172,25 @@ files:
|
|
157
172
|
- Rakefile
|
158
173
|
- bin/console
|
159
174
|
- bin/setup
|
175
|
+
- exe/fasten
|
160
176
|
- fasten.gemspec
|
161
177
|
- lib/fasten.rb
|
162
|
-
- lib/fasten/
|
163
|
-
- lib/fasten/
|
164
|
-
- lib/fasten/
|
165
|
-
- lib/fasten/
|
166
|
-
- lib/fasten/
|
178
|
+
- lib/fasten/runner.rb
|
179
|
+
- lib/fasten/std_thread_proxy.rb
|
180
|
+
- lib/fasten/support/dag.rb
|
181
|
+
- lib/fasten/support/fork_worker.rb
|
182
|
+
- lib/fasten/support/logger.rb
|
183
|
+
- lib/fasten/support/state.rb
|
184
|
+
- lib/fasten/support/stats.rb
|
185
|
+
- lib/fasten/support/thread_worker.rb
|
186
|
+
- lib/fasten/support/ui.rb
|
187
|
+
- lib/fasten/support/yaml.rb
|
167
188
|
- lib/fasten/task.rb
|
168
|
-
- lib/fasten/
|
189
|
+
- lib/fasten/timeout_queue.rb
|
169
190
|
- lib/fasten/ui/console.rb
|
170
191
|
- lib/fasten/ui/curses.rb
|
171
192
|
- lib/fasten/version.rb
|
172
193
|
- lib/fasten/worker.rb
|
173
|
-
- lib/fasten/yaml.rb
|
174
194
|
homepage: https://github.com/a0/fasten/
|
175
195
|
licenses:
|
176
196
|
- MIT
|
data/lib/fasten/dag.rb
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
module Fasten
|
2
|
-
module DAG
|
3
|
-
attr_reader :task_map, :task_list, :task_done_list, :task_error_list, :task_pending_list, :task_running_list
|
4
|
-
|
5
|
-
def initialize_dag
|
6
|
-
@task_map = {}
|
7
|
-
@task_list = []
|
8
|
-
@task_done_list = []
|
9
|
-
@task_error_list = []
|
10
|
-
@task_pending_list = []
|
11
|
-
@task_running_list = []
|
12
|
-
end
|
13
|
-
|
14
|
-
def add(task)
|
15
|
-
raise "Task '#{task.name}' already defined" if @task_map[task.name]
|
16
|
-
|
17
|
-
@task_map[task.name] = task
|
18
|
-
@task_list << task
|
19
|
-
@task_waiting_list = nil
|
20
|
-
end
|
21
|
-
|
22
|
-
def update_task(task)
|
23
|
-
if task.state == :DONE
|
24
|
-
update_done_task task
|
25
|
-
else
|
26
|
-
update_error_task task
|
27
|
-
end
|
28
|
-
|
29
|
-
stats_add_entry(task.state, task)
|
30
|
-
end
|
31
|
-
|
32
|
-
def update_done_task(task)
|
33
|
-
@task_done_list << task
|
34
|
-
@task_pending_list.delete task
|
35
|
-
task.dependants.each do |dependant_task|
|
36
|
-
dependant_task.depends.delete task
|
37
|
-
end
|
38
|
-
|
39
|
-
move_pending_to_waiting
|
40
|
-
end
|
41
|
-
|
42
|
-
def update_error_task(task)
|
43
|
-
@task_error_list << task
|
44
|
-
@task_pending_list.delete task
|
45
|
-
end
|
46
|
-
|
47
|
-
def next_task
|
48
|
-
task_waiting_list.shift
|
49
|
-
end
|
50
|
-
|
51
|
-
def task_waiting_list
|
52
|
-
return @task_waiting_list if @task_waiting_list
|
53
|
-
|
54
|
-
reset_tasks
|
55
|
-
setup_tasks_dependencies
|
56
|
-
setup_tasks_scores
|
57
|
-
move_pending_to_waiting
|
58
|
-
end
|
59
|
-
|
60
|
-
protected
|
61
|
-
|
62
|
-
def move_pending_to_waiting
|
63
|
-
move_list = task_pending_list.select do |task|
|
64
|
-
task.depends.count.zero?
|
65
|
-
end
|
66
|
-
|
67
|
-
@task_waiting_list ||= []
|
68
|
-
@task_pending_list -= move_list
|
69
|
-
@task_waiting_list += move_list
|
70
|
-
@task_waiting_list.sort_by!.with_index do |x, index|
|
71
|
-
x.state = :WAIT
|
72
|
-
[-x.run_score, index]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def reset_tasks
|
77
|
-
@task_pending_list.clear
|
78
|
-
@task_done_list.clear
|
79
|
-
@task_error_list.clear
|
80
|
-
|
81
|
-
@task_list.each do |task|
|
82
|
-
task.dependants = []
|
83
|
-
task.depends = []
|
84
|
-
task.run_score = 0
|
85
|
-
|
86
|
-
if task.state == :DONE
|
87
|
-
@task_done_list << task
|
88
|
-
elsif task.state == :FAIL
|
89
|
-
@task_error_list << task
|
90
|
-
else
|
91
|
-
task.state = :IDLE
|
92
|
-
@task_pending_list << task
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def setup_tasks_dependencies
|
98
|
-
@task_pending_list.each do |task|
|
99
|
-
next unless task.after
|
100
|
-
|
101
|
-
[task.after].flatten.each do |after|
|
102
|
-
after_task = after.is_a?(Task) ? after : @task_map[after]
|
103
|
-
raise "Dependency task '#{after}' not found on task '#{task.name}'." unless after_task
|
104
|
-
|
105
|
-
task.depends << after_task
|
106
|
-
after_task.dependants << task
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def setup_tasks_scores
|
112
|
-
@task_pending_list.each do |task|
|
113
|
-
task.run_score += task.dependants.count
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def no_waiting_tasks?
|
118
|
-
task_waiting_list.empty?
|
119
|
-
end
|
120
|
-
|
121
|
-
def no_running_tasks?
|
122
|
-
task_running_list.empty?
|
123
|
-
end
|
124
|
-
|
125
|
-
def tasks_waiting?
|
126
|
-
!task_waiting_list.empty?
|
127
|
-
end
|
128
|
-
|
129
|
-
def tasks_running?
|
130
|
-
!task_running_list.empty?
|
131
|
-
end
|
132
|
-
|
133
|
-
def tasks_failed?
|
134
|
-
!task_error_list.empty?
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
data/lib/fasten/logger.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
module Fasten
|
2
|
-
class << self
|
3
|
-
attr_accessor :logger
|
4
|
-
end
|
5
|
-
|
6
|
-
module Logger
|
7
|
-
attr_accessor :log_file
|
8
|
-
|
9
|
-
%w[debug info error].each do |method|
|
10
|
-
define_method "log_#{method}" do |msg|
|
11
|
-
return unless Fasten.logger.respond_to?(method)
|
12
|
-
|
13
|
-
caller_name = name if respond_to? :name
|
14
|
-
caller_name ||= Kernel.binding.of_caller(1).eval('self').class
|
15
|
-
Fasten.logger.send(method, caller_name) { msg }
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def initialize_logger
|
20
|
-
log_path = "#{fasten_dir}/log/executor/#{name}.log"
|
21
|
-
FileUtils.mkdir_p File.dirname(log_path)
|
22
|
-
self.log_file = File.new(log_path, 'a')
|
23
|
-
Fasten.logger.reopen log_file
|
24
|
-
end
|
25
|
-
|
26
|
-
def log_ini(object, message = nil)
|
27
|
-
object.ini ||= Time.new
|
28
|
-
log_info "Ini #{object.state} #{object.class} #{object} #{message}"
|
29
|
-
end
|
30
|
-
|
31
|
-
def log_fin(object, message = nil)
|
32
|
-
object.fin ||= Time.new
|
33
|
-
object.dif = object.fin - object.ini
|
34
|
-
|
35
|
-
log_info "Fin #{object.state} #{object.class} #{object} #{message} in #{object.dif}"
|
36
|
-
end
|
37
|
-
|
38
|
-
def redirect_std(path)
|
39
|
-
@saved_stdout = $stdout.clone
|
40
|
-
@saved_stderr = $stderr.clone
|
41
|
-
|
42
|
-
FileUtils.mkdir_p File.dirname(path)
|
43
|
-
log = File.new path, 'a'
|
44
|
-
log.sync = true
|
45
|
-
|
46
|
-
$stdout.reopen log
|
47
|
-
$stderr.reopen log
|
48
|
-
end
|
49
|
-
|
50
|
-
def restore_std
|
51
|
-
$stdout.reopen(@saved_stdout)
|
52
|
-
$stderr.reopen(@saved_stderr)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
Fasten.logger ||=
|
58
|
-
begin
|
59
|
-
Logger.new(STDOUT, level: Logger::DEBUG, progname: $PROGRAM_NAME)
|
60
|
-
rescue StandardError
|
61
|
-
nil
|
62
|
-
end
|
data/lib/fasten/state.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
module Fasten
|
2
|
-
module State
|
3
|
-
attr_accessor :error, :ini, :fin, :dif, :last
|
4
|
-
attr_writer :state
|
5
|
-
|
6
|
-
def state
|
7
|
-
@state || :IDLE
|
8
|
-
end
|
9
|
-
|
10
|
-
def running?
|
11
|
-
state == :RUNNING
|
12
|
-
end
|
13
|
-
|
14
|
-
def idle?
|
15
|
-
state == :IDLE
|
16
|
-
end
|
17
|
-
|
18
|
-
def pausing?
|
19
|
-
state == :PAUSING
|
20
|
-
end
|
21
|
-
|
22
|
-
def paused?
|
23
|
-
state == :PAUSED
|
24
|
-
end
|
25
|
-
|
26
|
-
def quitting?
|
27
|
-
state == :QUITTING
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/fasten/stats.rb
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
module Fasten
|
2
|
-
module Stats
|
3
|
-
attr_writer :stats_data, :stats_entries
|
4
|
-
attr_reader :stats_path
|
5
|
-
|
6
|
-
def initialize_stats
|
7
|
-
return unless stats
|
8
|
-
|
9
|
-
@stats_path = "#{ENV['HOME']}/.fasten/stats/#{name}.csv" if ENV['HOME']
|
10
|
-
FileUtils.mkdir_p File.dirname(@stats_path)
|
11
|
-
rescue StandardError
|
12
|
-
@stats_path = nil
|
13
|
-
end
|
14
|
-
|
15
|
-
def load_stats
|
16
|
-
return unless @stats_path && File.exist?(@stats_path)
|
17
|
-
|
18
|
-
self.stats_data = []
|
19
|
-
CSV.foreach(@stats_path, headers: true) do |row|
|
20
|
-
stats_data << row.to_h
|
21
|
-
end
|
22
|
-
|
23
|
-
@task_waiting_list = nil
|
24
|
-
rescue StandardError
|
25
|
-
nil
|
26
|
-
ensure
|
27
|
-
self.stats ||= {}
|
28
|
-
end
|
29
|
-
|
30
|
-
def save_stats
|
31
|
-
return unless @stats_path && stats_data
|
32
|
-
|
33
|
-
keys = %w[state kind name run cnt avg std err]
|
34
|
-
|
35
|
-
CSV.open(@stats_path, 'wb') do |csv|
|
36
|
-
csv << keys
|
37
|
-
|
38
|
-
stats_data.each do |data|
|
39
|
-
csv << keys.map { |i| data[i] }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def stats_create_entry(state, target)
|
45
|
-
{ 'state' => state.to_s,
|
46
|
-
'kind' => stats_kind_for(target),
|
47
|
-
'name' => target.name,
|
48
|
-
'ini' => target.ini.to_f,
|
49
|
-
'fin' => target.fin.to_f,
|
50
|
-
'run' => target.fin - target.ini,
|
51
|
-
'worker' => target.respond_to?(:worker) ? target.worker.name : nil }
|
52
|
-
end
|
53
|
-
|
54
|
-
def stats_data
|
55
|
-
@stats_data ||= []
|
56
|
-
end
|
57
|
-
|
58
|
-
def stats_entries
|
59
|
-
@stats_entries ||= []
|
60
|
-
end
|
61
|
-
|
62
|
-
def stats_kind_for(object)
|
63
|
-
object.is_a?(Fasten::Executor) ? 'executor' : 'task'
|
64
|
-
end
|
65
|
-
|
66
|
-
def stats_add_entry(state, target)
|
67
|
-
return unless target.ini && target.fin
|
68
|
-
|
69
|
-
entry = stats_create_entry(state, target)
|
70
|
-
stats_data << entry
|
71
|
-
stats_entries << entry
|
72
|
-
|
73
|
-
history = stats_history(entry)
|
74
|
-
|
75
|
-
update_stats(history, entry)
|
76
|
-
end
|
77
|
-
|
78
|
-
FLOAT_FORMATTER = ->(f) { format('%7.3f', f) }
|
79
|
-
|
80
|
-
def stats_table_run
|
81
|
-
sub = stats_entries.select { |x| x['kind'] == 'task' }.map { |x| x['run'] }.sum
|
82
|
-
tot = stats_entries.select { |x| x['kind'] == 'executor' }.map { |x| x['run'] }.sum
|
83
|
-
|
84
|
-
[sub, tot]
|
85
|
-
end
|
86
|
-
|
87
|
-
def split_time(time)
|
88
|
-
sign = time.negative? ? '-' : ''
|
89
|
-
time = -time if time.negative?
|
90
|
-
|
91
|
-
hours, seconds = time.divmod(3600)
|
92
|
-
minutes, seconds = seconds.divmod(60)
|
93
|
-
seconds, decimal = seconds.divmod(1)
|
94
|
-
milliseconds, _ignored = (decimal.round(4) * 1000).divmod(1)
|
95
|
-
|
96
|
-
[sign, hours, minutes, seconds, milliseconds]
|
97
|
-
end
|
98
|
-
|
99
|
-
def hformat(time, total = nil)
|
100
|
-
sign, hours, minutes, seconds, milliseconds = split_time time
|
101
|
-
|
102
|
-
str = hours.zero? ? format('%.1s%02d:%02d.%03d', sign, minutes, seconds, milliseconds) : format('%.1s%02d:%02d:%02d.%03d', sign, hours, minutes, seconds, milliseconds)
|
103
|
-
str += format(' (%.1f%%)', 100.0 * time / total) if total
|
104
|
-
|
105
|
-
str
|
106
|
-
end
|
107
|
-
|
108
|
-
def stats_table
|
109
|
-
sub, tot = stats_table_run
|
110
|
-
|
111
|
-
Hirb::Console.render_output(stats_entries,
|
112
|
-
fields: %w[state kind name run cnt avg std err worker], unicode: true, class: 'Hirb::Helpers::AutoTable',
|
113
|
-
filters: { 'run' => FLOAT_FORMATTER, 'avg' => FLOAT_FORMATTER, 'std' => FLOAT_FORMATTER, 'err' => FLOAT_FORMATTER },
|
114
|
-
description: false)
|
115
|
-
|
116
|
-
puts format('∑tasks: %<task>s ∑executed: %<executed>s saved: %<saved>s workers: %<workers>s',
|
117
|
-
task: hformat(sub), executed: hformat(tot, sub), saved: hformat(sub - tot, sub), workers: workers.to_s)
|
118
|
-
end
|
119
|
-
|
120
|
-
def stats_history(entry)
|
121
|
-
stats_data.select { |e| e['state'] == entry['state'] && e['kind'] == entry['kind'] && e['name'] == entry['name'] }
|
122
|
-
end
|
123
|
-
|
124
|
-
def stats_last(item)
|
125
|
-
return item.last if item.last
|
126
|
-
|
127
|
-
item.last = stats_data.select { |e| e['kind'] == stats_kind_for(item) && e['name'] == item.name }.last || {}
|
128
|
-
end
|
129
|
-
|
130
|
-
def update_stats(history, entry)
|
131
|
-
entry['cnt'] = count = history.size
|
132
|
-
entry['avg'] = avg = history.inject(0.0) { |s, x| s + x['run'].to_f } / count
|
133
|
-
entry['std'] = std = Math.sqrt(history.inject(0.0) { |v, x| v + (x['run'].to_f - avg)**2 })
|
134
|
-
entry['err'] = std / Math.sqrt(count) if count.positive?
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|