im-lost 1.0.2 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b3508c022462842507ce749828e2c74c4db9e9097496e68cedc758018017cb5
4
- data.tar.gz: 93ac54d98ae6474d4f7144b116cf0e2784c88babf57ff592a317b463caa7ee13
3
+ metadata.gz: 38b1a5b729438621357238f12f64f1d397ee56e18174c2cc1ec60067dd0ab15e
4
+ data.tar.gz: 157e15bcf3c9e9539467347e9cac31b5ed40adf9953bc6ab28dab6d163907288
5
5
  SHA512:
6
- metadata.gz: 4d45f27c98a6ed477ce4e696f4c41286ca24c105de1668889bf64fc0d57d3947e04c026b40fa7f5f79e2ce5016fcc8327e3c9a8b20a1e6ae0561900efae4f5f3
7
- data.tar.gz: 1502147a826924c39988f9272d62e3c0274dc8cdb961f25c405a229cf6273142579ad136af1972c06874d13af110486b79db45907c219bd3082a91cd507220ab
6
+ metadata.gz: 8ad0cc365f318ef0113d6f099618204b11ecd547a84d525893cc2691a59a5159594c8606de34bad9268867751708cf98f03102a1b0feefbab6cefa41c2436449
7
+ data.tar.gz: eb0a3abf3fffc2273593741c880c9aa5b4461d2b14877e324834216327d9ec427a80eaf2b075381df601b920477b50c8b95190c34ae9cc73fb3d1b25407aa4a1
data/README.md CHANGED
@@ -19,15 +19,16 @@ File.open('test.txt', 'w') do |file|
19
19
  file.puts(:world!)
20
20
  end
21
21
  end
22
+
22
23
  # output will look like
23
24
  # > IO#<<(?)
24
- # /projects/test.rb:1
25
+ # /examples/test.rb:1
25
26
  # > IO#write(*)
26
- # /projects/test.rb:1
27
+ # /examples/test.rb:1
27
28
  # > IO#puts(*)
28
- # /projects/test.rb:2
29
+ # /examples/test.rb:2
29
30
  # > IO#write(*)
30
- # /projects/test.rb:2
31
+ # /examples/test.rb:2
31
32
  ```
32
33
 
33
34
  When you need to know if exceptions are raised and handled you can use `ImLost.trace_exceptions`:
@@ -38,16 +39,17 @@ ImLost.trace_exceptions do
38
39
  rescue SystemCallError
39
40
  raise('something went wrong!')
40
41
  end
42
+
41
43
  # output will look like
42
44
  # x Errno::EEXIST: File exists @ rb_sysopen - /
43
- # /projects/test.rb:2
45
+ # /examples/test.rb:2
44
46
  # ! Errno::EEXIST: File exists @ rb_sysopen - /
45
- # /projects/test.rb:3
47
+ # /examples/test.rb:3
46
48
  # x RuntimeError: something went wrong!
47
- # /projects/test.rb:4
49
+ # /examples/test.rb:4
48
50
  ```
49
51
 
50
- When you like to know if and when a code point is reached, `ImLost.here` will help:
52
+ When you like to know if a code point is reached, `ImLost.here` will help:
51
53
 
52
54
  ```ruby
53
55
  ImLost.here
@@ -92,7 +94,6 @@ class Foo
92
94
  def bar = :bar
93
95
  end
94
96
 
95
- ImLost.trace_results = true
96
97
  ImLost.trace(Foo)
97
98
 
98
99
  my_foo = Foo.create(value: :foo!)
@@ -109,47 +110,55 @@ ImLost.vars(my_foo)
109
110
 
110
111
  # output will look like
111
112
  # > Foo.create(:foo!)
112
- # /projects/foo.rb:25
113
+ # /examples/foo.rb:24
113
114
  # > Foo.new(*)
114
- # /projects/foo.rb:6
115
+ # /examples/foo.rb:6
115
116
  # < Foo.new(*)
116
- # = #<Foo:0x00000001030810c0 @value=:foo!>
117
+ # /examples/foo.rb:6
118
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
117
119
  # < Foo.create(:foo!)
118
- # = #<Foo:0x00000001030810c0 @value=:foo!>
120
+ # /examples/foo.rb:24
121
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
119
122
  # > Foo#foo(1, *[], :none, **{}, &nil)
120
- # /projects/foo.rb:28
123
+ # /examples/foo.rb:27
121
124
  # > Foo#bar()
122
- # /projects/foo.rb:15
125
+ # /examples/foo.rb:15
123
126
  # < Foo#bar()
127
+ # /examples/foo.rb:15
124
128
  # = :bar
125
129
  # < Foo#foo(1, *[], :none, **{}, &nil)
130
+ # /examples/foo.rb:27
126
131
  # = "1-none-[]-{}-bar"
127
- # = /projects/foo.rb:29
128
- # instance variables:
129
- # @value: "1-none-[]-{}-bar"
132
+ # * /examples/foo.rb:28
133
+ # > instance variables
134
+ # @value: "1-none-[]-{}-bar"
130
135
  # > Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
131
- # /projects/foo.rb:31
136
+ # /examples/foo.rb:30
132
137
  # > Foo#bar()
133
- # /projects/foo.rb:15
138
+ # /examples/foo.rb:15
134
139
  # < Foo#bar()
140
+ # /examples/foo.rb:15
135
141
  # = :bar
136
142
  # < Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
143
+ # /examples/foo.rb:30
137
144
  # = "2-some-[a,b,c]-{:name=>:value}-bar"
138
- # = /projects/foo.rb:32
139
- # instance variables:
140
- # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
141
- # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
142
- # /projects/foo.rb:34
145
+ # * /examples/foo.rb:31
146
+ # > instance variables
147
+ # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
148
+ # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
149
+ # /examples/foo.rb:33
143
150
  # > Foo#bar()
144
- # /projects/foo.rb:15
151
+ # /examples/foo.rb:15
145
152
  # < Foo#bar()
153
+ # /examples/foo.rb:15
146
154
  # = :bar
147
155
  # 3--[]-{}-bar
148
- # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
156
+ # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
157
+ # /examples/foo.rb:33
149
158
  # = nil
150
- # = /projects/foo.rb:35
151
- # instance variables:
152
- # @value: "3--[]-{}-bar"
159
+ # * /examples/foo.rb:34
160
+ # > instance variables
161
+ # @value: "3--[]-{}-bar"
153
162
  ```
