ultra_marathon 0.1.7 → 0.1.10
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/README.md +97 -1
- data/lib/core_ext/class.rb +13 -0
- data/lib/core_ext/extensions.rb +4 -0
- data/lib/core_ext/object.rb +12 -0
- data/lib/core_ext/string.rb +9 -0
- data/lib/ultra_marathon.rb +2 -1
- data/lib/ultra_marathon/abstract_runner.rb +29 -144
- data/lib/ultra_marathon/base_runner.rb +216 -0
- data/lib/ultra_marathon/callbacks.rb +15 -50
- data/lib/ultra_marathon/collection_runner.rb +111 -0
- data/lib/ultra_marathon/contexticution.rb +53 -0
- data/lib/ultra_marathon/instrumentation.rb +32 -9
- data/lib/ultra_marathon/instrumentation/profile.rb +38 -9
- data/lib/ultra_marathon/instrumentation/store.rb +103 -0
- data/lib/ultra_marathon/logging.rb +1 -1
- data/lib/ultra_marathon/store.rb +13 -4
- data/lib/ultra_marathon/sub_context.rb +36 -0
- data/lib/ultra_marathon/sub_runner.rb +116 -60
- data/lib/ultra_marathon/version.rb +1 -1
- data/spec/spec_helper.rb +11 -0
- data/spec/ultra_marathon/abstract_runner_spec.rb +44 -1
- data/spec/ultra_marathon/collection_runner_spec.rb +96 -0
- data/spec/ultra_marathon/instrumentation/profile_spec.rb +5 -4
- data/spec/ultra_marathon/instrumentation/store_spec.rb +73 -0
- data/spec/ultra_marathon/instrumentation_spec.rb +1 -1
- data/spec/ultra_marathon/sub_runner_spec.rb +5 -1
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c86ad7025f579980952497fa5613049fc05f119f
|
4
|
+
data.tar.gz: a2c6d98d6e09a74e071fdb253e76a6831ee7be48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98f72f85992e4b2af816575ff76bcec3adeb6e536409c95ecbc49841bd8fe915104f0ee058cb12ed1417eaf904738be1437a2852f94037a5dced3a9f0c45ba23
|
7
|
+
data.tar.gz: 517904e35cd8cec588e7ec2dde635f59c9f9a8dfcc43f54a970bef170ec1ca29ca4183e7b919e3f53ac5976ba2e8042a178446bc8a0aaf885c6109f8877bebd6
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# UltraMarathon
|
2
|
+
[](https://travis-ci.org/tyre/ultra_marathon)
|
2
3
|
|
3
4
|
Fault tolerant platform for long running jobs.
|
4
5
|
|
@@ -86,6 +87,101 @@ In this instance, `bubbles` will not be run until `don_scuba_gear` successfully
|
|
86
87
|
finishes. If `don_scuba_gear` explicitly fails, such as by raising an error,
|
87
88
|
`bubbles` will never be run.
|
88
89
|
|
90
|
+
### Collections
|
91
|
+
|
92
|
+
Sometimes you want to run a given run block once for each of a given set. Just
|
93
|
+
pass the `:collection` option and all of your dreams will come true. Each
|
94
|
+
iteration will be passed one item along with the index.
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
class RangeRunner < UltraMarathon::AbstractRunner
|
98
|
+
|
99
|
+
run :counting!, collection: (1..100) do |number, index|
|
100
|
+
if index == 0
|
101
|
+
puts "We start with #{number}"
|
102
|
+
else
|
103
|
+
puts "And then comes #{number}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
The only requirement is that the `:collection` option responds to #each. But
|
111
|
+
what if it doesn't? Just pass in the `:iterator` option! This option was added
|
112
|
+
specifically for Rails ActiveRecord::Association instances that can fetch in
|
113
|
+
batches using `:find_each`
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
# Crow inherits from ActiveRecord::Base
|
117
|
+
|
118
|
+
class MurderRunner < UltraMarathon::AbstractRunner
|
119
|
+
|
120
|
+
run :coming_of_age, collection: :crows_to_bless, iterator: :find_each do |youngster_crow|
|
121
|
+
youngster_crow.update_attribute(blessed: true)
|
122
|
+
end
|
123
|
+
|
124
|
+
def crows_to_bless
|
125
|
+
Crow.unblessed.where(age: 10)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
```
|
131
|
+
|
132
|
+
### Threading
|
133
|
+
|
134
|
+
Passing `threaded: true` will run that run block in its own thread. This is particularly useful for collections or run blocks which contain external API calls, hit a database, or any other candidate for concurrency.
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
class NapRunner < UltraMarathon::AbstractRunner
|
138
|
+
run :mass_nap, collection: (1..100), threaded: true do
|
139
|
+
sleep(0.01)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
```
|
144
|
+
|
145
|
+
#### Example
|
146
|
+
|
147
|
+
As we will see in the example below, for longer-running processes that Ruby can run concurrently, threading is a muy bueno idea.
|
148
|
+
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
require 'benchmark'
|
152
|
+
require 'ultra_marathon'
|
153
|
+
|
154
|
+
class ThreadedNapRunner < UltraMarathon::AbstractRunner
|
155
|
+
run_collection :mass_nap, items: (1..100), threaded: true do |n|
|
156
|
+
sleep(1)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class UnthreadedNapRunner < UltraMarathon::AbstractRunner
|
161
|
+
run_collection :mass_nap, items: (1..100), threaded: false do |n|
|
162
|
+
sleep(1)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
Benchmark.bmbm do |reporter|
|
167
|
+
reporter.report(:threaded) { ThreadedNapRunner.new.run! }
|
168
|
+
reporter.report(:unthreaded) { UnthreadedNapRunner.new.run! }
|
169
|
+
end
|
170
|
+
|
171
|
+
# Rehearsal ----------------------------------------------
|
172
|
+
# threaded 1.270000 0.080000 1.350000 ( 1.346384)
|
173
|
+
# unthreaded 0.060000 0.010000 0.070000 (100.141031)
|
174
|
+
# ------------------------------------- total: 1.420000sec
|
175
|
+
#
|
176
|
+
# user system total real
|
177
|
+
# threaded 1.320000 0.060000 1.380000 ( 1.377940)
|
178
|
+
# unthreaded 0.060000 0.000000 0.060000 (100.128980)
|
179
|
+
```
|
180
|
+
|
181
|
+
Note, however, that threading is not free. In the final benchmark, we would expect a runtime of ~1 second but saw over a third of a second slower. If we run the same test with a run block of `(n * 10_000).times { 'derp' }`, for example, the unthreaded version is about 10% faster.
|
182
|
+
|
183
|
+
tl;dr don't thread because it sounds cool. Use it when you need it.
|
184
|
+
|
89
185
|
### Callbacks
|
90
186
|
|
91
187
|
`UltraMarathon::AbstractRunner` includes numerous life-cycle callbacks for
|
@@ -183,6 +279,6 @@ class WatRunner < UltraMarathon::AbstractRunner
|
|
183
279
|
end
|
184
280
|
|
185
281
|
WatRunner.run!.reset.run!
|
186
|
-
#=>
|
282
|
+
#=> wrong
|
187
283
|
#=> all is well in the universe
|
188
284
|
```
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Class
|
2
|
+
def attr_memo_reader(name, memoization_block)
|
3
|
+
define_method(name) do
|
4
|
+
instance_variable_get(:"@#{name}") ||
|
5
|
+
instance_variable_set(:"@#{name}", instance_exec(&memoization_block))
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def attr_memo_accessor(name, memoization_block)
|
10
|
+
attr_memo_reader(name, memoization_block)
|
11
|
+
attr_writer name
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Object
|
2
|
+
# Many times an option can either be a callable object (Proc/Lambda) or
|
3
|
+
# not (symbol/string/integer). This will call with the included arguments,
|
4
|
+
# if it is callable, or return the object if not.
|
5
|
+
def try_call(*args)
|
6
|
+
if respond_to? :call
|
7
|
+
call(*args)
|
8
|
+
else
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/ultra_marathon.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
require 'core_ext/
|
1
|
+
require 'core_ext/extensions'
|
2
2
|
require 'ultra_marathon/abstract_runner'
|
3
3
|
require 'ultra_marathon/callbacks'
|
4
4
|
require 'ultra_marathon/instrumentation'
|
5
5
|
require 'ultra_marathon/logging'
|
6
6
|
require 'ultra_marathon/sub_runner'
|
7
7
|
require 'ultra_marathon/store'
|
8
|
+
require 'ultra_marathon/collection_runner'
|
8
9
|
|
9
10
|
module UltraMarathon
|
10
11
|
|
@@ -1,80 +1,39 @@
|
|
1
|
-
require 'ultra_marathon/
|
2
|
-
require 'ultra_marathon/instrumentation'
|
3
|
-
require 'ultra_marathon/logging'
|
1
|
+
require 'ultra_marathon/base_runner'
|
4
2
|
require 'ultra_marathon/sub_runner'
|
5
3
|
require 'ultra_marathon/store'
|
6
4
|
|
7
5
|
module UltraMarathon
|
8
|
-
class AbstractRunner
|
9
|
-
include Logging
|
10
|
-
include Instrumentation
|
11
|
-
include Callbacks
|
12
|
-
attr_accessor :success
|
13
|
-
callbacks :before_run, :after_run, :on_error, :on_reset
|
14
|
-
|
6
|
+
class AbstractRunner < BaseRunner
|
15
7
|
after_run :write_log
|
16
8
|
on_error lambda { self.success = false }
|
17
9
|
on_error lambda { |error| logger.error(error) }
|
18
10
|
|
19
|
-
## Public Instance Methods
|
20
|
-
|
21
|
-
# Runs the run block safely in the context of the instance
|
22
|
-
def run!
|
23
|
-
if self.class.run_blocks.any?
|
24
|
-
begin
|
25
|
-
self.success = nil
|
26
|
-
invoke_before_run_callbacks
|
27
|
-
instrument(:run_unrun_sub_runners) { run_unrun_sub_runners }
|
28
|
-
# If any of the sub runners explicitly set the success flag, don't override it
|
29
|
-
self.success = failed_sub_runners.empty? if self.success.nil?
|
30
|
-
rescue StandardError => error
|
31
|
-
invoke_on_error_callbacks(error)
|
32
|
-
ensure
|
33
|
-
invoke_after_run_callbacks
|
34
|
-
end
|
35
|
-
self
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def success?
|
40
|
-
!!success
|
41
|
-
end
|
42
|
-
|
43
|
-
# Resets success to being true, unsets the failed sub_runners to [], and
|
44
|
-
# sets the unrun sub_runners to be the uncompleted/failed ones
|
45
|
-
def reset
|
46
|
-
reset_failed_runners
|
47
|
-
@success = nil
|
48
|
-
invoke_on_reset_callbacks
|
49
|
-
self
|
50
|
-
end
|
51
|
-
|
52
11
|
private
|
53
12
|
|
54
13
|
## Private Class Methods
|
55
14
|
|
56
15
|
class << self
|
16
|
+
attr_memo_reader :run_blocks, -> { Hash.new }
|
57
17
|
|
58
18
|
# This is where the magic happens.
|
59
19
|
# Called in the class context, it will be safely executed in
|
60
20
|
# the context of the instance.
|
61
21
|
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# end
|
22
|
+
# @example
|
23
|
+
# class BubblesRunner < AbstractRunner
|
24
|
+
# run do
|
25
|
+
# fire_the_missiles
|
26
|
+
# take_a_nap
|
27
|
+
# end
|
69
28
|
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
29
|
+
# def fire_the_missiles
|
30
|
+
# puts 'But I am le tired'
|
31
|
+
# end
|
73
32
|
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
33
|
+
# def take_a_nap
|
34
|
+
# puts 'zzzzzz'
|
35
|
+
# end
|
36
|
+
# end
|
78
37
|
#
|
79
38
|
# BubblesRunner.new.run!
|
80
39
|
# # => 'But I am le tired'
|
@@ -89,8 +48,9 @@ module UltraMarathon
|
|
89
48
|
end
|
90
49
|
end
|
91
50
|
|
92
|
-
def
|
93
|
-
|
51
|
+
def run_collection(name=:main, items=[], options={}, &block)
|
52
|
+
options.merge!(collection: true, items: items)
|
53
|
+
run(name, options, &block)
|
94
54
|
end
|
95
55
|
end
|
96
56
|
|
@@ -109,97 +69,22 @@ module UltraMarathon
|
|
109
69
|
|
110
70
|
# Creates a new sub runner, defaulting the context to `self`
|
111
71
|
def new_sub_runner(options, block)
|
112
|
-
|
113
|
-
context: self
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
# Stores sub runners which ran and were a success
|
120
|
-
def successful_sub_runners
|
121
|
-
@successful_sub_runners ||= Store.new
|
122
|
-
end
|
123
|
-
|
124
|
-
# Stores sub runners which ran and failed
|
125
|
-
# Also store children of those which failed
|
126
|
-
def failed_sub_runners
|
127
|
-
@failed_sub_runners ||= Store.new
|
128
|
-
end
|
129
|
-
|
130
|
-
# If all of the parents have been successfully run (or there are no
|
131
|
-
# parents), runs the sub_runner.
|
132
|
-
# If any one of the parents has failed, considers the runner a failure
|
133
|
-
# If some parents have not yet completed, carries on
|
134
|
-
def run_unrun_sub_runners
|
135
|
-
unrun_sub_runners.each do |sub_runner|
|
136
|
-
if sub_runner_can_run? sub_runner
|
137
|
-
run_sub_runner(sub_runner)
|
138
|
-
elsif sub_runner.parents.any? { |name| failed_sub_runners.exists? name }
|
139
|
-
failed_sub_runners << sub_runner
|
140
|
-
unrun_sub_runners.delete sub_runner.name
|
141
|
-
end
|
142
|
-
end
|
143
|
-
run_unrun_sub_runners unless complete?
|
144
|
-
end
|
145
|
-
|
146
|
-
# Runs the sub runner, adding it to the appropriate sub runner store based
|
147
|
-
# on its success or failure and removes it from the unrun_sub_runners
|
148
|
-
def run_sub_runner(sub_runner)
|
149
|
-
sub_runner.run!
|
150
|
-
logger.info sub_runner.logger.contents
|
151
|
-
if sub_runner.success
|
152
|
-
successful_sub_runners << sub_runner
|
72
|
+
options = {
|
73
|
+
context: self,
|
74
|
+
collection: false,
|
75
|
+
items: []
|
76
|
+
}.merge(options)
|
77
|
+
if options[:collection]
|
78
|
+
CollectionRunner.new(options.delete(:items), options, &block)
|
153
79
|
else
|
154
|
-
|
80
|
+
SubRunner.new(options, block)
|
155
81
|
end
|
156
|
-
unrun_sub_runners.delete sub_runner.name
|
157
|
-
end
|
158
|
-
|
159
|
-
## TODO: timeout option
|
160
|
-
def complete?
|
161
|
-
unrun_sub_runners.empty?
|
162
|
-
end
|
163
|
-
|
164
|
-
# A sub runner can run if all prerequisites have been satisfied.
|
165
|
-
# This means all parent runners - those specified by name using the
|
166
|
-
# :requires options - have successfully completed.
|
167
|
-
def sub_runner_can_run?(sub_runner)
|
168
|
-
successful_sub_runners.includes_all?(sub_runner.parents)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Resets all failed sub runners, then sets them as
|
172
|
-
# @unrun_sub_runners and @failed_sub_runners to an empty Store
|
173
|
-
def reset_failed_runners
|
174
|
-
failed_sub_runners.each(&:reset)
|
175
|
-
@unrun_sub_runners = failed_sub_runners
|
176
|
-
@failed_sub_runners = Store.new
|
177
82
|
end
|
178
83
|
|
179
84
|
def write_log
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
def summary
|
184
|
-
run_instrumentation = instrumentations[:run_unrun_sub_runners]
|
185
|
-
"""
|
186
|
-
|
187
|
-
Status: #{status}
|
188
|
-
Run Start Time: #{run_instrumentation.formatted_start_time}
|
189
|
-
End Time: #{run_instrumentation.formatted_end_time}
|
190
|
-
Total Time: #{run_instrumentation.formatted_total_time}
|
191
|
-
|
192
|
-
Successful SubRunners: #{successful_sub_runners.size}
|
193
|
-
Failed SubRunners: #{failed_sub_runners.size}
|
194
|
-
"""
|
85
|
+
log_all_sub_runners
|
86
|
+
log_summary
|
195
87
|
end
|
196
88
|
|
197
|
-
def status
|
198
|
-
if success
|
199
|
-
'Success'
|
200
|
-
else
|
201
|
-
'Failure'
|
202
|
-
end
|
203
|
-
end
|
204
89
|
end
|
205
90
|
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'ultra_marathon/callbacks'
|
2
|
+
require 'ultra_marathon/instrumentation'
|
3
|
+
require 'ultra_marathon/logging'
|
4
|
+
|
5
|
+
module UltraMarathon
|
6
|
+
class BaseRunner
|
7
|
+
RUN_INSTRUMENTATION_NAME = '__run!'.freeze
|
8
|
+
include Logging
|
9
|
+
include Instrumentation
|
10
|
+
include Callbacks
|
11
|
+
|
12
|
+
attr_accessor :success
|
13
|
+
attr_memo_accessor :running_sub_runners, -> { Store.new }
|
14
|
+
attr_memo_reader :successful_sub_runners, -> { Store.new }
|
15
|
+
attr_memo_reader :failed_sub_runners, -> { Store.new }
|
16
|
+
|
17
|
+
callbacks :before_run, :after_run, :on_error, :on_reset, :after_initialize
|
18
|
+
|
19
|
+
## Public Class Methods
|
20
|
+
|
21
|
+
def self.new(*args, &block)
|
22
|
+
super(*args, &block).tap do |instance|
|
23
|
+
instance.send(:invoke_after_initialize_callbacks)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
## Public Instance Methods
|
28
|
+
|
29
|
+
# Runs the run block safely in the context of the instance
|
30
|
+
def run!
|
31
|
+
if unrun_sub_runners.any?
|
32
|
+
instrument RUN_INSTRUMENTATION_NAME do
|
33
|
+
begin
|
34
|
+
self.success = nil
|
35
|
+
invoke_before_run_callbacks
|
36
|
+
instrument(:__run_unrun_sub_runners) { run_unrun_sub_runners }
|
37
|
+
# If any of the sub runners explicitly set the success flag, don't override it
|
38
|
+
self.success = failed_sub_runners.empty? if self.success.nil?
|
39
|
+
rescue StandardError => error
|
40
|
+
invoke_on_error_callbacks(error)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
invoke_after_run_callbacks
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def success?
|
49
|
+
!!success
|
50
|
+
end
|
51
|
+
|
52
|
+
# Resets success to being true, unsets the failed sub_runners to [], and
|
53
|
+
# sets the unrun sub_runners to be the uncompleted/failed ones
|
54
|
+
def reset
|
55
|
+
reset_failed_runners
|
56
|
+
@success = nil
|
57
|
+
invoke_on_reset_callbacks
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def run_instrumentation
|
62
|
+
instrumentations[RUN_INSTRUMENTATION_NAME]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
## Private Instance Methods
|
68
|
+
|
69
|
+
# If all of the parents have been successfully run (or there are no
|
70
|
+
# parents), runs the sub_runner.
|
71
|
+
# If any one of the parents has failed, considers the runner a failure
|
72
|
+
# If some parents have not yet completed, carries on
|
73
|
+
def run_unrun_sub_runners
|
74
|
+
until complete?
|
75
|
+
unrun_sub_runners.each do |sub_runner|
|
76
|
+
if sub_runner_can_run? sub_runner
|
77
|
+
running_sub_runners << sub_runner.run!
|
78
|
+
elsif sub_runner.parents.any? { |name| failed_sub_runners.exists? name }
|
79
|
+
failed_sub_runners << sub_runner
|
80
|
+
unrun_sub_runners.delete sub_runner.name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
clean_up_completed_sub_runners
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Cleans up all dead threads, settings
|
88
|
+
def clean_up_completed_sub_runners
|
89
|
+
completed_runners, unfinished_runners = running_sub_runners.partition(&:complete?)
|
90
|
+
completed_runners.each do |sub_runner|
|
91
|
+
clean_up_sub_runner(sub_runner)
|
92
|
+
end
|
93
|
+
self.running_sub_runners = unfinished_runners
|
94
|
+
end
|
95
|
+
|
96
|
+
# Adds a run sub runner to the appropriate sub runner store based
|
97
|
+
# on its success or failure and removes it from the unrun_sub_runners
|
98
|
+
# Also merges its instrumentation to the group's instrumentation
|
99
|
+
def clean_up_sub_runner(sub_runner)
|
100
|
+
if sub_runner.success
|
101
|
+
successful_sub_runners << sub_runner
|
102
|
+
else
|
103
|
+
failed_sub_runners << sub_runner
|
104
|
+
end
|
105
|
+
instrumentations.merge!(sub_runner.instrumentations)
|
106
|
+
unrun_sub_runners.delete sub_runner.name
|
107
|
+
end
|
108
|
+
|
109
|
+
## TODO: timeout option
|
110
|
+
def complete?
|
111
|
+
running_sub_runners.empty? && unrun_sub_runners.empty?
|
112
|
+
end
|
113
|
+
|
114
|
+
# Resets all failed sub runners, then sets them as
|
115
|
+
# unrun_sub_runners and failed_sub_runners to an empty Store
|
116
|
+
def reset_failed_runners
|
117
|
+
failed_sub_runners.each(&:reset)
|
118
|
+
@unrun_sub_runners = failed_sub_runners
|
119
|
+
@failed_sub_runners = Store.new
|
120
|
+
end
|
121
|
+
|
122
|
+
# A sub runner can run if all prerequisites have been satisfied.
|
123
|
+
# This means all parent runners - those specified by name using the
|
124
|
+
# :requires options - have successfully completed.
|
125
|
+
def sub_runner_can_run?(sub_runner)
|
126
|
+
successful_sub_runners.includes_all?(sub_runner.parents)
|
127
|
+
end
|
128
|
+
|
129
|
+
def status
|
130
|
+
if success?
|
131
|
+
'Success'
|
132
|
+
else
|
133
|
+
'Failure'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def log_all_sub_runners
|
138
|
+
log_failed_sub_runners if failed_sub_runners.any?
|
139
|
+
log_successful_sub_runners if successful_sub_runners.any?
|
140
|
+
end
|
141
|
+
|
142
|
+
def log_failed_sub_runners
|
143
|
+
logger.info """
|
144
|
+
|
145
|
+
== Failed SubRunners ==
|
146
|
+
|
147
|
+
"""
|
148
|
+
log_sub_runners(failed_sub_runners)
|
149
|
+
end
|
150
|
+
|
151
|
+
def log_successful_sub_runners
|
152
|
+
logger.info """
|
153
|
+
|
154
|
+
== Successful SubRunners ==
|
155
|
+
|
156
|
+
"""
|
157
|
+
log_sub_runners(successful_sub_runners)
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def log_sub_runners(sub_runners)
|
162
|
+
sub_runners.each do |sub_runner|
|
163
|
+
logger.info(sub_runner.logger.contents << "\n")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def log_summary
|
168
|
+
run_profile = instrumentations[:run!]
|
169
|
+
failed_names = failed_sub_runners.names.map(&:to_s).join(', ')
|
170
|
+
succcessful_names = successful_sub_runners.names.map(&:to_s).join(', ')
|
171
|
+
unrun_names = unrun_sub_runners.names.map(&:to_s).join(', ')
|
172
|
+
logger.info """
|
173
|
+
|
174
|
+
Status: #{status}
|
175
|
+
|
176
|
+
Failed (#{failed_sub_runners.size}): #{failed_names}
|
177
|
+
Successful (#{successful_sub_runners.size}): #{succcessful_names}
|
178
|
+
Unrun (#{unrun_sub_runners.size}): #{unrun_names}
|
179
|
+
|
180
|
+
#{time_summary}
|
181
|
+
|
182
|
+
"""
|
183
|
+
end
|
184
|
+
|
185
|
+
def sub_runner_instrumentations
|
186
|
+
@sub_runner_instrumentations ||= begin
|
187
|
+
sub_runner_profiles = instrumentations.select do |profile|
|
188
|
+
profile.name.to_s.start_with? 'sub_runner.'
|
189
|
+
end
|
190
|
+
UltraMarathon::Instrumentation::Store.new(sub_runner_profiles)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def time_summary
|
195
|
+
"""
|
196
|
+
Run Start Time: #{run_instrumentation.formatted_start_time}
|
197
|
+
End Time: #{run_instrumentation.formatted_end_time}
|
198
|
+
Total Time: #{run_instrumentation.formatted_total_time}
|
199
|
+
|
200
|
+
#{sub_runner_summary if sub_runner_instrumentations.any?}
|
201
|
+
"""
|
202
|
+
end
|
203
|
+
|
204
|
+
def sub_runner_summary
|
205
|
+
median_profile = sub_runner_instrumentations.median
|
206
|
+
max_profile = sub_runner_instrumentations.max
|
207
|
+
min_profile = sub_runner_instrumentations.min
|
208
|
+
"""
|
209
|
+
Max SubRunner Runtime: #{max_profile.name} (#{max_profile.total_time})
|
210
|
+
Min SubRunner Runtime: #{min_profile.name} (#{min_profile.total_time})
|
211
|
+
Median SubRunner Runtime: #{median_profile.name} (#{median_profile.total_time})
|
212
|
+
SubRunner Runtime Standard Deviation: #{sub_runner_instrumentations.standard_deviation}
|
213
|
+
"""
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|