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.
data/lib/timify.rb CHANGED
@@ -1,199 +1,153 @@
1
- ###############################################################
2
- # Calculates the time running from one location to another inside your code. More info: https://github.com/MarioRuiz/timify/
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
- # attr_accessor:
5
- # min_time_to_show: minimum time to show the elapsed time when calling 'add' method
6
- # show: print out results on screen
7
- # status: (default :on) You can set :on or :off the status so it will counting the time or not
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
- # attr_reader:
10
- # name: name given for the Timify instance
11
- # initial_time: when the instance was created
12
- # total: total time elapsed
13
- # max_time_spent: maximum time measured for this instance
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
- attr_accessor :min_time_to_show, :show, :status
17
- attr_reader :name, :total, :initial_time, :max_time_spent
18
-
19
- ###############################################################
20
- # input:
21
- # name: name for the instance
22
- # min_time_to_show: minimum time to show the elapsed time when calling 'add' method
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
- time_now=Time.new
76
- time_spent=(time_now-@timify_prev).to_f
77
- @total=(time_now-@initial_time).to_f
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
- if @total > 0
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
- # returns all data for this instance
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
- # input:
116
- # json: (boolean) in case of true the output will be in json format instead of a hash
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
- # output: (Hash or json string)
119
- # name: (String) name given for this instance
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
- if !@timify_by_range.empty?
177
- message+= "\nTotal time by range:\n"
178
- @timify_by_range.each {|range, secs|
179
- if @total==0 then
180
- percent=0
181
- else
182
- percent=(secs*100/(@total).to_f).round(0)
183
- end
184
- message+= "\t#{range}: #{secs.round(2)} (#{percent}%) ##{@count[range]}\n"
185
- output[:ranges][range]={
186
- secs: secs,
187
- percent: percent,
188
- count: @count[range]
189
- }
190
- }
191
- end
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
- message+= "\n\n"
194
- output[:message]=message
195
- puts message if @show
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.6
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: 2021-05-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description: Easily calculate the time running (elapsed time) from one location to
14
- another inside your code and reports statistics. It helps you improve your code
15
- and find out which part of your code is consuming more time.
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
- post_install_message:
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: '0'
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: 3.0.3
47
- signing_key:
55
+ rubygems_version: 4.0.6
48
56
  specification_version: 4
49
- summary: Easily calculate the time running (elapsed time) from one location to another
50
- inside your code and reports statistics.
57
+ summary: Calculate elapsed time from one location to another inside your code and
58
+ report statistics.
51
59
  test_files: []