154
163
 
155
164
  See [examples dir](./examples) for more…
data/examples/foo.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lib/im-lost'
3
+ require 'im-lost'
4
4
 
5
5
  class Foo
6
6
  def self.create(value:) = new(value)
@@ -19,7 +19,6 @@ class Foo
19
19
  def bar = :bar
20
20
  end
21
21
 
22
- ImLost.trace_results = true
23
22
  ImLost.trace(Foo)
24
23
 
25
24
  my_foo = Foo.create(value: :foo!)
@@ -36,44 +35,52 @@ ImLost.vars(my_foo)
36
35
 
37
36
  # output will look like
38
37
  # > Foo.create(:foo!)
39
- # /projects/foo.rb:25
38
+ # /examples/foo.rb:24
40
39
  # > Foo.new(*)
41
- # /projects/foo.rb:6
40
+ # /examples/foo.rb:6
42
41
  # < Foo.new(*)
43
- # = #<Foo:0x00000001030810c0 @value=:foo!>
42
+ # /examples/foo.rb:6
43
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
44
44
  # < Foo.create(:foo!)
45
- # = #<Foo:0x00000001030810c0 @value=:foo!>
45
+ # /examples/foo.rb:24
46
+ # = #<Foo:0x00000001006448c0 @value=:foo!>
46
47
  # > Foo#foo(1, *[], :none, **{}, &nil)
47
- # /projects/foo.rb:28
48
+ # /examples/foo.rb:27
48
49
  # > Foo#bar()
49
- # /projects/foo.rb:15
50
+ # /examples/foo.rb:15
50
51
  # < Foo#bar()
52
+ # /examples/foo.rb:15
51
53
  # = :bar
52
54
  # < Foo#foo(1, *[], :none, **{}, &nil)
55
+ # /examples/foo.rb:27
53
56
  # = "1-none-[]-{}-bar"
54
- # = /projects/foo.rb:29
55
- # instance variables:
56
- # @value: "1-none-[]-{}-bar"
57
+ # * /examples/foo.rb:28
58
+ # > instance variables
59
+ # @value: "1-none-[]-{}-bar"
57
60
  # > Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
58
- # /projects/foo.rb:31
61
+ # /examples/foo.rb:30
59
62
  # > Foo#bar()
60
- # /projects/foo.rb:15
63
+ # /examples/foo.rb:15
61
64
  # < Foo#bar()
65
+ # /examples/foo.rb:15
62
66
  # = :bar
63
67
  # < Foo#foo(2, *[:a, :b, :c], :some, **{:name=>:value}, &nil)
68
+ # /examples/foo.rb:30
64
69
  # = "2-some-[a,b,c]-{:name=>:value}-bar"
65
- # = /projects/foo.rb:32
66
- # instance variables:
67
- # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
68
- # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
69
- # /projects/foo.rb:34
70
+ # * /examples/foo.rb:31
71
+ # > instance variables
72
+ # @value: "2-some-[a,b,c]-{:name=>:value}-bar"
73
+ # > Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
74
+ # /examples/foo.rb:33
70
75
  # > Foo#bar()
71
- # /projects/foo.rb:15
76
+ # /examples/foo.rb:15
72
77
  # < Foo#bar()
78
+ # /examples/foo.rb:15
73
79
  # = :bar
74
80
  # 3--[]-{}-bar
75
- # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x00000001030aee30 /projects/foo.rb:34>)
81
+ # < Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
82
+ # /examples/foo.rb:33
76
83
  # = nil
