bosh_cli 1.2200.0 → 1.2291.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- attr_reader :current_stage
9
- attr_reader :events_count
11
+ def initialize
12
+ super
10
13
 
11
- def initialize(options={})
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
- if can_handle_event_without_progress_bar?(event)
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
- # One way to handle old stages is to prevent them
56
- # from appearing on screen altogether. That means
57
- # that we can always render the current stage only
58
- # and that simplifies housekeeping around progress
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
- unless @seen_stages.include?(stage_header)
71
- done_with_stage if @current_stage
72
- begin_stage(event, stage_header)
73
- end
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
- def add_error(event)
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 refresh
129
- # This is primarily used to refresh timer
130
- # without advancing rendering buffer
131
- @lock.synchronize do
132
- if @in_progress
133
- progress_bar.label = time_with_eta(Time.now - @local_start_time, @eta)
134
- progress_bar.refresh
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
- return if @events_count == 0
142
-
143
- @lock.synchronize do
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 append_stage_header
153
- @buffer.print "\n#{@current_stage}\n"
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
- if event['state'] == 'failed'
265
- status = [task_name.make_red, event_data['error']].compact.join(': ')
266
- @stage_has_error = true
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
- if @batches_count > 0 && @non_canary_event_start_time
277
- @eta = adjusted_time(@non_canary_event_start_time + @running_avg * @batches_count)
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 parse_event(event_line)
293
- event = JSON.parse(event_line)
294
- unless event.kind_of?(Hash)
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
- def validate_event(event)
303
- unless event['time'] && event['stage'] && event['task'] &&
304
- event['index'] && event['total'] && event['state']
305
- raise InvalidEvent, 'Invalid event structure: stage, time, task, ' +
306
- 'index, total, state are all required'
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 time_with_eta(time, eta)
311
- time_fmt = format_time(time)
312
- eta_fmt = eta && eta > Time.now ? format_time(eta - Time.now) : '--:--:--'
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 can_handle_event_without_progress_bar?(event)
321
- @stages_without_progress_bar.include?(event['stage'])
322
- end
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
- def header_for_stage(stage)
361
- tags = stage.tags
362
- tags_str = tags.size > 0 ? ' ' + tags.sort.join(', ').make_green : ''
363
- "#{stage.name.downcase}#{tags_str}"
364
- end
89
+ msg = 'Error'
90
+ msg += " #{code}" if code
91
+ msg += ": #{message}" if message
365
92
 
366
- def header_for_task(task)
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
- class Task
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
- def initialize(name)
379
- @name = name
380
- @progress = 0
381
- @start_time = nil
382
- @finish_time = nil
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.name == new_stage.name && s.tags == new_stage.tags }
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 { |t| t.name == new_task.name }
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(event)
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(event)
61
- if event['state'] == 'started' && event['index'] == 1
62
- callback = @callbacks[:stage_started]
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(event)
68
- if event['state'] == 'finished' && ((event['index'] == event['total']) || event['total'].nil?)
69
- callback = @callbacks[:stage_finished]
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(event)
75
- if event['state'] == 'failed'
76
- # If there are multiple failures do we need to only fire on the first one?
77
- callback = @callbacks[:stage_failed]
78
- callback.call(self) if callback
79
- end
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(stages_without_progress_bar:
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
- @done = true
36
+ end
37
+
38
+ def duration_known?
39
+ false
53
40
  end
54
41
  end
55
42
  end
@@ -1,5 +1,5 @@
1
1
  module Bosh
2
2
  module Cli
3
- VERSION = '1.2200.0'
3
+ VERSION = '1.2291.0'
4
4
  end
5
5
  end
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.2200.0
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-14 00:00:00.000000000 Z
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.2200.0
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.2200.0
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.2200.0
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.2200.0
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
- 87cbf9'
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/stage_progress_bar.rb
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: 1307915788410922984
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