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 +4 -4
- data/CHANGELOG +166 -0
- data/Gemfile +3 -0
- data/README.md +129 -0
- data/Rakefile +9 -0
- data/lib/Monotonic/Time.rb +40 -8
- data/lib/Monotonic/Timer.rb +52 -13
- data/lib/Monotonic/VERSION.rb +1 -1
- data/monotonic.rb.gemspec +47 -0
- data/test/Monotonic/Time_test.rb +81 -0
- data/test/Monotonic/Timer_test.rb +159 -0
- metadata +24 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 577e4ce149e58981c889c52eaed6256c4c11a5bf2d1e6144513d3cd8c0d72705
|
|
4
|
+
data.tar.gz: 60b83880903cbe10e28ab23462953ecc6b43296c1c39ae08e5c8cc6442697794
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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
data/lib/Monotonic/Time.rb
CHANGED
|
@@ -4,38 +4,70 @@
|
|
|
4
4
|
require 'sys-uptime'
|
|
5
5
|
|
|
6
6
|
module Monotonic
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
54
|
+
@nanoseconds_since_boot = Process.clock_gettime(CLOCK, :nanosecond)
|
|
22
55
|
end
|
|
23
56
|
|
|
24
57
|
def +(monotonic_time_addend)
|
|
25
|
-
|
|
58
|
+
seconds_since_boot + monotonic_time_addend.seconds_since_boot
|
|
26
59
|
end
|
|
27
60
|
|
|
28
61
|
def -(monotonic_time_subtrahend)
|
|
29
|
-
|
|
62
|
+
seconds_since_boot - monotonic_time_subtrahend.seconds_since_boot
|
|
30
63
|
end
|
|
31
64
|
|
|
32
65
|
def to_s
|
|
33
|
-
"#{
|
|
66
|
+
"#{seconds_since_boot} seconds since boot."
|
|
34
67
|
end
|
|
35
68
|
|
|
36
69
|
def to_time
|
|
37
|
-
@boot_time +
|
|
70
|
+
@boot_time + seconds_since_boot
|
|
38
71
|
end
|
|
39
|
-
|
|
40
72
|
end
|
|
41
73
|
end
|
data/lib/Monotonic/Timer.rb
CHANGED
|
@@ -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
|
-
@
|
|
20
|
-
@
|
|
43
|
+
@finish_nanoseconds = nil
|
|
44
|
+
@start_nanoseconds = now
|
|
21
45
|
end
|
|
22
46
|
|
|
23
47
|
def stop
|
|
24
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
data/lib/Monotonic/VERSION.rb
CHANGED
|
@@ -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.
|
|
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
|
-
-
|
|
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.
|
|
103
|
+
rubygems_version: 4.0.18
|
|
83
104
|
specification_version: 4
|
|
84
105
|
summary: Monotonic timing made easy.
|
|
85
106
|
test_files: []
|