77
- # = /projects/foo.rb:35
78
- # instance variables:
79
- # @value: "3--[]-{}-bar"
84
+ # * /examples/foo.rb:34
85
+ # > instance variables
86
+ # @value: "3--[]-{}-bar"
@@ -9,7 +9,6 @@ INFO
9
9
 
10
10
  require 'im-lost'
11
11
 
12
- ImLost.trace_results = true
13
12
  ImLost.trace(Kernel, Object, Module, Class, self) do
14
13
  puts '=' * 79
15
14
  pp Class.new
data/examples/timer.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ puts <<~INFO
4
+
5
+ This is an example how to use named and anonymous timers.
6
+
7
+ INFO
8
+
9
+ require_relative '../lib/im-lost'
10
+
11
+ puts 'Create a named timer:'
12
+ ImLost.timer.create(:first)
13
+
14
+ puts 'Create an anonymous timer:'
15
+ second = ImLost.timer.create
16
+
17
+ sleep(0.5) # or whatever
18
+
19
+ puts 'print runtime for named timer:'
20
+ ImLost.timer[:first]
21
+
22
+ puts 'print runtime for anonymous timer:'
23
+ ImLost.timer[second]
24
+
25
+ puts 'delete a named timer'
26
+ ImLost.timer.delete(:first)
27
+
28
+ puts 'delete an anonymous timer'
29
+ ImLost.timer.delete(second)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ImLost
4
4
  # The version number of the gem.
5
- VERSION = '1.0.2'
5
+ VERSION = '1.2.0'
6
6
  end
data/lib/im-lost.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #
3
4
  # If you have overlooked something again and don't really understand what your
4
5
  # code is doing. If you have to maintain this application but can't really find
5
6
  # your way around and certainly can't track down that stupid error. If you feel
@@ -25,9 +26,10 @@ module ImLost
25
26
 
26
27
  #
27
28
  # The output device used to write information.
28
- # This should be an `IO` device or any other object responding to `#puts`.
29
+ # This should be an `IO` device or any other object responding to `#<<`
30
+ # like a Logger.
29
31
  #
30
- # `$stderr` is configured by default.
32
+ # `STDERR` is configured by default.
31
33
  #
32
34
  # @example Write to a file
33
35
  # ImLost.output = File.new('./trace', 'w')
@@ -49,10 +51,21 @@ module ImLost
49
51
  attr_reader :output
50
52
 
51
53
  def output=(value)
52
- return @output = value if value.respond_to?(:puts)
53
- raise(ArgumentError, "invalid output device - #{value.inspect}")
54
+ return @output = value if defined?(value.<<)
55
+ raise(
56
+ NoMethodError,
57
+ "undefined method `<<' for an instance of #{
58
+ Kernel.instance_method(:class).bind(value).call
59
+ }"
60
+ )
54
61
  end
55
62
 
63
+ #
64
+ # @return [TimerStore] the timer store used to estimate the runtime of
65
+ # your code
66
+ #
67
+ attr_reader :timer
68
+
56
69
  #
57
70
  # Enables/disables tracing of method calls.
58
71
  # This is enabled by default.
@@ -79,13 +92,14 @@ module ImLost
79
92
  # rescue SystemCallError
80
93
  # raise('something went wrong!')
81
94
  # end
95
+ #
82
96
  # # output will look like
83
97
  # # x Errno::EEXIST: File exists @ rb_sysopen - /
84
- # # /projects/test.rb:2
98
+ # # /examples/test.rb:2
85
99
  # # ! Errno::EEXIST: File exists @ rb_sysopen - /
86
- # # /projects/test.rb:3
100
+ # # /examples/test.rb:3
87
101
  # # x RuntimeError: something went wrong!
88
- # # /projects/test.rb:4
102
+ # # /examples/test.rb:4
89
103
  #
90
104
  # @param with_locations [Boolean] wheter the locations should be included
91
105
  # into the exception trace information
@@ -105,7 +119,7 @@ module ImLost
105
119
 
106
120
  #
107
121
  # Enables/disables tracing of returned valuess of method calls.
108
- # This is disabled by default.
122
+ # This is enabled by default.
109
123
  #
110
124
  # @attribute [r] trace_results
111
125
  # @return [Boolean] whether return values will be traced
@@ -123,33 +137,33 @@ module ImLost
123
137
  #
124
138
  # Print the call location conditionally.
125
139
  #
126
- # @example simply print location
140
+ # @example Print current location
127
141
  # ImLost.here
128
142
  #
129
- # @example print location when instance variable is empty
143
+ # @example Print current location when instance variable is empty
130
144
  # ImLost.here(@name.empty?)
131
145
  #
132
- # @example print location when instance variable is nil or empty
146
+ # @example Print current location when instance variable is nil or empty
133
147
  # ImLost.here { @name.nil? || @name.empty? }
134
148
  #
135
149
  # @overload here
136
- # Prints the caller location.
150
+ # Prints the call location.
137
151
  # @return [true]
