time_up 0.0.2 → 0.0.6
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/.standard.yml +1 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +1 -1
- data/README.md +91 -23
- data/lib/time_up.rb +165 -33
- data/lib/time_up/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74e2d5917cd228f02d8e8b4ae785affd6f2feeeb689db16003a1ba397653496f
|
4
|
+
data.tar.gz: 2752fe3c544f39ba6c869e0a58ed628ed18f85c7d938ebdf2b0b2a8dcc879e35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74cb8513be353995a49ff1a2a39eaf7247da0c8344d29bd4e6eddc0fdb5a662a22ce0473378dc8f65bf8ae42b5c4284b49f46f587b32995856ea9b12d8da51a2
|
7
|
+
data.tar.gz: 5f9d08ad26099efdcf0410108fe7fc8d156e3fd7920e0c0461b0bee4d3a47b30487f4a7eb7b4441b1d64fde68c9c2e3b908cc231e783bd1332667153575a5d21
|
data/.standard.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby_version: 2.4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# 0.0.6
|
2
|
+
|
3
|
+
- Add `TimeUp.all_timers`. It's weird that it's not a thing.
|
4
|
+
|
5
|
+
# 0.0.5
|
6
|
+
|
7
|
+
- Add `median` and `percentile` timer statistics, and added them to
|
8
|
+
`print_detailed_summary`
|
9
|
+
|
10
|
+
# 0.0.4
|
11
|
+
|
12
|
+
- Add `TimeUp.print_detailed_summary`
|
13
|
+
|
14
|
+
# 0.0.3
|
15
|
+
|
16
|
+
- Change the return value of TimeUp.start when passed a block to be the
|
17
|
+
evaluated value of the block (for easier insertion into existing code without
|
18
|
+
adding a bunch of new assignment and returns)
|
19
|
+
- Allow timer instances' `start` method to be called with a block
|
20
|
+
- Add `timings`, `count`, `min`, `max`, and `mean` methods for basic stats
|
21
|
+
tracking
|
22
|
+
- Add `TimeUp.all_stats` to roll up all these
|
23
|
+
|
1
24
|
# 0.0.2
|
2
25
|
|
3
26
|
- Switch from a module method to Thread.current variable
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -11,6 +11,9 @@ process and that you want to measure in aggregate. (For example, to see how
|
|
11
11
|
much time your test suite spends creating factories, truncating the database, or
|
12
12
|
invoking a critical code path.)
|
13
13
|
|
14
|
+
Here's a [blog post about time_up](https://blog.testdouble.com/posts/2021-07-19-benchmarking-your-ruby-with-time_up/) and
|
15
|
+
a [great example of when it can be useful](https://gist.github.com/searls/feee0b0eac7c329b390fed90c4714afb).
|
16
|
+
|
14
17
|
## Install
|
15
18
|
|
16
19
|
Just run `gem install time_up` or add time_up to your Gemfile:
|
@@ -65,10 +68,10 @@ sleep 5
|
|
65
68
|
puts TimeUp.stop :eggs # => ~5.0
|
66
69
|
```
|
67
70
|
|
68
|
-
`TimeUp.start`
|
69
|
-
`start`, `stop`, `elaped`, and `reset` methods. If you want to
|
70
|
-
instance later, you can also call `TimeUp.timer(:some_name)`. So the
|
71
|
-
example could be rewritten as:
|
71
|
+
When passes without a block, `TimeUp.start` returns an instance of the timer,
|
72
|
+
which has its own `start`, `stop`, `elaped`, and `reset` methods. If you want to
|
73
|
+
find that instance later, you can also call `TimeUp.timer(:some_name)`. So the
|
74
|
+
above example could be rewritten as:
|
72
75
|
|
73
76
|
```ruby
|
74
77
|
egg_timer = TimeUp.start :eggs
|
@@ -107,7 +110,7 @@ TimeUp.print_summary
|
|
107
110
|
Which will output something like:
|
108
111
|
|
109
112
|
```
|
110
|
-
TimeUp
|
113
|
+
TimeUp summary
|
111
114
|
========================
|
112
115
|
:roast 0.07267s
|
113
116
|
:veggies 0.03760s
|
@@ -117,39 +120,87 @@ TimeUp timers summary
|
|
117
120
|
* Denotes that the timer is still active
|
118
121
|
```
|
119
122
|
|
123
|
+
And if you're calling the timers multiple times and want to see some basic
|
124
|
+
statistics in the print-out, you can call `TimeUp.print_detailed_summary`, which
|
125
|
+
will produce this:
|
126
|
+
|
127
|
+
```
|
128
|
+
=============================================================================
|
129
|
+
Name | Elapsed | Count | Min | Max | Mean | Median | 95th %
|
130
|
+
-----------------------------------------------------------------------------
|
131
|
+
:roast | 0.08454 | 3 | 0.00128 | 0.07280 | 0.02818 | 0.01046 | 0.06657
|
132
|
+
:veggies | 0.03779 | 1 | 0.03779 | 0.03779 | 0.03779 | 0.03779 | 0.03779
|
133
|
+
:pasta | 0.01260 | 11 | 0.00000 | 0.01258 | 0.00115 | 0.00000 | 0.00630
|
134
|
+
:souffle* | 0.00024 | 1 | 0.00024 | 0.00025 | 0.00025 | 0.00025 | 0.00026
|
135
|
+
|
136
|
+
* Denotes that the timer is still active
|
137
|
+
```
|
138
|
+
|
120
139
|
## API
|
121
140
|
|
122
141
|
This gem defines a bunch of public methods but they're all pretty short and
|
123
|
-
straightforward, so I'd encourage you to [read the
|
142
|
+
straightforward, so when in doubt, I'd encourage you to [read the
|
143
|
+
code](/lib/time_up.rb).
|
124
144
|
|
125
145
|
### `TimeUp` module
|
126
146
|
|
127
|
-
`TimeUp.
|
147
|
+
`TimeUp.timer(name)` - Returns the `Timer` instance named `name` (creating it,
|
148
|
+
if it doesn't exist)
|
128
149
|
|
129
|
-
`TimeUp.
|
150
|
+
`TimeUp.start(name, [&blk])` - Starts (or restarts) a named
|
151
|
+
[Timer](#timeuptimer-class). If passed a block, will return whatever the block
|
152
|
+
evaluates to. If called without a block, it will return the timer object
|
130
153
|
|
131
|
-
`TimeUp.stop(name)` - Stops the named timer
|
154
|
+
`TimeUp.stop(name)` - Stops the named timer
|
155
|
+
|
156
|
+
`TimeUp.reset(name)` - Resets the named timer's elapsed time to 0, effectively
|
157
|
+
restarting it if it's currently running
|
132
158
|
|
133
159
|
`TimeUp.elapsed(name)` - Returns a `Float` of the total elapsed seconds that the
|
134
|
-
named timer has been running
|
135
|
-
`name`)
|
160
|
+
named timer has been running
|
136
161
|
|
137
|
-
`TimeUp.
|
138
|
-
|
162
|
+
`TimeUp.timings(name)` - Returns an array of each recorded start-to-stop
|
163
|
+
duration (including the current one, if the timer is running) of the named timer
|
164
|
+
|
165
|
+
`TimeUp.count(name)` - The number of times the timer has been started (including
|
166
|
+
the current timing, if the timer is running)
|
167
|
+
|
168
|
+
`TimeUp.min(name)` - The shortest recording by the timer
|
169
|
+
|
170
|
+
`TimeUp.max(name)` - The longest recording by the timer
|
139
171
|
|
140
|
-
`TimeUp.
|
141
|
-
timers you've created
|
172
|
+
`TimeUp.mean(name)` - The arithmetic mean of all recordings by the timer
|
142
173
|
|
143
|
-
`TimeUp.
|
144
|
-
`elapsed` values. Handy for grabbing a snapshot of the state of things at a
|
145
|
-
particular point in time without stopping all your timers
|
174
|
+
`TimeUp.median(name)` - The median of all recordings by the timer
|
146
175
|
|
147
|
-
`TimeUp.
|
148
|
-
|
149
|
-
|
176
|
+
`TimeUp.percentile(name, percent)` - The timing for the given
|
177
|
+
[percentile](https://en.wikipedia.org/wiki/Percentile) of all recordings by the
|
178
|
+
timer
|
150
179
|
|
151
|
-
`TimeUp.
|
152
|
-
timers
|
180
|
+
`TimeUp.total_elapsed` - Returns a `Float` of the sum of `elapsed` across all
|
181
|
+
the timers you've created (note that because you can easily run multiple logical
|
182
|
+
timers simultaneously, this figure may exceed the total time spent by the
|
183
|
+
computer)
|
184
|
+
|
185
|
+
`TimeUp.all_elapsed` - Returns a Hash of timer name keys mapped to their
|
186
|
+
`elapsed` values. Handy for grabbing a reference to a snapshot of the state of
|
187
|
+
things without requiring you to stop your timers
|
188
|
+
|
189
|
+
`TimeUp.all_stats` - Returns a Hash of timer name keys mapped to another
|
190
|
+
hash of their basic statistics (`elapsed`, `count`, `min`, `max`,
|
191
|
+
and `mean`)
|
192
|
+
|
193
|
+
`TimeUp.active_timers` - Returns an Array of all timers that are currently
|
194
|
+
running. Useful for detecting cases where you might be keeping time in multiple
|
195
|
+
places simultaneously
|
196
|
+
|
197
|
+
`TimeUp.print_summary([io])` - Pretty-prints a multi-line summary of all your
|
198
|
+
timers' total elapsed times to standard output (or the provided
|
199
|
+
[IO](https://ruby-doc.org/core-3.0.1/IO.html))
|
200
|
+
|
201
|
+
`TimeUp.print_detailed_summary([io])` - Pretty-prints a multi-line summary of
|
202
|
+
all your timers' elapsed times and basic statistics to standard output (or the
|
203
|
+
provided [IO](https://ruby-doc.org/core-3.0.1/IO.html))
|
153
204
|
|
154
205
|
`TimeUp.stop_all` - Stops all timers
|
155
206
|
|
@@ -166,6 +217,23 @@ reference to them
|
|
166
217
|
|
167
218
|
`elapsed` - A `Float` of the total elapsed seconds the timer has been running
|
168
219
|
|
220
|
+
`timings` - Returns an Array of each recorded start-to-stop duration of the
|
221
|
+
timer (including the current one, if the timer is running)
|
222
|
+
|
223
|
+
`count` - The number of times the timer has been started and stopped
|
224
|
+
|
225
|
+
`min` - The shortest recording of the timer
|
226
|
+
|
227
|
+
`max` - The longest recording of the timer
|
228
|
+
|
229
|
+
`mean` - The arithmetic mean of all recorded durations of the timer
|
230
|
+
|
231
|
+
`median(name)` - The median of all recordings by the timer
|
232
|
+
|
233
|
+
`percentile(name, percent)` - The timing for the given
|
234
|
+
[percentile](https://en.wikipedia.org/wiki/Percentile) of all recordings by the
|
235
|
+
timer
|
236
|
+
|
169
237
|
`active?` - Returns `true` if the timer is running
|
170
238
|
|
171
239
|
`reset(force: false)` - Resets the timer to 0 elapsed seconds. If `force` is
|
data/lib/time_up.rb
CHANGED
@@ -5,35 +5,35 @@ module TimeUp
|
|
5
5
|
|
6
6
|
Thread.current[:time_up_timers] = {}
|
7
7
|
|
8
|
-
def self.start(name, &blk)
|
9
|
-
raise Error.new("Timer name must be a String or Symbol") unless name.is_a?(Symbol) || name.is_a?(String)
|
10
|
-
timer = __timers[name] ||= Timer.new(name)
|
11
|
-
timer.start
|
12
|
-
if blk
|
13
|
-
blk.call
|
14
|
-
timer.stop
|
15
|
-
end
|
16
|
-
timer
|
17
|
-
end
|
18
|
-
|
19
|
-
# Delegate methods
|
20
8
|
def self.timer(name)
|
21
|
-
__timers[name]
|
9
|
+
__timers[name] ||= Timer.new(name)
|
22
10
|
end
|
23
11
|
|
24
|
-
|
25
|
-
|
26
|
-
|
12
|
+
# Delegate methods
|
13
|
+
def self.start(name, &blk)
|
14
|
+
timer(name).start(&blk)
|
27
15
|
end
|
28
16
|
|
29
|
-
|
30
|
-
|
31
|
-
|
17
|
+
[
|
18
|
+
:stop,
|
19
|
+
:reset,
|
20
|
+
:elapsed,
|
21
|
+
:timings,
|
22
|
+
:count,
|
23
|
+
:min,
|
24
|
+
:max,
|
25
|
+
:mean,
|
26
|
+
:median
|
27
|
+
].each do |method_name|
|
28
|
+
define_singleton_method method_name do |name|
|
29
|
+
__ensure_timer(name)
|
30
|
+
__timers[name].send(method_name)
|
31
|
+
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def self.
|
34
|
+
def self.percentile(name, percentage)
|
35
35
|
__ensure_timer(name)
|
36
|
-
__timers[name].
|
36
|
+
__timers[name].percentile(percentage)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Interrogative methods
|
@@ -47,6 +47,24 @@ module TimeUp
|
|
47
47
|
}.to_h
|
48
48
|
end
|
49
49
|
|
50
|
+
def self.all_timers
|
51
|
+
__timers.values
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.all_stats
|
55
|
+
__timers.values.map { |timer|
|
56
|
+
[timer.name, {
|
57
|
+
elapsed: timer.elapsed,
|
58
|
+
count: timer.count,
|
59
|
+
min: timer.min,
|
60
|
+
max: timer.max,
|
61
|
+
mean: timer.mean,
|
62
|
+
median: timer.median,
|
63
|
+
"95th": timer.percentile(95)
|
64
|
+
}]
|
65
|
+
}.to_h
|
66
|
+
end
|
67
|
+
|
50
68
|
def self.active_timers
|
51
69
|
__timers.values.select(&:active?)
|
52
70
|
end
|
@@ -59,7 +77,7 @@ module TimeUp
|
|
59
77
|
}
|
60
78
|
io.puts <<~SUMMARY
|
61
79
|
|
62
|
-
TimeUp
|
80
|
+
TimeUp summary
|
63
81
|
========================
|
64
82
|
#{summaries.join("\n")}
|
65
83
|
|
@@ -67,6 +85,56 @@ module TimeUp
|
|
67
85
|
SUMMARY
|
68
86
|
end
|
69
87
|
|
88
|
+
def self.print_detailed_summary(io = $stdout)
|
89
|
+
cols = {
|
90
|
+
names: ["Name"],
|
91
|
+
elapsed: ["Elapsed"],
|
92
|
+
count: ["Count"],
|
93
|
+
min: ["Min"],
|
94
|
+
max: ["Max"],
|
95
|
+
mean: ["Mean"],
|
96
|
+
median: ["Median"],
|
97
|
+
"95th": ["95th %"]
|
98
|
+
}
|
99
|
+
__timers.values.each { |timer|
|
100
|
+
cols[:names] << "#{timer.name.inspect}#{"*" if timer.active?}"
|
101
|
+
cols[:elapsed] << "%.5f" % timer.elapsed
|
102
|
+
cols[:count] << timer.count.to_s
|
103
|
+
cols[:min] << "%.5f" % timer.min
|
104
|
+
cols[:max] << "%.5f" % timer.max
|
105
|
+
cols[:mean] << "%.5f" % timer.mean
|
106
|
+
cols[:median] << "%.5f" % timer.median
|
107
|
+
cols[:"95th"] << "%.5f" % timer.percentile(95)
|
108
|
+
}
|
109
|
+
|
110
|
+
widths = cols.map { |name, vals|
|
111
|
+
[name, vals.map(&:length).max]
|
112
|
+
}.to_h
|
113
|
+
|
114
|
+
rows = cols[:names].size.times.map { |i|
|
115
|
+
if i == 0
|
116
|
+
cols.keys.map { |name|
|
117
|
+
cols[name][i].center(widths[name])
|
118
|
+
}
|
119
|
+
else
|
120
|
+
cols.keys.map { |name|
|
121
|
+
cols[name][i].ljust(widths[name])
|
122
|
+
}
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
full_width = widths.values.sum + (rows[0].size - 1) * 3
|
127
|
+
io.puts <<~SUMMARY
|
128
|
+
|
129
|
+
#{"=" * full_width}
|
130
|
+
#{rows[0].join(" | ")}
|
131
|
+
#{"-" * full_width}
|
132
|
+
#{rows[1..-1].map { |row| row.join(" | ") }.join("\n")}
|
133
|
+
|
134
|
+
#{"* Denotes that the timer is still active\n" if __timers.values.any?(&:active?)}
|
135
|
+
SUMMARY
|
136
|
+
end
|
137
|
+
|
70
138
|
# Iterative methods
|
71
139
|
def self.stop_all
|
72
140
|
__timers.values.each(&:stop)
|
@@ -94,46 +162,110 @@ module TimeUp
|
|
94
162
|
attr_reader :name
|
95
163
|
|
96
164
|
def initialize(name)
|
165
|
+
validate!(name)
|
97
166
|
@name = name
|
98
167
|
@start_time = nil
|
99
|
-
@
|
168
|
+
@total_elapsed = 0.0
|
169
|
+
@past_timings = []
|
100
170
|
end
|
101
171
|
|
102
|
-
def start
|
172
|
+
def start(&blk)
|
103
173
|
@start_time ||= now
|
174
|
+
if blk
|
175
|
+
blk.call.tap do
|
176
|
+
stop
|
177
|
+
end
|
178
|
+
else
|
179
|
+
self
|
180
|
+
end
|
104
181
|
end
|
105
182
|
|
106
183
|
def stop
|
107
184
|
if @start_time
|
108
|
-
|
185
|
+
duration = now - @start_time
|
186
|
+
@past_timings.push(duration)
|
187
|
+
@total_elapsed += duration
|
188
|
+
|
109
189
|
@start_time = nil
|
110
190
|
end
|
111
|
-
@
|
191
|
+
@total_elapsed
|
112
192
|
end
|
113
193
|
|
114
194
|
def elapsed
|
115
195
|
if active?
|
116
|
-
@
|
196
|
+
@total_elapsed + (now - @start_time)
|
117
197
|
else
|
118
|
-
@
|
198
|
+
@total_elapsed
|
119
199
|
end
|
120
200
|
end
|
121
201
|
|
122
|
-
def active?
|
123
|
-
!!@start_time
|
124
|
-
end
|
125
|
-
|
126
202
|
def reset(force: false)
|
127
203
|
if force
|
128
204
|
@start_time = nil
|
129
205
|
elsif !@start_time.nil?
|
130
206
|
@start_time = now
|
131
207
|
end
|
132
|
-
@
|
208
|
+
@total_elapsed = 0.0
|
209
|
+
@past_timings = []
|
210
|
+
end
|
211
|
+
|
212
|
+
def count
|
213
|
+
timings.size
|
214
|
+
end
|
215
|
+
|
216
|
+
def min
|
217
|
+
timings.min
|
218
|
+
end
|
219
|
+
|
220
|
+
def max
|
221
|
+
timings.max
|
222
|
+
end
|
223
|
+
|
224
|
+
def mean
|
225
|
+
times = timings
|
226
|
+
return if times.empty?
|
227
|
+
times.sum / times.size
|
228
|
+
end
|
229
|
+
|
230
|
+
def median
|
231
|
+
times = timings.sort
|
232
|
+
return if times.empty?
|
233
|
+
(times[(times.size - 1) / 2] + times[times.size / 2]) / 2.0
|
234
|
+
end
|
235
|
+
|
236
|
+
def percentile(percent)
|
237
|
+
times = timings.sort
|
238
|
+
return if times.empty?
|
239
|
+
return 0 if percent <= 0
|
240
|
+
return max if percent >= 100
|
241
|
+
return times.first if times.size == 1
|
242
|
+
position = (percent / 100.0) * (times.size - 1)
|
243
|
+
|
244
|
+
partial_ratio = position - position.floor
|
245
|
+
whole, partial = times[position.floor, 2]
|
246
|
+
whole + (partial - whole) * partial_ratio
|
247
|
+
end
|
248
|
+
|
249
|
+
def timings
|
250
|
+
if active?
|
251
|
+
@past_timings + [now - @start_time]
|
252
|
+
else
|
253
|
+
@past_timings
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def active?
|
258
|
+
!!@start_time
|
133
259
|
end
|
134
260
|
|
135
261
|
private
|
136
262
|
|
263
|
+
def validate!(name)
|
264
|
+
unless name.is_a?(Symbol) || name.is_a?(String)
|
265
|
+
raise Error.new("Timer name must be a String or Symbol")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
137
269
|
def now
|
138
270
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
139
271
|
end
|
data/lib/time_up/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_up
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -19,6 +19,7 @@ extra_rdoc_files: []
|
|
19
19
|
files:
|
20
20
|
- ".github/workflows/main.yml"
|
21
21
|
- ".gitignore"
|
22
|
+
- ".standard.yml"
|
22
23
|
- CHANGELOG.md
|
23
24
|
- Gemfile
|
24
25
|
- Gemfile.lock
|