ultra_marathon 0.1.7 → 0.1.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/tyre/ultra_marathon.svg?branch=master)](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
|