monotonic.rb 0.8.0 → 0.9.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: 735b3915ec7e7b95ef1fde8effe2d2723fea5eb027f9af87d1bea9dc8552048a
4
- data.tar.gz: 8497225d7afb17a9e6c5f1c6a7c79510986fc74f731c81c06c4b0529ca1fa1d1
3
+ metadata.gz: 02f12d8ccd007cc410e5ebeb524fd6761088aebe771409a81323fd331e479e61
4
+ data.tar.gz: 9bfd7ff12760b554b0921e535803c4e3e07c0c0cdb166b8133f1ab710a6a1d10
5
5
  SHA512:
6
- metadata.gz: a9a637b1664afe2b51d8b2f6abedace3e2dd6faf150d5767da5a6add304acba0554db879f72f16867856219eee41da3ed1042418e832a76722928dea62e27511
7
- data.tar.gz: fed34f80416fa88c2b31fce83a9f21576a62d678c74ca0202bedf550149842de31cd121e55cec8f377f1354b4748cd4f818333b9aed726d778310630f9c8854b
6
+ metadata.gz: fb3b665160980c7d6319db88518e9bd6f74a5e7eade1035a61e62c6aaa88e06110b382f02900f4e61fc7509cb7d9167084d5650c95858912c4a5b5bb2ffdcd43
7
+ data.tar.gz: 9c7c70bb79a05df1608516272583870cd7aab4229e13dce3d626b38fd3426a58c3529bfe2e8ddd2607f3995508c3c1cdcda287212002349db0fabea114a373b5
data/CHANGELOG CHANGED
@@ -1,5 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 20260816
4
+
5
+ 0.9.0: Use measurand gem, + Monotonic::Measurement and Monotonic::Timer.floor.
6
+
7
+ 1. + measurand as a dependency, constrained to >= 0.1.0 where Measurand#place became public. How well a figure is known is its business, and it was being hand-rolled here: combining an absolute floor with a relative drift by addition gave 109.49ns where the correct combination in quadrature gives 87.78ns, a quarter out.
8
+ 2. + Monotonic::Timer.floor: what it costs to measure at all, in nanoseconds, measured by timing measurements of nothing and remembered. The clock's tick is not the limit, an interval wanting two readings and the taking of them taking time. A high percentile rather than a median, so that the ordinary case is covered and not merely the best one. It is a floor and not an error bar: the tail is unbounded and one-sided, the scheduler being free to take the processor away between the two readings, and no single measurement can detect that it did.
9
+ 3. + Monotonic::Measurement: an elapsed interval together with what it is worth. #to_s shows only as many digits as the uncertainty reaches, the least significant place being settled by Measurand#place upon the Particle Data Group convention, so 1265625ns against a floor of 84ns prints as 1.26562ms and the same count measured coarsely prints as 1.27ms. The unit ladder is powers of a thousand and stops at seconds, the scheme being a decimal-places one and minutes being sexagesimal. Nothing is capped, a day resolved to a hundred nanoseconds printing every figure it has: the absurdity is the mismatch between instrument and question, and is better seen than rounded away.
10
+ 4. + Monotonic::Measurement#uncertainty, #measurand, #unit_name, #decimals, #nanoseconds, #floor, #drift: every figure the rendering rests upon, so that a string can be walked back to the clock which produced it.
11
+ 5. + Monotonic::Measurement#floor takes a number or anything answering to #call, and Monotonic::Timer hands over its .floor method rather than its value. Measuring the floor is a thousand null measurements, and a caller which only wants a number should not set them going: a timed block costs 0.008ms where it would cost 0.307ms, the floor being measured when doubt is first asked about.
12
+ 6. + Monotonic::Measurement drift, optional and nil by default: the relative term, being the rate at which the clock's own oscillator wanders. It cannot be measured from within, weighing a clock wanting a better clock and this being the best one here, so it is supplied by a caller or not at all.
13
+ 7. + Monotonic::Measurement#to_i and #to_f, both in nanoseconds, its own unit, as Duration::Minutes answers 5 rather than 300; #to_duration for every other unit; and #<=> with Comparable, against another measurement only, a bare number having no unit to compare against.
14
+ 8. ~ BREAKING: Monotonic::Timer#time and .time return a Monotonic::Measurement rather than a Float of seconds. What comes back now says how much of itself is real, which a bare Float could not: 0.001295791 shows nine figures upon an instrument which resolves five. #total_time and #total_nanoseconds are unchanged for a number.
15
+ 9. + Monotonic::Timer#measurement and #to_s.
16
+ 10. + test/Monotonic/Measurement_test.rb: the unit chosen, the digits allowed, the uncertainty and that it combines in quadrature, the comparison, the conversions, the lazy floor, and that every figure the rendering rests upon is reachable.
17
+ 11. + test/Monotonic/Timer_test.rb: the floor, and that a timed block returns a measurement which carries the interval and the floor.
18
+ 12. ~ test/gemspec_test.rb: the runtime dependency list.
19
+ 13. ~ README.md: + the floor and the measurement, and what a timed block now hands back.
20
+ 14. ~ Monotonic::VERSION: /0.8.0/0.9.0/
21
+
22
+
3
23
  ## 20260816
