appmap 0.18.1

Sign up to get free protection for your applications and to get access to all the features.
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,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib')
6
+
7
+ require 'appmap'
8
+ require 'appmap/feature'
9
+ require 'shellwords'
10
+
11
+ def usage
12
+ warn 'Usage: trace-self <trace-file>'
13
+ exit 1
14
+ end
15
+
16
+ trace_file = ARGV.shift || usage
17
+ usage unless ARGV.empty?
18
+
19
+ replay_events = File.read(trace_file)
20
+ .split("\n")
21
+ .map(&:strip)
22
+ .reject(&:empty?)
23
+ .map(&JSON.method(:parse))
24
+ .map { |te| te['event'] = te['event'].intern; te }
25
+ .map { |te| OpenStruct.new(te) }
26
+
27
+ require 'appmap/trace/tracer'
28
+
29
+ def method_call_from_event(evt)
30
+ AppMap::Trace::MethodCall.new(evt.id, evt.event.intern, evt.defined_class, evt.method_id, evt.path, evt.lineno, evt.static, evt.thread_id, evt.variables)
31
+ end
32
+
33
+ # _parent_id and _elapsed are ignored since they are already specified in the
34
+ # data being replayed.
35
+ def method_return_from_event(evt, _parent_id, _elapsed)
36
+ AppMap::Trace::MethodReturn.new(evt.id, evt.event.intern, evt.defined_class, evt.method_id, evt.path, evt.lineno, evt.static, evt.thread_id, evt.variables).tap do |mr|
37
+ mr.parent_id = evt.parent_id
38
+ mr.elapsed = evt.elapsed
39
+ end
40
+ end
41
+
42
+ tracer = AppMap::Trace.tracer
43
+ handler = AppMap::Trace::TracePointHandler.new(tracer)
44
+ handler.call_constructor = method(:method_call_from_event)
45
+ handler.return_constructor = method(:method_return_from_event)
46
+
47
+ replay_events.each do |evt|
48
+ handler.handle evt
49
+ end
data/exe/appmap ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gli'
5
+
6
+ require 'appmap'
7
+ require 'appmap/version'
8
+
9
+ # AppMap CLI.
10
+ module AppMap
11
+ class App
12
+ extend GLI::App
13
+
14
+ program_desc 'AppMap client'
15
+
16
+ version AppMap::VERSION
17
+
18
+ subcommand_option_handling :normal
19
+ arguments :strict
20
+ preserve_argv true
21
+
22
+ class << self
23
+ protected
24
+
25
+ def default_appmap_file
26
+ ENV['APPMAP_FILE'] || 'appmap.json'
27
+ end
28
+
29
+ def output_file_flag(c, default_value: nil)
30
+ c.desc 'Name of the output file'
31
+ c.long_desc <<~DESC
32
+ Use a single dash '-' for stdout.
33
+ DESC
34
+ c.default_value default_value if default_value
35
+ c.arg_name 'filename'
36
+ c.flag %i[o output]
37
+ end
38
+ end
39
+
40
+ desc 'AppMap configuration file name'
41
+ default_value ENV['APPMAP_CONFIG'] || 'appmap.yml'
42
+ arg_name 'filename'
43
+ flag %i[c config]
44
+
45
+ desc 'Inspect code and generate a classmap file'
46
+ command :inspect do |c|
47
+ output_file_flag(c, default_value: default_appmap_file)
48
+
49
+ c.action do
50
+ require 'appmap/command/inspect'
51
+ features = AppMap::Command::Inspect.new(@config).perform
52
+ @output_file.write JSON.pretty_generate(features)
53
+ end
54
+ end
55
+
56
+ desc 'Record the execution of a program and generate an AppMap.'
57
+ arg_name 'program'
58
+ command :record do |c|
59
+ output_file_flag(c, default_value: default_appmap_file)
60
+
61
+ c.action do |_, _, args|
62
+ # My subcommand name
63
+ ARGV.shift
64
+
65
+ # Consume the :output option, if provided
66
+ if %w[-o --output].find { |arg_name| ARGV[0] == arg_name.to_s }
67
+ ARGV.shift
68
+ ARGV.shift
69
+ end
70
+
71
+ # Name of the program to execute. GLI will ensure that it's present.
72
+ program = args.shift or help_now!("'program' argument is required")
73
+
74
+ # Also pop the program name from ARGV, because the command will use raw ARGV
75
+ # to load the extra arguments into this Ruby process.
76
+ ARGV.shift
77
+
78
+ require 'appmap/command/record'
79
+ AppMap::Command::Record.new(@config, program).perform do |features, events|
80
+ @output_file.write JSON.generate(classMap: features,
81
+ metadata: AppMap::Command::Record.detect_metadata,
82
+ events: events)
83
+ end
84
+ end
85
+ end
86
+
87
+ desc 'Upload a scenario file to AppLand.'
88
+ arg_name 'filename'
89
+ command :upload do |c|
90
+ output_file_flag(c, default_value: '-')
91
+
92
+ c.desc 'Whether to open the new scenario in the browser'
93
+ c.default_value true
94
+ c.switch [:open]
95
+
96
+ c.desc 'AppLand website URL'
97
+ c.default_value ENV['APPLAND_URL'] || 'https://appland-staging.herokuapp.com'
98
+ c.flag :url
99
+
100
+ # TODO: This will be replaced with proper login
101
+ c.desc 'User id to own the scenario'
102
+ c.default_value(ENV['APPLAND_USER'])
103
+ c.flag :user
104
+
105
+ c.desc 'Organization id to own the scenario'
106
+ c.default_value(ENV['APPLAND_ORG'])
107
+ c.flag :org
108
+
109
+ c.action do |_, options, args|
110
+ require 'appmap/command/upload'
111
+
112
+ filenames = args
113
+ help_now!("'filename' argument is required") if filenames.empty?
114
+
115
+ url = options[:url]
116
+ user = options[:user]
117
+ org = options[:org]
118
+
119
+ batch_id = nil
120
+ uuids = filenames.map do |filename|
121
+ appmap = JSON.parse(File.read(filename))
122
+
123
+ warn "Uploading #{filename.inspect}"
124
+ upload = AppMap::Command::Upload.new(@config, appmap, url, user, org)
125
+ upload.batch_id = batch_id if batch_id
126
+ upload.perform.tap do |response|
127
+ batch_id ||= response.batch_id
128
+ @output_file.puts "Scenario Id: #{response.scenario_uuid}"
129
+ end.scenario_uuid
130
+ end
131
+ @output_file.puts "Batch Id: #{batch_id}"
132
+
133
+ if options[:open] && STDIN.tty?
134
+ if uuids.length == 1
135
+ system "open #{url}/scenarios/#{uuids.first}"
136
+ else
137
+ system "open #{url}/scenario_batches/#{batch_id}"
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ pre do |global, _, options, _|
144
+ @config = interpret_config_option(global[:config])
145
+ @output_file = interpret_output_file_option(options[:output])
146
+
147
+ true
148
+ end
149
+
150
+ class << self
151
+ protected
152
+
153
+ def interpret_config_option(fname)
154
+ require 'appmap/config'
155
+ AppMap::Config.load_from_file fname
156
+ end
157
+
158
+ def interpret_output_file_option(file_name)
159
+ Hash.new { |_, fname| -> { File.new(fname, 'w') } }.tap do |open_output_file|
160
+ open_output_file[nil] = -> { nil }
161
+ open_output_file['-'] = -> { $stdout }
162
+ end[file_name].call
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ exit AppMap::App.run(ARGV)
@@ -0,0 +1,65 @@
1
+ module AppMap
2
+ module Algorithm
3
+ # Prune a class map so that only functions, classes and packages which are referenced
4
+ # by some event are retained.
5
+ class PruneClassMap
6
+ attr_reader :class_map
7
+ # Set this attribute to a function which will log algorithm events.
8
+ attr_writer :logger
9
+ attr_accessor :events
10
+
11
+ # Construct the algorithm, with a class map that will be pruned in place.
12
+ def initialize(class_map)
13
+ @class_map = class_map
14
+ @logger = ->(msg) {}
15
+ end
16
+
17
+ def prune
18
+ # This proc counts the number of objects in the class map whose type is 'k'
19
+ count = proc do |k, e|
20
+ n = 0
21
+ n += 1 if e['type'] == k
22
+ n += (e['children'] || []).map { |child| count.call(k, child) }.reduce(0, :+)
23
+ n
24
+ end
25
+
26
+ @logger.call "Full classMap contains #{class_map.map { |m| count.call('class', m) }.reduce(0, :+)} classes"
27
+
28
+ # Prune all the classes which fail a test.
29
+ reject = proc do |list, test|
30
+ list.tap do |_|
31
+ list.each do |item|
32
+ children = item['children']
33
+ next unless children
34
+
35
+ reject.call(children, test)
36
+ end
37
+ list.reject!(&test)
38
+ end
39
+ end
40
+
41
+ if events
42
+ locations = \
43
+ Set.new(events.select { |e| e['event'] == 'call' }
44
+ .map { |e| [ e['path'], e['lineno'] ].join(':') })
45
+
46
+ # Prune all functions which aren't called
47
+ reject.call class_map,
48
+ ->(e) { e['type'] == 'function' && !locations.member?(e['location']) }
49
+ end
50
+
51
+ # Prune all empty classes
52
+ reject.call class_map,
53
+ ->(e) { e['type'] == 'class' && (e['children'] || []).empty? }
54
+
55
+ # Prune all empty packages
56
+ reject.call class_map,
57
+ ->(e) { e['type'] == 'package' && (e['children'] || []).empty? }
58
+
59
+ @logger.call "Pruned classMap contains #{class_map.map { |m| count.call('class', m) }.reduce(0, :+)} classes"
60
+
61
+ class_map
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module AppMap
2
+ module Command
3
+ InspectStruct = Struct.new(:config)
4
+
5
+ class Inspect < InspectStruct
6
+ def perform
7
+ AppMap.inspect(config)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,91 @@
1
+ module AppMap
2
+ module Command
3
+ RecordStruct = Struct.new(:config, :program)
4
+
5
+ class Record < RecordStruct
6
+ class << self
7
+ # Builds a Hash of metadata which can be detected by inspecting the system.
8
+ def detect_metadata
9
+ {
10
+ language: {
11
+ name: 'ruby',
12
+ engine: RUBY_ENGINE,
13
+ version: RUBY_VERSION
14
+ }
15
+ }.tap do |m|
16
+ if defined?(::Rails)
17
+ m[:frameworks] ||= []
18
+ m[:frameworks] << {
19
+ name: 'rails',
20
+ version: ::Rails.version
21
+ }
22
+ m[:layout] = 'rails'
23
+ end
24
+ m[:git] = git_metadata if git_available
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def git_available
31
+ @git_available = system('git status 2>&1 > /dev/null') if @git_available.nil?
32
+ end
33
+
34
+ def git_metadata
35
+ git_repo = `git config --get remote.origin.url`.strip
36
+ git_branch = `git rev-parse --abbrev-ref HEAD`.strip
37
+ git_sha = `git rev-parse HEAD`.strip
38
+ git_status = `git status -s`.split("\n").map(&:strip)
39
+ git_last_annotated_tag = `git describe --abbrev=0 2>/dev/null`.strip
40
+ git_last_annotated_tag = nil if git_last_annotated_tag.blank?
41
+ git_last_tag = `git describe --abbrev=0 --tags 2>/dev/null`.strip
42
+ git_last_tag = nil if git_last_tag.blank?
43
+ git_commits_since_last_annotated_tag = `git describe`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_annotated_tag
44
+ git_commits_since_last_tag = `git describe --tags`.strip =~ /-(\d+)-(\w+)$/[1] rescue 0 if git_last_tag
45
+
46
+ {
47
+ repository: git_repo,
48
+ branch: git_branch,
49
+ commit: git_sha,
50
+ status: git_status,
51
+ git_last_annotated_tag: git_last_annotated_tag,
52
+ git_last_tag: git_last_tag,
53
+ git_commits_since_last_annotated_tag: git_commits_since_last_annotated_tag,
54
+ git_commits_since_last_tag: git_commits_since_last_tag
55
+ }
56
+ end
57
+ end
58
+
59
+ def perform(&block)
60
+ features = AppMap.inspect(config)
61
+ functions = features.map(&:collect_functions).flatten
62
+
63
+ require 'appmap/trace/tracer'
64
+
65
+ tracer = AppMap::Trace.tracers.trace(functions)
66
+
67
+ events = []
68
+ quit = false
69
+ event_thread = Thread.new do
70
+ while tracer.event? || !quit
71
+ event = tracer.next_event
72
+ if event
73
+ events << event.to_h
74
+ else
75
+ sleep 0.0001
76
+ end
77
+ end
78
+ end
79
+ event_thread.abort_on_exception = true
80
+
81
+ at_exit do
82
+ quit = true
83
+ event_thread.join
84
+ yield features, events
85
+ end
86
+
87
+ load program if program
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,103 @@
1
+ require 'json'
2
+ require 'faraday'
3
+
4
+ module AppMap
5
+ module Command
6
+ UploadResponse = Struct.new(:batch_id, :scenario_uuid)
7
+
8
+ UploadStruct = Struct.new(:config, :data, :url, :user, :org)
9
+ class Upload < UploadStruct
10
+ MAX_DEPTH = 12
11
+
12
+ attr_accessor :batch_id
13
+
14
+ def initialize(config, data, url, user, org)
15
+ super
16
+
17
+ # TODO: Make this an option
18
+ @max_depth = MAX_DEPTH
19
+ end
20
+
21
+ def perform
22
+ appmap = data.clone
23
+
24
+ # If it's a list, upload it as a classMap
25
+ if data.is_a?(Hash)
26
+ events = data.delete('events') || []
27
+ class_map = data.delete('classMap') || []
28
+
29
+ pruned_events = []
30
+ stack = []
31
+ events.each do |evt|
32
+ if evt['event'] == 'call'
33
+ stack << evt
34
+ stack_depth = stack.length
35
+ else
36
+ stack_depth = stack.length
37
+ stack.pop
38
+ end
39
+
40
+ prune = stack_depth > @max_depth
41
+
42
+ pruned_events << evt unless prune
43
+ end
44
+
45
+ warn "Pruned events to #{pruned_events.length}" if events.length > pruned_events.length
46
+
47
+ events = pruned_events
48
+
49
+ class_map = prune(class_map, events: events)
50
+ appmap[:classMap] = class_map
51
+ appmap[:events] = events
52
+ else
53
+ class_map = prune(data)
54
+ appmap = { "classMap": class_map, "events": [] }
55
+ end
56
+
57
+ upload_file = { user: user, org: org, data: appmap }.compact
58
+
59
+ conn = Faraday.new(url: url)
60
+ response = conn.post do |req|
61
+ req.url '/api/scenarios'
62
+ req.headers['Content-Type'] = 'application/json'
63
+ req.headers[AppMap::BATCH_HEADER_NAME] = @batch_id if @batch_id
64
+ req.body = JSON.generate(upload_file)
65
+ end
66
+
67
+ unless response.body.blank?
68
+ message = begin
69
+ JSON.parse(response.body)
70
+ rescue JSON::ParserError => e
71
+ warn "Response is not valid JSON (#{e.message})"
72
+ nil
73
+ end
74
+ end
75
+
76
+ unless response.success?
77
+ error = [ 'Upload failed' ]
78
+ error << ": #{message}" if message
79
+ raise error.join
80
+ end
81
+
82
+ batch_id = @batch_id || response.headers[AppMap::BATCH_HEADER_NAME]
83
+
84
+ uuid = message['uuid']
85
+ UploadResponse.new(batch_id, uuid)
86
+ end
87
+
88
+ protected
89
+
90
+ def debug?
91
+ ENV['DEBUG'] == 'true' || ENV['GLI_DEBUG'] == 'true'
92
+ end
93
+
94
+ def prune(class_map, events: nil)
95
+ require 'appmap/algorithm/prune_class_map'
96
+ Algorithm::PruneClassMap.new(class_map).tap do |alg|
97
+ alg.events = events if events
98
+ alg.logger = ->(msg) { warn msg } if debug?
99
+ end.prune
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,65 @@
1
+ module AppMap
2
+ module Config
3
+ # A normal directory is scanned for AppMap features without interpreting the
4
+ # directory as a 'package'.
5
+ #
6
+ # @appmap
7
+ class Directory < Path
8
+ # @appmap
9
+ def initialize(path)
10
+ super
11
+ end
12
+
13
+ # @appmap
14
+ def children
15
+ child_files.sort + child_directories.sort
16
+ end
17
+
18
+ protected
19
+
20
+ def ruby_file?(path)
21
+ ::File.file?(path) && (path =~ /\.rb$/ || ruby_shebang?(path))
22
+ end
23
+
24
+ def ruby_shebang?(path)
25
+ lines = begin
26
+ ::File.read(path).split("\n")
27
+ rescue ArgumentError => e
28
+ if e.message.index 'invalid byte sequence'
29
+ warn "Unable to load file #{path.inspect} : #{e.message}"
30
+ return false
31
+ end
32
+ raise
33
+ end
34
+ lines[0] && lines[0].index('#!/usr/bin/env ruby') == 0
35
+ end
36
+
37
+ def child_files
38
+ expand_path = ->(fname) { ::File.join(path, fname) }
39
+ Dir.new(path).entries.select do |fname|
40
+ ::File.file?(expand_path.call(fname)) &&
41
+ !::File.symlink?(expand_path.call(fname)) &&
42
+ ruby_file?(expand_path.call(fname))
43
+ end.select do |fname|
44
+ !exclude?(::File.join(path, fname))
45
+ end.map do |fname|
46
+ File.new(expand_path.call(fname)).tap do |f|
47
+ f.mode = mode
48
+ end
49
+ end
50
+ end
51
+
52
+ def child_directories
53
+ File.new(path).entries.select do |fname|
54
+ !%w[. ..].include?(fname) && !::File.directory?(fname)
55
+ end.select do |dir|
56
+ !exclude?(::File.join(path, dir))
57
+ end.map do |dir|
58
+ PackageDir.new(dir, [module_name, dir].join('/')).tap do |m|
59
+ m.mode = mode
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module AppMap
2
+ module Config
3
+ # Scan a specific file for AppMap features.
4
+ #
5
+ # @appmap
6
+ class File < Path
7
+ # @appmap
8
+ def initialize(path)
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module AppMap
2
+ module Config
3
+ NamedFunctionStruct = Struct.new(:id, :gem_name, :file_path, :class_names, :method_name, :static)
4
+
5
+ # Identifies a specific function within a Gem to be instrumented.
6
+ #
7
+ # * `id` A unique identifier for the named function. This is used to associate custom logic with the
8
+ # named function when the trace events are being handled.
9
+ # * `gem_name` Name of the Gem.
10
+ # * `file_path` Name of the file within the Gem in which the function is located.
11
+ # * `class_names` Array of the module/class name scope which contains the function. For example,
12
+ # `%w[Rack Handler WEBrick]`.
13
+ # * `method_name` Name of the method within the class name scope.
14
+ # * `static` Whether it's a static or instance method.
15
+ class NamedFunction < NamedFunctionStruct
16
+ def children
17
+ []
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,52 @@
1
+ require 'pathname'
2
+
3
+ module AppMap
4
+ module Config
5
+ # Scan a directory for AppMap features, treating it as a package and its
6
+ # sub-folders as sub-packages.
7
+ #
8
+ # @appmap
9
+ class PackageDir < Directory
10
+ attr_accessor :package_name, :base_path, :exclude
11
+
12
+ # @appmap
13
+ def initialize(path, package_name = Pathname.new(path || '').basename.to_s)
14
+ super(path)
15
+
16
+ @package_name = package_name
17
+ @base_path = path
18
+ @exclude = []
19
+ end
20
+
21
+ def sub_package_dir(dir)
22
+ PackageDir.new(::File.join(path, dir), dir).tap do |m|
23
+ m.base_path = base_path
24
+ m.exclude = exclude
25
+ m.mode = mode
26
+ end
27
+ end
28
+
29
+ def exclude?(path)
30
+ relative_path = path.gsub("#{base_path}/", '')
31
+ exclude.member?(relative_path)
32
+ end
33
+
34
+ # @appmap
35
+ def children
36
+ child_files.sort + child_packages.sort
37
+ end
38
+
39
+ protected
40
+
41
+ def child_packages
42
+ ::Dir.new(path).entries.select do |fname|
43
+ !%w[. ..].include?(fname) && ::File.directory?(::File.join(path, fname))
44
+ end.select do |dir|
45
+ !exclude?(::File.join(path, dir))
46
+ end.map do |dir|
47
+ sub_package_dir(dir)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ module AppMap
2
+ module Config
3
+ PathStruct = Struct.new(:path)
4
+
5
+ # Path is an abstract configuration of a file, directory, or package.
6
+ class Path < PathStruct
7
+ attr_accessor :mode
8
+
9
+ def initialize(path)
10
+ super(path)
11
+
12
+ @mode = :implicit
13
+ end
14
+
15
+ def <=>(other)
16
+ path <=> other.path
17
+ end
18
+
19
+ # Automatically determined configurations of child file/package paths.
20
+ def children
21
+ []
22
+ end
23
+ end
24
+ end
25
+ end