appmap 0.18.1

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.
Files changed (262) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +5 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +18 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +100 -0
  7. data/Dockerfile.appmap +5 -0
  8. data/Gemfile +5 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +133 -0
  11. data/Rakefile +117 -0
  12. data/appmap.gemspec +41 -0
  13. data/appmap.yml +8 -0
  14. data/examples/install.rb +76 -0
  15. data/examples/mock_webapp/Gemfile +1 -0
  16. data/examples/mock_webapp/appmap.yml +2 -0
  17. data/examples/mock_webapp/exe/mock_webapp_request +12 -0
  18. data/examples/mock_webapp/lib/mock_webapp/controller.rb +23 -0
  19. data/examples/mock_webapp/lib/mock_webapp/request.rb +12 -0
  20. data/examples/mock_webapp/lib/mock_webapp/user.rb +18 -0
  21. data/exe/_appmap-record-self +49 -0
  22. data/exe/appmap +168 -0
  23. data/lib/appmap/algorithm/prune_class_map.rb +65 -0
  24. data/lib/appmap/command/inspect.rb +11 -0
  25. data/lib/appmap/command/record.rb +91 -0
  26. data/lib/appmap/command/upload.rb +103 -0
  27. data/lib/appmap/config/directory.rb +65 -0
  28. data/lib/appmap/config/file.rb +13 -0
  29. data/lib/appmap/config/named_function.rb +21 -0
  30. data/lib/appmap/config/package_dir.rb +52 -0
  31. data/lib/appmap/config/path.rb +25 -0
  32. data/lib/appmap/config.rb +65 -0
  33. data/lib/appmap/feature.rb +262 -0
  34. data/lib/appmap/inspect/inspector.rb +99 -0
  35. data/lib/appmap/inspect/parse_node.rb +170 -0
  36. data/lib/appmap/inspect/parser.rb +15 -0
  37. data/lib/appmap/inspect.rb +91 -0
  38. data/lib/appmap/middleware/remote_recording.rb +122 -0
  39. data/lib/appmap/parser.rb +60 -0
  40. data/lib/appmap/rails/action_handler.rb +77 -0
  41. data/lib/appmap/rails/sql_handler.rb +148 -0
  42. data/lib/appmap/railtie.rb +32 -0
  43. data/lib/appmap/rspec/parse_node.rb +41 -0
  44. data/lib/appmap/rspec/parser.rb +15 -0
  45. data/lib/appmap/rspec.rb +288 -0
  46. data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +65 -0
  47. data/lib/appmap/trace/tracer.rb +347 -0
  48. data/lib/appmap/version.rb +5 -0
  49. data/lib/appmap.rb +26 -0
  50. data/lore/pages/2019-05-21-install-and-record/index.pug +51 -0
  51. data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
  52. data/lore/pages/2019-05-21-install-and-record/metadata.yml +5 -0
  53. data/lore/pages/layout.pug +66 -0
  54. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +1912 -0
  55. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +1 -0
  56. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +7 -0
  57. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +1 -0
  58. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +331 -0
  59. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +1 -0
  60. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +8 -0
  61. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +1 -0
  62. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +9030 -0
  63. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +1 -0
  64. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +7 -0
  65. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +1 -0
  66. data/lore/public/stylesheets/style.css +8 -0
  67. data/package-lock.json +1066 -0
  68. data/package.json +24 -0
  69. data/spec/abstract_controller4_base_spec.rb +58 -0
  70. data/spec/abstract_controller_base_spec.rb +59 -0
  71. data/spec/fixtures/rack_users_app/.dockerignore +2 -0
  72. data/spec/fixtures/rack_users_app/.gitignore +2 -0
  73. data/spec/fixtures/rack_users_app/Dockerfile +32 -0
  74. data/spec/fixtures/rack_users_app/Gemfile +10 -0
  75. data/spec/fixtures/rack_users_app/appmap.yml +3 -0
  76. data/spec/fixtures/rack_users_app/config.ru +2 -0
  77. data/spec/fixtures/rack_users_app/docker-compose.yml +9 -0
  78. data/spec/fixtures/rack_users_app/lib/app.rb +36 -0
  79. data/spec/fixtures/rails4_users_app/.gitignore +13 -0
  80. data/spec/fixtures/rails4_users_app/.rbenv-gemsets +2 -0
  81. data/spec/fixtures/rails4_users_app/.ruby-version +1 -0
  82. data/spec/fixtures/rails4_users_app/Dockerfile +30 -0
  83. data/spec/fixtures/rails4_users_app/Dockerfile.pg +3 -0
  84. data/spec/fixtures/rails4_users_app/Gemfile +77 -0
  85. data/spec/fixtures/rails4_users_app/README.rdoc +28 -0
  86. data/spec/fixtures/rails4_users_app/Rakefile +6 -0
  87. data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
  88. data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +16 -0
  89. data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +15 -0
  90. data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +27 -0
  91. data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +5 -0
  92. data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
  93. data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +5 -0
  94. data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +5 -0
  95. data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +2 -0
  96. data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
  97. data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
  98. data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
  99. data/spec/fixtures/rails4_users_app/app/models/user.rb +18 -0
  100. data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +7 -0
  101. data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +7 -0
  102. data/spec/fixtures/rails4_users_app/appmap.yml +3 -0
  103. data/spec/fixtures/rails4_users_app/bin/rails +9 -0
  104. data/spec/fixtures/rails4_users_app/bin/setup +29 -0
  105. data/spec/fixtures/rails4_users_app/bin/spring +17 -0
  106. data/spec/fixtures/rails4_users_app/config/application.rb +26 -0
  107. data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
  108. data/spec/fixtures/rails4_users_app/config/database.yml +17 -0
  109. data/spec/fixtures/rails4_users_app/config/environment.rb +5 -0
  110. data/spec/fixtures/rails4_users_app/config/environments/development.rb +41 -0
  111. data/spec/fixtures/rails4_users_app/config/environments/production.rb +79 -0
  112. data/spec/fixtures/rails4_users_app/config/environments/test.rb +42 -0
  113. data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +11 -0
  114. data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +7 -0
  115. data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +3 -0
  116. data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +4 -0
  117. data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +16 -0
  118. data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +4 -0
  119. data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +3 -0
  120. data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +10 -0
  121. data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +14 -0
  122. data/spec/fixtures/rails4_users_app/config/locales/en.yml +23 -0
  123. data/spec/fixtures/rails4_users_app/config/routes.rb +12 -0
  124. data/spec/fixtures/rails4_users_app/config/secrets.yml +22 -0
  125. data/spec/fixtures/rails4_users_app/config.ru +4 -0
  126. data/spec/fixtures/rails4_users_app/create_app +23 -0
  127. data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +10 -0
  128. data/spec/fixtures/rails4_users_app/db/schema.rb +26 -0
  129. data/spec/fixtures/rails4_users_app/db/seeds.rb +7 -0
  130. data/spec/fixtures/rails4_users_app/docker-compose.yml +24 -0
  131. data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
  132. data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
  133. data/spec/fixtures/rails4_users_app/log/.keep +0 -0
  134. data/spec/fixtures/rails4_users_app/public/404.html +67 -0
  135. data/spec/fixtures/rails4_users_app/public/422.html +67 -0
  136. data/spec/fixtures/rails4_users_app/public/500.html +66 -0
  137. data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
  138. data/spec/fixtures/rails4_users_app/public/robots.txt +5 -0
  139. data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +49 -0
  140. data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +95 -0
  141. data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +96 -0
  142. data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +9 -0
  143. data/spec/fixtures/rails_users_app/.dockerignore +1 -0
  144. data/spec/fixtures/rails_users_app/.gitignore +39 -0
  145. data/spec/fixtures/rails_users_app/.rspec +1 -0
  146. data/spec/fixtures/rails_users_app/.ruby-version +1 -0
  147. data/spec/fixtures/rails_users_app/Dockerfile +29 -0
  148. data/spec/fixtures/rails_users_app/Dockerfile.pg +3 -0
  149. data/spec/fixtures/rails_users_app/Gemfile +51 -0
  150. data/spec/fixtures/rails_users_app/Rakefile +6 -0
  151. data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +27 -0
  152. data/spec/fixtures/rails_users_app/app/controllers/application_controller.rb +2 -0
  153. data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
  154. data/spec/fixtures/rails_users_app/app/controllers/health_controller.rb +5 -0
  155. data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +5 -0
  156. data/spec/fixtures/rails_users_app/app/models/activerecord/user.rb +18 -0
  157. data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
  158. data/spec/fixtures/rails_users_app/app/models/sequel/user.rb +25 -0
  159. data/spec/fixtures/rails_users_app/app/views/layouts/application.html.haml +7 -0
  160. data/spec/fixtures/rails_users_app/app/views/users/index.html.haml +7 -0
  161. data/spec/fixtures/rails_users_app/appmap.yml +3 -0
  162. data/spec/fixtures/rails_users_app/bin/_appmap-record-self +29 -0
  163. data/spec/fixtures/rails_users_app/bin/appmap +29 -0
  164. data/spec/fixtures/rails_users_app/bin/byebug +29 -0
  165. data/spec/fixtures/rails_users_app/bin/gli +29 -0
  166. data/spec/fixtures/rails_users_app/bin/htmldiff +29 -0
  167. data/spec/fixtures/rails_users_app/bin/ldiff +29 -0
  168. data/spec/fixtures/rails_users_app/bin/nokogiri +29 -0
  169. data/spec/fixtures/rails_users_app/bin/rackup +29 -0
  170. data/spec/fixtures/rails_users_app/bin/rails +4 -0
  171. data/spec/fixtures/rails_users_app/bin/rake +29 -0
  172. data/spec/fixtures/rails_users_app/bin/rspec +29 -0
  173. data/spec/fixtures/rails_users_app/bin/ruby-parse +29 -0
  174. data/spec/fixtures/rails_users_app/bin/ruby-rewrite +29 -0
  175. data/spec/fixtures/rails_users_app/bin/sequel +29 -0
  176. data/spec/fixtures/rails_users_app/bin/setup +25 -0
  177. data/spec/fixtures/rails_users_app/bin/sprockets +29 -0
  178. data/spec/fixtures/rails_users_app/bin/thor +29 -0
  179. data/spec/fixtures/rails_users_app/bin/update +25 -0
  180. data/spec/fixtures/rails_users_app/config/application.rb +51 -0
  181. data/spec/fixtures/rails_users_app/config/boot.rb +3 -0
  182. data/spec/fixtures/rails_users_app/config/credentials.yml.enc +1 -0
  183. data/spec/fixtures/rails_users_app/config/database.yml +17 -0
  184. data/spec/fixtures/rails_users_app/config/environment.rb +5 -0
  185. data/spec/fixtures/rails_users_app/config/environments/development.rb +40 -0
  186. data/spec/fixtures/rails_users_app/config/environments/production.rb +68 -0
  187. data/spec/fixtures/rails_users_app/config/environments/test.rb +36 -0
  188. data/spec/fixtures/rails_users_app/config/initializers/application_controller_renderer.rb +8 -0
  189. data/spec/fixtures/rails_users_app/config/initializers/backtrace_silencers.rb +7 -0
  190. data/spec/fixtures/rails_users_app/config/initializers/cors.rb +16 -0
  191. data/spec/fixtures/rails_users_app/config/initializers/filter_parameter_logging.rb +4 -0
  192. data/spec/fixtures/rails_users_app/config/initializers/inflections.rb +16 -0
  193. data/spec/fixtures/rails_users_app/config/initializers/mime_types.rb +4 -0
  194. data/spec/fixtures/rails_users_app/config/initializers/record_button.rb +3 -0
  195. data/spec/fixtures/rails_users_app/config/initializers/wrap_parameters.rb +9 -0
  196. data/spec/fixtures/rails_users_app/config/locales/en.yml +33 -0
  197. data/spec/fixtures/rails_users_app/config/routes.rb +11 -0
  198. data/spec/fixtures/rails_users_app/config.ru +5 -0
  199. data/spec/fixtures/rails_users_app/create_app +11 -0
  200. data/spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb +9 -0
  201. data/spec/fixtures/rails_users_app/db/schema.rb +23 -0
  202. data/spec/fixtures/rails_users_app/docker-compose.yml +24 -0
  203. data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
  204. data/spec/fixtures/rails_users_app/log/.keep +0 -0
  205. data/spec/fixtures/rails_users_app/public/robots.txt +1 -0
  206. data/spec/fixtures/rails_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
  207. data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +39 -0
  208. data/spec/fixtures/rails_users_app/spec/rails_helper.rb +66 -0
  209. data/spec/fixtures/rails_users_app/spec/spec_helper.rb +96 -0
  210. data/spec/fixtures/rails_users_app/users_app/.gitignore +20 -0
  211. data/spec/rack_handler_webrick_spec.rb +59 -0
  212. data/spec/rails_spec_helper.rb +34 -0
  213. data/spec/railtie_spec.rb +35 -0
  214. data/spec/record_sql_rails4_pg_spec.rb +76 -0
  215. data/spec/record_sql_rails_pg_spec.rb +68 -0
  216. data/spec/rspec_feature_metadata_spec.rb +30 -0
  217. data/spec/spec_helper.rb +6 -0
  218. data/test/cli_test.rb +81 -0
  219. data/test/config_test.rb +149 -0
  220. data/test/explict_inspect_test.rb +29 -0
  221. data/test/fixtures/active_record_like/active_record/aggregations.rb +4 -0
  222. data/test/fixtures/active_record_like/active_record/association.rb +4 -0
  223. data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +8 -0
  224. data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +8 -0
  225. data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +6 -0
  226. data/test/fixtures/active_record_like/active_record/caps/caps.rb +4 -0
  227. data/test/fixtures/active_record_like/active_record.rb +2 -0
  228. data/test/fixtures/cli_record_test/appmap.yml +2 -0
  229. data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +7 -0
  230. data/test/fixtures/ignore_non_ruby_file/class.rb +3 -0
  231. data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +1 -0
  232. data/test/fixtures/includes_excludes/lib/a/a_1.rb +6 -0
  233. data/test/fixtures/includes_excludes/lib/a/a_2.rb +6 -0
  234. data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +8 -0
  235. data/test/fixtures/includes_excludes/lib/b/b_1.rb +6 -0
  236. data/test/fixtures/includes_excludes/lib/root_1.rb +4 -0
  237. data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +5 -0
  238. data/test/fixtures/inspect_multiple_subdirs/module_a.rb +2 -0
  239. data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +5 -0
  240. data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +5 -0
  241. data/test/fixtures/inspect_multiple_subdirs/module_b.rb +2 -0
  242. data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +6 -0
  243. data/test/fixtures/parse_file/defs_static_function.rb +96 -0
  244. data/test/fixtures/parse_file/function_within_class.rb +36 -0
  245. data/test/fixtures/parse_file/include_public_methods.rb +127 -0
  246. data/test/fixtures/parse_file/instance_function.rb +17 -0
  247. data/test/fixtures/parse_file/modules.rb +71 -0
  248. data/test/fixtures/parse_file/sclass_static_function.rb +88 -0
  249. data/test/fixtures/parse_file/toplevel_class.rb +13 -0
  250. data/test/fixtures/parse_file/toplevel_function.rb +14 -0
  251. data/test/fixtures/rspec_recorder/Gemfile +5 -0
  252. data/test/fixtures/rspec_recorder/appmap.yml +3 -0
  253. data/test/fixtures/rspec_recorder/lib/hello.rb +5 -0
  254. data/test/fixtures/rspec_recorder/spec/hello_spec.rb +9 -0
  255. data/test/fixtures/trace_test/trace_program_1.rb +44 -0
  256. data/test/implicit_inspect_test.rb +33 -0
  257. data/test/include_exclude_test.rb +48 -0
  258. data/test/prerecorded_trace_test.rb +76 -0
  259. data/test/rspec_test.rb +22 -0
  260. data/test/test_helper.rb +46 -0
  261. data/test/trace_test.rb +92 -0
  262. metadata +501 -0
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Middleware
5
+ # RemoteRecording adds `/_appmap/record` routes to control recordings via HTTP requests
6
+ class RemoteRecording
7
+
8
+ def initialize(app)
9
+ require 'appmap/command/record'
10
+ require 'appmap/command/upload'
11
+ require 'appmap/trace/tracer'
12
+ require 'appmap/config'
13
+ require 'json'
14
+
15
+ @app = app
16
+ @features = AppMap.inspect(config)
17
+ @functions = @features.map(&:collect_functions).flatten
18
+ end
19
+
20
+ def event_loop
21
+ loop do
22
+ event = @tracer.next_event if @tracer
23
+ if event
24
+ @events << event.to_h
25
+ else
26
+ sleep 0.0001
27
+ end
28
+ end
29
+ end
30
+
31
+ def start_recording
32
+ return [ false, 'Recording is already in progress' ] if @tracer
33
+
34
+ @events = []
35
+ @tracer = AppMap::Trace.tracers.trace(@functions)
36
+ @event_thread = Thread.new { event_loop }
37
+ @event_thread.abort_on_exception = true
38
+
39
+ [ true ]
40
+ end
41
+
42
+ def stop_recording(req)
43
+ return [ false, 'No recording is in progress' ] unless @tracer
44
+
45
+ tracer = @tracer
46
+ @tracer = nil
47
+
48
+ AppMap::Trace.tracers.delete(tracer)
49
+
50
+ @event_thread.exit
51
+ @event_thread.join
52
+ @event_thread = nil
53
+
54
+ # Delete the events which are calls to or returns from the URL path _appmap/record
55
+ # because these are not of interest to the user.
56
+ is_control_command_event = lambda do |event|
57
+ event[:event] == :call &&
58
+ event[:http_server_request] &&
59
+ event[:http_server_request][:path_info] == '/_appmap/record'
60
+ end
61
+ control_command_events = @events.select(&is_control_command_event)
62
+
63
+ is_return_from_control_command_event = lambda do |event|
64
+ event[:parent_id] && control_command_events.find { |e| e[:id] == event[:parent_id] }
65
+ end
66
+
67
+ @events.delete_if(&is_control_command_event)
68
+ @events.delete_if(&is_return_from_control_command_event)
69
+
70
+ require 'appmap/command/record'
71
+ metadata = AppMap::Command::Record.detect_metadata
72
+
73
+ response = JSON.generate(classMap: @features, metadata: metadata, events: @events)
74
+
75
+ [ true, response ]
76
+ end
77
+
78
+ def call(env)
79
+ req = Rack::Request.new(env)
80
+ return handle_record_request(req) if req.path == '/_appmap/record'
81
+
82
+ @app.call(env)
83
+ end
84
+
85
+ def recording_state
86
+ [ 200, JSON.generate({ enabled: recording? }) ]
87
+ end
88
+
89
+ def handle_record_request(req)
90
+ method = req.env['REQUEST_METHOD']
91
+
92
+ status, body = \
93
+ if method.eql?('GET')
94
+ recording_state
95
+ elsif method.eql?('POST')
96
+ start_recording
97
+ elsif method.eql?('DELETE')
98
+ stop_recording(req)
99
+ else
100
+ [ 404, '' ]
101
+ end
102
+
103
+ status = 200 if status == true
104
+ status = 500 if status == false
105
+
106
+ [status, { 'Content-Type' => 'application/text' }, [body || '']]
107
+ end
108
+
109
+ def html_response?(headers)
110
+ headers['Content-Type'] && headers['Content-Type'] =~ /html/
111
+ end
112
+
113
+ def config
114
+ @config ||= AppMap::Config.load_from_file 'appmap.yml'
115
+ end
116
+
117
+ def recording?
118
+ !@event_thread.nil?
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,60 @@
1
+ require 'parser/current'
2
+ require 'set'
3
+
4
+ module AppMap
5
+ class Parser
6
+ def initialize(file_path: nil, code: nil)
7
+ @file_path = file_path
8
+ @code = code
9
+ end
10
+
11
+ def to_s
12
+ "Parse code #{file_path.inspect}"
13
+ end
14
+
15
+ # Parse the contents of a file into a list of features.
16
+ def parse
17
+ parse_tree, comments = parse_code_and_comments
18
+ parse_nodes = build_parse_nodes parse_tree
19
+ [ parse_nodes, comments ]
20
+ end
21
+
22
+ protected
23
+
24
+ def file_path
25
+ @file_path || '<inline>'
26
+ end
27
+
28
+ def code
29
+ @code ||= File.read(file_path)
30
+ end
31
+
32
+ def parse_code_and_comments
33
+ ::Parser::CurrentRuby.parse_with_comments(code)
34
+ rescue ::Parser::SyntaxError, EncodingError
35
+ warn "Unable to parse #{file_path.inspect} : #{$!.message}"
36
+ [ [], [] ]
37
+ end
38
+
39
+ # rubocop:disable Metrics/MethodLength
40
+ def build_parse_nodes(parse_tree)
41
+ parse_nodes = []
42
+ visit_methods = lambda do |node, ancestors|
43
+ return unless node.respond_to?(:type)
44
+
45
+ parse_node = build_parse_node(node, file_path, ancestors)
46
+ parse_nodes << parse_node if parse_node
47
+
48
+ ancestors << node
49
+ node.children.each do |child|
50
+ visit_methods.call(child, ancestors)
51
+ end
52
+ ancestors.pop
53
+ end
54
+ visit_methods.call(parse_tree, [])
55
+ parse_nodes
56
+ end
57
+ end
58
+ # rubocop:enable Metrics/MethodLength
59
+ end
60
+
@@ -0,0 +1,77 @@
1
+ module AppMap
2
+ module Rails
3
+ module ActionHandler
4
+ Context = Struct.new(:id, :start_time)
5
+
6
+ module ContextKey
7
+ def context_key
8
+ "#{HTTPServerRequest.name}#call"
9
+ end
10
+ end
11
+
12
+ class HTTPServerRequest
13
+ include ContextKey
14
+
15
+ class Call < AppMap::Trace::MethodEvent
16
+ attr_accessor :payload
17
+
18
+ def initialize(path, lineno, payload)
19
+ super AppMap::Trace::MethodEvent.next_id, :call, HTTPServerRequest, :call, path, lineno, false, Thread.current.object_id
20
+
21
+ self.payload = payload
22
+ end
23
+
24
+ def to_h
25
+ super.tap do |h|
26
+ h[:http_server_request] = {
27
+ request_method: payload[:method],
28
+ path_info: payload[:path]
29
+ }
30
+ h[:message] = payload[:params]
31
+ end
32
+ end
33
+ end
34
+
35
+ def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
36
+ event = Call.new(__FILE__, __LINE__, payload)
37
+ Thread.current[context_key] = Context.new(event.id, Time.now)
38
+ AppMap::Trace.tracers.record_event(event)
39
+ end
40
+ end
41
+
42
+ class HTTPServerResponse
43
+ include ContextKey
44
+
45
+ class Call < AppMap::Trace::MethodReturnIgnoreValue
46
+ attr_accessor :payload
47
+
48
+ def initialize(path, lineno, payload, parent_id, elapsed)
49
+ super AppMap::Trace::MethodEvent.next_id, :return, HTTPServerResponse, :call, path, lineno, false, Thread.current.object_id
50
+
51
+ self.payload = payload
52
+ self.parent_id = parent_id
53
+ self.elapsed = elapsed
54
+ end
55
+
56
+ def to_h
57
+ super.tap do |h|
58
+ h[:http_server_response] = {
59
+ status: payload[:status]
60
+ }
61
+ end
62
+ end
63
+ end
64
+
65
+ def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
66
+ return unless Thread.current[context_key]
67
+
68
+ context = Thread.current[context_key]
69
+ Thread.current[context_key] = nil
70
+
71
+ event = Call.new(__FILE__, __LINE__, payload, context.id, Time.now - context.start_time)
72
+ AppMap::Trace.tracers.record_event(event)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,148 @@
1
+ require 'appmap/trace/tracer'
2
+
3
+ module AppMap
4
+ module Rails
5
+ class SQLHandler
6
+ class SQLCall < AppMap::Trace::MethodEvent
7
+ attr_accessor :payload
8
+
9
+ def initialize(path, lineno, payload)
10
+ super AppMap::Trace::MethodEvent.next_id, :call, SQLHandler, :call, path, lineno, false, Thread.current.object_id
11
+
12
+ self.payload = payload
13
+ end
14
+
15
+ def to_h
16
+ super.tap do |h|
17
+ h[:sql_query] = {
18
+ sql: payload[:sql],
19
+ database_type: payload[:database_type]
20
+ }.tap do |sql_query|
21
+ %i[server_version explain_sql].each do |attribute|
22
+ sql_query[attribute] = payload[attribute] if payload[attribute]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class SQLReturn < AppMap::Trace::MethodReturnIgnoreValue
30
+ def initialize(path, lineno, parent_id, elapsed)
31
+ super AppMap::Trace::MethodEvent.next_id, :return, SQLHandler, :call, path, lineno, false, Thread.current.object_id
32
+
33
+ self.parent_id = parent_id
34
+ self.elapsed = elapsed
35
+ end
36
+ end
37
+
38
+ module SQLExaminer
39
+ class << self
40
+ def examine(payload, sql:)
41
+ return unless (examiner = build_examiner)
42
+
43
+ payload[:server_version] = examiner.server_version
44
+ payload[:database_type] = examiner.database_type.to_s
45
+ end
46
+
47
+ protected
48
+
49
+ def build_examiner
50
+ if defined?(Sequel)
51
+ SequelExaminer.new
52
+ elsif defined?(ActiveRecord)
53
+ ActiveRecordExaminer.new
54
+ end
55
+ end
56
+ end
57
+
58
+ class SequelExaminer
59
+ def server_version
60
+ Sequel::Model.db.server_version
61
+ end
62
+
63
+ def database_type
64
+ Sequel::Model.db.database_type.to_sym
65
+ end
66
+
67
+ def execute_query(sql)
68
+ Sequel::Model.db[sql].all
69
+ end
70
+ end
71
+
72
+ class ActiveRecordExaminer
73
+ def server_version
74
+ case database_type
75
+ when :postgres
76
+ ActiveRecord::Base.connection.postgresql_version
77
+ else
78
+ warn "Unable to determine database version for #{database_type.inspect}"
79
+ end
80
+ end
81
+
82
+ def database_type
83
+ return :postgres if ActiveRecord::Base.connection.respond_to?(:postgresql_version)
84
+
85
+ ActiveRecord::Base.connection.adapter_name.downcase.to_sym
86
+ end
87
+
88
+ def execute_query(sql)
89
+ ActiveRecord::Base.connection.execute(sql).inject([]) { |memo, r| memo << r; memo }
90
+ end
91
+ end
92
+ end
93
+
94
+ def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
95
+ return if AppMap::Trace.tracers.empty?
96
+
97
+ reentry_key = "#{self.class.name}#call"
98
+ return if Thread.current[reentry_key] == true
99
+
100
+ Thread.current[reentry_key] = true
101
+ begin
102
+ sql = payload[:sql].strip
103
+ sql_upper = sql.upcase
104
+
105
+ return unless WHITELIST.find { |keyword| sql_upper.index(keyword) == 0 }
106
+
107
+ # Detect whether a function call within a specified filename is present in the call stack.
108
+ find_in_backtrace = lambda do |file_name, function_name = nil|
109
+ Thread.current.backtrace.find do |line|
110
+ tokens = line.split(':')
111
+ matches_file = tokens.find { |t| t.rindex(file_name) == (t.length - file_name.length) }
112
+ matches_function = function_name.nil? || tokens.find { |t| t == "in `#{function_name}'" }
113
+ matches_file && matches_function
114
+ end
115
+ end
116
+
117
+ # Ignore SQL calls which are made while establishing a new connection.
118
+ #
119
+ # Example:
120
+ # /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/connection_pool.rb:122:in `make_new'
121
+ return if find_in_backtrace.call('lib/sequel/connection_pool.rb', 'make_new')
122
+ # lib/active_record/connection_adapters/abstract/connection_pool.rb:811:in `new_connection'
123
+ return if find_in_backtrace.call('lib/active_record/connection_adapters/abstract/connection_pool.rb', 'new_connection')
124
+
125
+ # Ignore SQL calls which are made while inspecting the DB schema.
126
+ #
127
+ # Example:
128
+ # /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/model/base.rb:812:in `get_db_schema'
129
+ return if find_in_backtrace.call('lib/sequel/model/base.rb', 'get_db_schema')
130
+ # /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/model_schema.rb:466:in `load_schema!'
131
+ return if find_in_backtrace.call('lib/active_record/model_schema.rb', 'load_schema!')
132
+ return if find_in_backtrace.call('lib/active_model/attribute_methods.rb', 'define_attribute_methods')
133
+ return if find_in_backtrace.call('lib/active_record/connection_adapters/schema_cache.rb')
134
+
135
+ SQLExaminer.examine payload, sql: sql
136
+
137
+ call = SQLCall.new(__FILE__, __LINE__, payload)
138
+ AppMap::Trace.tracers.record_event(call)
139
+ AppMap::Trace.tracers.record_event(SQLReturn.new(__FILE__, __LINE__, call.id, finished - started))
140
+ ensure
141
+ Thread.current[reentry_key] = nil
142
+ end
143
+ end
144
+
145
+ WHITELIST = %w[SELECT INSERT UPDATE DELETE].freeze
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,32 @@
1
+ module AppMap
2
+ class Railtie < ::Rails::Railtie
3
+ config.appmap = ActiveSupport::OrderedOptions.new
4
+
5
+ initializer 'appmap.trace' do |app|
6
+ lambda do
7
+ return unless app.config.appmap.enabled
8
+
9
+ require 'appmap'
10
+ require 'appmap/config'
11
+ config = AppMap::Config.load_from_file 'appmap.yml'
12
+
13
+ require 'appmap/command/record'
14
+ require 'json'
15
+ AppMap::Command::Record.new(config).perform do |features, events|
16
+ File.open('appmap.json', 'w').write JSON.generate(classMap: features, events: events)
17
+ end
18
+ end.call
19
+ end
20
+
21
+ initializer 'appmap.subscribe', after: 'appmap.trace' do |_| # params: app
22
+ lambda do
23
+ require 'appmap/rails/sql_handler'
24
+ require 'appmap/rails/action_handler'
25
+ ActiveSupport::Notifications.subscribe('sql.sequel', AppMap::Rails::SQLHandler.new)
26
+ ActiveSupport::Notifications.subscribe('sql.active_record', AppMap::Rails::SQLHandler.new)
27
+ ActiveSupport::Notifications.subscribe('start_processing.action_controller', AppMap::Rails::ActionHandler::HTTPServerRequest.new)
28
+ ActiveSupport::Notifications.subscribe('process_action.action_controller', AppMap::Rails::ActionHandler::HTTPServerResponse.new)
29
+ end.call
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ require 'forwardable'
2
+
3
+ module AppMap
4
+ module RSpec
5
+ # ParseNodeStruct wraps a generic AST parse node.
6
+ ParseNodeStruct = Struct.new(:node, :file_path, :ancestors) do
7
+ end
8
+
9
+ # ParseNode wraps a generic AST parse node.
10
+ class ParseNode < ParseNodeStruct
11
+ extend Forwardable
12
+
13
+ def_delegators :node, :type, :location
14
+
15
+ class << self
16
+ # Build a ParseNode from an AST node.
17
+ def from_node(node, file_path, ancestors)
18
+ case node.type
19
+ when :block
20
+ BlockParseNode.new(node, file_path, ancestors.dup)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # A Ruby block.
27
+ class BlockParseNode < ParseNode
28
+ def to_s
29
+ "RSpec block at #{file_path} #{first_line}:#{last_line}"
30
+ end
31
+
32
+ def first_line
33
+ node.location.first_line
34
+ end
35
+
36
+ def last_line
37
+ node.location.last_line
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
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