4
24
 
5
25
  0.8.0: Use duration.rb gem.
data/README.md CHANGED
@@ -35,6 +35,22 @@ timer.total_nanoseconds
35
35
  # => 2508000, upon a clock which ticks in whole microseconds
36
36
  ```
37
37
 
38
+ A timed block hands back a `Monotonic::Measurement` rather than a bare number, so that what comes back says how much of itself is real. `Monotonic::Timer.floor` is what it costs to measure at all, taken by timing measurements of nothing, and the measurement shows only as many digits as that floor reaches — the doubt itself being [measurand](https://github.com/thoran/measurand)'s business, which combines an absolute floor with a relative drift in quadrature rather than by addition.
39
+
40
+ ```ruby
41
+ Monotonic::Timer.floor
42
+ # => 84
43
+ measurement = Monotonic::Timer.time{sleep 0.001}
44
+ measurement.to_s
45
+ # => "1.26562 ms"
46
+ measurement.uncertainty
47
+ # => 84.0
48
+ measurement.to_duration.to_microseconds.to_f
49
+ # => 1265.625
50
+ ```
51
+
52
+ Nothing is capped: a day resolved to a hundred nanoseconds prints every figure it has, the absurdity being the mismatch between what the instrument resolves and what was asked of it, which is better seen than rounded away. The floor is a floor and not an error bar — the tail is unbounded and one-sided, the scheduler being free to take the processor away between the two readings — so repeat and take a robust statistic where that matters.
53
+
38
54
 
39
55
  ## Installation
40
56
 
@@ -115,26 +131,26 @@ timer.total_time
115
131
  ```ruby
116
132
  require 'monotonic.rb'
117
133
 
118
- time = Monotonic::Timer.time do
134
+ measurement = Monotonic::Timer.time do
119
135
  i = 0
120
136
  1_000_000.times{puts i += 1}
121
137
  end
122
- time
123
- # => 6.975823000073433
138
+ measurement.to_s
139
+ # => "6.97582 s"
124
140
  ```
125
141
 
126
142
  ### Monotonic::Timer with a block and block variable
127
143
 
128
144
  ```ruby
129
145
  require 'monotonic.rb'
130
- time = Monotonic::Timer.time do |timer|
146
+ measurement = Monotonic::Timer.time do |timer|
131
147
  i = 0
132
148
  500_000.times{puts i += 1}
133
149
  p timer.total_time
134
150
  500_000.times{puts i += 1}
135
151
  end
136
- time
137
- # => 6.975823000073433
152
+ measurement.to_s
153
+ # => "6.97582 s"
138
154
  ```
139
155
 
140
156
  ### Monotonic::Timer with a block on a timer instance
@@ -142,12 +158,12 @@ time
142
158
  ```ruby
143
159
  require 'monotonic.rb'
144
160
  timer = Monotonic::Timer.new
145
- time = timer.time do
161
+ measurement = timer.time do
146
162
  i = 0
147
163
  1_000_000.times{puts i += 1}
148
164
  end
