appmap 0.23.0 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +17 -8
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +43 -0
  6. data/README.md +33 -21
  7. data/Rakefile +3 -3
  8. data/appmap.gemspec +3 -1
  9. data/exe/appmap +5 -73
  10. data/lib/appmap.rb +61 -6
  11. data/lib/appmap/algorithm/prune_class_map.rb +2 -0
  12. data/lib/appmap/algorithm/stats.rb +4 -2
  13. data/lib/appmap/class_map.rb +143 -0
  14. data/lib/appmap/command/record.rb +8 -6
  15. data/lib/appmap/command/stats.rb +2 -0
  16. data/lib/appmap/event.rb +168 -0
  17. data/lib/appmap/hook.rb +152 -0
  18. data/lib/appmap/middleware/remote_recording.rb +14 -21
  19. data/lib/appmap/rails/action_handler.rb +10 -6
  20. data/lib/appmap/rails/sql_handler.rb +10 -13
  21. data/lib/appmap/railtie.rb +31 -18
  22. data/lib/appmap/rspec.rb +247 -260
  23. data/lib/appmap/trace.rb +88 -0
  24. data/lib/appmap/version.rb +1 -1
  25. data/package-lock.json +90 -92
  26. data/spec/abstract_controller4_base_spec.rb +1 -1
  27. data/spec/abstract_controller_base_spec.rb +7 -3
  28. data/spec/config_spec.rb +25 -0
  29. data/spec/fixtures/hook/attr_accessor.rb +5 -0
  30. data/spec/fixtures/hook/class_method.rb +17 -0
  31. data/spec/fixtures/hook/constructor.rb +7 -0
  32. data/spec/fixtures/hook/exception_method.rb +11 -0
  33. data/spec/fixtures/hook/instance_method.rb +23 -0
  34. data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +3 -3
  35. data/spec/fixtures/rails4_users_app/config/database.yml +2 -1
  36. data/spec/fixtures/rails4_users_app/docker-compose.yml +2 -0
  37. data/spec/fixtures/rails_users_app/.ruby-version +1 -1
  38. data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +2 -2
  39. data/spec/fixtures/rails_users_app/config/database.yml +2 -1
  40. data/spec/fixtures/rails_users_app/create_app +1 -0
  41. data/spec/fixtures/rails_users_app/docker-compose.yml +4 -0
  42. data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +1 -1
  43. data/spec/hook_spec.rb +369 -0
  44. data/spec/rails_spec_helper.rb +25 -16
  45. data/spec/railtie_spec.rb +1 -1
  46. data/spec/record_sql_rails_pg_spec.rb +1 -2
  47. data/spec/remote_recording_spec.rb +117 -0
  48. data/spec/spec_helper.rb +5 -0
  49. data/test/cli_test.rb +4 -46
  50. data/test/fixtures/cli_record_test/appmap.yml +2 -1
  51. data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +4 -2
  52. data/test/fixtures/rspec_recorder/Gemfile +1 -1
  53. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +12 -0
  54. data/test/rspec_test.rb +5 -0
  55. data/test/test_helper.rb +0 -42
  56. metadata +46 -63
  57. data/exe/_appmap-record-self +0 -49
  58. data/lib/appmap/command/inspect.rb +0 -14
  59. data/lib/appmap/command/upload.rb +0 -99
  60. data/lib/appmap/config.rb +0 -65
  61. data/lib/appmap/config/directory.rb +0 -65
  62. data/lib/appmap/config/file.rb +0 -13
  63. data/lib/appmap/config/named_function.rb +0 -21
  64. data/lib/appmap/config/package_dir.rb +0 -52
  65. data/lib/appmap/config/path.rb +0 -25
  66. data/lib/appmap/feature.rb +0 -262
  67. data/lib/appmap/inspect.rb +0 -91
  68. data/lib/appmap/inspect/inspector.rb +0 -99
  69. data/lib/appmap/inspect/parse_node.rb +0 -170
  70. data/lib/appmap/inspect/parser.rb +0 -15
  71. data/lib/appmap/parser.rb +0 -60
  72. data/lib/appmap/rspec/parse_node.rb +0 -41
  73. data/lib/appmap/rspec/parser.rb +0 -15
  74. data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +0 -65
  75. data/lib/appmap/trace/tracer.rb +0 -356
  76. data/spec/fixtures/rails_users_app/bin/_appmap-record-self +0 -29
  77. data/spec/rack_handler_webrick_spec.rb +0 -59
  78. data/test/config_test.rb +0 -149
  79. data/test/explict_inspect_test.rb +0 -29
  80. data/test/fixtures/active_record_like/active_record.rb +0 -2
  81. data/test/fixtures/active_record_like/active_record/aggregations.rb +0 -4
  82. data/test/fixtures/active_record_like/active_record/association.rb +0 -4
  83. data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +0 -6
  84. data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +0 -8
  85. data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +0 -8
  86. data/test/fixtures/active_record_like/active_record/caps/caps.rb +0 -4
  87. data/test/fixtures/ignore_non_ruby_file/class.rb +0 -3
  88. data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +0 -1
  89. data/test/fixtures/includes_excludes/lib/a/a_1.rb +0 -6
  90. data/test/fixtures/includes_excludes/lib/a/a_2.rb +0 -6
  91. data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +0 -8
  92. data/test/fixtures/includes_excludes/lib/b/b_1.rb +0 -6
  93. data/test/fixtures/includes_excludes/lib/root_1.rb +0 -4
  94. data/test/fixtures/inspect_multiple_subdirs/module_a.rb +0 -2
  95. data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +0 -5
  96. data/test/fixtures/inspect_multiple_subdirs/module_b.rb +0 -2
  97. data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +0 -5
  98. data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +0 -5
  99. data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +0 -6
  100. data/test/fixtures/parse_file/defs_static_function.rb +0 -96
  101. data/test/fixtures/parse_file/function_within_class.rb +0 -36
  102. data/test/fixtures/parse_file/include_public_methods.rb +0 -127
  103. data/test/fixtures/parse_file/instance_function.rb +0 -17
  104. data/test/fixtures/parse_file/modules.rb +0 -71
  105. data/test/fixtures/parse_file/sclass_static_function.rb +0 -88
  106. data/test/fixtures/parse_file/toplevel_class.rb +0 -13
  107. data/test/fixtures/parse_file/toplevel_function.rb +0 -14
  108. data/test/fixtures/trace_test/trace_program_1.rb +0 -44
  109. data/test/implicit_inspect_test.rb +0 -33
  110. data/test/include_exclude_test.rb +0 -48
  111. data/test/prerecorded_trace_test.rb +0 -76
  112. data/test/trace_test.rb +0 -92
