class DurationEstimate

Do something to a collection of items and see how long it is going to take. Useful for long-running Rake tasks.

items = 0..10000 # Some data set.

DurationEstimate.each(items) do |item, e|
  print "\r#{DurationEstimate::TerminalFormatter.format(e)}"

  # Do something time consuming with item.
  sleep 0.001
end

puts

You can specify the collection size if you are using some kind of ORM that does not respond to `:size` method.

media = Media
  .where { created_at > Time.parse('some time') }
  .order(:created_at)

File.open('missing-media.log', 'w') do |log|
  DurationEstimate.each(media, size: media.count) do |medium, e|
    print "\r#{DurationEstimate::TerminalFormatter.format(e)}"

    unless medium.on_s3?
      log.puts medium.id
      log.fsync # Write changes now, be able tail the file.
    end

    sleep 2 # Don't overload AWS S3
  end
end

puts

This is going to re-print a line with something like this:

 1/11 (  9.09 %) -, -
 2/11 ( 18.18 %) 11:47:29, 00:00:34
 3/11 ( 27.27 %) 11:47:30, 00:00:31
 4/11 ( 36.36 %) 11:47:31, 00:00:27
 5/11 ( 45.45 %) 11:47:31, 00:00:23
 6/11 ( 54.55 %) 11:47:30, 00:00:19
 7/11 ( 63.64 %) 11:47:30, 00:00:15
 8/11 ( 72.73 %) 11:47:30, 00:00:11
 9/11 ( 81.82 %) 11:47:30, 00:00:07
10/11 ( 90.91 %) 11:47:30, 00:00:03
11/11 (100.00 %) 11:47:30, 00:00:00

Constants

VERSION

Version, man!

Attributes

items[RW]

Some collection to run through.

@return [Enumerable]

items_done[RW]

Number of items done.

@return [Fixnum]

items_remaining[RW]

Number of items remaining.

@return [Fixnum]

items_size[RW]

Number of items.

@return [Fixnum]

times[RW]

Hold operation times.

@return [SizedQueue]

Public Class Methods

each(*args, &block) click to toggle source

Iterate through collection, measure how long it took, yield the item as well as self to use helper methods.

@yieldparam item [Object] Current item in collection. @yieldparam e [DurationEstimate] This instance.

# File lib/duration_estimate.rb, line 88
def self.each(*args, &block)
  new(*args).each(&block)
end
new(items, options = {}) click to toggle source

Setup.

@param items [Enumerable] Some collection to run through. @param options [Hash] @option options :size [Fixnum] Total number of items.

# File lib/duration_estimate.rb, line 97
def initialize(items, options = {})
  self.items           = items
  self.items_size      = options[:size] || items.size
  self.items_done      = 0
  self.items_remaining = items_size
  self.times           = SizedQueue.new(5)
end

Public Instance Methods

each() { |item, self| ... } click to toggle source

Iterate through collection, measure how long it took, yield the item as well as self to use helper methods.

@yieldparam item [Object] Current item in collection. @yieldparam e [DurationEstimate] This instance.

# File lib/duration_estimate.rb, line 110
def each
  unless block_given?
    return Enumerator.new do |y|
      items.each do |item|
        measure do
          self.items_done      += 1 # rubocop:disable Style/SpaceAroundOperators
          self.items_remaining -= 1

          y << [item, self]
        end
      end
    end
  end

  items.each do |item|
    measure do
      self.items_done      += 1 # rubocop:disable Style/SpaceAroundOperators
      self.items_remaining -= 1

      yield item, self
    end
  end
end
ends_at() click to toggle source

Calculate end time when all will be done.

@return [Time, NullTime]

# File lib/duration_estimate.rb, line 137
def ends_at
  seconds = seconds_left
  return NullTime.new unless seconds
  Time.now + seconds
end
percentage() click to toggle source

Calculates percentage of done items.

@return [Float]

# File lib/duration_estimate.rb, line 163
def percentage
  items_done.to_f / items_size * 100
end
time_remaining() click to toggle source

Calculate future time when all will be well and done.

@return [String]

# File lib/duration_estimate.rb, line 146
def time_remaining
  seconds = seconds_left
  return '-' unless seconds

  minutes, seconds = seconds.divmod(60)
  hours, minutes   = minutes.divmod(60)

  [
    "#{format('%02d', hours)}",
    "#{format('%02d', minutes)}",
    "#{format('%02d', seconds)}"
  ].compact.join(':')
end

Private Instance Methods

measure() { || ... } click to toggle source

Do some operation and record how long it took.

# File lib/duration_estimate.rb, line 179
def measure
  times << Benchmark.realtime { yield }
end
seconds_left() click to toggle source

Calculate how many seconds are left until the end.

@return [Numeric, nil]

# File lib/duration_estimate.rb, line 172
def seconds_left
  average = times.average
  return unless average
  items_remaining * average
end