Class: Philiprehberger::Stopwatch

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/stopwatch.rb,
lib/philiprehberger/stopwatch/version.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
'0.6.0'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeStopwatch

Returns a new instance of Stopwatch.



9
10
11
12
13
14
15
16
17
18
# File 'lib/philiprehberger/stopwatch.rb', line 9

def initialize
  @laps = []
  @checkpoints = {}
  @running = false
  @paused = false
  @start_time = nil
  @elapsed_before_pause = 0.0
  @lap_start = nil
  @last_tick = nil
end

Class Method Details

.format_duration(seconds) ⇒ String

Format a duration in seconds as a human-readable string

Parameters:

  • seconds (Float)

Returns:

  • (String)


276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/philiprehberger/stopwatch.rb', line 276

def self.format_duration(seconds)
  if seconds < 0.001
    format('%.2fus', seconds * 1_000_000)
  elsif seconds < 1.0
    format('%.2fms', seconds * 1_000)
  elsif seconds < 60.0
    format('%.3fs', seconds)
  elsif seconds < 3600.0
    minutes = (seconds / 60).to_i
    secs = (seconds % 60).to_i
    "#{minutes}m #{secs}s"
  else
    hours = (seconds / 3600).to_i
    minutes = ((seconds % 3600) / 60).to_i
    secs = (seconds % 60).to_i
    "#{hours}h #{minutes}m #{secs}s"
  end
end

.measure { ... } ⇒ Array

Measure execution time of a block

Yields:

  • block to measure

Returns:

  • (Array)

    [result, elapsed_seconds]



256
257
258
259
260
261
# File 'lib/philiprehberger/stopwatch.rb', line 256

def self.measure
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  result = yield
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
  [result, elapsed]
end

.measure_formatted { ... } ⇒ String

Measure execution time and return formatted string

Yields:

  • block to measure

Returns:

  • (String)

    formatted elapsed time



267
268
269
270
# File 'lib/philiprehberger/stopwatch.rb', line 267

def self.measure_formatted(&)
  _, elapsed = measure(&)
  format_duration(elapsed)
end

Instance Method Details

#checkpoint(name) ⇒ self

Record a named checkpoint at the current cumulative elapsed time

Parameters:

  • name (String, Symbol)

    checkpoint name

Returns:

  • (self)

Raises:



147
148
149
150
151
152
153
154
# File 'lib/philiprehberger/stopwatch.rb', line 147

def checkpoint(name)
  raise Error, 'stopwatch is not running' unless @running

  current = now
  split = @elapsed_before_pause + (current - @start_time)
  @checkpoints[name] = split
  self
end

#checkpointsHash

Return a copy of all recorded checkpoints

Returns:

  • (Hash)


179
180
181
# File 'lib/philiprehberger/stopwatch.rb', line 179

def checkpoints
  @checkpoints.dup
end

#elapsedFloat

Get total elapsed time in seconds

Returns:

  • (Float)


128
129
130
131
132
133
134
# File 'lib/philiprehberger/stopwatch.rb', line 128

def elapsed
  if @running && !@paused
    @elapsed_before_pause + (now - @start_time)
  else
    @elapsed_before_pause
  end
end

#elapsed_at(name) ⇒ Float

Return the cumulative elapsed time recorded at a named checkpoint

Parameters:

  • name (String, Symbol)

    checkpoint name

Returns:

  • (Float)

Raises:



160
161
162
163
164
# File 'lib/philiprehberger/stopwatch.rb', line 160

def elapsed_at(name)
  raise Error, "checkpoint '#{name}' not found" unless @checkpoints.key?(name)

  @checkpoints[name]
end

#elapsed_msFloat

Get total elapsed time in milliseconds

Returns:

  • (Float)


114
115
116
# File 'lib/philiprehberger/stopwatch.rb', line 114

def elapsed_ms
  elapsed * 1000.0
end

#elapsed_usFloat

Get total elapsed time in microseconds

Returns:

  • (Float)


121
122
123
# File 'lib/philiprehberger/stopwatch.rb', line 121

def elapsed_us
  elapsed * 1_000_000.0
end

#formatted_elapsedString

Format elapsed time as a human-readable string

Returns:

  • (String)


205
206
207
# File 'lib/philiprehberger/stopwatch.rb', line 205

def formatted_elapsed
  self.class.format_duration(elapsed)
end

#formatted_lapsArray<Hash>

Get formatted lap data

Returns:

  • (Array<Hash>)

    each with :name, :elapsed, :split, :formatted, :formatted_split



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/philiprehberger/stopwatch.rb', line 212

def formatted_laps
  @laps.map do |lap|
    {
      name: lap[:name],
      elapsed: lap[:elapsed],
      split: lap[:split],
      formatted: self.class.format_duration(lap[:elapsed]),
      formatted_split: self.class.format_duration(lap[:split])
    }
  end
