fasten 0.5.4 → 0.6.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 +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
|