fasten 0.2.0 → 0.4.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: 1ca1b131f4ee26ffb7f5dcaa5408e314988c3568d155b9cb804daba84c588f90
4
- data.tar.gz: 6bd08febfd39b5e0ab11c95e6e687acdb6778c67f493469033e49364f47b6012
3
+ metadata.gz: 3b5fa7853f4b7c0e9a187bb5f76f59019e7db4defdbc68d059ca71a90ad38b8b
4
+ data.tar.gz: 91db6845b40c890928b3f217e5d91ab88f127187b95257a4863533db92dc0840
5
5
  SHA512:
6
- metadata.gz: 5fa5a15d9c430cbbe53fee25917c9dcaba4dfb40e70b6b5323d1a46036d0e2a53fdc1d4960226bddf14a76946499fcc8e777b9431596c7c92bfd261093476ed3
7
- data.tar.gz: 4d0596481e975f9c4406f1c46ce707cde8f1f9d11b4089e94a825fe5281b3139e6a9fdf82ac3e905b606ef16a6157019aa04ee3bdd7a3687ebe5310d2303ede3
6
+ metadata.gz: 184e4868d0cc1b5e24ae365e636e7fb988c656665c05420abce2551024143b53857cf32ebb1e717cadedfba655cae42079d1044aa5ae8f82872478d762be8190
7
+ data.tar.gz: c7f0c1c4d1a8a23ee97be8916de501eacda6a70b526b387fbabbac130743b6aadcfccba1d626581cf0ebe860006cecb58cb7729cd48a4d8aa9d86633ef9a61ab
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  .fasten
13
+ *.testfile
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fasten (0.2.0)
4
+ fasten (0.4.0)
5
5
  binding_of_caller
6
6
  curses
7
7
 
@@ -6,11 +6,15 @@ require 'binding_of_caller'
6
6
  require 'logger'
7
7
  require 'ostruct'
8
8
  require 'curses'
9
+ require 'fileutils'
10
+ require 'csv'
9
11
 
10
12
  require 'fasten/log_support'
13
+ require 'fasten/stats'
11
14
  require 'fasten/task'
12
15
  require 'fasten/ui'
13
16
  require 'fasten/dag'
17
+ require 'fasten/load_save'
14
18
  require 'fasten/executor'
15
19
  require 'fasten/worker'
16
20
  require 'fasten/version'
@@ -26,20 +30,4 @@ module Fasten
26
30
  executor
27
31
  end
28
32
  end
29
-
30
- class Executor
31
- def load(path)
32
- items = YAML.safe_load(File.read(path)).each do |name, params|
33
- params.each do |key, val|
34
- next unless val.is_a?(String) && (match = %r{^/(.+)/$}.match(val))
35
-
36
- params[key] = Regexp.new(match[1])
37
- end
38
-
39
- add Fasten::Task.new({ name: name }.merge(params))
40
- end
41
-
42
- log_info "Loaded #{items.count} tasks from #{path}"
43
- end
44
- end
45
33
  end
@@ -1,6 +1,6 @@
1
1
  module Fasten
2
2
  module DAG
3
- attr_reader :task_list, :task_done_list, :task_error_list, :task_pending_list, :task_running_list
3
+ attr_reader :task_map, :task_list, :task_done_list, :task_error_list, :task_pending_list, :task_running_list
4
4
 
5
5
  def initialize_dag
6
6
  @task_map = {}
@@ -20,11 +20,13 @@ module Fasten
20
20
  end
21
21
 
22
22
  def update_task(task)
23
- if task.error
24
- update_error_task task
25
- else
23
+ if task.state == :DONE
26
24
  update_done_task task
25
+ else
26
+ update_error_task task
27
27
  end
28
+
29
+ stats_add_entry(self, task.state, task)
28
30
  end
29
31
 
30
32
  def update_done_task(task)
@@ -43,7 +45,7 @@ module Fasten
43
45
  end
44
46
 
45
47
  def next_task
46
- task_waiting_list.pop
48
+ task_waiting_list.sort_by!(&:run_score).pop
47
49
  end
48
50
 
49
51
  def task_waiting_list
@@ -51,6 +53,7 @@ module Fasten
51
53
 
52
54
  reset_tasks
53
55
  setup_tasks_dependencies
56
+ setup_tasks_scores
54
57
  move_pending_to_waiting
55
58
  end
56
59
 
@@ -72,7 +75,7 @@ module Fasten
72
75
  @task_list.each do |task|
73
76
  task.dependants = []
74
77
  task.depends = []
75
- task.level = 0
78
+ task.run_score = 0
76
79
  task.done ? @task_done_list << task : @task_pending_list << task
77
80
  end
78
81
  end