end

#lap(name = nil) ⇒ Hash

Record a lap

Parameters:

  • name (String, nil) (defaults to: nil)

    optional lap name

Returns:

  • (Hash)

    lap data with name, elapsed time, and cumulative split time

Raises:



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/philiprehberger/stopwatch.rb', line 80

def lap(name = nil)
  raise Error, 'stopwatch is not running' unless @running

  current = now
  lap_elapsed = current - @lap_start
  split = @elapsed_before_pause + (current - @start_time)
  lap_data = { name: name, elapsed: lap_elapsed, split: split }
  @laps << lap_data
  @lap_start = current
  lap_data
end

#lap_statsHash

Get aggregate statistics across all recorded laps

Returns:

  • (Hash)

    { count:, total:, avg:, min:, max: }



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/philiprehberger/stopwatch.rb', line 186

def lap_stats
  if @laps.empty?
    return { count: 0, total: 0.0, avg: 0.0, min: 0.0, max: 0.0 }
  end

  times = @laps.map { |l| l[:elapsed] }
  total = times.sum
  {
    count: times.length,
    total: total,
    avg: total / times.length,
    min: times.min,
    max: times.max
  }
end

#lapsArray<Hash>

Get all recorded laps

Returns:

  • (Array<Hash>)


139
140
141
# File 'lib/philiprehberger/stopwatch.rb', line 139

def laps
  @laps.dup
end

#paused?Boolean

Check if the stopwatch is paused

Returns:

  • (Boolean)


241
242
243
# File 'lib/philiprehberger/stopwatch.rb', line 241

def paused?
  @paused
end

#resetself

Reset the stopwatch

Returns:

  • (self)


53
54
55
56
57
58
59
60
61
62
63
# File 'lib/philiprehberger/stopwatch.rb', line 53

def reset
  @laps = []
  @checkpoints = {}
  @running = false
  @paused = false
  @start_time = nil
  @elapsed_before_pause = 0.0
  @lap_start = nil
  @last_tick = nil
  self
end

#restartself

Reset and start the stopwatch in one call

Returns:

  • (self)


68
69
70
71
# File 'lib/philiprehberger/stopwatch.rb', line 68

def restart
  reset
  start
end

#running?Boolean

Check if the stopwatch is running (and not paused)

Returns:

  • (Boolean)


248
249
250
# File 'lib/philiprehberger/stopwatch.rb', line 248

def running?
  @running && !@paused
end

#since(name) ⇒ Float

Return the elapsed time since a named checkpoint was recorded

Parameters:

  • name (String, Symbol)

    checkpoint name

Returns:

  • (Float)

Raises:



170
171
172
173
174
# File 'lib/philiprehberger/stopwatch.rb', line 170

def since(name)
  raise Error, "checkpoint '#{name}' not found" unless @checkpoints.key?(name)

  elapsed - @checkpoints[name]
end

#startself Also known as: resume

Start the stopwatch

Returns:

  • (self)

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/philiprehberger/stopwatch.rb', line 23

def start
  raise Error, 'stopwatch is already running' if @running && !@paused

  @start_time = now
  if @paused
    @paused = false
  else
    @elapsed_before_pause = 0.0
    @lap_start = @start_time
    @laps = []
  end
  @running = true
  self
end

#stopself Also known as: pause

Stop (pause) the stopwatch

Returns:

  • (self)

Raises:



41
42
43
44
45
46
47
48
# File 'lib/philiprehberger/stopwatch.rb', line 41

def stop
  raise Error, 'stopwatch is not running' unless @running
  raise Error, 'stopwatch is already paused' if @paused

  @elapsed_before_pause += now - @start_time
  @paused = true
  self
end

#tickFloat

Record an unnamed tick and return the seconds since the previous tick

On the first call after start, returns the seconds elapsed since the stopwatch began. Subsequent calls return the seconds since the previous tick. Useful for sequential timing in loops where naming each lap is noise — a thinner lap for ad-hoc measurement.

Returns:

  • (Float)

    seconds since the previous tick (or since start)

Raises:

  • (Error)

    if the stopwatch is not running



101
102
103
104
105
106
107
108
109
# File 'lib/philiprehberger/stopwatch.rb', line 101

def tick
  raise Error, 'stopwatch is not running' unless running?

  current = now
  previous = @last_tick || @start_time
  delta = current - previous
  @last_tick = current
  delta
end

#to_hHash

Serialize the stopwatch state to a hash

Returns:

  • (Hash)


227
228
229
230
231
232
233
234
235
236
# File 'lib/philiprehberger/stopwatch.rb', line 227

def to_h
  {
    running: running?,
    paused: paused?,
    elapsed: elapsed,
    formatted_elapsed: formatted_elapsed,
    laps: laps,
    lap_stats: lap_stats
  }
end