appmap 0.23.0 → 0.25.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/.gitignore +1 -0
- data/.rubocop.yml +17 -8
- data/.travis.yml +6 -0
- data/CHANGELOG.md +19 -0
- data/README.md +29 -12
- data/Rakefile +3 -3
- data/appmap.gemspec +3 -1
- data/exe/appmap +6 -18
- data/lib/appmap.rb +47 -6
- data/lib/appmap/algorithm/prune_class_map.rb +2 -0
- data/lib/appmap/algorithm/stats.rb +4 -2
- data/lib/appmap/class_map.rb +143 -0
- data/lib/appmap/command/record.rb +8 -6
- data/lib/appmap/command/stats.rb +2 -0
- data/lib/appmap/command/upload.rb +4 -2
- data/lib/appmap/event.rb +168 -0
- data/lib/appmap/hook.rb +151 -0
- data/lib/appmap/middleware/remote_recording.rb +14 -20
- data/lib/appmap/rails/action_handler.rb +10 -6
- data/lib/appmap/rails/sql_handler.rb +10 -8
- data/lib/appmap/railtie.rb +31 -18
- data/lib/appmap/rspec.rb +238 -261
- data/lib/appmap/trace.rb +88 -0
- data/lib/appmap/version.rb +1 -1
- data/package-lock.json +90 -92
- data/spec/abstract_controller4_base_spec.rb +1 -1
- data/spec/abstract_controller_base_spec.rb +7 -3
- data/spec/config_spec.rb +25 -0
- data/spec/fixtures/hook/attr_accessor.rb +5 -0
- data/spec/fixtures/hook/class_method.rb +17 -0
- data/spec/fixtures/hook/constructor.rb +7 -0
- data/spec/fixtures/hook/exception_method.rb +11 -0
- data/spec/fixtures/hook/instance_method.rb +23 -0
- data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +3 -3
- data/spec/fixtures/rails4_users_app/config/database.yml +2 -1
- data/spec/fixtures/rails4_users_app/docker-compose.yml +2 -0
- data/spec/fixtures/rails_users_app/.ruby-version +1 -1
- data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +2 -2
- data/spec/fixtures/rails_users_app/config/database.yml +2 -1
- data/spec/fixtures/rails_users_app/create_app +1 -0
- data/spec/fixtures/rails_users_app/docker-compose.yml +4 -0
- data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +1 -1
- data/spec/hook_spec.rb +357 -0
- data/spec/rails_spec_helper.rb +25 -16
- data/spec/railtie_spec.rb +1 -1
- data/spec/record_sql_rails_pg_spec.rb +1 -2
- data/spec/remote_recording_spec.rb +117 -0
- data/spec/spec_helper.rb +1 -0
- data/test/cli_test.rb +7 -36
- data/test/fixtures/cli_record_test/appmap.yml +2 -1
- data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +4 -2
- data/test/test_helper.rb +0 -42
- metadata +46 -62
- data/exe/_appmap-record-self +0 -49
- data/lib/appmap/command/inspect.rb +0 -14
- data/lib/appmap/config.rb +0 -65
- data/lib/appmap/config/directory.rb +0 -65
- data/lib/appmap/config/file.rb +0 -13
- data/lib/appmap/config/named_function.rb +0 -21
- data/lib/appmap/config/package_dir.rb +0 -52
- data/lib/appmap/config/path.rb +0 -25
- data/lib/appmap/feature.rb +0 -262
- data/lib/appmap/inspect.rb +0 -91
- data/lib/appmap/inspect/inspector.rb +0 -99
- data/lib/appmap/inspect/parse_node.rb +0 -170
- data/lib/appmap/inspect/parser.rb +0 -15
- data/lib/appmap/parser.rb +0 -60
- data/lib/appmap/rspec/parse_node.rb +0 -41
- data/lib/appmap/rspec/parser.rb +0 -15
- data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +0 -65
- data/lib/appmap/trace/tracer.rb +0 -356
- data/spec/fixtures/rails_users_app/bin/_appmap-record-self +0 -29
- data/spec/rack_handler_webrick_spec.rb +0 -59
- data/test/config_test.rb +0 -149
- data/test/explict_inspect_test.rb +0 -29
- data/test/fixtures/active_record_like/active_record.rb +0 -2
- data/test/fixtures/active_record_like/active_record/aggregations.rb +0 -4
- data/test/fixtures/active_record_like/active_record/association.rb +0 -4
- data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +0 -6
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +0 -8
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +0 -8
- data/test/fixtures/active_record_like/active_record/caps/caps.rb +0 -4
- data/test/fixtures/ignore_non_ruby_file/class.rb +0 -3
- data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +0 -1
- data/test/fixtures/includes_excludes/lib/a/a_1.rb +0 -6
- data/test/fixtures/includes_excludes/lib/a/a_2.rb +0 -6
- data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +0 -8
- data/test/fixtures/includes_excludes/lib/b/b_1.rb +0 -6
- data/test/fixtures/includes_excludes/lib/root_1.rb +0 -4
- data/test/fixtures/inspect_multiple_subdirs/module_a.rb +0 -2
- data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +0 -5
- data/test/fixtures/inspect_multiple_subdirs/module_b.rb +0 -2
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +0 -5
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +0 -5
- data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +0 -6
- data/test/fixtures/parse_file/defs_static_function.rb +0 -96
- data/test/fixtures/parse_file/function_within_class.rb +0 -36
- data/test/fixtures/parse_file/include_public_methods.rb +0 -127
- data/test/fixtures/parse_file/instance_function.rb +0 -17
- data/test/fixtures/parse_file/modules.rb +0 -71
- data/test/fixtures/parse_file/sclass_static_function.rb +0 -88
- data/test/fixtures/parse_file/toplevel_class.rb +0 -13
- data/test/fixtures/parse_file/toplevel_function.rb +0 -14
- data/test/fixtures/trace_test/trace_program_1.rb +0 -44
- data/test/implicit_inspect_test.rb +0 -33
- data/test/include_exclude_test.rb +0 -48
- data/test/prerecorded_trace_test.rb +0 -76
- data/test/trace_test.rb +0 -92
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module AppMap
|
|
2
4
|
module Command
|
|
3
5
|
RecordStruct = Struct.new(:config, :program)
|
|
@@ -61,12 +63,9 @@ module AppMap
|
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
def perform(&block)
|
|
64
|
-
|
|
65
|
-
functions = features.map(&:collect_functions).flatten
|
|
66
|
-
|
|
67
|
-
require 'appmap/trace/tracer'
|
|
66
|
+
AppMap::Hook.hook(config)
|
|
68
67
|
|
|
69
|
-
tracer = AppMap
|
|
68
|
+
tracer = AppMap.tracing.trace
|
|
70
69
|
|
|
71
70
|
events = []
|
|
72
71
|
quit = false
|
|
@@ -85,7 +84,10 @@ module AppMap
|
|
|
85
84
|
at_exit do
|
|
86
85
|
quit = true
|
|
87
86
|
event_thread.join
|
|
88
|
-
yield
|
|
87
|
+
yield AppMap::APPMAP_FORMAT_VERSION,
|
|
88
|
+
self.class.detect_metadata,
|
|
89
|
+
AppMap.class_map(config, tracer.event_methods),
|
|
90
|
+
events
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
load program if program
|
data/lib/appmap/command/stats.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'faraday'
|
|
3
5
|
|
|
@@ -5,13 +7,13 @@ module AppMap
|
|
|
5
7
|
module Command
|
|
6
8
|
UploadResponse = Struct.new(:batch_id, :scenario_uuid)
|
|
7
9
|
|
|
8
|
-
UploadStruct = Struct.new(:
|
|
10
|
+
UploadStruct = Struct.new(:data, :url, :user, :org)
|
|
9
11
|
class Upload < UploadStruct
|
|
10
12
|
MAX_DEPTH = 12
|
|
11
13
|
|
|
12
14
|
attr_accessor :batch_id
|
|
13
15
|
|
|
14
|
-
def initialize(
|
|
16
|
+
def initialize(data, url, user, org)
|
|
15
17
|
super
|
|
16
18
|
|
|
17
19
|
# TODO: Make this an option
|
data/lib/appmap/event.rb
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AppMap
|
|
4
|
+
module Event
|
|
5
|
+
@@id_counter = 0
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# reset_id_counter is used by test cases to get consistent event ids.
|
|
9
|
+
def reset_id_counter
|
|
10
|
+
@@id_counter = 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def next_id_counter
|
|
14
|
+
@@id_counter += 1
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
MethodEventStruct = Struct.new(:id, :event, :defined_class, :method_id, :path, :lineno, :static, :thread_id)
|
|
19
|
+
|
|
20
|
+
class MethodEvent < MethodEventStruct
|
|
21
|
+
LIMIT = 100
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def build_from_invocation(me, event_type, defined_class, method)
|
|
25
|
+
singleton = method.owner.singleton_class?
|
|
26
|
+
|
|
27
|
+
me.id = AppMap::Event.next_id_counter
|
|
28
|
+
me.event = event_type
|
|
29
|
+
me.defined_class = defined_class
|
|
30
|
+
me.method_id = method.name.to_s
|
|
31
|
+
path = method.source_location[0]
|
|
32
|
+
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
|
33
|
+
me.path = path
|
|
34
|
+
me.lineno = method.source_location[1]
|
|
35
|
+
me.static = singleton
|
|
36
|
+
me.thread_id = Thread.current.object_id
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Gets a display string for a value. This is not meant to be a machine deserializable value.
|
|
40
|
+
def display_string(value)
|
|
41
|
+
return nil unless value
|
|
42
|
+
|
|
43
|
+
last_resort_string = lambda do
|
|
44
|
+
warn "AppMap encountered an error inspecting a #{value.class.name}: #{$!.message}"
|
|
45
|
+
'*Error inspecting variable*'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
value_string = \
|
|
49
|
+
begin
|
|
50
|
+
value.to_s
|
|
51
|
+
rescue NoMethodError
|
|
52
|
+
begin
|
|
53
|
+
value.inspect
|
|
54
|
+
rescue StandardError
|
|
55
|
+
last_resort_string.call
|
|
56
|
+
end
|
|
57
|
+
rescue StandardError
|
|
58
|
+
last_resort_string.call
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
(value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
alias static? static
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class MethodCall < MethodEvent
|
|
69
|
+
attr_accessor :parameters, :receiver
|
|
70
|
+
|
|
71
|
+
class << self
|
|
72
|
+
def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
|
|
73
|
+
mc.tap do
|
|
74
|
+
mc.parameters = method.parameters.map.with_index do |method_param, idx|
|
|
75
|
+
param_type, param_name = method_param
|
|
76
|
+
param_name ||= 'arg'
|
|
77
|
+
value = arguments[idx]
|
|
78
|
+
{
|
|
79
|
+
name: param_name,
|
|
80
|
+
class: value.class.name,
|
|
81
|
+
object_id: value.__id__,
|
|
82
|
+
value: display_string(value),
|
|
83
|
+
kind: param_type
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
mc.receiver = {
|
|
87
|
+
class: receiver.class.name,
|
|
88
|
+
object_id: receiver.__id__,
|
|
89
|
+
value: display_string(receiver)
|
|
90
|
+
}
|
|
91
|
+
MethodEvent.build_from_invocation(mc, :call, defined_class, method)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def to_h
|
|
97
|
+
super.tap do |h|
|
|
98
|
+
h[:parameters] = parameters
|
|
99
|
+
h[:receiver] = receiver
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class MethodReturnIgnoreValue < MethodEvent
|
|
105
|
+
attr_accessor :parent_id, :elapsed
|
|
106
|
+
|
|
107
|
+
class << self
|
|
108
|
+
def build_from_invocation(mr = MethodReturnIgnoreValue.new, defined_class, method, parent_id, elapsed)
|
|
109
|
+
mr.tap do |_|
|
|
110
|
+
mr.parent_id = parent_id
|
|
111
|
+
mr.elapsed = elapsed
|
|
112
|
+
MethodEvent.build_from_invocation(mr, :return, defined_class, method)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_h
|
|
118
|
+
super.tap do |h|
|
|
119
|
+
h[:parent_id] = parent_id
|
|
120
|
+
h[:elapsed] = elapsed
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class MethodReturn < MethodReturnIgnoreValue
|
|
126
|
+
attr_accessor :return_value, :exceptions
|
|
127
|
+
|
|
128
|
+
class << self
|
|
129
|
+
def build_from_invocation(mr = MethodReturn.new, defined_class, method, parent_id, elapsed, return_value, exception)
|
|
130
|
+
mr.tap do |_|
|
|
131
|
+
if return_value
|
|
132
|
+
mr.return_value = {
|
|
133
|
+
class: return_value.class.name,
|
|
134
|
+
value: display_string(return_value),
|
|
135
|
+
object_id: return_value.__id__
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
if exception
|
|
139
|
+
next_exception = exception
|
|
140
|
+
exceptions = []
|
|
141
|
+
while next_exception
|
|
142
|
+
exception_backtrace = next_exception.backtrace_locations[0]
|
|
143
|
+
exceptions << {
|
|
144
|
+
class: next_exception.class.name,
|
|
145
|
+
message: next_exception.message,
|
|
146
|
+
object_id: next_exception.__id__,
|
|
147
|
+
path: exception_backtrace&.path,
|
|
148
|
+
lineno: exception_backtrace&.lineno
|
|
149
|
+
}.compact
|
|
150
|
+
next_exception = next_exception.cause
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
mr.exceptions = exceptions
|
|
154
|
+
end
|
|
155
|
+
MethodReturnIgnoreValue.build_from_invocation(mr, defined_class, method, parent_id, elapsed)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def to_h
|
|
161
|
+
super.tap do |h|
|
|
162
|
+
h[:return_value] = return_value if return_value
|
|
163
|
+
h[:exceptions] = exceptions if exceptions
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
data/lib/appmap/hook.rb
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
|
|
5
|
+
module AppMap
|
|
6
|
+
class Hook
|
|
7
|
+
LOG = false
|
|
8
|
+
|
|
9
|
+
Package = Struct.new(:path, :exclude) do
|
|
10
|
+
def to_h
|
|
11
|
+
{
|
|
12
|
+
path: path,
|
|
13
|
+
exclude: exclude.blank? ? nil : exclude
|
|
14
|
+
}.compact
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Config = Struct.new(:name, :packages) do
|
|
19
|
+
class << self
|
|
20
|
+
# Loads configuration data from a file, specified by the file name.
|
|
21
|
+
def load_from_file(config_file_name)
|
|
22
|
+
require 'yaml'
|
|
23
|
+
load YAML.safe_load(::File.read(config_file_name))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Loads configuration from a Hash.
|
|
27
|
+
def load(config_data)
|
|
28
|
+
packages = (config_data['packages'] || []).map do |package|
|
|
29
|
+
Package.new(package['path'], package['exclude'] || [])
|
|
30
|
+
end
|
|
31
|
+
Config.new config_data['name'], packages
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(name, packages = [])
|
|
36
|
+
super name, packages || []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_h
|
|
40
|
+
{
|
|
41
|
+
name: name,
|
|
42
|
+
packages: packages.map(&:to_h)
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
|
48
|
+
|
|
49
|
+
class << self
|
|
50
|
+
# Observe class loading and hook all methods which match the config.
|
|
51
|
+
def hook(config = AppMap.configure)
|
|
52
|
+
package_include_paths = config.packages.map(&:path)
|
|
53
|
+
package_exclude_paths = config.packages.map do |pkg|
|
|
54
|
+
pkg.exclude.map do |exclude|
|
|
55
|
+
File.join(pkg.path, exclude)
|
|
56
|
+
end
|
|
57
|
+
end.flatten
|
|
58
|
+
|
|
59
|
+
before_hook = lambda do |defined_class, method, receiver, args|
|
|
60
|
+
require 'appmap/event'
|
|
61
|
+
call_event = AppMap::Event::MethodCall.build_from_invocation(defined_class, method, receiver, args)
|
|
62
|
+
AppMap.tracing.record_event call_event, defined_class: defined_class, method: method
|
|
63
|
+
[ call_event, Time.now ]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
after_hook = lambda do |call_event, defined_class, method, start_time, return_value, exception|
|
|
67
|
+
require 'appmap/event'
|
|
68
|
+
elapsed = Time.now - start_time
|
|
69
|
+
return_event = AppMap::Event::MethodReturn.build_from_invocation \
|
|
70
|
+
defined_class, method, call_event.id, elapsed, return_value, exception
|
|
71
|
+
AppMap.tracing.record_event return_event
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
with_disabled_hook = lambda do |&fn|
|
|
75
|
+
# Don't record functions, such as to_s and inspect, that might be called
|
|
76
|
+
# by the fn. Otherwise there can be a stack oveflow.
|
|
77
|
+
Thread.current[HOOK_DISABLE_KEY] = true
|
|
78
|
+
begin
|
|
79
|
+
fn.call
|
|
80
|
+
ensure
|
|
81
|
+
Thread.current[HOOK_DISABLE_KEY] = false
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
TracePoint.trace(:end) do |tp|
|
|
86
|
+
cls = tp.self
|
|
87
|
+
|
|
88
|
+
instance_methods = cls.public_instance_methods(false)
|
|
89
|
+
class_methods = cls.singleton_class.public_instance_methods(false) - instance_methods
|
|
90
|
+
|
|
91
|
+
hook_method = lambda do |cls|
|
|
92
|
+
lambda do |method_id|
|
|
93
|
+
next if method_id.to_s =~ /_hooked_by_appmap$/
|
|
94
|
+
|
|
95
|
+
method = cls.public_instance_method(method_id)
|
|
96
|
+
location = method.source_location
|
|
97
|
+
location_file, = location
|
|
98
|
+
next unless location_file
|
|
99
|
+
|
|
100
|
+
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
|
101
|
+
match = package_include_paths.find { |p| location_file.index(p) == 0 }
|
|
102
|
+
match &&= !package_exclude_paths.find { |p| location_file.index(p) }
|
|
103
|
+
next unless match
|
|
104
|
+
|
|
105
|
+
disasm = RubyVM::InstructionSequence.disasm(method)
|
|
106
|
+
# Skip methods that have no instruction sequence, as they are obviously trivial.
|
|
107
|
+
next unless disasm
|
|
108
|
+
|
|
109
|
+
defined_class, method_symbol = \
|
|
110
|
+
if method.owner.singleton_class?
|
|
111
|
+
# Singleton class name is like: #<Class:<(.*)>>
|
|
112
|
+
class_name = method.owner.to_s['#<Class:<'.length-1..-2]
|
|
113
|
+
[ class_name, '.' ]
|
|
114
|
+
else
|
|
115
|
+
[ method.owner.name, '#' ]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
warn "AppMap: Hooking #{defined_class}#{method_symbol}#{method.name}" if LOG
|
|
119
|
+
|
|
120
|
+
cls.define_method method_id do |*args, &block|
|
|
121
|
+
base_method = method.bind(self).to_proc
|
|
122
|
+
|
|
123
|
+
hook_disabled = Thread.current[HOOK_DISABLE_KEY]
|
|
124
|
+
enabled = true if !hook_disabled && AppMap.tracing.enabled?
|
|
125
|
+
return base_method.call(*args, &block) unless enabled
|
|
126
|
+
|
|
127
|
+
call_event, start_time = with_disabled_hook.call do
|
|
128
|
+
before_hook.call(defined_class, method, self, args)
|
|
129
|
+
end
|
|
130
|
+
return_value = nil
|
|
131
|
+
exception = nil
|
|
132
|
+
begin
|
|
133
|
+
return_value = base_method.call(*args, &block)
|
|
134
|
+
rescue
|
|
135
|
+
exception = $ERROR_INFO
|
|
136
|
+
ensure
|
|
137
|
+
with_disabled_hook.call do
|
|
138
|
+
after_hook.call(call_event, defined_class, method, start_time, return_value, exception)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
instance_methods.each(&hook_method.call(cls))
|
|
146
|
+
class_methods.each(&hook_method.call(cls.singleton_class))
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -4,17 +4,14 @@ module AppMap
|
|
|
4
4
|
module Middleware
|
|
5
5
|
# RemoteRecording adds `/_appmap/record` routes to control recordings via HTTP requests
|
|
6
6
|
class RemoteRecording
|
|
7
|
-
|
|
8
7
|
def initialize(app)
|
|
9
8
|
require 'appmap/command/record'
|
|
10
9
|
require 'appmap/command/upload'
|
|
11
|
-
require 'appmap/trace/tracer'
|
|
12
|
-
require 'appmap/config'
|
|
13
10
|
require 'json'
|
|
14
11
|
|
|
15
12
|
@app = app
|
|
16
|
-
@
|
|
17
|
-
|
|
13
|
+
@config = AppMap.configure
|
|
14
|
+
AppMap::Hook.hook(@config)
|
|
18
15
|
end
|
|
19
16
|
|
|
20
17
|
def event_loop
|
|
@@ -29,23 +26,23 @@ module AppMap
|
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
def start_recording
|
|
32
|
-
return [
|
|
29
|
+
return [ 409, 'Recording is already in progress' ] if @tracer
|
|
33
30
|
|
|
34
31
|
@events = []
|
|
35
|
-
@tracer = AppMap
|
|
32
|
+
@tracer = AppMap.tracing.trace
|
|
36
33
|
@event_thread = Thread.new { event_loop }
|
|
37
34
|
@event_thread.abort_on_exception = true
|
|
38
35
|
|
|
39
|
-
[
|
|
36
|
+
[ 200 ]
|
|
40
37
|
end
|
|
41
38
|
|
|
42
39
|
def stop_recording(req)
|
|
43
|
-
return [
|
|
40
|
+
return [ 404, 'No recording is in progress' ] unless @tracer
|
|
44
41
|
|
|
45
42
|
tracer = @tracer
|
|
46
43
|
@tracer = nil
|
|
47
44
|
|
|
48
|
-
AppMap
|
|
45
|
+
AppMap.tracing.delete(tracer)
|
|
49
46
|
|
|
50
47
|
@event_thread.exit
|
|
51
48
|
@event_thread.join
|
|
@@ -73,9 +70,13 @@ module AppMap
|
|
|
73
70
|
name: 'remote_recording'
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
response = JSON.generate
|
|
73
|
+
response = JSON.generate \
|
|
74
|
+
version: AppMap::APPMAP_FORMAT_VERSION,
|
|
75
|
+
classMap: AppMap.class_map(@config, tracer.event_methods),
|
|
76
|
+
metadata: metadata,
|
|
77
|
+
events: @events
|
|
77
78
|
|
|
78
|
-
[
|
|
79
|
+
[ 200, response ]
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
def call(env)
|
|
@@ -103,20 +104,13 @@ module AppMap
|
|
|
103
104
|
[ 404, '' ]
|
|
104
105
|
end
|
|
105
106
|
|
|
106
|
-
status
|
|
107
|
-
status = 500 if status == false
|
|
108
|
-
|
|
109
|
-
[status, { 'Content-Type' => 'application/text' }, [body || '']]
|
|
107
|
+
[status, { 'Content-Type' => 'application/json' }, [body || '']]
|
|
110
108
|
end
|
|
111
109
|
|
|
112
110
|
def html_response?(headers)
|
|
113
111
|
headers['Content-Type'] && headers['Content-Type'] =~ /html/
|
|
114
112
|
end
|
|
115
113
|
|
|
116
|
-
def config
|
|
117
|
-
@config ||= AppMap::Config.load_from_file 'appmap.yml'
|
|
118
|
-
end
|
|
119
|
-
|
|
120
114
|
def recording?
|
|
121
115
|
!@event_thread.nil?
|
|
122
116
|
end
|