bosh_cli 1.2200.0 → 1.2291.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.
- data/lib/cli/task_tracking.rb +2 -1
- data/lib/cli/task_tracking/event_log_renderer.rb +63 -341
- data/lib/cli/task_tracking/smart_whitespace_printer.rb +56 -0
- data/lib/cli/task_tracking/stage_collection.rb +69 -21
- data/lib/cli/task_tracking/stage_collection_presenter.rb +111 -0
- data/lib/cli/task_tracking/task_log_renderer.rb +6 -19
- data/lib/cli/version.rb +1 -1
- metadata +10 -9
- data/lib/cli/task_tracking/stage_progress_bar.rb +0 -59
data/lib/cli/task_tracking.rb
CHANGED
@@ -4,8 +4,9 @@ end
|
|
4
4
|
|
5
5
|
require 'cli/task_tracking/task_tracker'
|
6
6
|
require 'cli/task_tracking/total_duration'
|
7
|
+
require 'cli/task_tracking/smart_whitespace_printer'
|
7
8
|
require 'cli/task_tracking/task_log_renderer'
|
8
9
|
require 'cli/task_tracking/null_task_log_renderer'
|
9
|
-
require 'cli/task_tracking/stage_progress_bar'
|
10
10
|
require 'cli/task_tracking/stage_collection'
|
11
|
+
require 'cli/task_tracking/stage_collection_presenter'
|
11
12
|
require 'cli/task_tracking/event_log_renderer'
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
3
|
+
|
1
4
|
module Bosh::Cli::TaskTracking
|
2
5
|
class EventLogRenderer < TaskLogRenderer
|
3
6
|
class InvalidEvent < StandardError; end
|
@@ -5,382 +8,101 @@ module Bosh::Cli::TaskTracking
|
|
5
8
|
extend Forwardable
|
6
9
|
def_delegators :@total_duration, :duration, :duration_known?, :started_at, :finished_at
|
7
10
|
|
8
|
-
|
9
|
-
|
11
|
+
def initialize
|
12
|
+
super
|
10
13
|
|
11
|
-
|
14
|
+
@printer = SmartWhitespacePrinter.new
|
12
15
|
@total_duration = TotalDuration.new
|
13
|
-
@lock = Monitor.new
|
14
|
-
@events_count = 0
|
15
|
-
@seen_stages = Set.new
|
16
|
-
@out = Bosh::Cli::Config.output || $stdout
|
17
|
-
@out.sync = true
|
18
|
-
@buffer = StringIO.new
|
19
|
-
@progress_bars = {}
|
20
|
-
@pos = 0
|
21
|
-
@time_adjustment = 0
|
22
|
-
@stages_without_progress_bar = options[:stages_without_progress_bar] || []
|
23
|
-
end
|
24
|
-
|
25
|
-
def add_output(output)
|
26
|
-
output.to_s.split("\n").each { |line| add_event(line) }
|
27
|
-
end
|
28
|
-
|
29
|
-
def add_event(event_line)
|
30
|
-
event = parse_event(event_line)
|
31
|
-
|
32
|
-
@lock.synchronize do
|
33
|
-
if event['type'] == 'deprecation'
|
34
|
-
@buffer.puts("Deprecation: #{event['message']}".make_red)
|
35
|
-
return
|
36
|
-
end
|
37
|
-
|
38
|
-
# Handling the special "error" event
|
39
|
-
if event['error']
|
40
|
-
done_with_stage if @current_stage
|
41
|
-
add_error(event)
|
42
|
-
return
|
43
|
-
end
|
44
16
|
|
45
|
-
|
46
|
-
if @current_stage
|
47
|
-
done_with_stage
|
48
|
-
@current_stage = nil
|
49
|
-
@buffer.print "\n"
|
50
|
-
end
|
51
|
-
handle_event_without_progress_bar(event)
|
52
|
-
return
|
53
|
-
end
|
17
|
+
presenter = StageCollectionPresenter.new(@printer)
|
54
18
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# bars and messages. However we could always support
|
60
|
-
# resuming the older stages rendering if we feel
|
61
|
-
# that it's valuable.
|
62
|
-
|
63
|
-
tags = event['tags'].is_a?(Array) ? event['tags'] : []
|
64
|
-
stage_header = event['stage']
|
65
|
-
|
66
|
-
if tags.size > 0
|
67
|
-
stage_header += ' ' + tags.sort.join(', ').make_green
|
68
|
-
end
|
19
|
+
@stage_collection = StageCollection.new(
|
20
|
+
stage_started: presenter.method(:start_stage),
|
21
|
+
stage_finished: presenter.method(:finish_stage),
|
22
|
+
stage_failed: presenter.method(:fail_stage),
|
69
23
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
if @current_stage == stage_header
|
76
|
-
append_event(event)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
rescue InvalidEvent => e
|
81
|
-
# Swallow for the moment
|
82
|
-
end
|
83
|
-
|
84
|
-
def begin_stage(event, header)
|
85
|
-
@current_stage = header
|
86
|
-
@seen_stages << @current_stage
|
87
|
-
|
88
|
-
@stage_start_time = Time.at(event['time']) rescue Time.now
|
89
|
-
@local_start_time = adjusted_time(@stage_start_time)
|
90
|
-
|
91
|
-
@tasks = {}
|
92
|
-
@done_tasks = []
|
93
|
-
|
94
|
-
@eta = nil
|
95
|
-
@stage_has_error = false # Error flag
|
96
|
-
# Tracks max_in_flight best guess
|
97
|
-
@tasks_batch_size = 0
|
98
|
-
@batches_count = 0
|
99
|
-
|
100
|
-
# Running average of task completion time
|
101
|
-
@running_avg = 0
|
102
|
-
|
103
|
-
append_stage_header
|
104
|
-
end
|
105
|
-
|
106
|
-
def render
|
107
|
-
@lock.synchronize do
|
108
|
-
@buffer.seek(@pos)
|
109
|
-
output = @buffer.read
|
110
|
-
@out.print output
|
111
|
-
@pos = @buffer.tell
|
112
|
-
output
|
113
|
-
end
|
24
|
+
task_started: presenter.method(:start_task),
|
25
|
+
task_finished: presenter.method(:finish_task),
|
26
|
+
task_failed: presenter.method(:fail_task),
|
27
|
+
)
|
114
28
|
end
|
115
29
|
|
116
|
-
|
117
|
-
error = event['error'] || {}
|
118
|
-
code = error['code']
|
119
|
-
message = error['message']
|
120
|
-
|
121
|
-
error = 'Error'
|
122
|
-
error += " #{code}" if code
|
123
|
-
error += ": #{message}" if message
|
124
|
-
|
125
|
-
@buffer.puts("\n" + error.make_red)
|
126
|
-
end
|
30
|
+
alias_method :add_raw_output, :add_output
|
127
31
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
32
|
+
def add_output(output)
|
33
|
+
output.to_s.split("\n").each do |line|
|
34
|
+
begin
|
35
|
+
event = parse_event(line)
|
36
|
+
add_event(event) if event
|
37
|
+
rescue InvalidEvent => e
|
38
|
+
@printer.print(:line_around, "Received invalid event: #{e.message}".make_red)
|
135
39
|
end
|
136
|
-
render
|
137
40
|
end
|
41
|
+
|
42
|
+
add_raw_output(@printer.output)
|
138
43
|
end
|
139
44
|
|
140
45
|
def finish(state)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@done = true
|
145
|
-
done_with_stage(state)
|
146
|
-
render
|
147
|
-
end
|
46
|
+
@printer.finish
|
47
|
+
add_raw_output(@printer.output)
|
48
|
+
super
|
148
49
|
end
|
149
50
|
|
150
51
|
private
|
151
52
|
|
152
|
-
def
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
def done_with_stage(state = nil)
|
157
|
-
return unless @in_progress
|
158
|
-
|
159
|
-
if @last_event
|
160
|
-
completion_time = Time.at(@last_event['time']) rescue Time.now
|
161
|
-
else
|
162
|
-
completion_time = Time.now
|
163
|
-
end
|
164
|
-
|
165
|
-
if state.nil?
|
166
|
-
state = @stage_has_error ? 'error' : 'done'
|
167
|
-
end
|
168
|
-
|
169
|
-
case state.to_s
|
170
|
-
when 'done'
|
171
|
-
progress_bar.title = 'Done'.make_green
|
172
|
-
progress_bar.finished_steps = progress_bar.total
|
173
|
-
when 'error'
|
174
|
-
progress_bar.title = 'Error'.make_red
|
175
|
-
else
|
176
|
-
progress_bar.title = 'Not done'.make_yellow
|
177
|
-
end
|
178
|
-
|
179
|
-
progress_bar.bar_visible = false
|
180
|
-
progress_bar.label = format_time(completion_time - @stage_start_time)
|
181
|
-
progress_bar.refresh
|
182
|
-
@buffer.print "\n"
|
183
|
-
@in_progress = false
|
184
|
-
end
|
185
|
-
|
186
|
-
def progress_bar
|
187
|
-
@progress_bars[@current_stage] ||= StageProgressBar.new(@buffer)
|
188
|
-
end
|
189
|
-
|
190
|
-
# We have to trust the first event in each stage
|
191
|
-
# to have correct "total" and "current" fields.
|
192
|
-
def append_event(event)
|
193
|
-
validate_event(event)
|
194
|
-
|
195
|
-
progress = 0
|
196
|
-
total = event['total'].to_i
|
197
|
-
|
198
|
-
if event['state'] == 'started'
|
199
|
-
task = Task.new(event['task'])
|
200
|
-
else
|
201
|
-
task = @tasks[event['index']]
|
202
|
-
end
|
203
|
-
|
204
|
-
event_data = event['data'] || {}
|
205
|
-
# Ignoring out-of-order events
|
206
|
-
return if task.nil?
|
207
|
-
|
208
|
-
@events_count += 1
|
209
|
-
@last_event = event
|
210
|
-
|
211
|
-
case event['state']
|
212
|
-
when 'started'
|
213
|
-
@total_duration.started_at = event['time']
|
214
|
-
|
215
|
-
begin
|
216
|
-
task.start_time = Time.at(event['time'])
|
217
|
-
rescue
|
218
|
-
task.start_time = Time.now
|
219
|
-
end
|
220
|
-
|
221
|
-
task.progress = 0
|
222
|
-
|
223
|
-
@tasks[event['index']] = task
|
224
|
-
|
225
|
-
if @tasks.size > @tasks_batch_size
|
226
|
-
# Heuristics here: we assume that local maximum of
|
227
|
-
# tasks number is a "max_in_flight" value and batches count
|
228
|
-
# should only be recalculated once we refresh this maximum.
|
229
|
-
# It's unlikely that the first task in a batch will be finished
|
230
|
-
# before the last one is started so @done_tasks is expected
|
231
|
-
# to only have canaries.
|
232
|
-
@tasks_batch_size = @tasks.size
|
233
|
-
@non_canary_event_start_time = task.start_time
|
234
|
-
@batches_count = ((total - @done_tasks.size) / @tasks_batch_size.to_f).ceil
|
235
|
-
end
|
236
|
-
|
237
|
-
when 'finished', 'failed'
|
238
|
-
@tasks.delete(event['index'])
|
239
|
-
@done_tasks << task
|
240
|
-
|
241
|
-
@total_duration.finished_at = event['time']
|
242
|
-
|
243
|
-
begin
|
244
|
-
task.finish_time = Time.at(event['time'])
|
245
|
-
rescue
|
246
|
-
task.finish_time = Time.now
|
247
|
-
end
|
248
|
-
|
249
|
-
task_time = task.finish_time - task.start_time
|
250
|
-
|
251
|
-
n_done_tasks = @done_tasks.size.to_f
|
252
|
-
@running_avg = @running_avg * (n_done_tasks - 1) / n_done_tasks + task_time.to_f / n_done_tasks
|
253
|
-
|
254
|
-
progress = 1
|
255
|
-
progress_bar.finished_steps += 1
|
256
|
-
progress_bar.label = time_with_eta(task_time, @eta)
|
257
|
-
progress_bar.clear_line
|
258
|
-
|
259
|
-
task_name = task.name.to_s
|
260
|
-
if task_name !~ /^[A-Z]{2}/
|
261
|
-
task_name = task_name[0..0].to_s.downcase + task_name[1..-1].to_s
|
262
|
-
end
|
53
|
+
def parse_event(event_line)
|
54
|
+
return if event_line.start_with?('#')
|
263
55
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
else
|
268
|
-
status = task_name.make_yellow
|
56
|
+
JSON.parse(event_line).tap do |result|
|
57
|
+
unless result.kind_of?(Hash)
|
58
|
+
raise InvalidEvent, "Hash expected, #{result.class} given"
|
269
59
|
end
|
270
|
-
@buffer.puts(" #{status} (#{format_time(task_time)})")
|
271
|
-
|
272
|
-
when 'in_progress'
|
273
|
-
progress = [event['progress'].to_f / 100, 1].min
|
274
60
|
end
|
275
61
|
|
276
|
-
|
277
|
-
|
278
|
-
end
|
279
|
-
|
280
|
-
progress_bar_gain = progress - task.progress
|
281
|
-
task.progress = progress
|
282
|
-
|
283
|
-
progress_bar.total = total
|
284
|
-
progress_bar.title = @tasks.values.map { |t| t.name }.sort.join(', ')
|
285
|
-
|
286
|
-
progress_bar.current += progress_bar_gain
|
287
|
-
progress_bar.refresh
|
288
|
-
|
289
|
-
@in_progress = true
|
62
|
+
rescue JSON::JSONError => e
|
63
|
+
raise InvalidEvent, "Invalid JSON: #{e.message}"
|
290
64
|
end
|
291
65
|
|
292
|
-
def
|
293
|
-
|
294
|
-
|
295
|
-
raise InvalidEvent, "Hash expected, #{event.class} given"
|
296
|
-
end
|
297
|
-
event
|
298
|
-
rescue JSON::JSONError
|
299
|
-
raise InvalidEvent, 'Cannot parse event, invalid JSON'
|
300
|
-
end
|
66
|
+
def add_event(event)
|
67
|
+
@total_duration.started_at = event['time']
|
68
|
+
@total_duration.finished_at = event['time']
|
301
69
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
70
|
+
if event['type'] == 'deprecation'
|
71
|
+
show_deprecation(event)
|
72
|
+
elsif event['error']
|
73
|
+
show_error(event)
|
74
|
+
else
|
75
|
+
show_stage_or_task(event)
|
307
76
|
end
|
308
77
|
end
|
309
78
|
|
310
|
-
def
|
311
|
-
|
312
|
-
|
313
|
-
"#{time_fmt} ETA: #{eta_fmt}"
|
314
|
-
end
|
315
|
-
|
316
|
-
def adjusted_time(time)
|
317
|
-
time + @time_adjustment.to_f
|
79
|
+
def show_deprecation(event)
|
80
|
+
msg = "Deprecation: #{event['message']}"
|
81
|
+
@printer.print(:line_around, msg.make_red)
|
318
82
|
end
|
319
83
|
|
320
|
-
def
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
def handle_event_without_progress_bar(event)
|
325
|
-
@total_duration.started_at = event['time']
|
326
|
-
@total_duration.finished_at = event['time']
|
327
|
-
stage_collection.update_with_event(event)
|
328
|
-
end
|
329
|
-
|
330
|
-
def stage_collection
|
331
|
-
@stage_collection ||= StageCollection.new(
|
332
|
-
stage_started: ->(stage){
|
333
|
-
@buffer.print(" Started #{header_for_stage(stage)}\n")
|
334
|
-
},
|
335
|
-
stage_finished: ->(stage){
|
336
|
-
duration = stage.duration ? " (#{format_time(stage.duration)})" : ''
|
337
|
-
@buffer.print(" Done #{header_for_stage(stage)}#{duration}\n\n")
|
338
|
-
},
|
339
|
-
stage_failed: ->(stage){
|
340
|
-
duration = stage.duration ? " (#{format_time(stage.duration)})" : ''
|
341
|
-
@buffer.print(" Failed #{header_for_stage(stage)}#{duration}\n")
|
342
|
-
},
|
343
|
-
|
344
|
-
task_started: ->(task){
|
345
|
-
@buffer.print(" Started #{header_for_task(task)}\n")
|
346
|
-
},
|
347
|
-
task_finished: ->(task){
|
348
|
-
duration = task.duration ? " (#{format_time(task.duration)})" : ''
|
349
|
-
@buffer.print(" Done #{header_for_task(task)}#{duration}\n")
|
350
|
-
},
|
351
|
-
task_failed: ->(task){
|
352
|
-
error_msg = task.error
|
353
|
-
error_msg = ": #{error_msg.make_red}" if error_msg
|
354
|
-
duration = task.duration ? " (#{format_time(task.duration)})" : ''
|
355
|
-
@buffer.print(" Failed #{header_for_task(task)}#{duration}#{error_msg}\n")
|
356
|
-
},
|
357
|
-
)
|
358
|
-
end
|
84
|
+
def show_error(event)
|
85
|
+
error = event['error'] || {}
|
86
|
+
code = error['code']
|
87
|
+
message = error['message']
|
359
88
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
"#{stage.name.downcase}#{tags_str}"
|
364
|
-
end
|
89
|
+
msg = 'Error'
|
90
|
+
msg += " #{code}" if code
|
91
|
+
msg += ": #{message}" if message
|
365
92
|
|
366
|
-
|
367
|
-
tags = task.stage.tags
|
368
|
-
tags_str = tags.size > 0 ? ' ' + tags.sort.join(', ').make_green : ''
|
369
|
-
"#{task.stage.name.downcase}#{tags_str}: #{task.name}"
|
93
|
+
@printer.print(:line_around, msg.make_red)
|
370
94
|
end
|
371
95
|
|
372
|
-
|
373
|
-
attr_accessor :name
|
374
|
-
attr_accessor :progress
|
375
|
-
attr_accessor :start_time
|
376
|
-
attr_accessor :finish_time
|
96
|
+
REQUIRED_STAGE_EVENT_KEYS = %w(time stage task index total state).freeze
|
377
97
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
98
|
+
def show_stage_or_task(event)
|
99
|
+
REQUIRED_STAGE_EVENT_KEYS.each do |key|
|
100
|
+
unless event.has_key?(key)
|
101
|
+
raise InvalidEvent, "Missing event key: #{key}"
|
102
|
+
end
|
383
103
|
end
|
104
|
+
|
105
|
+
@stage_collection.update_with_event(event)
|
384
106
|
end
|
385
107
|
end
|
386
108
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Bosh::Cli::TaskTracking
|
4
|
+
class SmartWhitespacePrinter
|
5
|
+
VALID_SEPARATORS = [:line_around, :line_before, :before, :none].freeze
|
6
|
+
|
7
|
+
SPACE_BETWEEN_LAST_SEP_AND_SEP = {
|
8
|
+
[:line_around, :line_around] => "\n\n",
|
9
|
+
[:line_around, :line_before] => "\n\n",
|
10
|
+
[:line_around, :before] => "\n\n",
|
11
|
+
[:line_around, :none] => "\n\n",
|
12
|
+
|
13
|
+
[:line_before, :line_around] => "\n\n",
|
14
|
+
[:line_before, :line_before] => "\n\n",
|
15
|
+
[:line_before, :before] => "\n",
|
16
|
+
[:line_before, :none] => nil,
|
17
|
+
|
18
|
+
[:before, :line_around] => "\n\n",
|
19
|
+
[:before, :line_before] => "\n\n",
|
20
|
+
[:before, :before] => "\n",
|
21
|
+
[:before, :none] => nil,
|
22
|
+
|
23
|
+
[:none, :line_around] => "\n\n",
|
24
|
+
[:none, :line_before] => "\n\n",
|
25
|
+
[:none, :before] => "\n",
|
26
|
+
[:none, :none] => nil,
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@buffer = StringIO.new
|
31
|
+
@last_sep = :start
|
32
|
+
end
|
33
|
+
|
34
|
+
def print(separator, msg)
|
35
|
+
unless VALID_SEPARATORS.include?(separator)
|
36
|
+
raise ArgumentError, "Unknown separator #{separator.inspect}"
|
37
|
+
end
|
38
|
+
|
39
|
+
space = SPACE_BETWEEN_LAST_SEP_AND_SEP[[@last_sep, separator]]
|
40
|
+
@buffer.print(space) if space
|
41
|
+
|
42
|
+
@last_sep = separator
|
43
|
+
@buffer.print(msg)
|
44
|
+
end
|
45
|
+
|
46
|
+
def output
|
47
|
+
@buffer.string.tap { @buffer.string = '' }
|
48
|
+
end
|
49
|
+
|
50
|
+
def finish
|
51
|
+
if VALID_SEPARATORS.include?(@last_sep)
|
52
|
+
@buffer.print("\n")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -9,7 +9,7 @@ module Bosh::Cli::TaskTracking
|
|
9
9
|
|
10
10
|
def update_with_event(event)
|
11
11
|
new_stage = Stage.new(event['stage'], event['tags'], event['total'], @callbacks)
|
12
|
-
unless found_stage = @stages.find { |s| s
|
12
|
+
unless found_stage = @stages.find { |s| s == new_stage }
|
13
13
|
found_stage = new_stage
|
14
14
|
@stages << new_stage
|
15
15
|
end
|
@@ -31,14 +31,14 @@ module Bosh::Cli::TaskTracking
|
|
31
31
|
|
32
32
|
def update_with_event(event)
|
33
33
|
new_task = Task.new(self, event['task'], event['progress'], @callbacks)
|
34
|
-
unless found_task = @tasks.find { |
|
34
|
+
unless found_task = @tasks.find { |s| s == new_task }
|
35
35
|
found_task = new_task
|
36
36
|
@tasks << new_task
|
37
37
|
end
|
38
|
-
fire_started_callback
|
38
|
+
fire_started_callback if started?
|
39
39
|
found_task.update_with_event(event)
|
40
|
-
fire_finished_callback(event)
|
41
|
-
fire_failed_callback(event)
|
40
|
+
fire_finished_callback if finished?(event)
|
41
|
+
fire_failed_callback if failed?(event)
|
42
42
|
found_task
|
43
43
|
end
|
44
44
|
|
@@ -55,28 +55,59 @@ module Bosh::Cli::TaskTracking
|
|
55
55
|
total_duration.duration
|
56
56
|
end
|
57
57
|
|
58
|
+
def similar?(other)
|
59
|
+
return false unless other.is_a?(Stage)
|
60
|
+
name == other.name
|
61
|
+
end
|
62
|
+
|
63
|
+
def ==(other)
|
64
|
+
return false unless other.is_a?(Stage)
|
65
|
+
[name, tags, total] == [other.name, other.tags, other.total]
|
66
|
+
end
|
67
|
+
|
58
68
|
private
|
59
69
|
|
60
|
-
def fire_started_callback
|
61
|
-
|
62
|
-
|
63
|
-
callback.call(self) if callback
|
64
|
-
end
|
70
|
+
def fire_started_callback
|
71
|
+
callback = @callbacks[:stage_started]
|
72
|
+
callback.call(self) if callback
|
65
73
|
end
|
66
74
|
|
67
|
-
def fire_finished_callback
|
68
|
-
|
69
|
-
|
70
|
-
callback.call(self) if callback
|
71
|
-
end
|
75
|
+
def fire_finished_callback
|
76
|
+
callback = @callbacks[:stage_finished]
|
77
|
+
callback.call(self) if callback
|
72
78
|
end
|
73
79
|
|
74
|
-
def fire_failed_callback
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
+
def fire_failed_callback
|
81
|
+
callback = @callbacks[:stage_failed]
|
82
|
+
callback.call(self) if callback
|
83
|
+
end
|
84
|
+
|
85
|
+
def started?
|
86
|
+
@started = true if !@started
|
87
|
+
end
|
88
|
+
|
89
|
+
def finished?(event)
|
90
|
+
seen_all_tasks?(event) && all_tasks_finished?
|
91
|
+
end
|
92
|
+
|
93
|
+
def failed?(event)
|
94
|
+
seen_all_tasks?(event) && all_tasks_done? && any_tasks_failed?
|
95
|
+
end
|
96
|
+
|
97
|
+
def all_tasks_done?
|
98
|
+
tasks.all? { |t| t.done? }
|
99
|
+
end
|
100
|
+
|
101
|
+
def all_tasks_finished?
|
102
|
+
tasks.all? { |t| t.finished? }
|
103
|
+
end
|
104
|
+
|
105
|
+
def any_tasks_failed?
|
106
|
+
tasks.any? { |t| t.failed? }
|
107
|
+
end
|
108
|
+
|
109
|
+
def seen_all_tasks?(event)
|
110
|
+
tasks.size == event['total'] || event['total'].nil?
|
80
111
|
end
|
81
112
|
end
|
82
113
|
|
@@ -105,6 +136,23 @@ module Bosh::Cli::TaskTracking
|
|
105
136
|
call_state_callback
|
106
137
|
end
|
107
138
|
|
139
|
+
def ==(other)
|
140
|
+
return false unless other.is_a?(Task)
|
141
|
+
[stage, name] == [other.stage, other.name]
|
142
|
+
end
|
143
|
+
|
144
|
+
def done?
|
145
|
+
%w(failed finished).include?(@state)
|
146
|
+
end
|
147
|
+
|
148
|
+
def failed?
|
149
|
+
@state == 'failed'
|
150
|
+
end
|
151
|
+
|
152
|
+
def finished?
|
153
|
+
@state == 'finished'
|
154
|
+
end
|
155
|
+
|
108
156
|
private
|
109
157
|
|
110
158
|
def call_state_callback
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Bosh::Cli::TaskTracking
|
2
|
+
class StageCollectionPresenter
|
3
|
+
JUSTIFY = 9
|
4
|
+
|
5
|
+
def initialize(printer)
|
6
|
+
@printer = printer
|
7
|
+
@last_stage = nil
|
8
|
+
@last_task = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_stage(stage)
|
12
|
+
msg_new_line = " Started #{header_for_stage(stage)}"
|
13
|
+
|
14
|
+
# Assume that duplicate start events are never received
|
15
|
+
if stage.similar?(@last_stage)
|
16
|
+
@printer.print(:before, msg_new_line)
|
17
|
+
else
|
18
|
+
@printer.print(:line_before, msg_new_line)
|
19
|
+
end
|
20
|
+
|
21
|
+
@last_stage = stage
|
22
|
+
@last_task = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def finish_stage(stage)
|
26
|
+
end_stage(stage, 'Done')
|
27
|
+
end
|
28
|
+
|
29
|
+
def fail_stage(stage)
|
30
|
+
end_stage(stage, 'Failed')
|
31
|
+
end
|
32
|
+
|
33
|
+
def end_stage(stage, prefix_msg)
|
34
|
+
duration = duration_str(stage)
|
35
|
+
msg = "#{prefix_msg.rjust(JUSTIFY)} #{header_for_stage(stage)}#{duration}"
|
36
|
+
|
37
|
+
if stage == @last_stage
|
38
|
+
if stage.total == 1
|
39
|
+
# end_task added inline end message
|
40
|
+
else
|
41
|
+
@printer.print(:before, msg)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@printer.print(:line_before, msg)
|
45
|
+
end
|
46
|
+
|
47
|
+
@last_stage = stage
|
48
|
+
@last_task = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def start_task(task)
|
52
|
+
msg = " Started #{full_header_for_task(task)}"
|
53
|
+
|
54
|
+
if task.stage == @last_stage && task.stage.total == 1
|
55
|
+
@printer.print(:none, " > #{task.name.make_green}")
|
56
|
+
elsif task.stage.similar?(@last_stage)
|
57
|
+
@printer.print(:before, msg)
|
58
|
+
else
|
59
|
+
@printer.print(:line_before, msg)
|
60
|
+
end
|
61
|
+
|
62
|
+
@last_stage = task.stage
|
63
|
+
@last_task = task
|
64
|
+
end
|
65
|
+
|
66
|
+
def finish_task(task)
|
67
|
+
end_task(task, 'Done', nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
def fail_task(task)
|
71
|
+
error_msg = task.error
|
72
|
+
error_msg = ": #{error_msg.make_red}" if error_msg
|
73
|
+
|
74
|
+
end_task(task, 'Failed', error_msg)
|
75
|
+
end
|
76
|
+
|
77
|
+
def end_task(task, prefix_msg, suffix_msg)
|
78
|
+
duration = duration_str(task)
|
79
|
+
msg = "#{prefix_msg.rjust(JUSTIFY)} #{full_header_for_task(task)}#{suffix_msg}#{duration}"
|
80
|
+
|
81
|
+
if task == @last_task
|
82
|
+
@printer.print(:none, ". #{prefix_msg}#{suffix_msg}#{duration}")
|
83
|
+
elsif task.stage.similar?(@last_stage)
|
84
|
+
@printer.print(:before, msg)
|
85
|
+
else
|
86
|
+
@printer.print(:line_before, msg)
|
87
|
+
end
|
88
|
+
|
89
|
+
@last_stage = task.stage
|
90
|
+
@last_task = task
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def header_for_stage(stage)
|
96
|
+
"#{stage.name.downcase}#{tags_for_stage(stage)}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def full_header_for_task(task)
|
100
|
+
"#{task.stage.name.downcase}#{tags_for_stage(task.stage)} > #{task.name.make_green}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def tags_for_stage(stage)
|
104
|
+
stage.tags.size > 0 ? ' ' + stage.tags.sort.join(', ').make_green : ''
|
105
|
+
end
|
106
|
+
|
107
|
+
def duration_str(stage_or_task)
|
108
|
+
stage_or_task.duration ? " (#{format_time(stage_or_task.duration)})" : ''
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -1,20 +1,8 @@
|
|
1
1
|
module Bosh::Cli::TaskTracking
|
2
2
|
class TaskLogRenderer
|
3
|
-
EVENT_LOG_STAGES_WITHOUT_PROGRESS_BAR = [
|
4
|
-
'Preparing DNS',
|
5
|
-
'Creating bound missing VMs',
|
6
|
-
'Binding instance VMs',
|
7
|
-
'Updating job',
|
8
|
-
'Deleting unneeded instances',
|
9
|
-
'Running errand',
|
10
|
-
'Deleting instances',
|
11
|
-
'Refilling resource pools',
|
12
|
-
]
|
13
|
-
|
14
3
|
def self.create_for_log_type(log_type)
|
15
4
|
if log_type == 'event'
|
16
|
-
EventLogRenderer.new
|
17
|
-
EVENT_LOG_STAGES_WITHOUT_PROGRESS_BAR)
|
5
|
+
EventLogRenderer.new
|
18
6
|
elsif log_type == 'result' || log_type == 'none'
|
19
7
|
NullTaskLogRenderer.new
|
20
8
|
else
|
@@ -28,16 +16,12 @@ module Bosh::Cli::TaskTracking
|
|
28
16
|
def initialize
|
29
17
|
@out = Bosh::Cli::Config.output || $stdout
|
30
18
|
@out.sync = true
|
31
|
-
@lock = Mutex.new
|
32
19
|
@output = ''
|
20
|
+
|
33
21
|
@time_adjustment = 0
|
34
22
|
@duration = nil
|
35
23
|
end
|
36
24
|
|
37
|
-
def duration_known?
|
38
|
-
false
|
39
|
-
end
|
40
|
-
|
41
25
|
def add_output(output)
|
42
26
|
@output = output
|
43
27
|
end
|
@@ -49,7 +33,10 @@ module Bosh::Cli::TaskTracking
|
|
49
33
|
|
50
34
|
def finish(state)
|
51
35
|
refresh
|
52
|
-
|
36
|
+
end
|
37
|
+
|
38
|
+
def duration_known?
|
39
|
+
false
|
53
40
|
end
|
54
41
|
end
|
55
42
|
end
|
data/lib/cli/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bosh_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2291.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-03-
|
12
|
+
date: 2014-03-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bosh_common
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.
|
21
|
+
version: 1.2291.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 1.
|
29
|
+
version: 1.2291.0
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: json_pure
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,7 +114,7 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 1.
|
117
|
+
version: 1.2291.0
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
requirements:
|
123
123
|
- - ~>
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: 1.
|
125
|
+
version: 1.2291.0
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: net-ssh
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -269,7 +269,7 @@ dependencies:
|
|
269
269
|
version: '0'
|
270
270
|
description: ! 'BOSH CLI
|
271
271
|
|
272
|
-
|
272
|
+
51a1bb'
|
273
273
|
email: support@cloudfoundry.com
|
274
274
|
executables:
|
275
275
|
- bosh
|
@@ -346,8 +346,9 @@ files:
|
|
346
346
|
- lib/cli/task_tracking.rb
|
347
347
|
- lib/cli/task_tracking/event_log_renderer.rb
|
348
348
|
- lib/cli/task_tracking/null_task_log_renderer.rb
|
349
|
+
- lib/cli/task_tracking/smart_whitespace_printer.rb
|
349
350
|
- lib/cli/task_tracking/stage_collection.rb
|
350
|
-
- lib/cli/task_tracking/
|
351
|
+
- lib/cli/task_tracking/stage_collection_presenter.rb
|
351
352
|
- lib/cli/task_tracking/task_log_renderer.rb
|
352
353
|
- lib/cli/task_tracking/task_tracker.rb
|
353
354
|
- lib/cli/task_tracking/total_duration.rb
|
@@ -379,7 +380,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
379
380
|
version: '0'
|
380
381
|
segments:
|
381
382
|
- 0
|
382
|
-
hash:
|
383
|
+
hash: -3167564348924441903
|
383
384
|
requirements: []
|
384
385
|
rubyforge_project:
|
385
386
|
rubygems_version: 1.8.23
|
@@ -1,59 +0,0 @@
|
|
1
|
-
module Bosh::Cli::TaskTracking
|
2
|
-
class StageProgressBar
|
3
|
-
attr_accessor :total
|
4
|
-
attr_accessor :title
|
5
|
-
attr_accessor :current
|
6
|
-
attr_accessor :label
|
7
|
-
attr_accessor :bar_visible
|
8
|
-
attr_accessor :finished_steps
|
9
|
-
attr_accessor :terminal_width
|
10
|
-
|
11
|
-
def initialize(output)
|
12
|
-
@output = output
|
13
|
-
@current = 0
|
14
|
-
@total = 100
|
15
|
-
@bar_visible = true
|
16
|
-
@finished_steps = 0
|
17
|
-
@filler = 'o'
|
18
|
-
@terminal_width = calculate_terminal_width
|
19
|
-
@bar_width = (0.24 * @terminal_width).to_i # characters
|
20
|
-
end
|
21
|
-
|
22
|
-
def refresh
|
23
|
-
clear_line
|
24
|
-
bar_repr = @bar_visible ? bar : ''
|
25
|
-
title_width = (0.35 * @terminal_width).to_i
|
26
|
-
title = @title.truncate(title_width).ljust(title_width)
|
27
|
-
@output.print "#{title} #{bar_repr} #{@finished_steps}/#{@total}"
|
28
|
-
@output.print " #{@label}" if @label
|
29
|
-
end
|
30
|
-
|
31
|
-
def bar
|
32
|
-
n_fillers = @total == 0 ? 0 : [(@bar_width *
|
33
|
-
(@current.to_f / @total.to_f)).floor, 0].max
|
34
|
-
|
35
|
-
fillers = "#{@filler}" * n_fillers
|
36
|
-
spaces = ' ' * [(@bar_width - n_fillers), 0].max
|
37
|
-
"|#{fillers}#{spaces}|"
|
38
|
-
end
|
39
|
-
|
40
|
-
def clear_line
|
41
|
-
@output.print("\r")
|
42
|
-
@output.print(' ' * @terminal_width)
|
43
|
-
@output.print("\r")
|
44
|
-
end
|
45
|
-
|
46
|
-
def calculate_terminal_width
|
47
|
-
if ENV['COLUMNS'].to_s =~ /^\d+$/
|
48
|
-
ENV['COLUMNS'].to_i
|
49
|
-
elsif !ENV['TERM'].blank?
|
50
|
-
width = `tput cols`
|
51
|
-
$?.exitstatus == 0 ? [width.to_i, 100].min : 80
|
52
|
-
else
|
53
|
-
80
|
54
|
-
end
|
55
|
-
rescue
|
56
|
-
80
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|