timify 0.0.6 → 1.0.0
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/.yardopts +6 -2
- data/CHANGELOG.md +41 -0
- data/LICENSE +1 -1
- data/README.md +233 -118
- data/lib/timify/recording.rb +375 -0
- data/lib/timify/registry.rb +31 -0
- data/lib/timify/report.rb +192 -0
- data/lib/timify/span.rb +71 -0
- data/lib/timify/trace.rb +27 -0
- data/lib/timify/version.rb +5 -0
- data/lib/timify.rb +136 -182
- metadata +21 -13
data/lib/timify.rb
CHANGED
|
@@ -1,199 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "timify/version"
|
|
4
|
+
require_relative "timify/span"
|
|
5
|
+
require_relative "timify/trace"
|
|
6
|
+
require_relative "timify/registry"
|
|
7
|
+
require_relative "timify/recording"
|
|
8
|
+
require_relative "timify/report"
|
|
9
|
+
|
|
10
|
+
# Measures elapsed time between points in Ruby code.
|
|
11
|
+
#
|
|
12
|
+
# {#measure} blocks nest. A parent span's +inclusive+ time is the whole block,
|
|
13
|
+
# and its +secs+ (self time) is what the children did not already take.
|
|
14
|
+
# {#add} marks the time since the previous mark on this thread. Intervals use
|
|
15
|
+
# a monotonic clock. {#totals} still reports +started+ and +finished+ as Time
|
|
16
|
+
# objects.
|
|
17
|
+
#
|
|
18
|
+
# Each thread has its own cursor and span stack. Totals are shared and guarded
|
|
19
|
+
# by a mutex, so two threads can mark the same timer. +total_time+ is the sum
|
|
20
|
+
# of self time and can exceed +wall_time+ when threads overlap.
|
|
3
21
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
22
|
+
# {.enabled} is a process-wide switch. When it is +false+, every timer is a
|
|
23
|
+
# no-op: {#add} and {#measure} record nothing, and {#initialize} does not
|
|
24
|
+
# print. The default comes from +TIMIFY_DISABLE+ (+1+, +true+, or +on+, case
|
|
25
|
+
# insensitive). Setting {.enabled=} overrides that for the rest of the process.
|
|
8
26
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
27
|
+
# @example Time a few nested steps without printing
|
|
28
|
+
# timer = Timify.new(:create_user, show: false)
|
|
29
|
+
# timer.measure(:request) do
|
|
30
|
+
# timer.measure(:database) { :saved }
|
|
31
|
+
# timer.add(:mail)
|
|
32
|
+
# end
|
|
33
|
+
# timer.totals
|
|
15
34
|
class Timify
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# show: print out results on screen
|
|
24
|
-
#
|
|
25
|
-
# examples:
|
|
26
|
-
# t = Timify.new :create_user
|
|
27
|
-
# t.show = false
|
|
28
|
-
# $tim = Timify.new :dbprocess, show:false, min_time_to_show: 0.5
|
|
29
|
-
###############################################################
|
|
30
|
-
def initialize(name, min_time_to_show: 0, show: true)
|
|
31
|
-
@name=name
|
|
32
|
-
@min_time_to_show=min_time_to_show
|
|
33
|
-
@show=show
|
|
34
|
-
@initial_time=Time.new
|
|
35
|
-
@max_time_spent=0
|
|
36
|
-
@timify_prev=@initial_time
|
|
37
|
-
@location_prev=nil
|
|
38
|
-
@timify={}
|
|
39
|
-
@timify_by_label={}
|
|
40
|
-
@timify_by_range={}
|
|
41
|
-
@count={}
|
|
42
|
-
@total=0
|
|
43
|
-
puts "<#{@name}> Timify init:<#{@initial_time}>. Location: #{caller[0].scan(/(.+):in\s/).join}"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
###############################################################
|
|
47
|
-
# Adds a new point to count the time elapsed.
|
|
48
|
-
# It will count from the last 'add' call or Timify creation in case of the first 'add'.
|
|
49
|
-
#
|
|
50
|
-
# input:
|
|
51
|
-
# label: (optional) In case supplied it will summarize all the ones with the same label
|
|
52
|
-
#
|
|
53
|
-
# output: (float)
|
|
54
|
-
# time elapsed in seconds
|
|
55
|
-
#
|
|
56
|
-
# examples:
|
|
57
|
-
# t=Timify.new :example
|
|
58
|
-
# t.add; run_sqls; t.add :database
|
|
59
|
-
# t.add
|
|
60
|
-
# #some processes
|
|
61
|
-
# t.add
|
|
62
|
-
# #some processes
|
|
63
|
-
# send_email_alert if t.add > 0.2
|
|
64
|
-
# #some processes
|
|
65
|
-
# do_log(t.totals[:message]) if t.add > 0.5
|
|
66
|
-
###############################################################
|
|
67
|
-
def add(*label)
|
|
68
|
-
return 0 if @status==:off
|
|
69
|
-
if !label.empty?
|
|
70
|
-
label=label[0]
|
|
71
|
-
else
|
|
72
|
-
label=""
|
|
73
|
-
end
|
|
35
|
+
class << self
|
|
36
|
+
# Process-wide recording switch. When +false+, every timer is a no-op.
|
|
37
|
+
# Defaults from +TIMIFY_DISABLE+ when the class loads; {.enabled=} overrides
|
|
38
|
+
# that for the rest of the process.
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
attr_accessor :enabled
|
|
74
42
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
new_max=false
|
|
79
|
-
location=caller[0].scan(/(.+):in\s/).join
|
|
80
|
-
if time_spent > @max_time_spent then
|
|
81
|
-
new_max = true
|
|
82
|
-
@max_time_spent = time_spent
|
|
83
|
-
end
|
|
84
|
-
@timify[location]=@timify[location].to_f+time_spent
|
|
85
|
-
@count[location]=@count[location].to_i+1
|
|
86
|
-
if !label.empty?
|
|
87
|
-
@timify_by_label[label]=@timify_by_label[label].to_f+time_spent
|
|
88
|
-
@count[label]=@count[label].to_i+1
|
|
89
|
-
end
|
|
90
|
-
if !@location_prev.nil?
|
|
91
|
-
@timify_by_range["#{@location_prev} - #{location}"]=@timify_by_range["#{@location_prev} - #{location}"].to_f + time_spent
|
|
92
|
-
@count["#{@location_prev} - #{location}"]=@count["#{@location_prev} - #{location}"].to_i+1
|
|
43
|
+
# @return [Boolean] whether recording is currently enabled
|
|
44
|
+
def enabled?
|
|
45
|
+
!!@enabled
|
|
93
46
|
end
|
|
94
47
|
|
|
48
|
+
# Whether +TIMIFY_DISABLE+ is set to a disabling value (+1+, +true+, or
|
|
49
|
+
# +on+, case insensitive). Used as the default for {.enabled} when the
|
|
50
|
+
# class loads.
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def env_disabled?
|
|
54
|
+
value = ENV["TIMIFY_DISABLE"]
|
|
55
|
+
return false if value.nil?
|
|
95
56
|
|
|
96
|
-
|
|
97
|
-
percent=((@timify[location]/@total)*100).round(0)
|
|
98
|
-
else
|
|
99
|
-
percent=0
|
|
100
|
-
end
|
|
101
|
-
if time_spent>=@min_time_to_show
|
|
102
|
-
if @show
|
|
103
|
-
puts "<#{@name}>#{"<#{label}>" if !label.empty?}#{"(New Max)" if new_max}: #{location} (#{percent}%): #{@total.round(2)}; #{time_spent.round(2)}"
|
|
104
|
-
end
|
|
57
|
+
%w[1 true on].include?(value.to_s.strip.downcase)
|
|
105
58
|
end
|
|
106
|
-
@timify_prev=time_now
|
|
107
|
-
@location_prev=location
|
|
108
|
-
return time_spent
|
|
109
59
|
end
|
|
110
60
|
|
|
61
|
+
self.enabled = !env_disabled?
|
|
62
|
+
|
|
63
|
+
# @return [Object] name given when the timer was created
|
|
64
|
+
attr_reader :name
|
|
65
|
+
|
|
66
|
+
# @return [Float] self time recorded so far, excluding paused time
|
|
67
|
+
attr_reader :total
|
|
111
68
|
|
|
112
|
-
|
|
113
|
-
|
|
69
|
+
# @return [Time] wall-clock time when the timer was created or last {#reset}
|
|
70
|
+
attr_reader :initial_time
|
|
71
|
+
|
|
72
|
+
# @return [Float] longest single self-time segment since creation or {#reset}
|
|
73
|
+
attr_reader :max_time_spent
|
|
74
|
+
|
|
75
|
+
# @return [Symbol] +:on+ while the timer is recording, +:off+ while it is paused
|
|
76
|
+
attr_reader :status
|
|
77
|
+
|
|
78
|
+
# Minimum segment length, in seconds, required before {#add} or {#measure}
|
|
79
|
+
# prints. Shorter segments are still recorded. Defaults to +0+.
|
|
80
|
+
#
|
|
81
|
+
# @return [Numeric]
|
|
82
|
+
attr_accessor :min_time_to_show
|
|
83
|
+
|
|
84
|
+
# When +false+, the timer stays silent. Segments are still recorded.
|
|
85
|
+
# Defaults to +true+.
|
|
114
86
|
#
|
|
115
|
-
#
|
|
116
|
-
|
|
87
|
+
# @return [Boolean]
|
|
88
|
+
attr_accessor :show
|
|
89
|
+
|
|
90
|
+
# Where printed lines are sent. An object that responds to +puts+ is written
|
|
91
|
+
# with +puts+. An object that responds to +info+ and not +puts+, such as a
|
|
92
|
+
# Logger, is written with +info+. Defaults to +$stdout+.
|
|
117
93
|
#
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
# total_time: (float) total elapsed time from initialization to last 'add' call
|
|
121
|
-
# started: (Time)
|
|
122
|
-
# finished: (Time)
|
|
123
|
-
# message: (String) a printable friendly message giving all information
|
|
124
|
-
# locations, labels, ranges: (Hash) the resultant hash contains:
|
|
125
|
-
# secs: (float) number of seconds
|
|
126
|
-
# percent: (integer) percentage in reference to the total time
|
|
127
|
-
# count: (integer) number of times
|
|
128
|
-
# locations: (Hash) All summary data by location where was called
|
|
129
|
-
# labels: (Hash) All summary data by label given on 'add' method
|
|
130
|
-
# ranges: (Hash) All summary data by ranges where was called, from last 'add' call to current 'add' call
|
|
131
|
-
###############################################################
|
|
132
|
-
def totals(json: false)
|
|
133
|
-
return {} if @status==:off
|
|
134
|
-
require 'json' if json
|
|
135
|
-
output={
|
|
136
|
-
name: @name,
|
|
137
|
-
total_time: @total.to_f,
|
|
138
|
-
started: @initial_time,
|
|
139
|
-
finished: @timify_prev,
|
|
140
|
-
locations: {},
|
|
141
|
-
labels: {},
|
|
142
|
-
ranges: {}
|
|
143
|
-
}
|
|
144
|
-
message="\n\nTotal time <#{@name}>:#{@total.to_f.round(2)}"
|
|
145
|
-
message+="\nTotal time by location:\n"
|
|
146
|
-
@timify.each {|location, secs|
|
|
147
|
-
if @total==0 then
|
|
148
|
-
percent=0
|
|
149
|
-
else
|
|
150
|
-
percent=(secs*100/(@total).to_f).round(0)
|
|
151
|
-
end
|
|
152
|
-
message+= "\t#{location}: #{secs.round(2)} (#{percent}%) ##{@count[location]}\n"
|
|
153
|
-
output[:locations][location]={
|
|
154
|
-
secs: secs,
|
|
155
|
-
percent: percent,
|
|
156
|
-
count: @count[location]
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if !@timify_by_label.empty?
|
|
160
|
-
message+= "\nTotal time by label:\n"
|
|
161
|
-
@timify_by_label.each {|label, secs|
|
|
162
|
-
if @total==0 then
|
|
163
|
-
percent=0
|
|
164
|
-
else
|
|
165
|
-
percent=(secs*100/(@total).to_f).round(0)
|
|
166
|
-
end
|
|
167
|
-
message+= "\t#{label}: #{secs.round(2)} (#{percent}%) ##{@count[label]}\n"
|
|
168
|
-
output[:labels][label]={
|
|
169
|
-
secs: secs,
|
|
170
|
-
percent: percent,
|
|
171
|
-
count: @count[label]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
end
|
|
94
|
+
# @return [#puts, #info]
|
|
95
|
+
attr_accessor :output
|
|
175
96
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
97
|
+
# @param name [Object] name included in reports and printed lines
|
|
98
|
+
# @param min_time_to_show [Numeric] shortest segment that should be printed
|
|
99
|
+
# @param show [Boolean] whether to print the init line, segments, and summaries
|
|
100
|
+
# @param output [#puts, #info] destination for printed lines
|
|
101
|
+
def initialize(name, min_time_to_show: 0, show: true, output: $stdout)
|
|
102
|
+
@name = name
|
|
103
|
+
@min_time_to_show = min_time_to_show
|
|
104
|
+
@show = show
|
|
105
|
+
@output = output
|
|
106
|
+
@mutex = Mutex.new
|
|
107
|
+
@generation = 0
|
|
108
|
+
@slow_handlers = []
|
|
109
|
+
@share_handlers = []
|
|
110
|
+
@resume_at = nil
|
|
111
|
+
@pause_started = nil
|
|
112
|
+
@paused_monotonic = 0.0
|
|
113
|
+
reset
|
|
114
|
+
emit("<#{@name}> Timify init:<#{@initial_time}>. Location: #{caller_location}") if self.class.enabled?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Creates a timer, yields it, and returns it after the block finishes.
|
|
118
|
+
# The block's return value is not returned; use {#measure} on the yielded
|
|
119
|
+
# timer when you need that value, or use {.trace} to keep both. If the block
|
|
120
|
+
# raises, the exception propagates and the timer is not returned.
|
|
121
|
+
#
|
|
122
|
+
# @param name [Object]
|
|
123
|
+
# @param options [Hash] keyword arguments accepted by {#initialize}
|
|
124
|
+
# @yield [timer] the new timer
|
|
125
|
+
# @yieldparam timer [Timify]
|
|
126
|
+
# @return [Timify]
|
|
127
|
+
# @raise [ArgumentError] when no block is given
|
|
128
|
+
def self.measure(name, **options)
|
|
129
|
+
raise ArgumentError, "measure requires a block" unless block_given?
|
|
130
|
+
|
|
131
|
+
timer = new(name, **options)
|
|
132
|
+
yield timer
|
|
133
|
+
timer
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Creates a timer, yields it, and returns a {Trace} with the block's value
|
|
137
|
+
# and the timer. If the block raises, the exception propagates and no
|
|
138
|
+
# {Trace} is returned.
|
|
139
|
+
#
|
|
140
|
+
# @param name [Object]
|
|
141
|
+
# @param options [Hash] keyword arguments accepted by {#initialize}
|
|
142
|
+
# @yield [timer] the new timer
|
|
143
|
+
# @yieldparam timer [Timify]
|
|
144
|
+
# @return [Timify::Trace]
|
|
145
|
+
# @raise [ArgumentError] when no block is given
|
|
146
|
+
def self.trace(name, **options)
|
|
147
|
+
raise ArgumentError, "trace requires a block" unless block_given?
|
|
192
148
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
output=output.to_json if json
|
|
197
|
-
return output
|
|
149
|
+
timer = new(name, **options)
|
|
150
|
+
value = yield timer
|
|
151
|
+
Trace.new(value: value, timer: timer)
|
|
198
152
|
end
|
|
199
153
|
end
|
metadata
CHANGED
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: timify
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mario Ruiz
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
|
-
description:
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
description: Calculate elapsed time from one location to another inside your code
|
|
13
|
+
and report statistics. It helps you improve your code and find out which part of
|
|
14
|
+
your code is consuming more time.
|
|
16
15
|
email: marioruizs@gmail.com
|
|
17
16
|
executables: []
|
|
18
17
|
extensions: []
|
|
19
18
|
extra_rdoc_files:
|
|
19
|
+
- CHANGELOG.md
|
|
20
20
|
- LICENSE
|
|
21
21
|
- README.md
|
|
22
22
|
files:
|
|
23
23
|
- ".yardopts"
|
|
24
|
+
- CHANGELOG.md
|
|
24
25
|
- LICENSE
|
|
25
26
|
- README.md
|
|
26
27
|
- lib/timify.rb
|
|
28
|
+
- lib/timify/recording.rb
|
|
29
|
+
- lib/timify/registry.rb
|
|
30
|
+
- lib/timify/report.rb
|
|
31
|
+
- lib/timify/span.rb
|
|
32
|
+
- lib/timify/trace.rb
|
|
33
|
+
- lib/timify/version.rb
|
|
27
34
|
homepage: https://github.com/MarioRuiz/timify
|
|
28
35
|
licenses:
|
|
29
36
|
- MIT
|
|
30
|
-
metadata:
|
|
31
|
-
|
|
37
|
+
metadata:
|
|
38
|
+
changelog_uri: https://github.com/MarioRuiz/timify/blob/master/CHANGELOG.md
|
|
39
|
+
source_code_uri: https://github.com/MarioRuiz/timify
|
|
40
|
+
rubygems_mfa_required: 'true'
|
|
32
41
|
rdoc_options: []
|
|
33
42
|
require_paths:
|
|
34
43
|
- lib
|
|
@@ -36,16 +45,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
36
45
|
requirements:
|
|
37
46
|
- - ">="
|
|
38
47
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
48
|
+
version: '3.2'
|
|
40
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
50
|
requirements:
|
|
42
51
|
- - ">="
|
|
43
52
|
- !ruby/object:Gem::Version
|
|
44
53
|
version: '0'
|
|
45
54
|
requirements: []
|
|
46
|
-
rubygems_version:
|
|
47
|
-
signing_key:
|
|
55
|
+
rubygems_version: 4.0.6
|
|
48
56
|
specification_version: 4
|
|
49
|
-
summary:
|
|
50
|
-
|
|
57
|
+
summary: Calculate elapsed time from one location to another inside your code and
|
|
58
|
+
report statistics.
|
|
51
59
|
test_files: []
|