138
152
  #
139
153
  # @overload here(test)
140
- # Prints the caller location when given argument is truthy.
154
+ # Prints the call location when given argument is truthy.
141
155
  # @param test [Object]
142
156
  # @return [Object] test
143
157
  #
144
158
  # @overload here
145
- # Prints the caller location when given block returns a truthy result.
159
+ # Prints the call location when given block returns a truthy result.
146
160
  # @yield When the block returns a truthy result the location will be print
147
161
  # @yieldreturn [Object] return result
148
162
  #
149
163
  def here(test = true)
150
164
  return test if !test || (block_given? && !(test = yield))
151
165
  loc = Kernel.caller_locations(1, 1)[0]
152
- @output.puts(": #{loc.path}:#{loc.lineno}")
166
+ @output << "* #{loc.path}:#{loc.lineno}\n"
153
167
  test
154
168
  end
155
169
 
@@ -158,36 +172,36 @@ module ImLost
158
172
  #
159
173
  # The given arguments can be any object instance or module or class.
160
174
  #
161
- # @example trace method calls of an instance variable for a while
175
+ # @example Trace method calls of an instance variable for a while
162
176
  # ImLost.trace(@file)
163
177
  # # ...
164
178
  # ImLost.untrace(@file)
165
179
  #
166
- # @example temporary trace method calls
180
+ # @example Temporary trace method calls
167
181
  # File.open('test.txt', 'w') do |file|
168
182
  # ImLost.trace(file) do
169
183
  # file << 'hello '
170
184
  # file.puts(:world!)
171
185
  # end
172
186
  # end
173
- # output will look like
174
- # > IO#<<(?)
175
- # /projects/test.rb:1
176
- # > IO#write(*)
177
- # /projects/test.rb:1
178
- # > IO#puts(*)
179
- # /projects/test.rb:2
180
- # > IO#write(*)
181
- # /projects/test.rb:2
187
+ #
188
+ # # output will look like
189
+ # # > IO#<<(?)
190
+ # # /examples/test.rb:1
191
+ # # > IO#write(*)
192
+ # # /examples/test.rb:1
193
+ # # > IO#puts(*)
194
+ # # /examples/test.rb:2
195
+ # # > IO#write(*)
196
+ # # /examples/test.rb:2
182
197
  #
183
198
  # @overload trace(*args)
184
199
  # @param args [[Object]] one or more objects to be traced
185
- # @return [[Object]] the traced object(s)
200
+ # @return [Array<Object>] the traced object(s)
186
201
  # Start tracing the given objects.
187
202
  # @see untrace
188
203
  # @see untrace_all!
189
204
  #
190
- #
191
205
  # @overload trace(*args)
192
206
  # @param args [[Object]] one or more objects to be traced
193
207
  # @yieldparam args [Object] the traced object(s)
@@ -201,30 +215,37 @@ module ImLost
201
215
  args.size == 1 ? _trace_b(args[0], &block) : _trace_all_b(args, &block)
202
216
  end
203
217
 
218
+ #
219
+ # Test if a given object is currently traced.
220
+ #
221
+ # @param arg [Object] object to be tested
222
+ # @return [Boolean] wheter the object is beeing traced
223
+ #
224
+ def traced?(obj) = @trace.key?(obj)
225
+
204
226
  #
205
227
  # Stop tracing objects.
206
228
  #
207
- # @example trace some objects for some code lines
208
- # traced_vars = ImLost.trace(@file, @client)
229
+ # @example Trace some objects for some code lines
230
+ # traced_obj = ImLost.trace(@file, @client)
209
231
  # # ...
210
- # ImLost.untrace(*traced_vars)
232
+ # ImLost.untrace(*traced_obj)
211
233
  #
212
234
  # @see trace
213
235
  #
214
- # @param args [[Object]] one or more objects which should not longer be
236
+ # @param args [[]Object]] one or more objects which should not longer be
215
237
  # traced
216
- # @return [[Object]] the object(s) which are not longer be traced
238
+ # @return [Array<Object>] the object(s) which are not longer be traced
217
239
  # @return [nil] when none of the objects was traced before
218
240
  #
219
241
  def untrace(*args)
220
- ret = args.filter_map { @trace.delete(_1.__id__) ? _1 : nil }
221
- args.size == 1 ? ret[0] : ret
242
+ args = args.filter_map { @trace.delete(_1) }
243
+ args.size < 2 ? args[0] : args
222
244
  end
223
245
 
224
246
  #
225
- # Stop tracing any object.
226
- # (When you are really lost and just like to stop tracing of all your
227
- # objects.)
247
+ # Stop tracing any object. When you are really lost and just like to stop
248
+ # tracing of all your objects.
228
249
  #
229
250
  # @see trace
230
251
  #
@@ -236,171 +257,396 @@ module ImLost
236
257
  end
237
258
 
238
259
  #
