time_up 0.0.2 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|