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,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