@@ -86,12 +89,17 @@ module Fasten
86
89
  raise "Dependency task '#{after}' not found on task '#{task.name}'." unless after_task
87
90
 
88
91
  task.depends << after_task
89
- task.level += 1
90
92
  after_task.dependants << task
91
93
  end
92
94
  end
93
95
  end
94
96
 
97
+ def setup_tasks_scores
98
+ @task_pending_list.each do |task|
99
+ task.run_score += task.dependants.count
100
+ end
101
+ end
102
+
95
103
  def no_waiting_tasks?
96
104
  task_waiting_list.empty?
97
105
  end
@@ -3,8 +3,11 @@ module Fasten
3
3
  include Fasten::LogSupport
4
4
  include Fasten::DAG
5
5
  include Fasten::UI
6
+ include Fasten::LoadSave
7
+ include Fasten::Stats
6
8
 
7
9
  def initialize(name: nil, workers: 8, worker_class: Fasten::Worker, fasten_dir: '.fasten')
10
+ setup_stats(name)
8
11
  super name: name || "#{self.class} #{$PID}", workers: workers, pid: $PID, state: :IDLE, worker_class: worker_class, fasten_dir: fasten_dir
9
12
  initialize_dag
10
13
 
@@ -16,22 +19,26 @@ module Fasten
16
19
  end
17
20
 
18
21
  def perform
19
- log_ini self, running_stats
22
+ log_ini self, running_counters
20
23
  self.state = :RUNNING
24
+ load_stats
21
25
 
22
26
  run_ui do
23
27
  perform_loop
24
28
  end
25
29
 
26
- self.state = :IDLE
27
- log_fin self, running_stats
30
+ self.state = task_list.map(&:state).all?(:DONE) ? :DONE : :FAIL
31
+ log_fin self, running_counters
32
+
33
+ stats_add_entry(self, state, self)
34
+ save_stats
28
35
  end
29
36
 
30
- def done_stats
37
+ def done_counters
31
38
  "#{task_done_list.count}/#{task_list.count}"
32
39
  end
33
40
 
34
- def running_stats
41
+ def running_counters
35
42
  "#{task_done_list.count + task_running_list.count}/#{task_list.count}"
36
43
  end
37
44
 
@@ -68,7 +75,7 @@ module Fasten
68
75
 
69
76
  update_task task
70
77
 
71
- log_fin task, done_stats
78
+ log_fin task, done_counters
72
79
  end
73
80
  end
74
81
 
@@ -0,0 +1,69 @@
1
+ module Fasten
2
+ module LoadSave
3
+ attr_reader :stats_path
4
+
5
+ def load(path)
6
+ items = YAML.safe_load(File.read(path)).each do |name, params|
7
+ params.each do |key, val|
8
+ next unless val.is_a?(String) && (match = %r{^/(.+)/$}.match(val))
9
+
10
+ params[key] = Regexp.new(match[1])
11
+ end
12
+
13
+ add Fasten::Task.new({ name: name }.merge(params))
14
+ end
15
+
16
+ log_info "Loaded #{items.count} tasks from #{path}"
17
+ end
18
+
19
+ def save(path)
20
+ keys = %i[after shell]
21
+
22
+ items = task_list.map do |task|
23
+ data = task.to_h.select do |key, _val|
24
+ keys.include? key
25
+ end
26
+
27
+ [task.name, data]
28
+ end.to_h
29
+
30
+ File.write path, items.to_yaml
31
+
32
+ log_info "Loaded #{items.count} tasks into #{path}"
33
+ end
34
+
35
+ def load_stats
36
+ return unless @stats_path && File.exist?(@stats_path)
37
+
38
+ self.stats_data = []
39
+ CSV.foreach(@stats_path, headers: true) do |row|
40
+ stats_data << row.to_h
41
+ end
42
+
43
+ @task_waiting_list = nil
44
+ rescue StandardError
45
+ nil
46
+ ensure
47
+ self.stats ||= {}
48
+ end
49
+
50
+ def save_stats
51
+ return unless @stats_path && stats_data
52
+
53
+ CSV.open(@stats_path, 'wb') do |csv|
54
+ csv << stats_data.first.keys
55
+
56
+ stats_data.each do |data|
57
+ csv << data.values
58
+ end
59
+ end
60
+ end
61
+
62
+ def setup_stats(name)
63
+ @stats_path = "#{ENV['HOME']}/.fasten/stats/#{name}.csv" if ENV['HOME'] && name
64
+ FileUtils.mkdir_p File.dirname(@stats_path)
65
+ rescue StandardError
66
+ @stats_path = nil
67
+ end
68
+ end
69
+ end
@@ -32,6 +32,7 @@ module Fasten
32
32
 