@@ -1,15 +0,0 @@
1
- require 'appmap/parser'
2
- require 'appmap/rspec/parse_node'
3
-
4
- module AppMap
5
- module RSpec
6
- # Parser processes a Ruby into a list of parse nodes and a list of comments.
7
- class Parser < ::AppMap::Parser
8
- protected
9
-
10
- def build_parse_node(node, file_path, ancestors)
11
- ParseNode.from_node(node, file_path, ancestors)
12
- end
13
- end
14
- end
15
- end
@@ -1,65 +0,0 @@
1
- module AppMap
2
- module Trace
3
- module EventHandler
4
- # Use the `req` and `res` parameters on Rack::Handler::WEBrick to populate the
5
- # `http_server_request` and `http_server_response` info on the trace event.
6
- #
7
- # See https://github.com/rack/rack/blob/b72bfc9435c118c54019efae1fedd119521b76df/lib/rack/handler/webrick.rb#L26
8
- module RackHandlerWebrick
9
- class Call < MethodEvent
10
- attr_accessor :http_server_request
11
-
12
- class << self
13
- def build_from_tracepoint(mc = Call.new, tp, path)
14
- mc.tap do |_|
15
- req = value_in_binding(tp, :req)
16
-
17
- # Don't try and grab 'parameters', because:
18
- # a) They aren't needed.
19
- # b) We want to avoid triggering side effects like reading the request body.
20
-
21
- mc.http_server_request = {
22
- request_method: req.request_method,
23
- path_info: req.path_info,
24
- protocol: "HTTP/#{req.http_version}"
25
- }
26
-
27
- MethodEvent.build_from_tracepoint(mc, tp, path)
28
- end
29
- end
30
- end
31
-
32
- def to_h
33
- super.tap do |h|
34
- h[:http_server_request] = http_server_request
35
- end
36
- end
37
- end
38
-
39
- class Return < MethodReturnIgnoreValue
40
- attr_accessor :http_server_response
41
-
42
- class << self
43
- def build_from_tracepoint(mr = Return.new, tp, path, parent_id, elapsed)
44
- mr.tap do |_|
45
- res = value_in_binding(tp, :res)
46
-
47
- mr.http_server_response = {
48
- status: res.status
49
- }
50
-
51
- MethodReturnIgnoreValue.build_from_tracepoint(mr, tp, path, parent_id, elapsed)
52
- end
53
- end
54
- end
55
-
56
- def to_h
57
- super.tap do |h|
58
- h[:http_server_response] = http_server_response
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,356 +0,0 @@
1
- module AppMap
2
- module Trace
3
- MethodEventStruct =
4
- Struct.new(:id, :event, :defined_class, :method_id, :path, :lineno, :static, :thread_id)
5
-
6
- class Tracers
7
- def initialize
8
- @tracers = []
9
- end
10
-
11
- def empty?
12
- @tracers.empty?
13
- end
14
-
15
- def trace(functions, enable: true)
16
- AppMap::Trace::Tracer.new(functions).tap do |tracer|
17
- @tracers << tracer
18
- tracer.enable if enable
19
- end
20
- end
21
-
22
- def record_event(event)
23
- @tracers.each do |tracer|
24
- tracer.record_event(event)
25
- end
26
- end
27
-
28
- def delete(tracer)
29
- return unless @tracers.member?(tracer)
30
-
31
- @tracers.delete(tracer)
32
- tracer.disable
33
- end
34
- end
35
-
36
- class << self
37
- def tracers
38
- @tracers ||= Tracers.new
39
- end
40
- end
41
-
42
- # @appmap
43
- class MethodEvent < MethodEventStruct
44
- LIMIT = 100
45
-
46
- COUNTER_LOCK = Mutex.new # :nodoc:
47
- @@id_counter = 0
48
-
49
- class << self
50
- # Build a new instance from a TracePoint.
51
- def build_from_tracepoint(me, tp, path)
52
- me.id = next_id
53
- me.event = tp.event
54
-
55
- if tp.defined_class.singleton_class?
56
- me.defined_class = (tp.self.is_a?(Class) || tp.self.is_a?(Module)) ? tp.self.name : tp.self.class.name
57
- else
58
- me.defined_class = tp.defined_class.name
59
- end
60
-
61
- me.method_id = tp.method_id
62
- me.path = path
63
- me.lineno = tp.lineno
64
- me.static = tp.defined_class.name.nil?
65
- me.thread_id = Thread.current.object_id
66
- end
67
-
68
- # Gets the next serial id.
69
- #
70
- # This method is thread-safe.
71
- # @appmap
72
- def next_id
73
- COUNTER_LOCK.synchronize do
74
- @@id_counter += 1
75
- end
76
- end
77
-
78
- # Gets a value, by key, from the trace point binding.
79
- # If the method raises an error, it can be handled by the optional block.
80
- def value_in_binding(tp, key, &block)
81
- tp.binding.eval(key.to_s)
82
- rescue NameError, ArgumentError
83
- yield if block_given?
84
- end
85
-
86
- # Gets a display string for a value. This is not meant to be a machine deserializable value.
87
- def display_string(value)
88
- return nil unless value
89
-
90
- last_resort_string = lambda do
91
- warn "AppMap encountered an error inspecting a #{value.class.name}: #{$!.message}"
92
- '*Error inspecting variable*'
93
- end
94
-
95
- value_string = \
96
- begin
97
- value.to_s
98
- rescue NoMethodError
99
- begin
100
- value.inspect
101
- rescue StandardError
102
- last_resort_string.call
103
- end
104
- rescue StandardError
105
- last_resort_string.call
106
- end
107
-
108
- value_string[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
109
- end
110
- end
111
-
112
- alias static? static
113
- end
114
-
115
- # @appmap
116
- class MethodCall < MethodEvent
117
- attr_accessor :parameters, :receiver
118
-
119
- class << self
120
- # @appmap
121
- def build_from_tracepoint(mc = MethodCall.new, tp, path)
122
- mc.tap do |_|
123
- mc.parameters = collect_parameters(tp)
124
- mc.receiver = collect_self(tp)
125
- MethodEvent.build_from_tracepoint(mc, tp, path)
126
- end
127
- end
128
-
129
- def collect_self(tp)
130
- {
131
- class: tp.self.class.name,
132
- object_id: tp.self.__id__,
133
- value: display_string(tp.self)
134
- }
135
- end
136
-
137
- def collect_parameters(tp)
138
- -> { tp.self.method(tp.method_id).parameters rescue [] }.call.collect do |pinfo|
139
- kind, key = pinfo
140
- value = value_in_binding(tp, key)
141
- {
142
- name: key,
143
- class: value.class.name,
144
- object_id: value.__id__,
145
- value: display_string(value),
146
- kind: kind # :req, :rest, :key, :keyrest, :block
147
- }
148
- end
149
- end
150
- end
151
-
152
- def to_h
153
- super.tap do |h|
154
- h[:parameters] = parameters
155
- h[:receiver] = receiver
156
- end
157
- end
158
- end
159
-
160
- class MethodReturnIgnoreValue < MethodEvent
161
- attr_accessor :parent_id, :elapsed
162
-
163
- class << self
164
- def build_from_tracepoint(mr = MethodReturnIgnoreValue.new, tp, path, parent_id, elapsed)
165
- mr.tap do |_|
166
- mr.parent_id = parent_id
167
- mr.elapsed = elapsed
168
- MethodEvent.build_from_tracepoint(mr, tp, path)
169
- end
170
- end
171
- end
172
-
173
- def to_h
174
- super.tap do |h|
175
- h[:parent_id] = parent_id
176
- h[:elapsed] = elapsed
177
- end
178
- end
179
- end
180
-
181
- class MethodReturn < MethodReturnIgnoreValue
182
- attr_accessor :return_value
183
-
184
- class << self
185
- def build_from_tracepoint(mr = MethodReturn.new, tp, path, parent_id, elapsed)
186
- mr.tap do |_|
187
- mr.return_value = {
188
- class: tp.return_value.class.name,
189
- value: display_string(tp.return_value),
190
- object_id: tp.return_value.__id__
191
- }
192
- MethodReturnIgnoreValue.build_from_tracepoint(mr, tp, path, parent_id, elapsed)
193
- end
194
- end
195
- end
196
-
197
- def to_h
198
- super.tap do |h|
199
- h[:return_value] = return_value
200
- end
201
- end
202
- end
203
-
204
- # Processes a series of calls into recorded events.
205
- # Each call to the handle should provide a TracePoint (or duck-typed object) as the argument.
206
- # On each call, a MethodEvent is constructed according to the nature of the TracePoint, and then
207
- # stored using the record_event method.
208
- # @appmap
209
- class TracePointHandler
210
- attr_accessor :call_constructor, :return_constructor
211
-
212
- DEFAULT_HANDLER_CLASSES = {
213
- call: MethodCall,
214
- return: MethodReturn
215
- }.freeze
216
-
217
- # @appmap
218
- def initialize(tracer)
219
- @pwd = Dir.pwd
220
- @tracer = tracer
221
- @call_stack = Hash.new { |h, k| h[k] = [] }
222
- @handler_classes = {}
223
- end
224
-
225
- # @appmap
226
- def handle(tp)
227
- # Absoute paths which are within the current working directory are normalized
228
- # to be relative paths.
229
- path = tp.path
230
- if path.index(@pwd) == 0
231
- path = path[@pwd.length+1..-1]
232
- end
233
-
234
- method_event = \
235
- if tp.event == :call && (function = @tracer.lookup_function(path, tp.lineno))
236
- call_constructor = handler_class(function, tp.event)
237
- call_constructor.build_from_tracepoint(tp, path).tap do |c|
238
- @call_stack[Thread.current.object_id] << [ tp.defined_class, tp.method_id, c.id, Time.now, function ]
239
- end
240
- elsif (c = @call_stack[Thread.current.object_id].last) &&
241
- c[0] == tp.defined_class &&
242
- c[1] == tp.method_id
243
- function = c[4]
244
- @call_stack[Thread.current.object_id].pop
245
- return_constructor = handler_class(function, tp.event)
246
- return_constructor.build_from_tracepoint(tp, path, c[2], Time.now - c[3])
247
- end
248
-
249
- @tracer.record_event method_event if method_event
250
-
251
- method_event
252
- rescue
253
- puts $!.message
254
- puts $!.backtrace.join("\n")
255
- # XXX If this exception doesn't get reraised, internal errors
256
- # (e.g. a missing method on TracePoint) get silently
257
- # ignored. This allows tests to pass that should fail, which
258
- # is bad, but is it desirable otherwise?
259
- raise
260
- end
261
-
262
- protected
263
-
264
- # Figure out which handler class should be used for a trace event. It may be
265
- # a custom handler, e.g. in case we are processing a special named function such as a
266
- # web server entry point, or it may be the standard :call or :return handler.
267
- def handler_class(function, event)
268
- cache_key = [function.location, event]
269
- cached_handler = @handler_classes[cache_key]
270
- return cached_handler if cached_handler
271
-
272
- return default_handler_class(event) unless function.handler_id
273
-
274
- require "appmap/trace/event_handler/#{function.handler_id}"
275
-
276
- AppMap::Trace::EventHandler
277
- .const_get(function.handler_id.to_s.camelize)
278
- .const_get(event.to_s.capitalize).tap do |handler|
279
- @handler_classes[cache_key] = handler
280
- end
281
- end
282
-
283
- def default_handler_class(event)
284
- DEFAULT_HANDLER_CLASSES[event] or raise "No handler class for #{event.inspect}"
285
- end
286
- end
287
-
288
- # @appmap
289
- class Tracer
290
- # Trace a specified set of functions.
291
- #
292
- # functions Array of AppMap::Feature::Function.
293
- # @appmap
294
- def initialize(functions)
295
- @functions = functions
296
-
297
- @functions_by_location = functions.each_with_object({}) do |m, memo|
298
- path, lineno = m.location.split(':', 2)
299
- memo[path] ||= {}
300
- memo[path][lineno.to_i] = m
301
- memo
302
- end
303
-
304
- @events_mutex = Mutex.new
305
- @events = []
306
- end
307
-
308
- def enable
309
- handler = TracePointHandler.new(self)
310
- @trace_point = TracePoint.trace(:call, :return, &handler.method(:handle))
311
- end
312
-
313
- # Private function. Use AppMap.tracers#delete.
314
- def disable # :nodoc:
315
- @trace_point.disable
316
- end
317
-
318
- # Whether the indicated file path and lineno is a breakpoint on which
319
- # execution should interrupted.
320
- # @appmap
321
- def lookup_function(path, lineno)
322
- (methods_by_path = @functions_by_location[path]) && methods_by_path[lineno]
323
- end
324
-
325
- # Record a program execution event.
326
- #
327
- # The event should be one of the MethodEvent subclasses.
328
- #
329
- # This method is thread-safe.
330
- # @appmap
331
- def record_event(event)
332
- @events_mutex.synchronize do
333
- @events << event
334
- end
335
- end
336
-
337
- # Whether there is an event available for processing.
338
- #
339
- # This method is thread-safe.
340
- def event?
341
- @events_mutex.synchronize do
342
- !@events.empty?
343
- end
344
- end
345
-
346
- # Gets the next available event, if any.
347
- #
348
- # This method is thread-safe.
349
- def next_event
350
- @events_mutex.synchronize do
351
- @events.shift
352
- end
353
- end
354
- end
355
- end
356
- end
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application '_appmap-record-self' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path("../bundle", __FILE__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("appmap", "_appmap-record-self")