239
- # Inspect internal variables.
260
+ # Inspect internal variables of a given object.
261
+ #
262
+ # @note The dedictaed handling of `Fiber` is platform dependend!
263
+ #
264
+ # @example Inspect current instance variables
265
+ # @a = 22
266
+ # b = 20
267
+ # c = @a + b
268
+ # ImLost.vars(self)
269
+ # # => print value of `@a`
270
+ #
271
+ # @example Inspect local variables
272
+ # @a = 22
273
+ # b = 20
274
+ # c = @a + b
275
+ # ImLost.vars(binding)
276
+ # # => print values of `b` and 'c'
277
+ #
278
+ # @example Inspect a thread's variables
279
+ # th = Thread.new { th[:var1] += 20 }
280
+ # th[:var1] = 22
281
+ # ImLost.vars(th)
282
+ # # => print value of `var1`
283
+ # th.join
284
+ # ImLost.vars(th)
240
285
  #
241
- # @overload vars(binding)
242
- # Inspect local variables of given Binding.
243
- # @param binding [Binding] which local variables should be print
244
- # @return [self] itself
286
+ # @example Inspect the current fiber's storage
287
+ # Fiber[:var1] = 22
288
+ # Fiber[:var2] = 20
289
+ # Fiber[:var3] = Fiber[:var1] + Fiber[:var2]
290
+ # ImLost.vars(Fiber.current)
245
291
  #
246
- # @overload vars(object)
247
- # Inspect instance variables of given object.
248
- # @param object [Object] which instance variables should be print
249
- # @return [Object] the given object
292
+ # When the given object is
293
+ #
294
+ # - a `Binding` it prints the local variables of the binding
295
+ # - a `Thread` it prints the fiber-local and thread variables
296
+ # - the current `Fiber` it prints the fibers' storage
297
+ #
298
+ # Be aware that only the current fiber can be inspected.
299
+ #
300
+ # When the given object can not be inspected it prints an error message.
301
+ #
302
+ # @param object [Object] which instance variables should be print
303
+ # @return [Object] the given object
250
304
  #
251
305
  def vars(object)
252
- traced = @trace.delete(object.__id__)
253
- return _local_vars(object) if object.is_a?(Binding)
254
- return unless object.respond_to?(:instance_variables)
255
- _vars(object, Kernel.caller_locations(1, 1)[0])
306
+ out = Out.new
307
+ traced = @trace.delete(object)
308
+ return _local_vars(out, object) if Binding === object
309
+ location = Kernel.caller_locations(1, 1)[0]
310
+ out << "* #{location.path}:#{location.lineno}"
311
+ return _thread_vars(out, object) if Thread === object
312
+ return _fiber_vars(out, object) if @fiber_supported && Fiber === object
313
+ return _instance_vars(out, object) if defined?(object.instance_variables)
314
+ out << ' !!! unable to retrieve vars'
315
+ object
256
316
  ensure
257
317
  @trace[traced] = traced if traced
318
+ out.flush(@output)
258
319
  end
259
320
 
260
- protected
321
+ private
261
322
 
262
- def as_sig(prefix, info, args)
263
- args = args.join(', ')
264
- case info.self
265
- when Class, Module
266
- "#{prefix} #{info.self}.#{info.method_id}(#{args})"
267
- else
268
- "#{prefix} #{info.defined_class}##{info.method_id}(#{args})"
269
- end
323
+ def _can_trace?(arg)
324
+ (id = arg.__id__) != __id__ && id != @output.__id__
270
325
  end
271
326
 
272
- private
273
-
274
327
  def _trace(arg)
275
- id = arg.__id__
276
- @trace[id] = id if __id__ != id && @output.__id__ != id
328
+ @trace[arg] = arg if _can_trace?(arg)
277
329
  arg
278
330
  end
279
331
 
280
332
  def _trace_all(args)
281
- args.each do |arg|
282
- arg = arg.__id__
283
- @trace[arg] = arg if __id__ != arg && @output.__id__ != arg
284
- end
333
+ args.each { |arg| @trace[arg] = arg if _can_trace?(arg) }
285
334
  args
286
335
  end
287
336
 
288
337
  def _trace_b(arg)
289
- id = arg.__id__
290
- return yield(arg) if __id__ == id || @output.__id__ == id
338
+ return yield(arg) if @trace.key?(arg) || !_can_trace?(arg)
291
339
  begin
292
- @trace[id] = id
340
+ @trace[arg] = arg
293
341
  yield(arg)
294
342
  ensure
295
- @trace.delete(id) if id
343
+ @trace.delete(arg)
296
344
  end
297
345
  end
298
346
 
299
347
  def _trace_all_b(args)
300
- ids =
348
+ temp =
301
349
  args.filter_map do |arg|
302
- arg = arg.__id__
303
- @trace[arg] = arg if __id__ != arg && @output.__id__ != arg
350
+ @trace[arg] = arg if !@trace.key?(arg) && _can_trace?(arg)
304
351
  end
305
352
  yield(args)
