Class: Progressor

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

Overview

Used to measure the running time of parts of a long-running task and output an estimation based on the average of the last 10-100 measurements.

Example usage:

progressor = Progressor.new(total_count: Product.count)

Product.find_each do |product|
  if product.not_something_we_want_to_process?
    progressor.skip(1)
    next
  end

  progressor.run do |progress|
    puts "[#{progress}] Product #{product.id}"
    product.calculate_interesting_stats
  end
end

Example output:

...
[0038/1000, (004%), t/i: 0.5s, ETA: 8m:0.27s] Product 38
[0039/1000, (004%), t/i: 0.5s, ETA: 7m:58.47s] Product 39
[0040/1000, (004%), t/i: 0.5s, ETA: 7m:57.08s] Product 40
...

Constant Summary collapse

VERSION =
'0.0.1'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(total_count:) ⇒ Progressor

Returns a new instance of Progressor.



47
48
49
50
51
52
53
# File 'lib/progressor.rb', line 47

def initialize(total_count:)
  @total_count = total_count
  @total_count_digits = total_count.to_s.length
  @current = 0
  @measurements = []
  @averages = []
end

Class Method Details

.puts(message, &block) ⇒ Object

Utility method to print a message with the time it took to run the contents of the block.

> Progressor.puts(“Working on a thing”) { thing_work }

Working on a thing… Working on a thing DONE: 2.1s



41
42
43
44
45
# File 'lib/progressor.rb', line 41

def self.puts(message, &block)
  Kernel.puts "#{message}..."
  measurement = Benchmark.measure { block.call }
  Kernel.puts "#{message} DONE: #{format_time(measurement.real)}"
end

Instance Method Details

#etaObject



88
89
90
91
92
93
# File 'lib/progressor.rb', line 88

def eta
  return nil if @measurements.count < 10

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

#per_iterationObject



83
84
85
86
# File 'lib/progressor.rb', line 83

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

#runObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/progressor.rb', line 55

def run
  @current += 1

  measurement = Benchmark.measure { yield self }

  @measurements << measurement.real
  # only keep last 1000
  @measurements.shift if @measurements.count > 1000

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

#skip(n) ⇒ Object



70
71
72
# File 'lib/progressor.rb', line 70

def skip(n)
  @total_count -= n
end

#to_sObject



74
75
76
77
78
79
80
81
# File 'lib/progressor.rb', line 74

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