33
33
  FileUtils.mkdir_p File.dirname(path)
34
34
  log = File.new path, 'a'
35
+ log.sync = true
35
36
 
36
37
  $stdout.reopen log
37
38
  $stderr.reopen log
@@ -0,0 +1,39 @@
1
+ module Fasten
2
+ module Stats
3
+ def stats_create_entry(state, target)
4
+ {
5
+ 'state' => state.to_s,
6
+ 'kind' => target.is_a?(Fasten::Executor) ? 'executor' : 'task',
7
+ 'name' => target.name,
8
+ 'ini' => target.ini.to_f,
9
+ 'fin' => target.fin.to_f,
10
+ 'run' => target.fin - target.ini
11
+ }
12
+ end
13
+
14
+ def stats_add_entry(object, state, target)
15
+ return unless target.ini && target.fin
16
+
17
+ entry = stats_create_entry(state, target)
18
+ object.stats_data ||= []
19
+ object.stats_data << entry
20
+
21
+ history = stats_history(object, entry)
22
+
23
+ update_avg(history, entry)
24
+ update_std(history, entry)
25
+ end
26
+
27
+ def stats_history(object, entry)
28
+ object.stats_data.select { |e| e['state'] == entry['state'] && e['kind'] == entry['kind'] && e['name'] == entry['name'] }
29
+ end
30
+
31
+ def update_avg(history, entry)
32
+ entry['avg'] = history.inject(0.0) { |s, x| s + x['run'].to_f } / history.size
33
+ end
34
+
35
+ def update_std(history, entry)
36
+ entry['std'] = Math.sqrt(history.inject(0.0) { |v, x| v + (x['run'].to_f - entry['avg'])**2 })
37
+ end
38
+ end
39
+ end
@@ -108,7 +108,7 @@ module Fasten
108
108
  when :RUNNING
109
109
  attrs = color_pair(1) | A_TOP
110
110
  icon = SPINNER_STR[task.worker&.spinner] if icon
111
- when :ERROR
111
+ when :FAIL
112
112
  attrs = color_pair(3)
113
113
  icon = '✘︎' if icon
114
114
  when :DONE
@@ -143,14 +143,15 @@ module Fasten
143
143
  ui_progressbar(2, col_ini, col_fin, count_done, count_total)
144
144
 
145
145
  max = 2
146
- task_list.each_with_index do |task, index|
146
+ list = task_list.sort_by(&:run_score)
147
+ list.each_with_index do |task, index|
147
148
  next if 3 + index >= ui_rows
148
149
 
149
150
  x = ui_task_string(task, 3 + index, 2, icon: true)
150
151
  max = x if x > max
151
152
  end
152
153
 
153
- task_list.each_with_index do |task, index|
154
+ list.each_with_index do |task, index|
154
155
  next if 3 + index >= ui_rows || task.depends.nil? || task.depends.empty?
155
156
 
156
157
  setpos 3 + index, max
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fasten
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -30,13 +30,13 @@ module Fasten
30
30
  end
31
31
 
32
32
  def receive
33
- updated_task = Marshal.load(parent_read) # rubocop:disable Security/MarshalLoad
33
+ updated_task = Marshal.load(parent_read) # rubocop:disable Security/MarshalLoad because pipe is a secure channel
34
34
 
35
35
  %i[ini fin response error].each { |key| running_task[key] = updated_task[key] }
36
36
 
37
37
  task = running_task
38
38
  self.running_task = nil
39
- task.state = task.error ? :ERROR : :DONE
39
+ task.state = task.error ? :FAIL : :DONE
40
40
 
41
41
  task
42
42
  end
@@ -77,7 +77,7 @@ module Fasten
77
77
  def process_incoming_tasks
78
78
  log_ini self, 'process_incoming_tasks'
79
79
 
80
- while (object = Marshal.load(child_read)) # rubocop:disable Security/MarshalLoad
80
+ while (object = Marshal.load(child_read)) # rubocop:disable Security/MarshalLoad because pipe is a secure channel
81
81
  run_task(object) if object.is_a? Fasten::Task
82
82
  end
83
83
 
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.2.0
4
+ version: 0.4.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-14 00:00:00.000000000 Z
11
+ date: 2018-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -133,7 +133,9 @@ files:
133
133
  - lib/fasten.rb
134
134
  - lib/fasten/dag.rb
135
135
  - lib/fasten/executor.rb
136
+ - lib/fasten/load_save.rb
136
137
  - lib/fasten/log_support.rb
138
+ - lib/fasten/stats.rb
137
139
  - lib/fasten/task.rb
138
140
  - lib/fasten/ui.rb
139
141
  - lib/fasten/version.rb