diff --git a/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb b/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb index c9f4b369a..883c3348d 100644 --- a/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +++ b/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb @@ -10,18 +10,43 @@ def initialize if defined?(Process::CLOCK_MONOTONIC) # @!visibility private - def get_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) + def get_time(unit) + Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) end elsif Concurrent.on_jruby? # @!visibility private - def get_time - java.lang.System.nanoTime() / 1_000_000_000.0 + TIME_UNITS = Hash.new { |_hash, key| raise ArgumentError, "unexpected unit: #{key}" }.compare_by_identity + private_constant :TIME_UNITS + TIME_UNITS.merge!( + second: 1_000_000_000, + millisecond: 1_000_000, + microsecond: 1_000, + nanosecond: 1, + float_second: 1_000_000_000.0, + float_millisecond: 1_000_000.0, + float_microsecond: 1_000.0, + ) + + # @!visibility private + def get_time(unit) + java.lang.System.nanoTime() / TIME_UNITS[unit] end else + # @!visibility private + TIME_UNITS = Hash.new { |_hash, key| raise ArgumentError, "unexpected unit: #{key}" }.compare_by_identity + private_constant :TIME_UNITS + TIME_UNITS.merge!( + second: [nil, true], + millisecond: [1_000, true], + microsecond: [1_000_000, true], + nanosecond: [1_000_000_000, true], + float_second: [nil, false], + float_millisecond: [1_000.0, false], + float_microsecond: [1_000_000.0, false], + ) # @!visibility private - def get_time + def get_time(unit) synchronize do now = Time.now.to_f if @last_time < now @@ -29,6 +54,10 @@ def get_time else # clock has moved back in time @last_time += 0.000_001 end + scale, to_int = TIME_UNITS[unit] + now *= scale if scale + now = now.to_i if to_int + now end end @@ -46,12 +75,16 @@ def get_time # # Returns the current time a tracked by the application monotonic clock. # + # @param [Symbol] unit the time unit to be returned, can be either + # :float_second, :float_millisecond, :float_microsecond, :second, + # :millisecond, :microsecond, or :nanosecond default to :float_second. + # # @return [Float] The current monotonic time since some unspecified # starting point # # @!macro monotonic_clock_warning - def monotonic_time - GLOBAL_MONOTONIC_CLOCK.get_time + def monotonic_time(unit = :float_second) + GLOBAL_MONOTONIC_CLOCK.get_time(unit) end module_function :monotonic_time diff --git a/spec/concurrent/monotonic_time_spec.rb b/spec/concurrent/monotonic_time_spec.rb new file mode 100644 index 000000000..2af8d891f --- /dev/null +++ b/spec/concurrent/monotonic_time_spec.rb @@ -0,0 +1,30 @@ +module Concurrent + + RSpec.describe :monotonic_time do + context 'behavior' do + + it 'returns seconds as float' do + expect(Concurrent.monotonic_time).to be_a(Float) + end + + %i(float_second float_millisecond float_microsecond).each do |unit| + it "returns a Float when unit = #{unit.inspect}" do + expect(Concurrent.monotonic_time(unit)).to be_a(Float) + end + end + + %i(second millisecond microsecond nanosecond).each do |unit| + it "returns an Integer when unit = #{unit.inspect}" do + expect(Concurrent.monotonic_time(unit)).to be_an(Integer) + end + end + + it 'raises ArgumentError on unknown units' do + expect { + Concurrent.monotonic_time(:foo) + }.to raise_error(ArgumentError) + end + + end + end +end