306
353
  ensure
307
- ids.each { @trace.delete(_1) }
354
+ temp.each { @trace.delete(_1) }
355
+ end
356
+
357
+ def _local_vars(out, binding)
358
+ out << "* #{binding.source_location.join(':')}"
359
+ out.vars('local variables', binding.local_variables) do |name|
360
+ binding.local_variable_get(name)
361
+ end
362
+ binding
363
+ end
364
+
365
+ def _thread_vars(out, thread)
366
+ out << " #{_thread_identifier(thread)}"
367
+ flv = thread.keys
368
+ out.vars('fiber-local variables', flv) { thread[_1] } unless flv.empty?
369
+ out.vars('thread variables', thread.thread_variables) do |name|
370
+ thread.thread_variable_get(name)
371
+ end
372
+ thread
308
373
  end
309
374
 
310
- def _vars(obj, location)
311
- @output.puts("= #{location.path}:#{location.lineno}")
312
- vars = obj.instance_variables
313
- if vars.empty?
314
- @output.puts(' <no instance variables defined>')
375
+ def _fiber_vars(out, fiber)
376
+ if Fiber.current == fiber
377
+ storage = fiber.storage || {}
378
+ out.vars('fiber storage', storage.keys) { storage[_1] }
315
379
  else
316
- @output.puts(' instance variables:')
317
- vars.sort!.each do |name|
318
- @output.puts(" #{name}: #{obj.instance_variable_get(name).inspect}")
380
+ out << ' !!! given Fiber is not the current Fiber' <<
381
+ " #{fiber.inspect}"
382
+ end
383
+ fiber
384
+ end
385
+
386
+ def _instance_vars(out, object)
387
+ out.vars('instance variables', object.instance_variables) do |n|
388
+ object.instance_variable_get(n)
389
+ end
390
+ object
391
+ end
392
+
393
+ def _thread_identifier(thread)
394
+ "#{THREAD_STATE[thread.status] || thread.status} Thread #{
395
+ if defined?(thread.native_thread_id)
396
+ thread.native_thread_id
397
+ else
398
+ thread.__id__
399
+ end
400
+ } #{thread.name}".rstrip
401
+ end
402
+ end
403
+
404
+ #
405
+ # A store to create and register timers you can use to estimate the runtime of
406
+ # some code.
407
+ #
408
+ # All timers are identified by an unique ID or a name.
409
+ #
410
+ # @example Use a named timer
411
+ # ImLost.timer.create('my_test')
412
+ #
413
+ # # ...your code here...
414
+ #
415
+ # ImLost.timer['my_test']
416
+ # # => prints the timer name, this location and runtime so far
417
+ #
418
+ # # ...more code here...
419
+ #
420
+ # ImLost.timer['my_test']
421
+ # # => prints the timer name, this location and runtime since the timer was created
422
+ #
423
+ # ImLost.timer.delete('my_test')
424
+ # # the timer with name 'my_test' is not longer valid now
425
+ #
426
+ #
427
+ # @example Use an anonymous timer
428
+ # tmr = ImLost.timer.create
429
+ #
430
+ # # ...your code here...
431
+ #
432
+ # ImLost.timer[tmr]
433
+ # # => prints the timer ID, this location and runtime so far
434
+ #
435
+ # # ...more code here...
436
+ #
437
+ # ImLost.timer[tmr]
438
+ # # => prints the timer ID, this location and runtime since the timer was created
439
+ #
440
+ # ImLost.timer.delete(tmr)
441
+ # # the timer with the ID `tmr` is not longer valid now
442
+ #
443
+ # @see ImLost.timer
444
+ #
445
+ class TimerStore
446
+ if defined?(Process::CLOCK_MONOTONIC)
447
+ # @return [Float] current time
448
+ def self.now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
449
+ else
450
+ # @return [Float] current time
451
+ def self.now = ::Time.now
452
+ end
453
+
454
+ # @attribute [r] count
455
+ # @return [Integer] the number of registered timers
456
+ def count = ids.size
457
+
458
+ # @attribute [r] empty?
459
+ # @return [Boolean] wheter the timer store is empty or not
460
+ def empty? = ids.empty?
461
+
462
+ # @attribute [r] ids
463
+ # @return [Array<Integer>] IDs of all registered timers
464
+ def ids = (@ll.keys.keep_if { _1.is_a?(Integer) })
465
+
466
+ #
467
+ # Create and register a new named or anonymous timer.
468
+ # It print the ID or name of the created timer and includes the location.
469
+ #
470
+ # @param name [#to_s] optional timer name
471
+ # @return [Integer] timer ID
472
+ #
473
+ def create(name = nil)
474
+ timer = []
475
+ @ll[id = timer.__id__] = timer
476
+ name ? @ll[name = name.to_s] = timer : name = id
477
+ @cb[name, Kernel.caller_locations(1, 1)[0]]
478
+ timer << name << self.class.now
479
+ id
480
+ end
481
+
482
+ #
483
+ # Delete and unregister timers.
484
+ #
485
+ # @param id_or_names [Array<Integer, #to_s>] the IDs or the names
486
+ # @return [nil]
487
+ #
488
+ def delete(*id_or_names)
489
+ id_or_names.flatten.each do |id|
490
+ if id.is_a?(Integer)
491
+ del = @ll.delete(id)
492
+ @ll.delete(del[0]) if del
493
+ else
494
+ del = @ll.delete(id.to_s)
495
+ @ll.delete(del.__id__) if del
319
496
  end
