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.
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.5.4
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-10-30 00:00:00.000000000 Z
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: '1.16'
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: '1.16'
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: curses
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: hirb
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/dag.rb
163
- - lib/fasten/executor.rb
164
- - lib/fasten/logger.rb
165
- - lib/fasten/state.rb
166
- - lib/fasten/stats.rb
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/ui.rb
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
@@ -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
@@ -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
@@ -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
@@ -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