monotonic.rb 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17e3187f97eede654634d62e5eeba6595b19df22dd6e38be3d5a64f5ee9cbc68
4
- data.tar.gz: d72962badf2f68b748d8fe48ebab3e3e1fcf78a8a4fd133d3c1bce663459fc5f
3
+ metadata.gz: 577e4ce149e58981c889c52eaed6256c4c11a5bf2d1e6144513d3cd8c0d72705
4
+ data.tar.gz: 60b83880903cbe10e28ab23462953ecc6b43296c1c39ae08e5c8cc6442697794
5
5
  SHA512:
6
- metadata.gz: 74ec59b8280c65b333fd22b4293370784c6756a4980d316700833645f6a67305393a06098ce5d1950c9e4b331049d3662b7fbd1915590cbb40074c1aaafd9217
7
- data.tar.gz: 0b31c5c4dad215a636b7053008353af3097db2fbdb0e2fb1654bbf7fd2ed341f065c5a867a9d072c96107d7d9e486458e741e76df012d2cc31cdef941e1b78e0
6
+ metadata.gz: '084908b43a5095d6ff4a3928a6161abb5800ce113714036afdd3bbc89c361886292c603d2f44bd33014d4fa0c5f8e25779c183933e1c4336c2767991ec21b194'
7
+ data.tar.gz: 252ba0130e324f322c37cb4df402a307ff534580ff422fc8b7ddbda494b3e8b28d0c681417dde96989d6d8f1d4dc8f2eb6bd5c9847b167b3176295fc88f27561
data/CHANGELOG ADDED
@@ -0,0 +1,166 @@
1
+ # CHANGELOG
2
+
3
+ ## 20260811
4
+
5
+ 0.7.0: Read the clock exactly, and read a clock worth reading exactly.
6
+
7
+ 1. ~ lib/Monotonic/Time.rb: Process.clock_gettime is now asked for :nanosecond, where it answers with an Integer, rather than for seconds, where it answers with a Float. A Float's mantissa is of a fixed width wherever it sits, so its absolute resolution falls away as the uptime grows: around 2.2e-16s near 1.0, but only 4.7e-10s at a month of uptime.
8
+ 2. + Monotonic::Time#nanoseconds_since_boot: the reading itself, exact.
9
+ 3. ~ Monotonic::Time#seconds_since_boot: /attr_reader/derived from the nanosecond reading/, so that the rounding happens where it is asked for rather than where the clock is read. It returns a Float as before.
10
+ 4. + Monotonic::NANOSECONDS_PER_SECOND, being needed by both Time and Timer.
11
+ 5. + Monotonic::Timer#total_nanoseconds: an elapsed interval as an exact Integer. This is where the reading was formerly spent, an interval being the difference of two Floats drawn from an uptime many orders of magnitude larger than itself.
12
+ 6. ~ Monotonic::Timer#total_time: derived from #total_nanoseconds. It returns a Float as before.
13
+ 7. ~ Monotonic::Timer#total_time: - the branch upon @finish_time, which moved to a private #finish_nanoseconds, so that the running and stopped cases are distinguished in one place rather than in each caller.
14
+ 8. + Monotonic::Timer.clock_name and Monotonic::Time.clock_name, and Monotonic::Timer::CLOCK, being CLOCK_UPTIME_RAW where it is defined and CLOCK_MONOTONIC otherwise. An interval is not an instant and wants a different clock: upon Darwin CLOCK_UPTIME_RAW advances in some 42ns against the 1000ns of CLOCK_MONOTONIC, and holds while the machine sleeps, a closed lid being no part of execution. Linux has no such clock, but its CLOCK_MONOTONIC already holds while suspended and advances finely, so the fallback carries the same meaning and not merely the same name. Exactness alone was worth little upon the old clock, whose 1000ns tick a Float would not have troubled for 142 years of uptime; upon 42ns it begins to tell after six.
15
+ 9. ~ Monotonic::Timer: - its use of Monotonic::Time, since the two now read different clocks. @start_time and @finish_time become @start_nanoseconds and @finish_nanoseconds, read through a private #now.
16
+ 10. ~ Monotonic::Time::CLOCK, being CLOCK_MONOTONIC still, and now named. #to_time maps an instant back onto the wall clock by way of the boot time, so the clock wanted there is the one tracking time since boot as the wall clock understands it, sleep and all.
17
+ 11. + Monotonic::Time.resolution and Monotonic::Timer.resolution, from Process.clock_getres. How finely a clock advances is a property of the processor and the operating system, so it is asked rather than tabulated, and a README sentence about one platform cannot stand in for it. A reading is denominated in nanoseconds whether or not the clock affords them; this is the method which says what a reading is worth.
18
+ 12. ~ .clock_name is a method upon both classes rather than a CLOCK_NAME constant, since which clock is read is something found by asking the platform, as .resolution is, and not a property this library declares. A constant would also have frozen a contestable decision: sleep is excluded from a timed interval here, and nothing could have said otherwise. CLOCK stays a constant, being read twice upon every measurement against a floor of some tens of nanoseconds, and is now commented as such.
19
+ 13. ~ test/Monotonic/Time_test.rb: ~ the initialize test, which read @seconds_since_boot, to read @nanoseconds_since_boot.
20
+ 14. + test/Monotonic/Time_test.rb: tests for .clock_name, .resolution and #nanoseconds_since_boot, and that #seconds_since_boot is that reading expressed in seconds.
21
+ 15. + test/Monotonic/Timer_test.rb: tests for #total_nanoseconds, including that it holds while the timer is stopped, and for the clock: which one is chosen, that CLOCK follows .clock_name, that it reports its resolution, and that it is no coarser than the one Monotonic::Time reads.
22
+ 16. ~ README.md: + #nanoseconds_since_boot, #total_nanoseconds and .resolution to the description and the usage. The description now distinguishes denomination from resolution, asking for :nanosecond settling only what the figure counts, and points at .resolution rather than making a claim about any one platform.
23
+ 17. ~ Monotonic::VERSION: /0.6.7/0.7.0/
24
+
25
+
26
+ ## 20260716
27
+
28
+ 0.6.7: Block exception bug fix, relicensing, and gem packaging changes.
29
+
30
+ 1. ~ Monotonic::Timer#time: - the `return total_time` from the ensure clause, since returning from an ensure swallowed any exception raised by the block, handing back a duration in place of the error. The exception is now allowed to propagate. The ensure itself is kept, and narrowed to the block, so that stop still runs when the block is interrupted: the time up to an interruption is worth having, and without the stop total_time would go on running rather than hold at the point of interruption. It is read from the timer, since the return value belongs to the exception.
31
+ 2. ~ test/Monotonic/Timer_test.rb: + a test to demonstrate that an exception raised within the block is no longer swallowed.
32
+ 3. ~ test/Monotonic/Timer_test.rb: + a test to demonstrate that the time up to an interruption holds rather than goes on running.
33
+ 4. - test/monotonic_test.rb: A Rakefile has been introduced to run all files.
34
+ 5. ~ monotonic.rb.gemspec: /Ruby/MIT/ for the license.
35
+ 6. ~ monotonic.rb.gemspec: Gem::Specification#dependencies= and #development_dependencies=, so that dependencies may be enumerated as a list rather than a call apiece.
36
+ 7. ~ monotonic.rb.gemspec: spec.files to include CHANGELOG, Gemfile, the gemspec, the Rakefile, README.md and the tests, rather than lib/**/*.rb alone.
37
+ 8. ~ monotonic.rb.gemspec: spec.require_paths, since spec.files is no longer confined to lib/.
38
+ 9. + monotonic.rb.gemspec: rake as a development dependency.
39
+ 10. + Rakefile: a test task, run by default.
40
+ 11. ~ lib/Monotonic/Time.rb: - empty lines from within the class body.
41
+ 12. ~ lib/Monotonic/Timer.rb: - empty lines from within the class body.
42
+ 13. ~ CHANGELOG: Reformat headers, number the changes, and remove `.md`.
43
+ 14. ~ Monotonic::VERSION: /0.6.6/0.6.7/
44
+
45
+
46
+ ## 20260716
47
+
48
+ 0.6.6: Case-sensitive filesystem fixes.
49
+
50
+ 1. ~ lib/monotonic/ --> lib/Monotonic/: the directory was the only reference using lowercase, while the entry require, the test requires and the gemspec all already used Monotonic/, so the published gem failed to load on case-sensitive (Linux) filesystems.
51
+ 2. ~ lib/Monotonic/VERSION.rb: /class Monotonic/module Monotonic/ so it matches the module reopened in Time.rb and Timer.rb, rather than raising TypeError when loaded alongside them.
52
+ 3. + lib/monotonic.rb: require_relative './Monotonic/VERSION' (as Duration.rb does for its own VERSION), so Monotonic::VERSION is available after requiring the gem. VERSION.rb being off the load path is what let the class/module error above go unnoticed: nothing loaded it alongside Time.rb and Timer.rb, so the conflict never fired.
53
+
54
+
55
+ ## 20240822
56
+
57
+ 0.6.5: Block usage bug fix
58
+
59
+ 1. ~ lib/monotonic/Timer.rb: The total_time must be explicitly returned since values in ensure are not returned as the default return value.
60
+
61
+
62
+ ## 20240822
63
+
64
+ 0.6.4: Correct README.md.
65
+
66
+ 1. ~ README.md: In the Usage section: /MonotonicTime/Monotonic::Time/
67
+ 2. ~ lib/monotonic/VERSION.rb: 0.6.3 --> 0.6.4 because I mistakenly pulled the 0.6.3 gem.
68
+
69
+
70
+ ## 20210627
71
+
72
+ 0.6.3: Using gemspec in Gemfile.
73
+
74
+ 1. ~ Gemfile to use gemspec rather than having dependencies enumerated there
75
+
76
+
77
+ ## 20210625
78
+
79
+ 0.6.2: Fixed missing sys-uptime dependency.
80
+
81
+ 1. ~ montonic.rb.gemspec to include the sys-uptime dependency
82
+
83
+
84
+ ## 20210623
85
+
86
+ 0.6.1: README fix and a small refactor
87
+
88
+ 1. ~ README.md: /monotonic/monotonic.rb/
89
+ 2. ~ Monotonic::Timer.time to make use of the same-named instance method now that it's available.
90
+
91
+
92
+ ## 20210623
93
+
94
+ 0.6.0: Version number bump due to name change from monotony.rb to monotonic.rb
95
+
96
+ 1. /Monotony//, Monotonic::Timer is shorter than Monotony::MonotonicTimer
97
+
98
+
99
+ ## 20210607
100
+
101
+ 0.5.0: Added timing block on an instance of MonotonicTimer
102
+
103
+ 1. + MonotonicTime#+
104
+ 2. + MonotonicTime#-
105
+ 3. ~ MonotonicTimer#start: - use of MonotonicTime#to_time (using MonotonicTime#- instead)
106
+ 4. ~ MonotonicTimer#stop: - use of MonotonicTime#to_time (using MonotonicTime#- instead)
107
+ 5. + MonotonicTimer#time
108
+
109
+
110
+ ## 20210607
111
+
112
+ 0.4.1: Fixed starting and stopping
113
+
114
+ 1. - MonotonicTimer#initialize: - @total_time as it just made things more complicated unless needing splits
115
+ 2. ~ MonotonicTimer#start: - @start_monotonic_time as it wasn't really needed
116
+ 3. ~ MonotonicTimer#stop: - @stop_monotonic_time as it wasn't really needed
117
+ 4. ~ MonotonicTimer#total_time: - @total_time as it just made things more complicated unless needing splits
118
+
119
+
120
+ ## 20210606
121
+
122
+ 0.4.0: Named Monotony and prepped as a gem.
123
+
124
+ 1. /monotonic_time.rb/MonotonicTime.rb/
125
+ 2. /monotonic_timer.rb/MonotonicTimer.rb/
126
+ 3. + Monotony namespace
127
+ 4. + monotony.gemspec
128
+
129
+
130
+ ## 20210604
131
+
132
+ 0.3.0
133
+
134
+ 1. ~ MonotonicTimer.time, so that it returns the total time to the value of the block
135
+ 2. + ./test/monotonic_time_test.rb
136
+ 3. ~ ./test/monotonic_timer_test.rb
137
+
138
+
139
+ ## 20210510
140
+
141
+ 0.2.0: Added somewhat proper testing
142
+
143
+ 1. + ./test/monotonic_timer_test.rb
144
+ 2. ~ ./lib/monotonic_timer.rb: - self-run section test
145
+
146
+
147
+ ## 20200510
148
+
149
+ 0.1.1
150
+
151
+ 1. ~ ./lib/monotonic_timer.rb: + self-run section test
152
+
153
+
154
+ ## 20200115
155
+
156
+ 0.1.0: Added classes for better structure
157
+
158
+ 1. + MonotonicTime
159
+ 2. + MonotonicTimer
160
+
161
+
162
+ ## 20200115
163
+
164
+ 0.0.0
165
+
166
+ 1. + timer()
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://www.rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # monotonic.rb
2
+
3
+
4
+ ## Description
5
+
6
+ Create accurate timings of excution in Ruby.
7
+
8
+ What accurate means is to capture many digits as an operating system is willing to afford. Hence, the clock is read in nanoseconds by default. If a number in seconds is requested, then that is derived from nanoseconds rather than read directly, so the rounding happens upon request and not before.
9
+
10
+ It tells most upon an elapsed interval, that being the difference of two readings drawn from an uptime many orders of magnitude larger than itself. A `Float` carries a mantissa of fixed width wherever it sits, so its absolute resolution falls away as the uptime grows: around 2.2e-16s near 1.0, but only 4.7e-10s after a month of uptime. Differencing two exact integers spends none of the reading, however long the machine has been up.
11
+
12
+ Which clock is read follows from that. `Monotonic::Timer` measures intervals and so takes the finest available which holds while the machine sleeps, a closed lid being no part of execution: `CLOCK_UPTIME_RAW` upon Darwin, where it advances in some 42ns against the 1000ns of `CLOCK_MONOTONIC`, and `CLOCK_MONOTONIC` elsewhere, which upon Linux already holds while suspended and advances finely. `Monotonic::Time` is an instant rather than an interval, and `#to_time` maps it back onto the wall clock, so it stays upon `CLOCK_MONOTONIC`, sleep and all.
13
+
14
+ How finely a clock advances is the platform's business and not this library's to claim, so it is asked rather than tabulated:
15
+
16
+ ```ruby
17
+ Monotonic::Timer.resolution
18
+ # => 42
19
+ Monotonic::Time.resolution
20
+ # => 1000
21
+ ```
22
+
23
+ A reading is denominated in nanoseconds whether or not the clock affords them, so that is the method which says what a reading is worth. Two figures give the sense of it upon the finer clock: reading it costs about 34ns against the 42ns it takes to advance, the two being close enough that there is little to be had by going finer, and a pair of `Float`s would not begin to lose it until some six years of uptime — but they would begin.
24
+
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'monotonic.rb'
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ ```bash
37
+ $ bundle install
38
+ ```
39
+
40
+ Or install it yourself as:
41
+
42
+ ```bash
43
+ $ gem install monotonic.rb
44
+ ```
45
+
46
+
47
+ ## Usage
48
+
49
+ ### Monotonic::Time
50
+
51
+ ```ruby
52
+ require 'monotonic.rb'
53
+ monotonic_time = Monotonic::Time.new
54
+ monotonic_time.nanoseconds_since_boot
55
+ # => 2614365376498000
56
+ monotonic_time.seconds_since_boot
57
+ # => 1208799.325906
58
+ monotonic_time + Monotonic::Time.now
59
+ # => 2417598.681896
60
+ monotonic_time - Monotonic::Time.now
61
+ # => -0.044104999862611294
62
+ monotonic_time.to_s
63
+ # => "1164320.268127 seconds since boot."
64
+ monotonic_time.to_time
65
+ # => 2021-06-07 09:27:08 8249692651179/8388608000000 +1000
66
+ ```
67
+
68
+ ### Monotonic::Timer without a block
69
+
70
+ ```ruby
71
+ require 'monotonic.rb'
72
+ timer = Monotonic::Timer.new
73
+ timer.start
74
+ i = 0
75
+ 1_000_000.times{puts i += 1}
76
+ timer.stop
77
+ timer.total_nanoseconds
78
+ # => 27734000
79
+ timer.total_time
80
+ # => 7.166559999808669
81
+ ```
82
+
83
+ ### Monotonic::Timer with a block
84
+
85
+ ```ruby
86
+ require 'monotonic.rb'
87
+ time = Monotonic::Timer.time do
88
+ i = 0
89
+ 1_000_000.times{puts i += 1}
90
+ end
91
+ time
92
+ # => 6.975823000073433
93
+ ```
94
+
95
+ ### Monotonic::Timer with a block and block variable
96
+
97
+ ```ruby
98
+ require 'monotonic.rb'
99
+ time = Monotonic::Timer.time do |timer|
100
+ i = 0
101
+ 500_000.times{puts i += 1}
102
+ p timer.total_time
103
+ 500_000.times{puts i += 1}
104
+ end
105
+ time
106
+ # => 6.975823000073433
107
+ ```
108
+
109
+ ### Monotonic::Timer with a block on a timer instance
110
+
111
+ ```ruby
112
+ require 'monotonic.rb'
113
+ timer = Monotonic::Timer.new
114
+ time = timer.time do
115
+ i = 0
116
+ 1_000_000.times{puts i += 1}
117
+ end
118
+ time
119
+ # => 7.033131000120193
120
+ ```
121
+
122
+
123
+ ## Contributing
124
+
125
+ 1. Fork it ( https://github.com/thoran/monotonic.rb/fork )
126
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
127
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
128
+ 4. Push to the branch (`git push origin my-new-feature`)
129
+ 5. Create a new pull request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # Rakefile
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ task default: :test
@@ -4,38 +4,70 @@
4
4
  require 'sys-uptime'
5
5
 
6
6
  module Monotonic
7
- class Time
7
+ NANOSECONDS_PER_SECOND = 1_000_000_000
8
8
 
9
+ class Time
9
10
  class << self
11
+ # An instant, which #to_time maps back onto the wall clock by way of the
12
+ # boot time, so the clock wanted here is the one which tracks time since
13
+ # boot as the wall clock understands it. That is CLOCK_MONOTONIC, sleep
14
+ # and all. Monotonic::Timer measures intervals rather than instants and
15
+ # chooses a finer clock of its own.
16
+ #
17
+ # Unlike Timer's, this one is chosen rather than found, there being no
18
+ # alternative which would still answer to #to_time. It is a method all
19
+ # the same, so that the two classes answer the question alike.
20
+ def clock_name
21
+ :CLOCK_MONOTONIC
22
+ end
10
23
 
11
24
  def now
12
25
  self.new
13
26
  end
14
27
 
28
+ # How finely this clock advances, in nanoseconds. It is asked rather than
29
+ # tabulated, being a property of the processor and the operating system
30
+ # and not of this library: upon macOS CLOCK_MONOTONIC answers 1000 here.
31
+ # A reading is denominated in nanoseconds whatever the answer, so this is
32
+ # the method which says what a reading is worth.
33
+ def resolution
34
+ Process.clock_getres(CLOCK, :nanosecond)
35
+ end
15
36
  end # class << self
16
37
 
17
- attr_reader :seconds_since_boot
38
+ # Read upon every instance, so a constant rather than a lookup. It follows
39
+ # .clock_name, and so must come after it.
40
+ CLOCK = Process.const_get(clock_name)
41
+
42
+ attr_reader :nanoseconds_since_boot
43
+
44
+ # The clock is read in nanoseconds because it answers there with an Integer,
45
+ # which is exact and stays exact however long the machine has been up.
46
+ # Seconds are derived rather than read, so that the reading loses nothing and
47
+ # the rounding happens where it is asked for.
48
+ def seconds_since_boot
49
+ @nanoseconds_since_boot / NANOSECONDS_PER_SECOND.to_f
50
+ end
18
51
 
19
52
  def initialize
20
53
  @boot_time = Sys::Uptime.boot_time
21
- @seconds_since_boot = Process.clock_gettime(Process::CLOCK_MONOTONIC)
54
+ @nanoseconds_since_boot = Process.clock_gettime(CLOCK, :nanosecond)
22
55
  end
23
56
 
24
57
  def +(monotonic_time_addend)
25
- @seconds_since_boot + monotonic_time_addend.seconds_since_boot
58
+ seconds_since_boot + monotonic_time_addend.seconds_since_boot
26
59
  end
27
60
 
28
61
  def -(monotonic_time_subtrahend)
29
- @seconds_since_boot - monotonic_time_subtrahend.seconds_since_boot
62
+ seconds_since_boot - monotonic_time_subtrahend.seconds_since_boot
30
63
  end
31
64
 
32
65
  def to_s
33
- "#{@seconds_since_boot} seconds since boot."
66
+ "#{seconds_since_boot} seconds since boot."
34
67
  end
35
68
 
36
69
  def to_time
37
- @boot_time + @seconds_since_boot
70
+ @boot_time + seconds_since_boot
38
71
  end
39
-
40
72
  end
41
73
  end
@@ -5,40 +5,79 @@ require_relative './Time'
5
5
 
6
6
  module Monotonic
7
7
  class Timer
8
-
9
8
  class << self
9
+ # An interval is not an instant, and wants a different clock. Upon Darwin
10
+ # CLOCK_UPTIME_RAW advances in some 42ns against the 1000ns of
11
+ # CLOCK_MONOTONIC, and holds while the machine sleeps, which is the right
12
+ # answer for a timed block: a closed lid is not execution. Linux has no
13
+ # such clock, but its CLOCK_MONOTONIC already holds while suspended and
14
+ # advances finely, so the fallback carries the same meaning and not merely
15
+ # the same name.
16
+ #
17
+ # Which clocks exist is the platform's business, so this is asked rather
18
+ # than declared, as .resolution is.
19
+ def clock_name
20
+ @clock_name ||= %i[CLOCK_UPTIME_RAW CLOCK_MONOTONIC].find{|name| Process.const_defined?(name)}
21
+ end
10
22
 
11
23
  def time(&block)
12
24
  timer = Timer.new
13
25
  timer.time(&block)
14
26
  end
15
27
 
28
+ # How finely this clock advances, in nanoseconds. Asked of the platform
29
+ # rather than claimed by the library, and worth asking: a reading is
30
+ # denominated in nanoseconds whether the clock affords them or not.
31
+ def resolution
32
+ Process.clock_getres(CLOCK, :nanosecond)
33
+ end
16
34
  end # class << self
17
35
 
36
+ # Read twice upon every measurement, against a floor of some tens of
37
+ # nanoseconds, so this is the one place here where a constant is worth the
38
+ # rigidity: a method call would be a measurable part of what it measures.
39
+ # It follows .clock_name, and so must come after it.
40
+ CLOCK = Process.const_get(clock_name)
41
+
18
42
  def start
19
- @finish_time = nil
20
- @start_time = Monotonic::Time.now
43
+ @finish_nanoseconds = nil
44
+ @start_nanoseconds = now
21
45
  end
22
46
 
23
47
  def stop
24
- @finish_time = Monotonic::Time.now
48
+ @finish_nanoseconds = now
49
+ end
50
+
51
+ # Two exact integers differenced, which spends none of the reading. A pair
52
+ # of Floats would not begin to lose the clock at this resolution until some
53
+ # six years of uptime, but they would begin.
54
+ def total_nanoseconds
55
+ finish_nanoseconds - @start_nanoseconds
25
56
  end
26
57
 
27
58
  def total_time
28
- if @finish_time
29
- @finish_time - @start_time
30
- else
31
- Monotonic::Time.now - @start_time
32
- end
59
+ total_nanoseconds / NANOSECONDS_PER_SECOND.to_f
33
60
  end
34
61
 
35
62
  def time
36
63
  start
37
- yield self
38
- ensure
39
- stop
40
- return total_time
64
+ begin
65
+ yield self
66
+ ensure
67
+ stop
68
+ end
69
+ total_time
70
+ end
71
+
72
+ private
73
+
74
+ # A timer which has not been stopped is still running, so the finish is now.
75
+ def finish_nanoseconds
76
+ @finish_nanoseconds || now
41
77
  end
42
78
 
79
+ def now
80
+ Process.clock_gettime(CLOCK, :nanosecond)
81
+ end
43
82
  end
44
83
  end
@@ -2,5 +2,5 @@
2
2
  # Monotonic::VERSION
3
3
 
4
4
  module Monotonic
5
- VERSION = '0.6.6'
5
+ VERSION = '0.7.0'
6
6
  end
@@ -0,0 +1,47 @@
1
+ require_relative './lib/Monotonic/VERSION'
2
+
3
+ class Gem::Specification
4
+ def dependencies=(gems)
5
+ gems.each{|gem| add_dependency(*gem)}
6
+ end
7
+
8
+ def development_dependencies=(gems)
9
+ gems.each{|gem| add_development_dependency(*gem)}
10
+ end
11
+ end
12
+
13
+ Gem::Specification.new do |spec|
14
+ spec.name = 'monotonic.rb'
15
+ spec.version = Monotonic::VERSION
16
+
17
+ spec.summary = "Monotonic timing made easy."
18
+ spec.description = "Create accurate timings of excution in Ruby."
19
+
20
+ spec.author = 'thoran'
21
+ spec.email = 'code@thoran.com'
22
+ spec.homepage = 'https://github.com/thoran/monotonic.rb'
23
+ spec.license = 'MIT'
24
+
25
+ spec.require_paths = ['lib']
26
+ spec.required_ruby_version = '>= 2.5'
27
+
28
+ spec.files = [
29
+ 'CHANGELOG',
30
+ 'Gemfile',
31
+ Dir['lib/**/*.rb'],
32
+ 'monotonic.rb.gemspec',
33
+ 'Rakefile',
34
+ 'README.md',
35
+ Dir['test/**/*.rb'],
36
+ ].flatten
37
+
38
+ spec.dependencies = %w{
39
+ sys-uptime
40
+ }
41
+
42
+ spec.development_dependencies = %w{
43
+ minitest
44
+ minitest-spec-context
45
+ rake
46
+ }
47
+ end
@@ -0,0 +1,81 @@
1
+ require_relative '../../lib/Monotonic/Time'
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+
6
+ describe Monotonic::Time do
7
+ subject{Monotonic::Time.now}
8
+
9
+ describe "#initialize" do
10
+ it "the time spent in the block is returned as the value of the block" do
11
+ expect(subject.instance_variable_get(:@boot_time)) \
12
+ .must_equal(Sys::Uptime.boot_time)
13
+ end
14
+
15
+ it "the reading is taken from the monotonic clock in nanoseconds" do
16
+ expect((subject.instance_variable_get(:@nanoseconds_since_boot) / 1_000_000_000.0).round(2)) \
17
+ .must_equal((Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) / 1_000_000_000.0).round(2))
18
+ end
19
+ end
20
+
21
+ describe ".clock_name" do
22
+ it "is the clock which tracks time since boot as the wall clock understands it" do
23
+ expect(Monotonic::Time.clock_name).must_equal(:CLOCK_MONOTONIC)
24
+ end
25
+
26
+ it "names the clock which is read" do
27
+ expect(Monotonic::Time::CLOCK).must_equal(Process.const_get(Monotonic::Time.clock_name))
28
+ end
29
+ end
30
+
31
+ describe ".resolution" do
32
+ it "returns how finely the clock advances, in nanoseconds" do
33
+ expect(Monotonic::Time.resolution) \
34
+ .must_equal(Process.clock_getres(Process::CLOCK_MONOTONIC, :nanosecond))
35
+ end
36
+
37
+ it "returns an instance of integer" do
38
+ expect(Monotonic::Time.resolution.class).must_equal(Integer)
39
+ end
40
+ end
41
+
42
+ describe "#nanoseconds_since_boot" do
43
+ it "returns an instance of integer" do
44
+ expect((subject.nanoseconds_since_boot).class).must_equal(Integer)
45
+ end
46
+ end
47
+
48
+ describe "#seconds_since_boot" do
49
+ it "returns an instance of float" do
50
+ expect((subject.seconds_since_boot).class).must_equal(Float)
51
+ end
52
+
53
+ it "is the nanoseconds reading expressed in seconds" do
54
+ expect(subject.seconds_since_boot).must_equal(subject.nanoseconds_since_boot / 1_000_000_000.0)
55
+ end
56
+ end
57
+
58
+ describe "#+" do
59
+ it "returns an instance of string" do
60
+ expect((subject + Monotonic::Time.now).class).must_equal(Float)
61
+ end
62
+ end
63
+
64
+ describe "#-" do
65
+ it "returns an instance of time" do
66
+ expect((subject - Monotonic::Time.now).class).must_equal(Float)
67
+ end
68
+ end
69
+
70
+ describe "#to_s" do
71
+ it "returns an instance of string" do
72
+ expect(subject.to_s.class).must_equal(String)
73
+ end
74
+ end
75
+
76
+ describe "#to_time" do
77
+ it "returns an instance of time" do
78
+ expect(subject.to_time.class).must_equal(Time)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,159 @@
1
+ require_relative '../../lib/Monotonic/Timer'
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+
6
+ describe Monotonic::Timer do
7
+ context "with a block" do
8
+ it "the time spent in the block is returned as the value of the block" do
9
+ block_time = Monotonic::Timer.time do |timer|
10
+ sleep 3
11
+ end
12
+ expect(block_time.round).must_equal(3)
13
+ end
14
+
15
+ # Test introduced in 0.6.5 to demonstrate that the bug had been fixed.
16
+ it "the time spent in the block is returned as the value of the block and not a value at the end of the block" do
17
+ block_time = Monotonic::Timer.time do |timer|
18
+ sleep 3
19
+ 4
20
+ end
21
+ expect(block_time.round).must_equal(3)
22
+ end
23
+
24
+ # Test introduced in 0.6.7 to demonstrate that the bug had been fixed.
25
+ it "an exception raised within the block is not swallowed" do
26
+ expect{Monotonic::Timer.time{raise(ArgumentError)}}.must_raise(ArgumentError)
27
+ end
28
+
29
+ it "allows the timer to be started and stopped within the block" do
30
+ Monotonic::Timer.time do |timer|
31
+ sleep 1
32
+ expect(timer.total_time.round).must_equal(1)
33
+ timer.stop
34
+ expect(timer.total_time.round).must_equal(1)
35
+ timer.start
36
+ expect(timer.total_time.round).must_equal(0)
37
+ sleep 1
38
+ expect(timer.total_time.round).must_equal(1)
39
+ sleep 1
40
+ expect(timer.total_time.round).must_equal(2)
41
+ timer.start
42
+ expect(timer.total_time.round).must_equal(0)
43
+ end
44
+ end
45
+
46
+ it "alters the block time if it is started and stopped within the block" do
47
+ block_time = Monotonic::Timer.time do |timer|
48
+ sleep 1
49
+ timer.stop
50
+ sleep 1
51
+ timer.start
52
+ sleep 1
53
+ end
54
+ expect(block_time.round).must_equal(1)
55
+ end
56
+ end
57
+
58
+ context "without a block" do
59
+ it 'works' do
60
+ timer = Monotonic::Timer.new
61
+ timer.start
62
+ sleep 1
63
+ expect(timer.total_time.round).must_equal(1)
64
+ timer.stop
65
+ expect(timer.total_time.round).must_equal(1)
66
+ timer.start
67
+ expect(timer.total_time.round).must_equal(0)
68
+ sleep 1
69
+ expect(timer.total_time.round).must_equal(1)
70
+ sleep 1
71
+ expect(timer.total_time.round).must_equal(2)
72
+ timer.start
73
+ expect(timer.total_time.round).must_equal(0)
74
+ end
75
+ end
76
+
77
+ context "with a block on an timer instance" do
78
+ it 'works' do
79
+ timer = Monotonic::Timer.new
80
+ time = timer.time do |timer|
81
+ sleep 1
82
+ expect(timer.total_time.round).must_equal(1)
83
+ timer.stop
84
+ expect(timer.total_time.round).must_equal(1)
85
+ sleep 1
86
+ expect(timer.total_time.round).must_equal(1)
87
+ timer.start
88
+ expect(timer.total_time.round).must_equal(0)
89
+ sleep 1
90
+ end
91
+ expect(time.round).must_equal(1)
92
+ expect(timer.total_time.round).must_equal(1)
93
+ end
94
+
95
+ # Test introduced in 0.6.7 to demonstrate that the time up to an
96
+ # interruption is not left running.
97
+ it "the time up to an interruption remains available from the timer" do
98
+ timer = Monotonic::Timer.new
99
+ begin
100
+ timer.time do
101
+ sleep 1
102
+ raise(ArgumentError)
103
+ end
104
+ rescue ArgumentError
105
+ end
106
+ sleep 1
107
+ expect(timer.total_time.round).must_equal(1)
108
+ end
109
+ end
110
+
111
+ context "the clock" do
112
+ it "is the finest available which holds while the machine sleeps" do
113
+ expect(Monotonic::Timer.clock_name) \
114
+ .must_equal(Process.const_defined?(:CLOCK_UPTIME_RAW) ? :CLOCK_UPTIME_RAW : :CLOCK_MONOTONIC)
115
+ end
116
+
117
+ it "is asked of the platform rather than declared" do
118
+ expect(Monotonic::Timer::CLOCK).must_equal(Process.const_get(Monotonic::Timer.clock_name))
119
+ end
120
+
121
+ it "reports its resolution rather than leaving it to be assumed" do
122
+ expect(Monotonic::Timer.resolution) \
123
+ .must_equal(Process.clock_getres(Monotonic::Timer::CLOCK, :nanosecond))
124
+ end
125
+
126
+ it "is no coarser than the one Monotonic::Time reads" do
127
+ expect(Monotonic::Timer.resolution).must_be :<=, Monotonic::Time.resolution
128
+ end
129
+ end
130
+
131
+ context "in nanoseconds" do
132
+ it "returns an instance of integer" do
133
+ timer = Monotonic::Timer.new
134
+ timer.start
135
+ sleep 1
136
+ timer.stop
137
+ expect(timer.total_nanoseconds.class).must_equal(Integer)
138
+ end
139
+
140
+ it "is the total time expressed in nanoseconds" do
141
+ timer = Monotonic::Timer.new
142
+ timer.start
143
+ sleep 1
144
+ timer.stop
145
+ expect(timer.total_nanoseconds / 1_000_000_000.0).must_equal(timer.total_time)
146
+ end
147
+
148
+ it "holds while the timer is stopped and runs on while it is not" do
149
+ timer = Monotonic::Timer.new
150
+ timer.start
151
+ timer.stop
152
+ held = timer.total_nanoseconds
153
+ sleep 1
154
+ expect(timer.total_nanoseconds).must_equal(held)
155
+ timer.start
156
+ expect(timer.total_nanoseconds).must_be :>, 0
157
+ end
158
+ end
159
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monotonic.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.6
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -51,19 +51,40 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  description: Create accurate timings of excution in Ruby.
55
69
  email: code@thoran.com
56
70
  executables: []
57
71
  extensions: []
58
72
  extra_rdoc_files: []
59
73
  files:
74
+ - CHANGELOG
75
+ - Gemfile
76
+ - README.md
77
+ - Rakefile
60
78
  - lib/Monotonic/Time.rb
61
79
  - lib/Monotonic/Timer.rb
62
80
  - lib/Monotonic/VERSION.rb
63
81
  - lib/monotonic.rb
82
+ - monotonic.rb.gemspec
83
+ - test/Monotonic/Time_test.rb
84
+ - test/Monotonic/Timer_test.rb
64
85
  homepage: https://github.com/thoran/monotonic.rb
65
86
  licenses:
66
- - Ruby
87
+ - MIT
67
88
  metadata: {}
68
89
  rdoc_options: []
69
90
  require_paths:
@@ -79,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
100
  - !ruby/object:Gem::Version
80
101
  version: '0'
81
102
  requirements: []
82
- rubygems_version: 4.0.16
103
+ rubygems_version: 4.0.18
83
104
  specification_version: 4
84
105
  summary: Monotonic timing made easy.
85
106
  test_files: []