320
497
  end
321
- obj
498
+ nil
322
499
  end
323
500
 
324
- def _local_vars(binding)
325
- @output.puts("= #{binding.source_location.join(':')}")
326
- vars = binding.local_variables
327
- if vars.empty?
328
- @output.puts(' <no local variables>')
501
+ #
502
+ # Print the ID or name and the runtime since a timer was created.
503
+ # It includes the location.
504
+ #
505
+ # @param id_or_name [Integer, #to_s] the identifier or the name of the timer
506
+ # @return [Integer] timer ID
507
+ # @raise [ArgumentError] when the given id or name is not a registered timer
508
+ # identifier or name
509
+ #
510
+ def [](id_or_name)
511
+ time = self.class.now
512
+ timer = @ll[id_or_name.is_a?(Integer) ? id_or_name : id_or_name.to_s]
513
+ raise(ArgumentError, "not a timer - #{id_or_name.inspect}") unless timer
514
+ @cb[timer[0], Kernel.caller_locations(1, 1)[0], time - timer[1]]
515
+ timer.__id__
516
+ end
517
+
518
+ #
519
+ # Print the ID or name and the runtime of all active timers.
520
+ # It includes the location.
521
+ #
522
+ # @return [nil]
523
+ #
524
+ def all
525
+ now = self.class.now
526
+ loc = Kernel.caller_locations(1, 1)[0]
527
+ @ll.values.uniq.reverse_each { |name, start| @cb[name, loc, now - start] }
528
+ nil
529
+ end
530
+
531
+ # @!visibility private
532
+ def initialize(&block)
533
+ @cb = block
534
+ @ll = {}
535
+ end
536
+ end
537
+
538
+ class Out
539
+ def initialize(*lines) = (@lines = lines)
540
+ def <<(str) = @lines << str
541
+ def location(loc) = @lines << " #{loc.path}:#{loc.lineno}"
542
+ def flush(dev) = dev << (@lines << nil).join("\n")
543
+
544
+ def sig(prefix, info, args)
545
+ args = args.join(', ')
546
+ @lines << case info.self
547
+ when Class, Module
548
+ "#{prefix} #{info.self}.#{info.method_id}(#{args})"
329
549
  else
330
- @output.puts(' local variables:')
331
- vars.sort!.each do |name|
332
- @output.puts(" #{name}: #{binding.local_variable_get(name).inspect}")
333
- end
550
+ "#{prefix} #{info.defined_class}##{info.method_id}(#{args})"
334
551
  end
335
- self
552
+ end
553
+
554
+ def vars(kind, names)
555
+ return @lines << " <no #{kind} defined>" if names.empty?
556
+ @lines << " > #{kind}"
557
+ names.sort!.each { @lines << " #{_1}: #{yield(_1).inspect}" }
336
558
  end
337
559
  end
560
+ private_constant :Out
338
561
 
339
562
  ARG_SIG = { rest: '*', keyrest: '**', block: '&' }.compare_by_identity.freeze
340
563
  NO_NAME = { :* => 1, :** => 1, :& => 1 }.compare_by_identity.freeze
341
- private_constant :ARG_SIG, :NO_NAME
564
+ THREAD_STATE = {
565
+ false => 'terminated',
566
+ nil => 'aborted'
567
+ }.compare_by_identity.freeze
568
+ private_constant :ARG_SIG, :NO_NAME, :THREAD_STATE
342
569
 
343
570
  @trace = {}.compare_by_identity
344
- @caller_locations = true
345
- @output = $stderr.respond_to?(:puts) ? $stderr : STDERR
571
+ @caller_locations = @exception_locations = true
572
+ @output = STDERR
573
+
574
+ @timer = TimerStore.new { |title, location, time| @output << <<~TIMER_MSG }
575
+ T #{title}: #{time ? "#{time} sec." : 'created'}
576
+ #{location.path}:#{location.lineno}
577
+ TIMER_MSG
578
+ TimerStore.private_class_method(:new)
346
579
 