149
- time
150
- # => 7.033131000120193
165
+ measurement.to_s
166
+ # => "7.03313 s"
151
167
  ```
152
168
 
153
169
 
@@ -0,0 +1,118 @@
1
+ # Monotonic/Measurement.rb
2
+ # Monotonic::Measurement
3
+
4
+ require 'duration.rb'
5
+ require 'measurand'
6
+
7
+ module Monotonic
8
+ # An elapsed interval together with what it is worth. The count alone says
9
+ # nothing of how much of itself is real, so #to_s shows only as many digits as
10
+ # the uncertainty supports. Nothing is capped: a day resolved to a hundred
11
+ # nanoseconds prints every figure it has, which looks absurd and is — the
12
+ # absurdity being the mismatch between what the instrument resolves and what
13
+ # was asked of it, and better seen than rounded away.
14
+ #
15
+ # The doubt is a Measurand's business and the unit is a Duration's; what is
16
+ # left here is the ladder between them and the string at the end of it.
17
+ class Measurement
18
+ include Comparable
19
+
20
+ # Powers of a thousand, and no further. The scheme is a decimal-places one,
21
+ # so minutes and hours have no place in it: 90s is plainer than 1.5min, and
22
+ # a run of a day reads as 86400s rather than reaching for a unit which does
23
+ # not divide by ten.
24
+ UNITS = [[1, 'ns'], [1_000, 'us'], [1_000_000, 'ms'], [1_000_000_000, 's']]
25
+
26
+ attr_reader :nanoseconds
27
+ attr_reader :drift
28
+
29
+ # The floor may be given as a number or as anything which answers to #call,
30
+ # so that measuring it can wait until somebody asks about doubt.
31
+ # Monotonic::Timer hands over its .floor method rather than its value, and a
32
+ # caller which only wants a number never sets the thousand null measurements
33
+ # going.
34
+ def floor
35
+ @floor = @floor.respond_to?(:call) ? @floor.call : @floor
36
+ end
37
+
38
+ # The floor is absolute and tells upon short intervals; drift is relative
39
+ # and tells upon long ones, being the rate at which the clock's own
40
+ # oscillator wanders. Combining them is not addition — they are independent,
41
+ # so they go in quadrature — which is why the arithmetic is Measurand's and
42
+ # not done here.
43
+ def measurand
44
+ @measurand ||= (
45
+ absolute = Measurand.new(@nanoseconds, floor)
46
+ @drift ? absolute * Measurand.relative(1, @drift) : absolute
47
+ )
48
+ end
49
+
50
+ def uncertainty
51
+ measurand.uncertainty
52
+ end
53
+
54
+ def unit
55
+ @unit ||= UNITS.reverse.find{|scale, _| @nanoseconds.abs >= scale} || UNITS.first
56
+ end
57
+
58
+ def scale
59
+ unit.first
60
+ end
61
+
62
+ def unit_name
63
+ unit.last
64
+ end
65
+
66
+ # The least significant digit worth showing is the one the uncertainty
67
+ # reaches, which Measurand settles by the Particle Data Group convention and
68
+ # reports as #place, a power of ten. Rendering in a coarser unit moves it
69
+ # along by however many tens that unit is worth. A measurand with no
70
+ # uncertainty has no last real digit, and answers nil.
71
+ def decimals
72
+ @decimals ||= (
73
+ place = measurand.place
74
+ place ? [Math.log10(scale) - place, 0].max.to_i : Math.log10(scale).to_i
75
+ )
76
+ end
77
+
78
+ def to_duration
79
+ Duration::Nanoseconds.new(@nanoseconds)
80
+ end
81
+
82
+ # Both answer in nanoseconds, this measurement's own unit, as
83
+ # Duration::Minutes answers 5 rather than 300. Every other unit comes from
84
+ # #to_duration, which names the one it is asked for. A number handed out
85
+ # without its unit stated must at least always mean the same thing.
86
+ def to_i
87
+ @nanoseconds
88
+ end
89
+
90
+ def to_f
91
+ @nanoseconds.to_f
92
+ end
93
+
94
+ def to_s
95
+ format("%.#{decimals}f #{unit_name}", @nanoseconds.to_f / scale)
96
+ end
97
+
98
+ # Against another measurement only. A bare number has no unit, and comparing
99
+ # against one would have to assume which was meant, so nil is returned and ==
100
+ # is false, as Duration::Common does.
101
+ def <=>(other)
102
+ return nil unless other.is_a?(Monotonic::Measurement)
103
+ @nanoseconds <=> other.nanoseconds
104
+ end
105
+
106
+ def inspect
107
+ "#<#{self.class} #{self} (#{measurand} ns)>"
108
+ end
109
+
110
+ private
111
+
112
+ def initialize(nanoseconds, floor:, drift: nil)
113
+ @nanoseconds = nanoseconds
114
+ @floor = floor
115
+ @drift = drift
116
+ end
117
+ end
118
+ end
@@ -2,6 +2,7 @@
2
2
  # Monotonic::Timer
3
3
 
4
4
  require 'duration.rb'
5
+ require_relative './Measurement'
5
6
 
6
7
  module Monotonic
7
8
  class Timer
@@ -20,6 +21,10 @@ module Monotonic
20
21
  # rigidity: a method call would be a measurable part of what it measures.
21
22
  CLOCK = Process.const_get(CLOCK_NAMES.find{|name| Process.const_defined?(name)})
22
23
 
24
+ # How many measurements of nothing .floor takes, and which of them it keeps.
25
+ SAMPLES = 1_000
26
+ PERCENTILE = 0.95
27
+
23
28
  class << self
24
29
  def clock_name
25
30
  @clock_name ||= CLOCK_NAMES.find{|name| Process.const_defined?(name)}
@@ -36,6 +41,24 @@ module Monotonic
36
41
  def resolution
37
42
  Process.clock_getres(CLOCK, :nanosecond)
38
43
  end
44
+
45
+ # What it costs to measure at all, in nanoseconds, measured by timing
46
+ # measurements of nothing. The clock's tick is not the limit: two readings
47
+ # must be taken, and taking them takes time, so an interval near this
48
+ # figure is mostly the measuring.
49
+ #
50
+ # It is a floor and not an error bar. The typical cost is a tick or two;
51
+ # the tail is unbounded and one-sided, since the scheduler may take the
52
+ # processor away between the two readings and no single measurement can
53
+ # detect that it did. A high percentile is taken rather than a median, so
54
+ # that the ordinary case is covered rather than merely the best one.
55
+ # Repeat and take a robust statistic if the tail matters.
56
+ def floor
57
+ @floor ||= (
58
+ samples = SAMPLES.times.map{timer = new; timer.start; timer.stop; timer.total_nanoseconds}.sort
59
+ samples[(samples.length * PERCENTILE).to_i]
60
+ )
61
+ end
39
62
  end # class << self
40
63
 
41
64
  # Where the instants come from. Nothing supplied means the clock is read
@@ -87,6 +110,22 @@ module Monotonic
87
110
  to_duration.to_seconds.to_f
88
111
  end
89
112
 
113
+ # The elapsed interval together with what it is worth. Not memoized: a timer
114
+ # which has not been stopped is still running, and so is its measurement.
115
+ # The floor is handed over as a method rather than a figure, so that a caller
116
+ # which only ever wants a number never sets the thousand null measurements
117
+ # going.
118
+ def measurement(drift: nil)
119
+ Monotonic::Measurement.new(total_nanoseconds, floor: self.class.method(:floor), drift: drift)
120
+ end
121
+
122
+ def to_s
123
+ measurement.to_s
124
+ end
125
+
126
+ # A Monotonic::Measurement rather than a bare Float, so that what comes back
127
+ # says how much of itself is real. #total_time and #total_nanoseconds are
128
+ # still there for a number.
90
129
  def time
91
130
  start
92
131
  begin
@@ -94,7 +133,7 @@ module Monotonic
94
133
  ensure
95
134
  stop
96
135
  end
97
- total_time
136
+ measurement
98
137
  end
99
138
 
100
139
  private
@@ -2,5 +2,5 @@
2
2
  # Monotonic::VERSION
3
3
 
4
4
  module Monotonic
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.0'
6
6
  end
data/monotonic.rb.gemspec CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
 
38
38
  spec.dependencies = [
39
39
  ['duration.rb', '>= 0.4.0'],
40
+ ['measurand', '>= 0.1.0'],
40
41
  'sys-uptime'
41
42
  ]
42
43
 
@@ -0,0 +1,118 @@
1
+ require_relative '../../lib/monotonic.rb'
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+
6
+ describe Monotonic::Measurement do
7
+ def measurement(nanoseconds, floor: 84, drift: nil)
8
+ Monotonic::Measurement.new(nanoseconds, floor: floor, drift: drift)
9
+ end
10
+
11
+ describe "the unit" do
12
+ it "is the largest which leaves a mantissa of at least one" do
13
+ _(measurement(999).unit_name).must_equal('ns')
14
+ _(measurement(1_000).unit_name).must_equal('us')
15
+ _(measurement(1_265_625).unit_name).must_equal('ms')
16
+ _(measurement(1_000_000_000).unit_name).must_equal('s')
17
+ end
18
+
19
+ it "stops at seconds, the ladder being powers of a thousand" do
20
+ _(measurement(86_400_000_000_000).unit_name).must_equal('s')
21
+ end
22
+
23
+ it "is nanoseconds for nothing at all" do
24
+ _(measurement(0).to_s).must_equal('0 ns')
25
+ end
26
+ end
27
+
28
+ describe "the digits" do
29
+ it "reach as far as the uncertainty does and no further" do
30
+ _(measurement(1_265_625).to_s).must_equal('1.26562 ms')
31
+ end
32
+
33
+ it "are fewer upon a coarser instrument" do
34
+ _(measurement(1_265_625, floor: 100_000).to_s).must_equal('1.27 ms')
35
+ end
36
+
37
+ it "are none where the interval is near the floor" do
38
+ _(measurement(999).to_s).must_equal('999 ns')
39
+ end
40
+
41
+ it "are not capped, a day resolved to a hundred nanoseconds saying so" do
42
+ _(measurement(86_400_000_000_000).to_s).must_equal('86400.00000000 s')
43
+ end
44
+ end
45
+
46
+ describe "the uncertainty" do
47
+ it "is the floor alone when no drift is supplied" do
48
+ _(measurement(1_000_000).uncertainty).must_equal(84)
49
+ end
50
+
51
+ it "combines a drift in quadrature rather than by addition" do
52
+ combined = measurement(1_000_000_000, drift: 1e-6).uncertainty
53
+ _(combined).must_be_close_to(Math.sqrt(84**2 + 1_000**2), 0.01)
54
+ _(combined).wont_be_close_to(84 + 1_000, 1)
55
+ end
56
+
57
+ it "takes digits away as it grows" do
58
+ _(measurement(7_166_559_999, drift: 20e-6).decimals) \
59
+ .must_be(:<, measurement(7_166_559_999).decimals)
60
+ end
61
+
62
+ it "comes from a Measurand, which owns the arithmetic" do
63
+ _(measurement(1_000_000).measurand).must_be_instance_of(Measurand)
64
+ end
65
+ end
66
+
67
+ describe "the floor" do
68
+ it "may be a number" do
69
+ _(measurement(1_000_000, floor: 84).floor).must_equal(84)
70
+ end
71
+
72
+ it "may be something callable, and is not called until doubt is asked about" do
73
+ called = false
74
+ subject = Monotonic::Measurement.new(1_000_000, floor: ->{called = true; 84})
75
+ subject.to_i
76
+ subject.to_duration
77
+ _(called).must_equal(false)
78
+ subject.uncertainty
79
+ _(called).must_equal(true)
80
+ _(subject.floor).must_equal(84)
81
+ end
82
+ end
83
+
84
+ describe "comparison" do
85
+ it "orders one measurement against another" do
86
+ _(measurement(999)).must_be(:<, measurement(1_000))
87
+ _([measurement(1_000), measurement(999)].sort.map(&:nanoseconds)).must_equal([999, 1_000])
88
+ end
89
+
90
+ it "is nil against a bare number, which has no unit" do
91
+ _(measurement(999) <=> 999).must_be_nil
92
+ end
93
+
94
+ it "is therefore not equal to a bare number" do
95
+ _(measurement(999) == 999).must_equal(false)
96
+ end
97
+ end
98
+
99
+ describe "#to_duration" do
100
+ it "hands the interval to duration.rb, whose business the units are" do
101
+ _(measurement(1_500_000_000).to_duration).must_be_instance_of(Duration::Nanoseconds)
102
+ _(measurement(1_500_000_000).to_duration.to_seconds.to_f).must_equal(1.5)
103
+ end
104
+ end
105
+
106
+ describe "#to_i and #to_f" do
107
+ it "both answer in nanoseconds, the measurement's own unit" do
108
+ _(measurement(1_274_625).to_i).must_equal(1_274_625)
109
+ _(measurement(1_274_625).to_f).must_equal(1_274_625.0)
110
+ end
111
+
112
+ it "agree with one another, and with #nanoseconds" do
113
+ subject = measurement(1_274_625)
114
+ _(subject.to_f).must_equal(subject.to_i.to_f)
115
+ _(subject.to_i).must_equal(subject.nanoseconds)
116
+ end
117
+ end
118
+ end
@@ -9,7 +9,7 @@ describe Monotonic::Timer do
9
9
  block_time = Monotonic::Timer.time do |timer|
10
10
  sleep 3
11
11
  end
12
- expect(block_time.round).must_equal(3)
12
+ expect(block_time.to_duration.to_seconds.to_f.round).must_equal(3)
13
13
  end
14
14
 
15
15
  # Test introduced in 0.6.5 to demonstrate that the bug had been fixed.
@@ -18,7 +18,7 @@ describe Monotonic::Timer do
18
18
  sleep 3
19
19
  4
20
20
  end
21
- expect(block_time.round).must_equal(3)
21
+ expect(block_time.to_duration.to_seconds.to_f.round).must_equal(3)
22
22
  end
23
23
 
24
24
  # Test introduced in 0.6.7 to demonstrate that the bug had been fixed.
@@ -51,7 +51,7 @@ describe Monotonic::Timer do
51
51
  timer.start
52
52
  sleep 1
53
53
  end
54
- expect(block_time.round).must_equal(1)
54
+ expect(block_time.to_duration.to_seconds.to_f.round).must_equal(1)
55
55
  end
56
56
  end
57
57
 
@@ -88,7 +88,7 @@ describe Monotonic::Timer do
88
88
  expect(timer.total_time.round).must_equal(0)
89
89
  sleep 1
90
90
  end
91
- expect(time.round).must_equal(1)
91
+ expect(time.to_duration.to_seconds.to_f.round).must_equal(1)
92
92
  expect(timer.total_time.round).must_equal(1)
93
93
  end
94
94
 
data/test/gemspec_test.rb CHANGED
@@ -19,7 +19,7 @@ describe 'monotonic.rb.gemspec' do
19
19
  end
20
20
 
21
21
  it "declares its runtime dependencies" do
22
- _(spec.runtime_dependencies.map(&:name).sort).must_equal(%w{duration.rb sys-uptime})
22
+ _(spec.runtime_dependencies.map(&:name).sort).must_equal(%w{duration.rb measurand sys-uptime})
23
23
  end
24
24
 
25
25
  it "declares its development dependencies" do
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.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.4.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: measurand
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.0
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: sys-uptime
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -89,11 +103,13 @@ files:
89
103
  - Gemfile
90
104
  - README.md
91
105
  - Rakefile
106
+ - lib/Monotonic/Measurement.rb
92
107
  - lib/Monotonic/Time.rb
93
108
  - lib/Monotonic/Timer.rb
94
109
  - lib/Monotonic/VERSION.rb
95
110
  - lib/monotonic.rb
96
111
  - monotonic.rb.gemspec
112
+ - test/Monotonic/Measurement_test.rb
97
113
  - test/Monotonic/Time_test.rb
98
114
  - test/Monotonic/Timer_test.rb
99
115
  - test/Monotonic/VERSION_test.rb