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.5.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
# 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
end

Class Method Details

.format_duration(seconds) ⇒ String

Format a duration in seconds as a human-readable string

Parameters:

  • seconds (Float)

Returns:

  • (String)


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/philiprehberger/stopwatch.rb', line 255

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


235
236
237
238
239
240
# File 'lib/philiprehberger/stopwatch.rb', line 235

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



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

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:



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

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)


158
159
160
# File 'lib/philiprehberger/stopwatch.rb', line 158

def checkpoints
  @checkpoints.dup
end

#elapsedFloat

Get total elapsed time in seconds

Returns:

  • (Float)


107
108
109
110
111
112
113
# File 'lib/philiprehberger/stopwatch.rb', line 107

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:



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

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)


93
94
95
# File 'lib/philiprehberger/stopwatch.rb', line 93

def elapsed_ms
  elapsed * 1000.0
end

#elapsed_usFloat

Get total elapsed time in microseconds

Returns:

  • (Float)


100
101
102
# File 'lib/philiprehberger/stopwatch.rb', line 100

def elapsed_us
  elapsed * 1_000_000.0
end

#formatted_elapsedString

Format elapsed time as a human-readable string

Returns:

  • (String)


184
185
186
# File 'lib/philiprehberger/stopwatch.rb', line 184

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



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/philiprehberger/stopwatch.rb', line 191

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:



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

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: }



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/philiprehberger/stopwatch.rb', line 165

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>)


118
119
120
# File 'lib/philiprehberger/stopwatch.rb', line 118

def laps
  @laps.dup
end

#paused?Boolean

Check if the stopwatch is paused

Returns:

  • (Boolean)


220
221
222
# File 'lib/philiprehberger/stopwatch.rb', line 220

def paused?
  @paused
end

#resetself

Reset the stopwatch

Returns:

  • (self)


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

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

#restartself

Reset and start the stopwatch in one call

Returns:

  • (self)


66
67
68
69
# File 'lib/philiprehberger/stopwatch.rb', line 66

def restart
  reset
  start
end

#running?Boolean

Check if the stopwatch is running (and not paused)

Returns:

  • (Boolean)


227
228
229
# File 'lib/philiprehberger/stopwatch.rb', line 227

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:



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

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:



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

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:



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

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

#to_hHash

Serialize the stopwatch state to a hash

Returns:

  • (Hash)


206
207
208
209
210
211
212
213
214
215
# File 'lib/philiprehberger/stopwatch.rb', line 206

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