appmap 0.44.0 → 0.47.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.releaserc.yml +11 -0
- data/.travis.yml +28 -12
- data/CHANGELOG.md +42 -0
- data/README.md +39 -11
- data/README_CI.md +29 -0
- data/Rakefile +4 -2
- data/appmap.gemspec +5 -1
- data/lib/appmap/class_map.rb +7 -15
- data/lib/appmap/config.rb +203 -95
- data/lib/appmap/event.rb +29 -28
- data/lib/appmap/handler/function.rb +19 -0
- data/lib/appmap/handler/net_http.rb +107 -0
- data/lib/appmap/handler/rails/request_handler.rb +124 -0
- data/lib/appmap/handler/rails/sql_handler.rb +152 -0
- data/lib/appmap/handler/rails/template.rb +155 -0
- data/lib/appmap/hook.rb +109 -71
- data/lib/appmap/hook/method.rb +6 -8
- data/lib/appmap/railtie.rb +5 -5
- data/lib/appmap/trace.rb +47 -6
- data/lib/appmap/util.rb +41 -2
- data/lib/appmap/version.rb +2 -2
- data/package-lock.json +3 -3
- data/release.sh +17 -0
- data/spec/abstract_controller_base_spec.rb +74 -11
- data/spec/class_map_spec.rb +3 -3
- data/spec/config_spec.rb +3 -1
- data/spec/hook_spec.rb +12 -66
- data/spec/record_net_http_spec.rb +160 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/util_spec.rb +18 -1
- metadata +16 -10
- data/lib/appmap/rails/request_handler.rb +0 -140
- data/lib/appmap/rails/sql_handler.rb +0 -150
- data/patch +0 -1447
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/event'
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
module Handler
|
7
|
+
module Rails
|
8
|
+
class Template
|
9
|
+
LOG = (ENV['APPMAP_TEMPLATE_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
10
|
+
|
11
|
+
# All the code which is touched by the AppMap is recorded in the classMap.
|
12
|
+
# This duck-typed 'method' is used to represent a view template as a package,
|
13
|
+
# class, and method in the classMap.
|
14
|
+
# The class name is generated from the template path. The package name is
|
15
|
+
# 'app/views', and the method name is 'render'. The source location of the method
|
16
|
+
# is, of course, the path to the view template.
|
17
|
+
TemplateMethod = Struct.new(:path) do
|
18
|
+
private_instance_methods :path
|
19
|
+
attr_reader :class_name
|
20
|
+
|
21
|
+
def initialize(path)
|
22
|
+
super
|
23
|
+
|
24
|
+
@class_name = path.parameterize.underscore
|
25
|
+
end
|
26
|
+
|
27
|
+
def package
|
28
|
+
'app/views'
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
'render'
|
33
|
+
end
|
34
|
+
|
35
|
+
def source_location
|
36
|
+
path
|
37
|
+
end
|
38
|
+
|
39
|
+
def static
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def comment
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def labels
|
48
|
+
[ 'mvc.template' ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# TemplateCall is a type of function call which is specialized to view template rendering. Since
|
53
|
+
# there isn't really a perfect method in Rails to hook, this one is synthesized from the available
|
54
|
+
# information.
|
55
|
+
class TemplateCall < AppMap::Event::MethodEvent
|
56
|
+
# This is basically the +self+ parameter.
|
57
|
+
attr_reader :render_instance
|
58
|
+
# Path to the view template.
|
59
|
+
attr_accessor :path
|
60
|
+
|
61
|
+
def initialize(render_instance)
|
62
|
+
super :call
|
63
|
+
|
64
|
+
AppMap::Event::MethodEvent.build_from_invocation(:call, event: self)
|
65
|
+
@render_instance = render_instance
|
66
|
+
end
|
67
|
+
|
68
|
+
def static?
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_h
|
73
|
+
super.tap do |h|
|
74
|
+
h[:defined_class] = path ? path.parameterize.underscore : 'inline_template'
|
75
|
+
h[:method_id] = 'render'
|
76
|
+
h[:path] = path
|
77
|
+
h[:static] = static?
|
78
|
+
h[:parameters] = []
|
79
|
+
h[:receiver] = {
|
80
|
+
class: AppMap::Event::MethodEvent.best_class_name(render_instance),
|
81
|
+
object_id: render_instance.__id__,
|
82
|
+
value: AppMap::Event::MethodEvent.display_string(render_instance)
|
83
|
+
}
|
84
|
+
h.compact
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
TEMPLATE_RENDERER = 'appmap.handler.rails.template.renderer'
|
90
|
+
|
91
|
+
# Hooks the ActionView::Resolver methods +find_all+, +find_all_anywhere+. The resolver is used
|
92
|
+
# during template rendering to lookup the template file path from parameters such as the
|
93
|
+
# template name, prefix, and partial (boolean).
|
94
|
+
class ResolverHandler
|
95
|
+
class << self
|
96
|
+
# Handled as a normal function call.
|
97
|
+
def handle_call(defined_class, hook_method, receiver, args)
|
98
|
+
name, prefix, partial = args
|
99
|
+
warn "Resolver: #{{ name: name, prefix: prefix, partial: partial }}" if LOG
|
100
|
+
|
101
|
+
AppMap::Handler::Function.handle_call(defined_class, hook_method, receiver, args)
|
102
|
+
end
|
103
|
+
|
104
|
+
# When the resolver returns, look to see if there is template rendering underway.
|
105
|
+
# If so, populate the template path. In all cases, add a TemplateMethod so that the
|
106
|
+
# template will be recorded in the classMap.
|
107
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
108
|
+
renderer = Array(Thread.current[TEMPLATE_RENDERER]).last
|
109
|
+
path_obj = Array(return_value).first
|
110
|
+
|
111
|
+
warn "Resolver return: #{path_obj}" if LOG
|
112
|
+
|
113
|
+
if path_obj
|
114
|
+
path = if path_obj.respond_to?(:identifier) && path_obj.inspect.index('#<')
|
115
|
+
path_obj.identifier
|
116
|
+
else
|
117
|
+
path_obj.inspect
|
118
|
+
end
|
119
|
+
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
120
|
+
AppMap.tracing.record_method(TemplateMethod.new(path))
|
121
|
+
renderer.path ||= path if renderer
|
122
|
+
end
|
123
|
+
|
124
|
+
AppMap::Handler::Function.handle_return(call_event_id, elapsed, return_value, exception)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Hooks the ActionView::Renderer method +render+. This method is used by Rails to perform
|
130
|
+
# template rendering. The TemplateCall event which is emitted by this handler has a
|
131
|
+
# +path+ parameter, which is nil until it's filled in by a ResolverHandler.
|
132
|
+
class RenderHandler
|
133
|
+
class << self
|
134
|
+
def handle_call(defined_class, hook_method, receiver, args)
|
135
|
+
context, options = args
|
136
|
+
|
137
|
+
warn "Renderer: #{options}" if LOG
|
138
|
+
|
139
|
+
TemplateCall.new(receiver).tap do |call|
|
140
|
+
Thread.current[TEMPLATE_RENDERER] ||= []
|
141
|
+
Thread.current[TEMPLATE_RENDERER] << call
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
146
|
+
Array(Thread.current[TEMPLATE_RENDERER]).pop
|
147
|
+
|
148
|
+
AppMap::Event::MethodReturnIgnoreValue.build_from_invocation(call_event_id, elapsed: elapsed)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -5,6 +5,7 @@ require 'English'
|
|
5
5
|
module AppMap
|
6
6
|
class Hook
|
7
7
|
LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
8
|
+
LOG_HOOK = (ENV['DEBUG_HOOK'] == 'true')
|
8
9
|
|
9
10
|
OBJECT_INSTANCE_METHODS = %i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods taint tainted? tap then to_enum to_s to_h to_a trust untaint untrust untrusted? yield_self].freeze
|
10
11
|
OBJECT_STATIC_METHODS = %i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr attr_accessor attr_reader attr_writer autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_method define_singleton_method deprecate_constant display dup enum_for eql? equal? extend freeze frozen? hash include include? included_modules inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method method_defined? methods module_eval module_exec name new nil? object_id prepend private_class_method private_constant private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_constant public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable remove_instance_variable remove_method respond_to? send singleton_class singleton_class? singleton_method singleton_methods superclass taint tainted? tap then to_enum to_s trust undef_method untaint untrust untrusted? yield_self].freeze
|
@@ -35,73 +36,21 @@ module AppMap
|
|
35
36
|
|
36
37
|
def initialize(config)
|
37
38
|
@config = config
|
39
|
+
@trace_locations = []
|
40
|
+
# Paths that are known to be non-tracing
|
41
|
+
@notrace_paths = Set.new
|
38
42
|
end
|
39
43
|
|
40
44
|
# Observe class loading and hook all methods which match the config.
|
41
|
-
def enable
|
45
|
+
def enable(&block)
|
42
46
|
require 'appmap/hook/method'
|
43
47
|
|
44
48
|
hook_builtins
|
45
49
|
|
46
|
-
|
47
|
-
|
50
|
+
@trace_begin = TracePoint.new(:class, &method(:trace_class))
|
51
|
+
@trace_end = TracePoint.new(:end, &method(:trace_end))
|
48
52
|
|
49
|
-
|
50
|
-
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
51
|
-
class_methods = begin
|
52
|
-
if cls.respond_to?(:singleton_class)
|
53
|
-
cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
54
|
-
else
|
55
|
-
[]
|
56
|
-
end
|
57
|
-
rescue NameError
|
58
|
-
[]
|
59
|
-
end
|
60
|
-
|
61
|
-
hook = lambda do |hook_cls|
|
62
|
-
lambda do |method_id|
|
63
|
-
# Don't try and trace the AppMap methods or there will be
|
64
|
-
# a stack overflow in the defined hook method.
|
65
|
-
return if (hook_cls&.name || '').split('::')[0] == AppMap.name
|
66
|
-
|
67
|
-
method = begin
|
68
|
-
hook_cls.public_instance_method(method_id)
|
69
|
-
rescue NameError
|
70
|
-
warn "AppMap: Method #{hook_cls} #{method.name} is not accessible" if LOG
|
71
|
-
return
|
72
|
-
end
|
73
|
-
|
74
|
-
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
75
|
-
|
76
|
-
disasm = RubyVM::InstructionSequence.disasm(method)
|
77
|
-
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
78
|
-
next unless disasm
|
79
|
-
|
80
|
-
next if config.never_hook?(method)
|
81
|
-
|
82
|
-
next unless \
|
83
|
-
config.always_hook?(hook_cls, method.name) ||
|
84
|
-
config.included_by_location?(method)
|
85
|
-
|
86
|
-
package = config.package_for_method(method)
|
87
|
-
|
88
|
-
hook_method = Hook::Method.new(package, hook_cls, method)
|
89
|
-
|
90
|
-
hook_method.activate
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
instance_methods.each(&hook.(cls))
|
95
|
-
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
96
|
-
begin
|
97
|
-
class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
|
98
|
-
rescue NameError
|
99
|
-
# NameError:
|
100
|
-
# uninitialized constant Faraday::Connection
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
tp.enable(&block)
|
53
|
+
@trace_begin.enable(&block)
|
105
54
|
end
|
106
55
|
|
107
56
|
# hook_builtins builds hooks for code that is built in to the Ruby standard library.
|
@@ -115,30 +64,119 @@ module AppMap
|
|
115
64
|
end
|
116
65
|
end
|
117
66
|
|
118
|
-
config.
|
67
|
+
config.builtin_hooks.each do |class_name, hooks|
|
119
68
|
Array(hooks).each do |hook|
|
120
69
|
require hook.package.package_name if hook.package.package_name
|
121
70
|
Array(hook.method_names).each do |method_name|
|
122
71
|
method_name = method_name.to_sym
|
72
|
+
base_cls = class_from_string.(class_name)
|
123
73
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
cls.instance_method(method_name)
|
128
|
-
rescue NameError
|
129
|
-
cls.method(method_name) rescue nil
|
130
|
-
end
|
131
|
-
|
132
|
-
next if config.never_hook?(method)
|
74
|
+
hook_method = lambda do |entry|
|
75
|
+
cls, method = entry
|
76
|
+
return false if config.never_hook?(cls, method)
|
133
77
|
|
134
|
-
if method
|
135
78
|
Hook::Method.new(hook.package, cls, method).activate
|
79
|
+
end
|
80
|
+
|
81
|
+
methods = []
|
82
|
+
methods << [ base_cls, base_cls.public_instance_method(method_name) ] rescue nil
|
83
|
+
if base_cls.respond_to?(:singleton_class)
|
84
|
+
methods << [ base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name) ] rescue nil
|
85
|
+
end
|
86
|
+
methods.compact!
|
87
|
+
if methods.empty?
|
88
|
+
warn "Method #{method_name} not found on #{base_cls.name}"
|
136
89
|
else
|
137
|
-
|
90
|
+
methods.each(&hook_method)
|
138
91
|
end
|
139
92
|
end
|
140
93
|
end
|
141
94
|
end
|
142
95
|
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def trace_class(trace_point)
|
100
|
+
path = trace_point.path
|
101
|
+
|
102
|
+
return if @notrace_paths.member?(path)
|
103
|
+
|
104
|
+
if config.path_enabled?(path)
|
105
|
+
location = trace_location(trace_point)
|
106
|
+
warn "Entering hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
|
107
|
+
@trace_locations << location
|
108
|
+
unless @trace_end.enabled?
|
109
|
+
warn "Enabling hooking" if Hook::LOG || Hook::LOG_HOOK
|
110
|
+
@trace_end.enable
|
111
|
+
end
|
112
|
+
else
|
113
|
+
@notrace_paths << path
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def trace_location(trace_point)
|
118
|
+
[ trace_point.path, trace_point.lineno ].join(':')
|
119
|
+
end
|
120
|
+
|
121
|
+
def trace_end(trace_point)
|
122
|
+
cls = trace_point.self
|
123
|
+
|
124
|
+
instance_methods = cls.public_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
125
|
+
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
126
|
+
class_methods = begin
|
127
|
+
if cls.respond_to?(:singleton_class)
|
128
|
+
cls.singleton_class.public_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
129
|
+
else
|
130
|
+
[]
|
131
|
+
end
|
132
|
+
rescue NameError
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
|
136
|
+
hook = lambda do |hook_cls|
|
137
|
+
lambda do |method_id|
|
138
|
+
# Don't try and trace the AppMap methods or there will be
|
139
|
+
# a stack overflow in the defined hook method.
|
140
|
+
next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
|
141
|
+
|
142
|
+
next if method_id == :call
|
143
|
+
|
144
|
+
method = begin
|
145
|
+
hook_cls.public_instance_method(method_id)
|
146
|
+
rescue NameError
|
147
|
+
warn "AppMap: Method #{hook_cls} #{method.name} is not accessible" if LOG
|
148
|
+
next
|
149
|
+
end
|
150
|
+
|
151
|
+
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
152
|
+
|
153
|
+
disasm = RubyVM::InstructionSequence.disasm(method)
|
154
|
+
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
155
|
+
next unless disasm
|
156
|
+
|
157
|
+
package = config.lookup_package(hook_cls, method)
|
158
|
+
next unless package
|
159
|
+
|
160
|
+
Hook::Method.new(package, hook_cls, method).activate
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
instance_methods.each(&hook.(cls))
|
165
|
+
begin
|
166
|
+
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
167
|
+
class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
|
168
|
+
rescue NameError
|
169
|
+
# NameError:
|
170
|
+
# uninitialized constant Faraday::Connection
|
171
|
+
warn "NameError in #{__FILE__}: #{$!.message}"
|
172
|
+
end
|
173
|
+
|
174
|
+
location = @trace_locations.pop
|
175
|
+
warn "Leaving hook-enabled location #{location}" if Hook::LOG || Hook::LOG_HOOK
|
176
|
+
if @trace_locations.empty?
|
177
|
+
warn "Disabling hooking" if Hook::LOG || Hook::LOG_HOOK
|
178
|
+
@trace_end.disable
|
179
|
+
end
|
180
|
+
end
|
143
181
|
end
|
144
182
|
end
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -76,7 +76,7 @@ module AppMap
|
|
76
76
|
raise
|
77
77
|
ensure
|
78
78
|
with_disabled_hook.call do
|
79
|
-
after_hook.call(self, call_event, start_time, return_value, exception)
|
79
|
+
after_hook.call(self, call_event, start_time, return_value, exception) if call_event
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -87,18 +87,16 @@ module AppMap
|
|
87
87
|
protected
|
88
88
|
|
89
89
|
def before_hook(receiver, defined_class, args)
|
90
|
-
|
91
|
-
|
92
|
-
AppMap.tracing.record_event call_event, package: hook_package, defined_class: defined_class, method: hook_method
|
90
|
+
call_event = hook_package.handler_class.handle_call(defined_class, hook_method, receiver, args)
|
91
|
+
AppMap.tracing.record_event(call_event, package: hook_package, defined_class: defined_class, method: hook_method) if call_event
|
93
92
|
[ call_event, TIME_NOW.call ]
|
94
93
|
end
|
95
94
|
|
96
95
|
def after_hook(_receiver, call_event, start_time, return_value, exception)
|
97
|
-
require 'appmap/event'
|
98
96
|
elapsed = TIME_NOW.call - start_time
|
99
|
-
return_event =
|
100
|
-
|
101
|
-
|
97
|
+
return_event = hook_package.handler_class.handle_return(call_event.id, elapsed, return_value, exception)
|
98
|
+
AppMap.tracing.record_event(return_event) if return_event
|
99
|
+
nil
|
102
100
|
end
|
103
101
|
|
104
102
|
def with_disabled_hook(&function)
|
data/lib/appmap/railtie.rb
CHANGED
@@ -8,12 +8,12 @@ module AppMap
|
|
8
8
|
# appmap.subscribe subscribes to ActiveSupport Notifications so that they can be recorded as
|
9
9
|
# AppMap events.
|
10
10
|
initializer 'appmap.subscribe' do |_| # params: app
|
11
|
-
require 'appmap/rails/sql_handler'
|
12
|
-
require 'appmap/rails/request_handler'
|
13
|
-
ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Rails::SQLHandler.new
|
14
|
-
ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Rails::SQLHandler.new
|
11
|
+
require 'appmap/handler/rails/sql_handler'
|
12
|
+
require 'appmap/handler/rails/request_handler'
|
13
|
+
ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Handler::Rails::SQLHandler.new
|
14
|
+
ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Handler::Rails::SQLHandler.new
|
15
15
|
|
16
|
-
AppMap::Rails::RequestHandler::HookMethod.new.activate
|
16
|
+
AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
|
17
17
|
end
|
18
18
|
|
19
19
|
# appmap.trace begins recording an AppMap trace and writes it to appmap.json.
|
data/lib/appmap/trace.rb
CHANGED
@@ -2,14 +2,36 @@
|
|
2
2
|
|
3
3
|
module AppMap
|
4
4
|
module Trace
|
5
|
-
class
|
6
|
-
attr_reader :
|
5
|
+
class RubyMethod
|
6
|
+
attr_reader :class_name, :static
|
7
7
|
|
8
|
-
def initialize(package,
|
8
|
+
def initialize(package, class_name, method, static)
|
9
9
|
@package = package
|
10
|
-
@
|
10
|
+
@class_name = class_name
|
11
|
+
@method = method
|
11
12
|
@static = static
|
12
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
def source_location
|
16
|
+
@method.source_location
|
17
|
+
end
|
18
|
+
|
19
|
+
def comment
|
20
|
+
@method.comment
|
21
|
+
rescue MethodSource::SourceNotFoundError
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def package
|
26
|
+
@package.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
@method.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def labels
|
34
|
+
@package.labels
|
13
35
|
end
|
14
36
|
end
|
15
37
|
|
@@ -43,6 +65,12 @@ module AppMap
|
|
43
65
|
end
|
44
66
|
end
|
45
67
|
|
68
|
+
def record_method(method)
|
69
|
+
@tracers.each do |tracer|
|
70
|
+
tracer.record_method(method)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
46
74
|
def delete(tracer)
|
47
75
|
return unless @tracers.member?(tracer)
|
48
76
|
|
@@ -82,10 +110,23 @@ module AppMap
|
|
82
110
|
|
83
111
|
@last_package_for_thread[Thread.current.object_id] = package if package
|
84
112
|
@events << event
|
85
|
-
|
113
|
+
static = event.static if event.respond_to?(:static)
|
114
|
+
@methods << Trace::RubyMethod.new(package, defined_class, method, static) \
|
86
115
|
if package && defined_class && method && (event.event == :call)
|
87
116
|
end
|
88
117
|
|
118
|
+
# +method+ should be duck-typed to respond to the following:
|
119
|
+
# * package
|
120
|
+
# * defined_class
|
121
|
+
# * name
|
122
|
+
# * static
|
123
|
+
# * comment
|
124
|
+
# * labels
|
125
|
+
# * source_location
|
126
|
+
def record_method(method)
|
127
|
+
@methods << method
|
128
|
+
end
|
129
|
+
|
89
130
|
# Gets the last package which was observed on the current thread.
|
90
131
|
def last_package_for_current_thread
|
91
132
|
@last_package_for_thread[Thread.current.object_id]
|