Last active
October 30, 2019 17:47
-
-
Save wojtha/89eac31193e108bc79cd2fd24f1ae5f3 to your computer and use it in GitHub Desktop.
Ruby library to measure time using the Process::CLOCK_MONOTONIC.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Library to measure time using the Process::CLOCK_MONOTONIC. | |
# | |
# @example Usage | |
# timer = MicroTimer.start | |
# sleep 0.1 # some process to measure | |
# puts timer.duration.milliseconds | |
# sleep 0.1 # another process to measure | |
# puts timer.duration.seconds | |
# | |
# @example Usage with block | |
# duration = MicroTimer.duration do | |
# sleep 0.1 # some process to measure | |
# end | |
# puts duration.to_f | |
# | |
class MicroTimer | |
attr_reader :start | |
# Using Time.now to measure elapsed time is wrong. | |
# https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/ | |
def self.uptime | |
Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
end | |
def self.start | |
new | |
end | |
def self.duration(&block) | |
timer = new | |
block.call | |
timer.stop | |
end | |
def initialize | |
@start = MicroTimer.uptime | |
end | |
def duration | |
Duration.new(start, MicroTimer.uptime) | |
end | |
def stop | |
Duration.new(start, MicroTimer.uptime) | |
end | |
class Duration | |
MILLISECOND = 1_000 | |
SECOND = 1_000_000 | |
MINUTE = 60_000_000 | |
HOUR = 3_600_000_000 | |
def self.timer | |
start = MicroTimer.uptime | |
yield | |
finish = MicroTimer.uptime | |
new(start, finish) | |
end | |
attr_reader :start, :finish, :value | |
def initialize(start, finish) | |
@start = start | |
@finish = finish | |
@value = (finish - start) * 1_000_000.0 | |
end | |
def hours | |
value / HOUR | |
end | |
alias :h :hours | |
def minutes | |
value / MINUTE | |
end | |
alias :m :minutes | |
def seconds | |
value / SECOND | |
end | |
alias :s :seconds | |
def milliseconds | |
value / MILLISECOND | |
end | |
alias :ms :milliseconds | |
def microseconds | |
value.to_i | |
end | |
alias :us :microseconds | |
# Fixes Duration can't be coerced into Integer (TypeError) | |
# see https://www.mutuallyhuman.com/blog/2011/01/25/class-coercion-in-ruby/ | |
def coerce(other) | |
[value, other] | |
end | |
# Fixes no implicit conversion of Duration into Integer (TypeError) | |
def to_i | |
milliseconds | |
end | |
# Fixes no implicit conversion of Duration into String (TypeError) | |
def to_s | |
milliseconds.to_s | |
end | |
alias :to_str :to_s | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'micro_timer' | |
describe MicroTimer do | |
describe '.uptime' do | |
subject { MicroTimer.uptime } | |
it 'returns Float' do | |
is_expected.to be_instance_of Float | |
end | |
it 'returns uptime in seconds' do | |
is_expected.to be > 0 | |
end | |
end | |
describe '.duration' do | |
it 'yields control' do | |
expect { |b| MicroTimer.duration(&b) }.to yield_control | |
end | |
it 'returns instance of MicroTimer::Duration' do | |
duration = MicroTimer.duration { sleep 0.05 } | |
expect( duration ).to be_instance_of(MicroTimer::Duration) | |
expect( duration.seconds ).to be_within(0.01).of(0.05) | |
end | |
end | |
describe '.start' do | |
it 'does not yield control' do | |
expect { |b| MicroTimer.start(&b) }.not_to yield_control | |
end | |
it 'returns an instance of MicroTimer' do | |
expect( MicroTimer.start ).to be_instance_of MicroTimer | |
end | |
end | |
describe '.stop' do | |
it 'does not yield control' do | |
expect { |b| MicroTimer.new.stop(&b) }.not_to yield_control | |
end | |
it 'returns instance of MicroTimer::Duration' do | |
timer = MicroTimer.start | |
expect( timer.stop ).to be_instance_of(MicroTimer::Duration) | |
end | |
it 'returns new instance of MicroTimer::Duration each time' do | |
timer = MicroTimer.start | |
duration1 = timer.stop | |
sleep 0.01 | |
duration2 = timer.stop | |
expect( duration1.start ).to eq duration2.start | |
expect( duration1.finish ).to be < duration2.finish | |
expect( duration1.value ).to be < duration2.value | |
end | |
end | |
end | |
describe MicroTimer::Duration do | |
describe 'time conversion' do | |
let(:duration) { MicroTimer::Duration.new(0.000, 3_600.900) } | |
specify '#microseconds (Integer)' do | |
expect( duration.microseconds ).to be_instance_of Integer | |
expect( duration.microseconds ).to eq 3_600_900_000 | |
end | |
specify '#milliseconds (Float)' do | |
expect( duration.milliseconds ).to be_instance_of Float | |
expect( duration.milliseconds ).to eq 3_600_900.0 | |
end | |
specify '#seconds (Float)' do | |
expect( duration.seconds ).to be_instance_of Float | |
expect( duration.seconds ).to eq 3_600.900 | |
end | |
specify '#minutes (Float)' do | |
expect( duration.minutes ).to be_instance_of Float | |
expect( duration.minutes ).to eq 60.015 | |
end | |
specify '#hours (Float)' do | |
expect( duration.hours ).to be_instance_of Float | |
expect( duration.hours ).to eq 1.000_25 | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment