Class: Timify
- Inherits:
-
Object
- Object
- Timify
- Defined in:
- lib/timify.rb,
lib/timify/span.rb,
lib/timify/trace.rb,
lib/timify/report.rb,
lib/timify/version.rb,
lib/timify/registry.rb,
lib/timify/recording.rb
Overview
Measures elapsed time between points in Ruby code.
#measure blocks nest. A parent span's inclusive time is the whole block,
and its secs (self time) is what the children did not already take.
#add marks the time since the previous mark on this thread. Intervals use
a monotonic clock. #totals still reports started and finished as Time
objects.
Each thread has its own cursor and span stack. Totals are shared and guarded
by a mutex, so two threads can mark the same timer. total_time is the sum
of self time and can exceed wall_time when threads overlap.
Timify.enabled is a process-wide switch. When it is false, every timer is a
no-op: #add and #measure record nothing, and #initialize does not
print. The default comes from TIMIFY_DISABLE (+1+, true, or on, case
insensitive). Setting Timify.enabled= overrides that for the rest of the process.
Defined Under Namespace
Constant Summary collapse
- VERSION =
"1.0.0"- REGISTRY_MUTEX =
Mutex.new
- MAX_SAMPLES =
Retained samples per bucket. min, max, count, and the sums stay exact after this cap; percentiles then use the retained prefix.
10_000- THREAD_STATE_KEY =
:__timify_thread_state__
Class Attribute Summary collapse
-
.enabled ⇒ Boolean
Process-wide recording switch.
Instance Attribute Summary collapse
-
#initial_time ⇒ Time
readonly
Wall-clock time when the timer was created or last #reset.
-
#max_time_spent ⇒ Float
readonly
Longest single self-time segment since creation or #reset.
- #min_time_to_show ⇒ Numeric
-
#name ⇒ Object
readonly
Name given when the timer was created.
-
#output ⇒ #puts, #info
Where printed lines are sent.
-
#show ⇒ Boolean
When
false, the timer stays silent. -
#status ⇒ Symbol
:onwhile the timer is recording,:offwhile it is paused. -
#total ⇒ Float
readonly
Self time recorded so far, excluding paused time.
Class Method Summary collapse
-
.[](name) ⇒ Timify
Returns the timer registered under
name, creating it on first use. -
.clear! ⇒ void
Drops every timer created through Timify.[].
-
.enabled? ⇒ Boolean
Whether recording is currently enabled.
-
.env_disabled? ⇒ Boolean
Whether
TIMIFY_DISABLEis set to a disabling value (+1+,true, oron, case insensitive). -
.measure(name, **options) {|timer| ... } ⇒ Timify
Creates a timer, yields it, and returns it after the block finishes.
-
.trace(name, **options) {|timer| ... } ⇒ Timify::Trace
Creates a timer, yields it, and returns a Trace with the block's value and the timer.
Instance Method Summary collapse
-
#add(label = nil) ⇒ Float
Records the time elapsed since the previous mark on this thread.
-
#initialize(name, min_time_to_show: 0, show: true, output: $stdout) ⇒ Timify
constructor
A new instance of Timify.
-
#measure(label = nil) { ... } ⇒ Object
Records the time spent inside
blockand returns the block's value. -
#on_share(ratio) {|event| ... } ⇒ Timify
Registers a callback invoked when a child span's inclusive time is at least
ratioof the parent's elapsed time so far. -
#on_slow(seconds) {|event| ... } ⇒ Timify
Registers a callback invoked when a segment is at least
secondslong. -
#reset ⇒ Timify
Clears recorded segments and starts the clock again.
-
#totals(json: false, group: false) ⇒ Hash, String
Returns the report for every segment recorded so far.
Constructor Details
#initialize(name, min_time_to_show: 0, show: true, output: $stdout) ⇒ Timify
Returns a new instance of Timify.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/timify.rb', line 101 def initialize(name, min_time_to_show: 0, show: true, output: $stdout) @name = name @min_time_to_show = min_time_to_show @show = show @output = output @mutex = Mutex.new @generation = 0 @slow_handlers = [] @share_handlers = [] @resume_at = nil @pause_started = nil @paused_monotonic = 0.0 reset emit("<#{@name}> Timify init:<#{@initial_time}>. Location: #{caller_location}") if self.class.enabled? end |
Class Attribute Details
.enabled ⇒ Boolean
Process-wide recording switch. When false, every timer is a no-op.
Defaults from TIMIFY_DISABLE when the class loads; enabled= overrides
that for the rest of the process.
41 42 43 |
# File 'lib/timify.rb', line 41 def enabled @enabled end |
Instance Attribute Details
#initial_time ⇒ Time (readonly)
Returns wall-clock time when the timer was created or last #reset.
70 71 72 |
# File 'lib/timify.rb', line 70 def initial_time @initial_time end |
#max_time_spent ⇒ Float (readonly)
Returns longest single self-time segment since creation or #reset.
73 74 75 |
# File 'lib/timify.rb', line 73 def max_time_spent @max_time_spent end |
#min_time_to_show ⇒ Numeric
82 83 84 |
# File 'lib/timify.rb', line 82 def min_time_to_show @min_time_to_show end |
#name ⇒ Object (readonly)
Returns name given when the timer was created.
64 65 66 |
# File 'lib/timify.rb', line 64 def name @name end |
#output ⇒ #puts, #info
Where printed lines are sent. An object that responds to puts is written
with puts. An object that responds to info and not puts, such as a
Logger, is written with info. Defaults to $stdout.
95 96 97 |
# File 'lib/timify.rb', line 95 def output @output end |
#show ⇒ Boolean
When false, the timer stays silent. Segments are still recorded.
Defaults to true.
88 89 90 |
# File 'lib/timify.rb', line 88 def show @show end |
#status ⇒ Symbol
Returns :on while the timer is recording, :off while it is paused.
76 77 78 |
# File 'lib/timify.rb', line 76 def status @status end |
#total ⇒ Float (readonly)
Returns self time recorded so far, excluding paused time.
67 68 69 |
# File 'lib/timify.rb', line 67 def total @total end |
Class Method Details
.[](name) ⇒ Timify
Returns the timer registered under name, creating it on first use.
Registered timers start with show: false so fetching one does not print.
11 12 13 14 15 |
# File 'lib/timify/registry.rb', line 11 def self.[](name) REGISTRY_MUTEX.synchronize do registry[name] ||= new(name, show: false) end end |
.clear! ⇒ void
This method returns an undefined value.
Drops every timer created through [].
20 21 22 |
# File 'lib/timify/registry.rb', line 20 def self.clear! REGISTRY_MUTEX.synchronize { @registry = {} } end |
.enabled? ⇒ Boolean
Returns whether recording is currently enabled.
44 45 46 |
# File 'lib/timify.rb', line 44 def enabled? !!@enabled end |
.env_disabled? ⇒ Boolean
Whether TIMIFY_DISABLE is set to a disabling value (+1+, true, or
on, case insensitive). Used as the default for enabled when the
class loads.
53 54 55 56 57 58 |
# File 'lib/timify.rb', line 53 def env_disabled? value = ENV["TIMIFY_DISABLE"] return false if value.nil? %w[1 true on].include?(value.to_s.strip.downcase) end |
.measure(name, **options) {|timer| ... } ⇒ Timify
128 129 130 131 132 133 134 |
# File 'lib/timify.rb', line 128 def self.measure(name, **) raise ArgumentError, "measure requires a block" unless block_given? timer = new(name, **) yield timer timer end |
.trace(name, **options) {|timer| ... } ⇒ Timify::Trace
146 147 148 149 150 151 152 |
# File 'lib/timify.rb', line 146 def self.trace(name, **) raise ArgumentError, "trace requires a block" unless block_given? timer = new(name, **) value = yield timer Trace.new(value: value, timer: timer) end |
Instance Method Details
#add(label = nil) ⇒ Float
Records the time elapsed since the previous mark on this thread.
Inside #measure, the mark becomes a child span instead of being counted
again as part of the parent's self time. The first call on a thread has no
range. Later calls record the jump from the previous call site to this one.
When enabled is false, returns 0.0 and records nothing.
46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/timify/recording.rb', line 46 def add(label = nil) return 0.0 unless self.class.enabled? location = caller_location normalized = normalize_label(label) time_spent, events = synchronize do paused? ? [0.0, []] : record_add(location, normalized) end deliver(events) time_spent end |
#measure(label = nil) { ... } ⇒ Object
Records the time spent inside block and returns the block's value.
Time from the previous mark up to this call is stored first, when it is
longer than zero, as an unlabeled segment. The block is a span: its
inclusive time is the whole block, and its self time is what nested
#measure and #add calls did not take. If the block raises, the span is
still recorded and the exception propagates. While paused, or when
enabled is false, the block runs and nothing is recorded.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/timify/recording.rb', line 70 def measure(label = nil) raise ArgumentError, "measure requires a block" unless block_given? return yield unless self.class.enabled? location = caller_location normalized = normalize_label(label) opened = false events = [] synchronize do unless paused? open_measure(location, normalized) opened = true end end return yield unless opened begin yield ensure synchronize { events = close_measure } deliver(events) end end |
#on_share(ratio) {|event| ... } ⇒ Timify
Registers a callback invoked when a child span's inclusive time is at least
ratio of the parent's elapsed time so far. Fires for labeled #measure
and #add children, not for root spans or unlabeled gap spans. Several
callbacks can be registered. They run after the segment is recorded,
outside the timer mutex.
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/timify/recording.rb', line 125 def on_share(ratio, &block) raise ArgumentError, "on_share requires a block" unless block value = Float(ratio) unless value >= 0.0 && value <= 1.0 raise ArgumentError, "ratio must be between 0 and 1" end synchronize { @share_handlers << [value, block] } self end |
#on_slow(seconds) {|event| ... } ⇒ Timify
104 105 106 107 108 109 110 111 112 |
# File 'lib/timify/recording.rb', line 104 def on_slow(seconds, &block) raise ArgumentError, "on_slow requires a block" unless block threshold = Float(seconds) raise ArgumentError, "threshold must be >= 0" if threshold.negative? synchronize { @slow_handlers << [threshold, block] } self end |
#reset ⇒ Timify
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/timify/recording.rb', line 143 def reset synchronize do @generation += 1 @status = :on @locations = {} @labels = {} @ranges = {} @roots = [] @total = 0.0 @max_time_spent = 0.0 @initial_time = Time.now @finished_time = @initial_time @paused_monotonic = 0.0 @pause_started = nil @resume_at = nil now = monotonic @origin_mark = now @last_record_mark = now thread_table[object_id] = { generation: @generation, last_mark: now, stack: [], location_prev: nil } end self end |
#totals(json: false, group: false) ⇒ Hash, String
Returns the report for every segment recorded so far. Pausing the timer does not hide this report. When #show is true, the printable summary is also written to #output.
locations, labels, and ranges are ordered by self time, slowest
first. Each entry contains:
secs[Float] accumulated self timeinclusive[Float] accumulated inclusive timepercent[Integer] share of #total, rounded to the nearest percentcount[Integer] number of segmentsmin[Float] shortest self timemax[Float] longest self timeavg[Float]secs / countp50,p95,p99[Float] nearest-rank percentiles of self timesamples_truncated[Boolean] present when the percentile sample was capped
tree is the chronological list of top-level spans. Each node has
label, location, secs, inclusive, and children.
grouped_tree merges sibling nodes with the same label recursively:
secs and inclusive are summed, count is how many spans were merged,
and children are grouped the same way. Order of first appearance is
preserved. wall_time is monotonic time from the start to the latest mark,
minus pauses.
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/timify/report.rb', line 33 def totals(json: false, group: false) report = synchronize { build_report(group: group) } emit(report[:message]) if json require "json" report.to_json else report end end |