347
580
  @trace_calls = [
348
581
  TracePoint.new(:c_call) do |tp|
349
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
350
- @output.puts(as_sig('>', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' }))
351
- @output.puts(" #{tp.path}:#{tp.lineno}") if @caller_locations
582
+ next if !@trace.key?(tp.self) || tp.path == __FILE__
583
+ out = Out.new
584
+ out.sig('>', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' })
585
+ out.location(tp) if @caller_locations
586
+ out.flush(@output)
352
587
  end,
353
588
  TracePoint.new(:call) do |tp|
354
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
589
+ next if !@trace.key?(tp.self) || tp.path == __FILE__
355
590
  ctx = tp.binding
356
- @output.puts(
357
- as_sig(
358
- '>',
359
- tp,
360
- tp.parameters.map do |kind, name|
361
- next name if NO_NAME.key?(name)
362
- "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
363
- end
364
- )
591
+ out = Out.new
592
+ out.sig(
593
+ '>',
594
+ tp,
595
+ tp.parameters.map do |kind, name|
596
+ next name if NO_NAME.key?(name)
597
+ "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
598
+ end
365
599
  )
366
- next unless @caller_locations
367
- loc = ctx.eval('caller_locations(4,1)')[0]
368
- @output.puts(" #{loc.path}:#{loc.lineno}")
600
+ out.location(ctx.eval('caller_locations(4,1)')[0]) if @caller_locations
601
+ out.flush(@output)
369
602
  end
370
603
  ]
371
604
 
372
605
  @trace_results = [
373
606
  TracePoint.new(:c_return) do |tp|
374
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
375
- @output.puts(as_sig('<', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' }))
376
- @output.puts(" = #{tp.return_value.inspect}")
607
+ next if !@trace.key?(tp.self) || tp.path == __FILE__
608
+ out = Out.new
609
+ out.sig('<', tp, tp.parameters.map { ARG_SIG[_1[0]] || '?' })
610
+ out.location(tp) if @caller_locations
611
+ out << " = #{tp.return_value.inspect}"
612
+ out.flush(@output)
377
613
  end,
378
614
  TracePoint.new(:return) do |tp|
379
- next if !@trace.key?(tp.self.__id__) || tp.path == __FILE__
615
+ next if !@trace.key?(tp.self) || tp.path == __FILE__
380
616
  ctx = tp.binding
381
- @output.puts(
382
- as_sig(
383
- '<',
384
- tp,
385
- tp.parameters.map do |kind, name|
386
- next name if %i[* ** &].include?(name)
387
- "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
388
- end
389
- )
617
+ out = Out.new
618
+ out.sig(
619
+ '<',
620
+ tp,
621
+ tp.parameters.map do |kind, name|
622
+ next name if NO_NAME.key?(name)
623
+ "#{ARG_SIG[kind]}#{ctx.local_variable_get(name).inspect}"
624
+ end
390
625
  )
391
- @output.puts(" = #{tp.return_value.inspect}")
626
+ out.location(ctx.eval('caller_locations(4,1)')[0]) if @caller_locations
627
+ out << " = #{tp.return_value.inspect}"
628
+ out.flush(@output)
392
629
  end
393
630
  ]
394
631
 
395
- supported = RUBY_VERSION >= '3.3.0' ? %i[raise rescue] : %i[raise]
632
+ supported = RUBY_VERSION.to_f < 3.3 ? %i[raise] : %i[raise rescue]
396
633
  @trace_exceptions =
397
634
  TracePoint.new(*supported) do |tp|
398
- ex = tp.raised_exception.inspect
399
- @output.puts(
400
- "#{tp.event == :raise ? 'x' : '!'} #{ex[0] == '#' ? ex[2..-2] : ex}"
401
- )
402
- @output.puts(" #{tp.path}:#{tp.lineno}") if @exception_locations
635
+ ex = tp.raised_exception
636
+ mark, parent = tp.event == :rescue ? ['!', ex.cause] : 'x'
637
+ ex = ex.inspect
638
+ out = Out.new("#{mark} #{ex[0] == '#' ? ex[2..-2] : ex}")
639
+ while parent
640
+ ex = parent.inspect
641
+ out << " [#{ex[0] == '#' ? ex[2..-2] : ex}]"
642
+ parent = parent.cause
643
+ end
644
+ out.location(tp) if @exception_locations
645
+ out.flush(@output)
403
646
  end
404
647
 
405
- self.trace_calls = true
648
+ @fiber_supported =
649
+ !!(defined?(Fiber.current) && defined?(Fiber.current.storage))
650
+
651
+ self.trace_calls = self.trace_results = true
406
652
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: im-lost
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-13 00:00:00.000000000 Z
11
+ date: 2024-06-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  If you have overlooked something again and don't really understand what
@@ -30,6 +30,7 @@ files:
30
30
  - README.md
31
31
  - examples/foo.rb
32
32
  - examples/kernel_calls.rb
33
+ - examples/timer.rb
33
34
  - lib/im-lost.rb
34
35
  - lib/im-lost/version.rb
35
36
  - lib/im_lost.rb
@@ -56,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
57
  - !ruby/object:Gem::Version
57
58
  version: '0'
58
59
  requirements: []
59
- rubygems_version: 3.5.10
60
+ rubygems_version: 3.5.13
60
61
  signing_key:
61
62
  specification_version: 4
62
63
  summary: Your debugging helper.