appmap 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +5 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +27 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +44 -0
  7. data/CHANGELOG.md +199 -0
  8. data/Dockerfile.appmap +5 -0
  9. data/Gemfile +5 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +300 -0
  12. data/Rakefile +132 -0
  13. data/appmap.gemspec +44 -0
  14. data/appmap.yml +8 -0
  15. data/examples/install.rb +76 -0
  16. data/examples/mock_webapp/Gemfile +1 -0
  17. data/examples/mock_webapp/appmap.yml +2 -0
  18. data/examples/mock_webapp/exe/mock_webapp_request +12 -0
  19. data/examples/mock_webapp/lib/mock_webapp/controller.rb +23 -0
  20. data/examples/mock_webapp/lib/mock_webapp/request.rb +12 -0
  21. data/examples/mock_webapp/lib/mock_webapp/user.rb +18 -0
  22. data/exe/appmap +154 -0
  23. data/lib/appmap.rb +87 -0
  24. data/lib/appmap/algorithm/prune_class_map.rb +67 -0
  25. data/lib/appmap/algorithm/stats.rb +91 -0
  26. data/lib/appmap/class_map.rb +135 -0
  27. data/lib/appmap/command/record.rb +38 -0
  28. data/lib/appmap/command/stats.rb +14 -0
  29. data/lib/appmap/config.rb +91 -0
  30. data/lib/appmap/cucumber.rb +89 -0
  31. data/lib/appmap/event.rb +168 -0
  32. data/lib/appmap/hook.rb +130 -0
  33. data/lib/appmap/metadata.rb +62 -0
  34. data/lib/appmap/middleware/remote_recording.rb +114 -0
  35. data/lib/appmap/minitest.rb +141 -0
  36. data/lib/appmap/rails/action_handler.rb +91 -0
  37. data/lib/appmap/rails/sql_handler.rb +145 -0
  38. data/lib/appmap/railtie.rb +45 -0
  39. data/lib/appmap/record.rb +27 -0
  40. data/lib/appmap/rspec.rb +301 -0
  41. data/lib/appmap/trace.rb +96 -0
  42. data/lib/appmap/util.rb +40 -0
  43. data/lib/appmap/version.rb +9 -0
  44. data/lore/pages/2019-05-21-install-and-record/index.pug +51 -0
  45. data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
  46. data/lore/pages/2019-05-21-install-and-record/metadata.yml +5 -0
  47. data/lore/pages/layout.pug +66 -0
  48. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +1912 -0
  49. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +1 -0
  50. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +7 -0
  51. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +1 -0
  52. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +331 -0
  53. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +1 -0
  54. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +8 -0
  55. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +1 -0
  56. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +9030 -0
  57. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +1 -0
  58. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +7 -0
  59. data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +1 -0
  60. data/lore/public/stylesheets/style.css +8 -0
  61. data/package-lock.json +1064 -0
  62. data/package.json +24 -0
  63. data/spec/abstract_controller4_base_spec.rb +67 -0
  64. data/spec/abstract_controller_base_spec.rb +72 -0
  65. data/spec/config_spec.rb +25 -0
  66. data/spec/fixtures/hook/attr_accessor.rb +5 -0
  67. data/spec/fixtures/hook/compare.rb +7 -0
  68. data/spec/fixtures/hook/constructor.rb +7 -0
  69. data/spec/fixtures/hook/exception_method.rb +11 -0
  70. data/spec/fixtures/hook/instance_method.rb +23 -0
  71. data/spec/fixtures/hook/openssl_sign.rb +87 -0
  72. data/spec/fixtures/hook/singleton_method.rb +54 -0
  73. data/spec/fixtures/rack_users_app/.dockerignore +2 -0
  74. data/spec/fixtures/rack_users_app/.gitignore +2 -0
  75. data/spec/fixtures/rack_users_app/Dockerfile +32 -0
  76. data/spec/fixtures/rack_users_app/Gemfile +10 -0
  77. data/spec/fixtures/rack_users_app/appmap.yml +3 -0
  78. data/spec/fixtures/rack_users_app/config.ru +2 -0
  79. data/spec/fixtures/rack_users_app/docker-compose.yml +9 -0
  80. data/spec/fixtures/rack_users_app/lib/app.rb +36 -0
  81. data/spec/fixtures/rails4_users_app/.gitignore +13 -0
  82. data/spec/fixtures/rails4_users_app/.rbenv-gemsets +2 -0
  83. data/spec/fixtures/rails4_users_app/.ruby-version +1 -0
  84. data/spec/fixtures/rails4_users_app/Dockerfile +30 -0
  85. data/spec/fixtures/rails4_users_app/Dockerfile.pg +3 -0
  86. data/spec/fixtures/rails4_users_app/Gemfile +77 -0
  87. data/spec/fixtures/rails4_users_app/README.rdoc +28 -0
  88. data/spec/fixtures/rails4_users_app/Rakefile +6 -0
  89. data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
  90. data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +16 -0
  91. data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +15 -0
  92. data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +27 -0
  93. data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +5 -0
  94. data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
  95. data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +5 -0
  96. data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +5 -0
  97. data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +2 -0
  98. data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
  99. data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
  100. data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
  101. data/spec/fixtures/rails4_users_app/app/models/user.rb +18 -0
  102. data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +7 -0
  103. data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +7 -0
  104. data/spec/fixtures/rails4_users_app/appmap.yml +3 -0
  105. data/spec/fixtures/rails4_users_app/bin/rails +9 -0
  106. data/spec/fixtures/rails4_users_app/bin/setup +29 -0
  107. data/spec/fixtures/rails4_users_app/bin/spring +17 -0
  108. data/spec/fixtures/rails4_users_app/config.ru +4 -0
  109. data/spec/fixtures/rails4_users_app/config/application.rb +26 -0
  110. data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
  111. data/spec/fixtures/rails4_users_app/config/database.yml +18 -0
  112. data/spec/fixtures/rails4_users_app/config/environment.rb +5 -0
  113. data/spec/fixtures/rails4_users_app/config/environments/development.rb +41 -0
  114. data/spec/fixtures/rails4_users_app/config/environments/production.rb +79 -0
  115. data/spec/fixtures/rails4_users_app/config/environments/test.rb +42 -0
  116. data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +11 -0
  117. data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +7 -0
  118. data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +3 -0
  119. data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +4 -0
  120. data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +16 -0
  121. data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +4 -0
  122. data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +3 -0
  123. data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +10 -0
  124. data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +14 -0
  125. data/spec/fixtures/rails4_users_app/config/locales/en.yml +23 -0
  126. data/spec/fixtures/rails4_users_app/config/routes.rb +12 -0
  127. data/spec/fixtures/rails4_users_app/config/secrets.yml +22 -0
  128. data/spec/fixtures/rails4_users_app/create_app +23 -0
  129. data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +10 -0
  130. data/spec/fixtures/rails4_users_app/db/schema.rb +26 -0
  131. data/spec/fixtures/rails4_users_app/db/seeds.rb +7 -0
  132. data/spec/fixtures/rails4_users_app/docker-compose.yml +26 -0
  133. data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
  134. data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
  135. data/spec/fixtures/rails4_users_app/log/.keep +0 -0
  136. data/spec/fixtures/rails4_users_app/public/404.html +67 -0
  137. data/spec/fixtures/rails4_users_app/public/422.html +67 -0
  138. data/spec/fixtures/rails4_users_app/public/500.html +66 -0
  139. data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
  140. data/spec/fixtures/rails4_users_app/public/robots.txt +5 -0
  141. data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +49 -0
  142. data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +95 -0
  143. data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +96 -0
  144. data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +9 -0
  145. data/spec/fixtures/rails_users_app/.dockerignore +1 -0
  146. data/spec/fixtures/rails_users_app/.gitignore +39 -0
  147. data/spec/fixtures/rails_users_app/.rspec +1 -0
  148. data/spec/fixtures/rails_users_app/.ruby-version +1 -0
  149. data/spec/fixtures/rails_users_app/Dockerfile +29 -0
  150. data/spec/fixtures/rails_users_app/Dockerfile.pg +3 -0
  151. data/spec/fixtures/rails_users_app/Gemfile +52 -0
  152. data/spec/fixtures/rails_users_app/Rakefile +6 -0
  153. data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +27 -0
  154. data/spec/fixtures/rails_users_app/app/controllers/application_controller.rb +2 -0
  155. data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
  156. data/spec/fixtures/rails_users_app/app/controllers/health_controller.rb +5 -0
  157. data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +5 -0
  158. data/spec/fixtures/rails_users_app/app/models/activerecord/user.rb +18 -0
  159. data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
  160. data/spec/fixtures/rails_users_app/app/models/sequel/user.rb +25 -0
  161. data/spec/fixtures/rails_users_app/app/views/layouts/application.html.haml +7 -0
  162. data/spec/fixtures/rails_users_app/app/views/users/index.html.haml +7 -0
  163. data/spec/fixtures/rails_users_app/appmap.yml +3 -0
  164. data/spec/fixtures/rails_users_app/bin/appmap +29 -0
  165. data/spec/fixtures/rails_users_app/bin/byebug +29 -0
  166. data/spec/fixtures/rails_users_app/bin/gli +29 -0
  167. data/spec/fixtures/rails_users_app/bin/htmldiff +29 -0
  168. data/spec/fixtures/rails_users_app/bin/ldiff +29 -0
  169. data/spec/fixtures/rails_users_app/bin/nokogiri +29 -0
  170. data/spec/fixtures/rails_users_app/bin/rackup +29 -0
  171. data/spec/fixtures/rails_users_app/bin/rails +4 -0
  172. data/spec/fixtures/rails_users_app/bin/rake +29 -0
  173. data/spec/fixtures/rails_users_app/bin/rspec +29 -0
  174. data/spec/fixtures/rails_users_app/bin/ruby-parse +29 -0
  175. data/spec/fixtures/rails_users_app/bin/ruby-rewrite +29 -0
  176. data/spec/fixtures/rails_users_app/bin/sequel +29 -0
  177. data/spec/fixtures/rails_users_app/bin/setup +25 -0
  178. data/spec/fixtures/rails_users_app/bin/sprockets +29 -0
  179. data/spec/fixtures/rails_users_app/bin/thor +29 -0
  180. data/spec/fixtures/rails_users_app/bin/update +25 -0
  181. data/spec/fixtures/rails_users_app/config.ru +5 -0
  182. data/spec/fixtures/rails_users_app/config/application.rb +51 -0
  183. data/spec/fixtures/rails_users_app/config/boot.rb +3 -0
  184. data/spec/fixtures/rails_users_app/config/credentials.yml.enc +1 -0
  185. data/spec/fixtures/rails_users_app/config/database.yml +18 -0
  186. data/spec/fixtures/rails_users_app/config/environment.rb +5 -0
  187. data/spec/fixtures/rails_users_app/config/environments/development.rb +40 -0
  188. data/spec/fixtures/rails_users_app/config/environments/production.rb +68 -0
  189. data/spec/fixtures/rails_users_app/config/environments/test.rb +36 -0
  190. data/spec/fixtures/rails_users_app/config/initializers/application_controller_renderer.rb +8 -0
  191. data/spec/fixtures/rails_users_app/config/initializers/backtrace_silencers.rb +7 -0
  192. data/spec/fixtures/rails_users_app/config/initializers/cors.rb +16 -0
  193. data/spec/fixtures/rails_users_app/config/initializers/filter_parameter_logging.rb +4 -0
  194. data/spec/fixtures/rails_users_app/config/initializers/inflections.rb +16 -0
  195. data/spec/fixtures/rails_users_app/config/initializers/mime_types.rb +4 -0
  196. data/spec/fixtures/rails_users_app/config/initializers/record_button.rb +3 -0
  197. data/spec/fixtures/rails_users_app/config/initializers/wrap_parameters.rb +9 -0
  198. data/spec/fixtures/rails_users_app/config/locales/en.yml +33 -0
  199. data/spec/fixtures/rails_users_app/config/routes.rb +11 -0
  200. data/spec/fixtures/rails_users_app/create_app +27 -0
  201. data/spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb +9 -0
  202. data/spec/fixtures/rails_users_app/db/schema.rb +23 -0
  203. data/spec/fixtures/rails_users_app/docker-compose.yml +28 -0
  204. data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
  205. data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
  206. data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
  207. data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
  208. data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
  209. data/spec/fixtures/rails_users_app/log/.keep +0 -0
  210. data/spec/fixtures/rails_users_app/public/robots.txt +1 -0
  211. data/spec/fixtures/rails_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
  212. data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +39 -0
  213. data/spec/fixtures/rails_users_app/spec/rails_helper.rb +66 -0
  214. data/spec/fixtures/rails_users_app/spec/spec_helper.rb +96 -0
  215. data/spec/fixtures/rails_users_app/users_app/.gitignore +20 -0
  216. data/spec/hook_spec.rb +576 -0
  217. data/spec/rails_spec_helper.rb +60 -0
  218. data/spec/railtie_spec.rb +44 -0
  219. data/spec/record_sql_rails4_pg_spec.rb +76 -0
  220. data/spec/record_sql_rails_pg_spec.rb +68 -0
  221. data/spec/remote_recording_spec.rb +117 -0
  222. data/spec/rspec_feature_metadata_spec.rb +32 -0
  223. data/spec/spec_helper.rb +15 -0
  224. data/spec/util_spec.rb +21 -0
  225. data/test/cli_test.rb +116 -0
  226. data/test/cucumber_test.rb +72 -0
  227. data/test/fixtures/cli_record_test/appmap.yml +3 -0
  228. data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +9 -0
  229. data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
  230. data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
  231. data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
  232. data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
  233. data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
  234. data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
  235. data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
  236. data/test/fixtures/cucumber_recorder/Gemfile +5 -0
  237. data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
  238. data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
  239. data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
  240. data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
  241. data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
  242. data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
  243. data/test/fixtures/minitest_recorder/Gemfile +5 -0
  244. data/test/fixtures/minitest_recorder/appmap.yml +3 -0
  245. data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
  246. data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
  247. data/test/fixtures/process_recorder/appmap.yml +3 -0
  248. data/test/fixtures/process_recorder/hello.rb +9 -0
  249. data/test/fixtures/rspec_recorder/Gemfile +5 -0
  250. data/test/fixtures/rspec_recorder/appmap.yml +3 -0
  251. data/test/fixtures/rspec_recorder/lib/hello.rb +5 -0
  252. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +21 -0
  253. data/test/fixtures/rspec_recorder/spec/labeled_hello_spec.rb +9 -0
  254. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +9 -0
  255. data/test/minitest_test.rb +38 -0
  256. data/test/record_process_test.rb +35 -0
  257. data/test/rspec_test.rb +82 -0
  258. data/test/test_helper.rb +4 -0
  259. metadata +525 -0
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
6
+ rescue NameError
7
+ warn 'active_support is not available. AppMap execution will continue optimistically without it...'
8
+ end
9
+
10
+ require 'appmap/version'
11
+ require 'appmap/hook'
12
+ require 'appmap/config'
13
+ require 'appmap/trace'
14
+ require 'appmap/class_map'
15
+ require 'appmap/metadata'
16
+
17
+ module AppMap
18
+ class << self
19
+ @configuration = nil
20
+ @configuration_file_path = nil
21
+
22
+ # configuration gets the configuration. If there is no configuration, the default
23
+ # configuration is initialized.
24
+ def configuration
25
+ @configuration ||= configure
26
+ end
27
+
28
+ # configuration= sets the configuration. This is only expected to happen once per
29
+ # Ruby process.
30
+ def configuration=(config)
31
+ warn 'AppMap is already configured' if @configuration && config
32
+
33
+ @configuration = config
34
+ end
35
+
36
+ # initialize configures AppMap for recording. Default behavior is to configure from "appmap.yml".
37
+ # This method also activates the code hooks which record function calls as trace events.
38
+ # Call this function before the program code is loaded by the Ruby VM, otherwise
39
+ # the load events won't be seen and the hooks won't activate.
40
+ def initialize(config_file_path = 'appmap.yml')
41
+ warn "Configuring AppMap from path #{config_file_path}"
42
+ self.configuration = Config.load_from_file(config_file_path)
43
+ Hook.new(configuration).enable
44
+ end
45
+
46
+ # tracing can be used to start tracing, stop tracing, and record events.
47
+ def tracing
48
+ @tracing ||= Trace::Tracing.new
49
+ end
50
+
51
+ # record records the events which occur while processing a block,
52
+ # and returns an AppMap as a Hash.
53
+ def record
54
+ tracer = tracing.trace
55
+ begin
56
+ yield
57
+ ensure
58
+ tracing.delete(tracer)
59
+ end
60
+
61
+ events = [].tap do |event_list|
62
+ event_list << tracer.next_event.to_h while tracer.event?
63
+ end
64
+ {
65
+ 'version' => AppMap::APPMAP_FORMAT_VERSION,
66
+ 'metadata' => detect_metadata,
67
+ 'classMap' => class_map(tracer.event_methods),
68
+ 'events' => events
69
+ }
70
+ end
71
+
72
+ # class_map builds a class map from a config and a list of Ruby methods.
73
+ def class_map(methods)
74
+ ClassMap.build_from_methods(configuration, methods)
75
+ end
76
+
77
+ # detect_metadata returns default metadata detected from the Ruby system and from the
78
+ # filesystem.
79
+ def detect_metadata
80
+ @metadata ||= Metadata.detect.freeze
81
+ @metadata.deep_dup
82
+ end
83
+ end
84
+ end
85
+
86
+ require 'appmap/railtie' if defined?(::Rails::Railtie)
87
+ AppMap.initialize unless ENV['APPMAP_INITIALIZE'] == 'false'
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Algorithm
5
+ # Prune a class map so that only functions, classes and packages which are referenced
6
+ # by some event are retained.
7
+ class PruneClassMap
8
+ attr_reader :class_map
9
+ # Set this attribute to a function which will log algorithm events.
10
+ attr_writer :logger
11
+ attr_accessor :events
12
+
13
+ # Construct the algorithm, with a class map that will be pruned in place.
14
+ def initialize(class_map)
15
+ @class_map = class_map
16
+ @logger = ->(msg) {}
17
+ end
18
+
19
+ def perform
20
+ # This proc counts the number of objects in the class map whose type is 'k'
21
+ count = proc do |k, e|
22
+ n = 0
23
+ n += 1 if e['type'] == k
24
+ n += (e['children'] || []).map { |child| count.call(k, child) }.reduce(0, :+)
25
+ n
26
+ end
27
+
28
+ @logger.call "Full classMap contains #{class_map.map { |m| count.call('class', m) }.reduce(0, :+)} classes"
29
+
30
+ # Prune all the classes which fail a test.
31
+ reject = proc do |list, test|
32
+ list.tap do |_|
33
+ list.each do |item|
34
+ children = item['children']
35
+ next unless children
36
+
37
+ reject.call(children, test)
38
+ end
39
+ list.reject!(&test)
40
+ end
41
+ end
42
+
43
+ if events
44
+ locations = \
45
+ Set.new(events.select { |e| e['event'] == 'call' }
46
+ .map { |e| [ e['path'], e['lineno'] ].join(':') })
47
+
48
+ # Prune all functions which aren't called
49
+ reject.call class_map,
50
+ ->(e) { e['type'] == 'function' && !locations.member?(e['location']) }
51
+ end
52
+
53
+ # Prune all empty classes
54
+ reject.call class_map,
55
+ ->(e) { e['type'] == 'class' && (e['children'] || []).empty? }
56
+
57
+ # Prune all empty packages
58
+ reject.call class_map,
59
+ ->(e) { e['type'] == 'package' && (e['children'] || []).empty? }
60
+
61
+ @logger.call "Pruned classMap contains #{class_map.map { |m| count.call('class', m) }.reduce(0, :+)} classes"
62
+
63
+ class_map
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Algorithm
5
+ StatsStruct = Struct.new(:appmap)
6
+
7
+ # Compute AppMap statistics.
8
+ class Stats < StatsStruct
9
+ Result = Struct.new(:class_frequency, :method_frequency) do
10
+ def merge!(other)
11
+ merge = lambda do |freq, other_freq|
12
+ freq_by_name = freq.inject({}) do |table, entry|
13
+ table.tap do
14
+ table[entry.name] = entry
15
+ end
16
+ end
17
+ other_freq.each do |other_entry|
18
+ entry = freq_by_name[other_entry.name]
19
+ if entry
20
+ entry.count += other_entry.count
21
+ else
22
+ freq << other_entry
23
+ end
24
+ end
25
+ end
26
+ merge.call(class_frequency, other.class_frequency)
27
+ merge.call(method_frequency, other.method_frequency)
28
+
29
+ self
30
+ end
31
+
32
+ def sort!
33
+ comparator = ->(a,b) { b.count <=> a.count }
34
+ class_frequency.sort!(&comparator)
35
+ method_frequency.sort!(&comparator)
36
+
37
+ self
38
+ end
39
+
40
+ def limit!(limit)
41
+ self.class_frequency = class_frequency[0...limit]
42
+ self.method_frequency = method_frequency[0...limit]
43
+
44
+ self
45
+ end
46
+
47
+ def as_text
48
+ lines = [
49
+ "Class frequency:",
50
+ "----------------",
51
+ ] + class_frequency.map(&:to_a).map(&:reverse).map { |line | line.join("\t") } + [
52
+ "",
53
+ "Method frequency:",
54
+ "----------------",
55
+ ] + method_frequency.map(&:to_a).map(&:reverse).map { |line | line.join("\t") }
56
+ lines.join("\n")
57
+ end
58
+ end
59
+ Frequency = Struct.new(:name, :count)
60
+
61
+ def perform(limit: nil)
62
+ events = appmap['events'] || []
63
+ frequency_calc = lambda do |key_func|
64
+ events_by_key = events.inject(Hash.new(0)) do |memo, event|
65
+ key = key_func.call(event)
66
+ memo.tap do
67
+ memo[key] += 1 if key
68
+ end
69
+ end
70
+ events_by_key.map do |key, count|
71
+ Frequency.new(key, count)
72
+ end
73
+ end
74
+
75
+ class_name_func = ->(event) { event['defined_class'] }
76
+ full_name_func = lambda do |event|
77
+ call = event['event'] == 'call'
78
+ class_name = event['defined_class']
79
+ static = event['static']
80
+ function_name = event['method_id']
81
+ [ class_name, static ? '.' : '#', function_name ].join if call && class_name && !static.nil? && function_name
82
+ end
83
+
84
+ class_frequency = frequency_calc.call(class_name_func)
85
+ method_frequency = frequency_calc.call(full_name_func)
86
+
87
+ Result.new(class_frequency, method_frequency)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ class ClassMap
5
+ module HasChildren
6
+ def self.included(base)
7
+ base.module_eval do
8
+ def children
9
+ @children ||= []
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ module Types
16
+ class Root
17
+ include HasChildren
18
+ end
19
+
20
+ Package = Struct.new(:name) do
21
+ include HasChildren
22
+
23
+ def type
24
+ 'package'
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ name: name,
30
+ type: type,
31
+ children: children.map(&:to_h)
32
+ }
33
+ end
34
+ end
35
+ Class = Struct.new(:name) do
36
+ include HasChildren
37
+
38
+ def type
39
+ 'class'
40
+ end
41
+
42
+ def to_h
43
+ {
44
+ name: name,
45
+ type: type,
46
+ children: children.map(&:to_h)
47
+ }
48
+ end
49
+ end
50
+ Function = Struct.new(:name) do
51
+ attr_accessor :static, :location, :labels
52
+
53
+ def type
54
+ 'function'
55
+ end
56
+
57
+ def to_h
58
+ {
59
+ name: name,
60
+ type: type,
61
+ location: location,
62
+ static: static,
63
+ labels: labels
64
+ }.delete_if {|k,v| v.nil?}
65
+ end
66
+ end
67
+ end
68
+
69
+ class << self
70
+ def build_from_methods(config, methods)
71
+ root = Types::Root.new
72
+ methods.each do |method|
73
+ package = config.package_for_method(method) \
74
+ or raise "No package found for method #{method}"
75
+ add_function root, package, method
76
+ end
77
+ root.children.map(&:to_h)
78
+ end
79
+
80
+ protected
81
+
82
+ def add_function(root, package, method)
83
+ location = method.source_location
84
+ location_file, lineno = location
85
+ location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
86
+
87
+ static = method.static
88
+
89
+ object_infos = [
90
+ {
91
+ name: package.path,
92
+ type: 'package'
93
+ }
94
+ ]
95
+ object_infos += method.defined_class.split('::').map do |name|
96
+ {
97
+ name: name,
98
+ type: 'class'
99
+ }
100
+ end
101
+ function_info = {
102
+ name: method.name,
103
+ type: 'function',
104
+ location: [ location_file, lineno ].join(':'),
105
+ static: static
106
+ }
107
+ function_info[:labels] = package.labels if package.labels
108
+ object_infos << function_info
109
+
110
+ parent = root
111
+ object_infos.each do |info|
112
+ parent = find_or_create parent.children, info do
113
+ Types.const_get(info[:type].classify).new(info[:name].to_s).tap do |type|
114
+ info.keys.tap do |keys|
115
+ keys.delete(:name)
116
+ keys.delete(:type)
117
+ end.each do |key|
118
+ type.send "#{key}=", info[key]
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def find_or_create(list, info)
126
+ obj = list.find { |item| item.type == info[:type] && item.name == info[:name] }
127
+ return obj if obj
128
+
129
+ yield.tap do |new_obj|
130
+ list << new_obj
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Command
5
+ RecordStruct = Struct.new(:config, :program)
6
+
7
+ class Record < RecordStruct
8
+ def perform(&block)
9
+ tracer = AppMap.tracing.trace
10
+
11
+ events = []
12
+ quit = false
13
+ event_thread = Thread.new do
14
+ while tracer.event? || !quit
15
+ event = tracer.next_event
16
+ if event
17
+ events << event.to_h
18
+ else
19
+ sleep 0.0001
20
+ end
21
+ end
22
+ end
23
+ event_thread.abort_on_exception = true
24
+
25
+ at_exit do
26
+ quit = true
27
+ event_thread.join
28
+ yield AppMap::APPMAP_FORMAT_VERSION,
29
+ AppMap.detect_metadata,
30
+ AppMap.class_map(tracer.event_methods),
31
+ events
32
+ end
33
+
34
+ load program if program
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ module Command
5
+ StatsStruct = Struct.new(:appmap)
6
+
7
+ class Stats < StatsStruct
8
+ def perform(limit: nil)
9
+ require 'appmap/algorithm/stats'
10
+ AppMap::Algorithm::Stats.new(appmap).perform(limit: limit)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppMap
4
+ Package = Struct.new(:path, :exclude, :labels) do
5
+ def initialize(path, exclude, labels = nil)
6
+ super
7
+ end
8
+
9
+ def to_h
10
+ {
11
+ path: path,
12
+ exclude: exclude.blank? ? nil : exclude,
13
+ labels: labels.blank? ? nil : labels
14
+ }.compact
15
+ end
16
+ end
17
+
18
+ class Config
19
+ # Methods that should always be hooked, with their containing
20
+ # package and labels that should be applied to them.
21
+ HOOKED_METHODS = {
22
+ 'ActiveSupport::SecurityUtils' => {
23
+ secure_compare: Package.new('active_support', nil, ['security'])
24
+ },
25
+ 'OpenSSL::X509::Certificate' => {
26
+ sign: Package.new('openssl', nil, ['security'])
27
+ }
28
+ }
29
+
30
+ attr_reader :name, :packages
31
+ def initialize(name, packages = [])
32
+ @name = name
33
+ @packages = packages
34
+ end
35
+
36
+ class << self
37
+ # Loads configuration data from a file, specified by the file name.
38
+ def load_from_file(config_file_name)
39
+ require 'yaml'
40
+ load YAML.safe_load(::File.read(config_file_name))
41
+ end
42
+
43
+ # Loads configuration from a Hash.
44
+ def load(config_data)
45
+ packages = (config_data['packages'] || []).map do |package|
46
+ Package.new(package['path'], package['exclude'] || [])
47
+ end
48
+ Config.new config_data['name'], packages
49
+ end
50
+ end
51
+
52
+ def to_h
53
+ {
54
+ name: name,
55
+ packages: packages.map(&:to_h)
56
+ }
57
+ end
58
+
59
+ def package_for_method(method)
60
+ location = method.source_location
61
+ location_file, = location
62
+ return unless location_file
63
+
64
+ defined_class,_,method_name = Hook.qualify_method_name(method)
65
+ hooked_method = find_hooked_method(defined_class, method_name)
66
+ return hooked_method if hooked_method
67
+
68
+ location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
69
+ packages.find do |pkg|
70
+ (location_file.index(pkg.path) == 0) &&
71
+ !pkg.exclude.find { |p| location_file.index(p) }
72
+ end
73
+ end
74
+
75
+ def included_by_location?(method)
76
+ !!package_for_method(method)
77
+ end
78
+
79
+ def always_hook?(defined_class, method_name)
80
+ !!find_hooked_method(defined_class, method_name)
81
+ end
82
+
83
+ def find_hooked_method(defined_class, method_name)
84
+ find_hooked_class(defined_class)[method_name]
85
+ end
86
+
87
+ def find_hooked_class(defined_class)
88
+ HOOKED_METHODS[defined_class] || {}
89
+ end
90
+ end
91
+ end