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 +4 -4
- data/README.md +39 -30
- data/examples/foo.rb +30 -23
- data/examples/kernel_calls.rb +0 -1
- data/examples/timer.rb +29 -0
- data/lib/im-lost/version.rb +1 -1
- data/lib/im-lost.rb +381 -135
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38b1a5b729438621357238f12f64f1d397ee56e18174c2cc1ec60067dd0ab15e
|
4
|
+
data.tar.gz: 157e15bcf3c9e9539467347e9cac31b5ed40adf9953bc6ab28dab6d163907288
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
# /
|
25
|
+
# /examples/test.rb:1
|
25
26
|
# > IO#write(*)
|
26
|
-
# /
|
27
|
+
# /examples/test.rb:1
|
27
28
|
# > IO#puts(*)
|
28
|
-
# /
|
29
|
+
# /examples/test.rb:2
|
29
30
|
# > IO#write(*)
|
30
|
-
# /
|
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
|
-
# /
|
45
|
+
# /examples/test.rb:2
|
44
46
|
# ! Errno::EEXIST: File exists @ rb_sysopen - /
|
45
|
-
# /
|
47
|
+
# /examples/test.rb:3
|
46
48
|
# x RuntimeError: something went wrong!
|
47
|
-
# /
|
49
|
+
# /examples/test.rb:4
|
48
50
|
```
|
49
51
|
|
50
|
-
When you like to know if
|
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
|
-
# /
|
113
|
+
# /examples/foo.rb:24
|
113
114
|
# > Foo.new(*)
|
114
|
-
# /
|
115
|
+
# /examples/foo.rb:6
|
115
116
|
# < Foo.new(*)
|
116
|
-
#
|
117
|
+
# /examples/foo.rb:6
|
118
|
+
# = #<Foo:0x00000001006448c0 @value=:foo!>
|
117
119
|
# < Foo.create(:foo!)
|
118
|
-
#
|
120
|
+
# /examples/foo.rb:24
|
121
|
+
# = #<Foo:0x00000001006448c0 @value=:foo!>
|
119
122
|
# > Foo#foo(1, *[], :none, **{}, &nil)
|
120
|
-
# /
|
123
|
+
# /examples/foo.rb:27
|
121
124
|
# > Foo#bar()
|
122
|
-
# /
|
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
|
-
#
|
128
|
-
# instance variables
|
129
|
-
#
|
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
|
-
# /
|
136
|
+
# /examples/foo.rb:30
|
132
137
|
# > Foo#bar()
|
133
|
-
# /
|
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
|
-
#
|
139
|
-
# instance variables
|
140
|
-
#
|
141
|
-
# > Foo#foo(3, *[], nil, **{}, &#<Proc:
|
142
|
-
# /
|
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
|
-
# /
|
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:
|
156
|
+
# < Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
|
157
|
+
# /examples/foo.rb:33
|
149
158
|
# = nil
|
150
|
-
#
|
151
|
-
# instance variables
|
152
|
-
#
|
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
|
-
|
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
|
-
# /
|
38
|
+
# /examples/foo.rb:24
|
40
39
|
# > Foo.new(*)
|
41
|
-
# /
|
40
|
+
# /examples/foo.rb:6
|
42
41
|
# < Foo.new(*)
|
43
|
-
#
|
42
|
+
# /examples/foo.rb:6
|
43
|
+
# = #<Foo:0x00000001006448c0 @value=:foo!>
|
44
44
|
# < Foo.create(:foo!)
|
45
|
-
#
|
45
|
+
# /examples/foo.rb:24
|
46
|
+
# = #<Foo:0x00000001006448c0 @value=:foo!>
|
46
47
|
# > Foo#foo(1, *[], :none, **{}, &nil)
|
47
|
-
# /
|
48
|
+
# /examples/foo.rb:27
|
48
49
|
# > Foo#bar()
|
49
|
-
# /
|
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
|
-
#
|
55
|
-
# instance variables
|
56
|
-
#
|
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
|
-
# /
|
61
|
+
# /examples/foo.rb:30
|
59
62
|
# > Foo#bar()
|
60
|
-
# /
|
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
|
-
#
|
66
|
-
# instance variables
|
67
|
-
#
|
68
|
-
# > Foo#foo(3, *[], nil, **{}, &#<Proc:
|
69
|
-
# /
|
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
|
-
# /
|
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:
|
81
|
+
# < Foo#foo(3, *[], nil, **{}, &#<Proc:0x0000000100641d28 /examples/foo.rb:33>)
|
82
|
+
# /examples/foo.rb:33
|
76
83
|
# = nil
|
77
|
-
#
|
78
|
-
# instance variables
|
79
|
-
#
|
84
|
+
# * /examples/foo.rb:34
|
85
|
+
# > instance variables
|
86
|
+
# @value: "3--[]-{}-bar"
|
data/examples/kernel_calls.rb
CHANGED
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)
|
data/lib/im-lost/version.rb
CHANGED
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
|
29
|
+
# This should be an `IO` device or any other object responding to `#<<`
|
30
|
+
# like a Logger.
|
29
31
|
#
|
30
|
-
#
|
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
|
53
|
-
raise(
|
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
|
-
# #
|
98
|
+
# # /examples/test.rb:2
|
85
99
|
# # ! Errno::EEXIST: File exists @ rb_sysopen - /
|
86
|
-
# #
|
100
|
+
# # /examples/test.rb:3
|
87
101
|
# # x RuntimeError: something went wrong!
|
88
|
-
# #
|
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
|
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
|
140
|
+
# @example Print current location
|
127
141
|
# ImLost.here
|
128
142
|
#
|
129
|
-
# @example
|
143
|
+
# @example Print current location when instance variable is empty
|
130
144
|
# ImLost.here(@name.empty?)
|
131
145
|
#
|
132
|
-
# @example
|
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
|
150
|
+
# Prints the call location.
|
137
151
|
# @return [true]
|
138
152
|
#
|
139
153
|
# @overload here(test)
|
140
|
-
# Prints the
|
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
|
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
|
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
|
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
|
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
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
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 [
|
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
|
208
|
-
#
|
229
|
+
# @example Trace some objects for some code lines
|
230
|
+
# traced_obj = ImLost.trace(@file, @client)
|
209
231
|
# # ...
|
210
|
-
# ImLost.untrace(*
|
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 [
|
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
|
-
|
221
|
-
args.size
|
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
|
-
#
|
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
|
-
# @
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
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
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
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
|
-
|
253
|
-
|
254
|
-
return
|
255
|
-
|
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
|
-
|
321
|
+
private
|
261
322
|
|
262
|
-
def
|
263
|
-
|
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
|
-
|
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
|
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
|
-
|
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[
|
340
|
+
@trace[arg] = arg
|
293
341
|
yield(arg)
|
294
342
|
ensure
|
295
|
-
@trace.delete(
|
343
|
+
@trace.delete(arg)
|
296
344
|
end
|
297
345
|
end
|
298
346
|
|
299
347
|
def _trace_all_b(args)
|
300
|
-
|
348
|
+
temp =
|
301
349
|
args.filter_map do |arg|
|
302
|
-
arg = arg.
|
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
|
-
|
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
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
498
|
+
nil
|
322
499
|
end
|
323
500
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
350
|
-
|
351
|
-
|
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
|
589
|
+
next if !@trace.key?(tp.self) || tp.path == __FILE__
|
355
590
|
ctx = tp.binding
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
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
|
-
|
367
|
-
|
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
|
375
|
-
|
376
|
-
|
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
|
615
|
+
next if !@trace.key?(tp.self) || tp.path == __FILE__
|
380
616
|
ctx = tp.binding
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
-
|
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
|
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
|
399
|
-
|
400
|
-
|
401
|
-
)
|
402
|
-
|
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
|
-
|
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
|
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-
|
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.
|
60
|
+
rubygems_version: 3.5.13
|
60
61
|
signing_key:
|
61
62
|
specification_version: 4
|
62
63
|
summary: Your debugging helper.
|