Class: Progressor::LimitedSequence

Inherits:
Object
  • Object
show all
Includes:
Formatting
Defined in:
lib/progressor/limited_sequence.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Formatting

#format_float, #format_int, #format_time

Constructor Details

#initialize(total_count:, min_samples: 1, max_samples: 100, formatter: nil) ⇒ LimitedSequence

Creates a new LimitedSequence with the given parameters:

  • total_count: The expected number of loops.

  • min_samples: The number of samples to collect before attempting to calculate a time per iteration. Default: 1

  • max_samples: The maximum number of measurements to collect and average. Default: 100.

  • formatter: A callable that accepts the sequence object and returns a custom formatted string.

Raises:



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/progressor/limited_sequence.rb', line 26

def initialize(total_count:, min_samples: 1, max_samples: 100, formatter: nil)
  @total_count = total_count
  @min_samples = min_samples
  @max_samples = [max_samples, total_count].min
  @formatter   = formatter

  raise Error.new("min_samples needs to be a positive number") if min_samples <= 0
  raise Error.new("max_samples needs to be larger than min_samples") if max_samples <= min_samples

  @start_time         = Time.now
  @total_count_digits = total_count.to_s.length
  @current            = 0
  @measurements       = []
  @averages           = []
end

Instance Attribute Details

#currentObject (readonly)

The current loop index, starts at 1



8
9
10
# File 'lib/progressor/limited_sequence.rb', line 8

def current
  @current
end

#max_samplesObject (readonly)

Returns the value of attribute max_samples.



5
6
7
# File 'lib/progressor/limited_sequence.rb', line 5

def max_samples
  @max_samples
end

#min_samplesObject (readonly)

Returns the value of attribute min_samples.



5
6
7
# File 'lib/progressor/limited_sequence.rb', line 5

def min_samples
  @min_samples
end

#start_timeObject (readonly)

The time the object was created



11
12
13
# File 'lib/progressor/limited_sequence.rb', line 11

def start_time
  @start_time
end

#total_countObject (readonly)

Returns the value of attribute total_count.



5
6
7
# File 'lib/progressor/limited_sequence.rb', line 5

def total_count
  @total_count
end

Instance Method Details

#elapsed_timeObject

Returns the time since the object was instantiated, formatted like all the other durations. Useful for a final message to compare initial estimation to actual elapsed time.



124
125
126
# File 'lib/progressor/limited_sequence.rb', line 124

def elapsed_time
  format_time(Time.now - @start_time)
end

#etaObject

Returns an estimation for the Estimated Time of Arrival (time until done).

Calculated by multiplying the average time per iteration with the remaining number of loops.



113
114
115
116
117
118
# File 'lib/progressor/limited_sequence.rb', line 113

def eta
  return nil if @measurements.count < min_samples

  remaining_time = per_iteration * (@total_count - @current)
  remaining_time.round(2)
end

#per_iterationObject

Returns an estimation for the time per single iteration. Implemented as an average of averages to provide a smoother gradient from loop to loop.

Returns nil if not enough samples have been collected yet.



102
103
104
105
# File 'lib/progressor/limited_sequence.rb', line 102

def per_iteration
  return nil if @measurements.count < min_samples
  average(@averages)
end

#push(duration) ⇒ Object

Adds a duration in seconds to the internal storage of samples. Updates averages accordingly.



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/progressor/limited_sequence.rb', line 45

def push(duration)
  @current += 1
  @measurements << duration
  # only keep last `max_samples`
  @measurements.shift if @measurements.count > max_samples

  @averages << average(@measurements)
  @averages = @averages.compact
  # only keep last `max_samples`
  @averages.shift if @averages.count > max_samples
end

#skip(n) ⇒ Object

Skips an iteration, updating the total count and ETA



59
60
61
# File 'lib/progressor/limited_sequence.rb', line 59

def skip(n)
  @total_count -= n
end

#to_sObject

Outputs a textual representation of the current state of the LimitedSequence. Shows:

  • the current number of iterations and the total count

  • completion level in percentage

  • how long a single iteration takes

  • estimated time of arrival (ETA) – time until it’s done

A custom ‘formatter` provided at construction time overrides this default output.

If the “current” number of iterations goes over the total count, an ETA can’t be shown anymore, so it’ll just be the current number over the expected one, and the time per iteration.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/progressor/limited_sequence.rb', line 78

def to_s
  return @formatter.call(self).to_s if @formatter

  if @current > @total_count
    return [
      "#{@current} (expected #{@total_count})",
      "t/i: #{format_time(per_iteration)}",
      "ETA: ???",
    ].join(', ')
  end

  [
    "#{@current.to_s.rjust(@total_count_digits, '0')}/#{@total_count}",
    "#{((@current / @total_count.to_f) * 100).round.to_s.rjust(3, '0')}%",
    "t/i: #{format_time(per_iteration)}",
    "ETA: #{format_time(eta)}",
  ].join(', ')
end