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.
- checksums.yaml +7 -0
- data/.dockerignore +5 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +27 -0
- data/.ruby-version +1 -0
- data/.travis.yml +44 -0
- data/CHANGELOG.md +199 -0
- data/Dockerfile.appmap +5 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +300 -0
- data/Rakefile +132 -0
- data/appmap.gemspec +44 -0
- data/appmap.yml +8 -0
- data/examples/install.rb +76 -0
- data/examples/mock_webapp/Gemfile +1 -0
- data/examples/mock_webapp/appmap.yml +2 -0
- data/examples/mock_webapp/exe/mock_webapp_request +12 -0
- data/examples/mock_webapp/lib/mock_webapp/controller.rb +23 -0
- data/examples/mock_webapp/lib/mock_webapp/request.rb +12 -0
- data/examples/mock_webapp/lib/mock_webapp/user.rb +18 -0
- data/exe/appmap +154 -0
- data/lib/appmap.rb +87 -0
- data/lib/appmap/algorithm/prune_class_map.rb +67 -0
- data/lib/appmap/algorithm/stats.rb +91 -0
- data/lib/appmap/class_map.rb +135 -0
- data/lib/appmap/command/record.rb +38 -0
- data/lib/appmap/command/stats.rb +14 -0
- data/lib/appmap/config.rb +91 -0
- data/lib/appmap/cucumber.rb +89 -0
- data/lib/appmap/event.rb +168 -0
- data/lib/appmap/hook.rb +130 -0
- data/lib/appmap/metadata.rb +62 -0
- data/lib/appmap/middleware/remote_recording.rb +114 -0
- data/lib/appmap/minitest.rb +141 -0
- data/lib/appmap/rails/action_handler.rb +91 -0
- data/lib/appmap/rails/sql_handler.rb +145 -0
- data/lib/appmap/railtie.rb +45 -0
- data/lib/appmap/record.rb +27 -0
- data/lib/appmap/rspec.rb +301 -0
- data/lib/appmap/trace.rb +96 -0
- data/lib/appmap/util.rb +40 -0
- data/lib/appmap/version.rb +9 -0
- data/lore/pages/2019-05-21-install-and-record/index.pug +51 -0
- data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
- data/lore/pages/2019-05-21-install-and-record/metadata.yml +5 -0
- data/lore/pages/layout.pug +66 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +1912 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +7 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +331 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +8 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +9030 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +7 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +1 -0
- data/lore/public/stylesheets/style.css +8 -0
- data/package-lock.json +1064 -0
- data/package.json +24 -0
- data/spec/abstract_controller4_base_spec.rb +67 -0
- data/spec/abstract_controller_base_spec.rb +72 -0
- data/spec/config_spec.rb +25 -0
- data/spec/fixtures/hook/attr_accessor.rb +5 -0
- data/spec/fixtures/hook/compare.rb +7 -0
- data/spec/fixtures/hook/constructor.rb +7 -0
- data/spec/fixtures/hook/exception_method.rb +11 -0
- data/spec/fixtures/hook/instance_method.rb +23 -0
- data/spec/fixtures/hook/openssl_sign.rb +87 -0
- data/spec/fixtures/hook/singleton_method.rb +54 -0
- data/spec/fixtures/rack_users_app/.dockerignore +2 -0
- data/spec/fixtures/rack_users_app/.gitignore +2 -0
- data/spec/fixtures/rack_users_app/Dockerfile +32 -0
- data/spec/fixtures/rack_users_app/Gemfile +10 -0
- data/spec/fixtures/rack_users_app/appmap.yml +3 -0
- data/spec/fixtures/rack_users_app/config.ru +2 -0
- data/spec/fixtures/rack_users_app/docker-compose.yml +9 -0
- data/spec/fixtures/rack_users_app/lib/app.rb +36 -0
- data/spec/fixtures/rails4_users_app/.gitignore +13 -0
- data/spec/fixtures/rails4_users_app/.rbenv-gemsets +2 -0
- data/spec/fixtures/rails4_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails4_users_app/Dockerfile +30 -0
- data/spec/fixtures/rails4_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails4_users_app/Gemfile +77 -0
- data/spec/fixtures/rails4_users_app/README.rdoc +28 -0
- data/spec/fixtures/rails4_users_app/Rakefile +6 -0
- data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +16 -0
- data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +15 -0
- data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +27 -0
- data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/user.rb +18 -0
- data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +7 -0
- data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +7 -0
- data/spec/fixtures/rails4_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails4_users_app/bin/rails +9 -0
- data/spec/fixtures/rails4_users_app/bin/setup +29 -0
- data/spec/fixtures/rails4_users_app/bin/spring +17 -0
- data/spec/fixtures/rails4_users_app/config.ru +4 -0
- data/spec/fixtures/rails4_users_app/config/application.rb +26 -0
- data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/database.yml +18 -0
- data/spec/fixtures/rails4_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails4_users_app/config/environments/development.rb +41 -0
- data/spec/fixtures/rails4_users_app/config/environments/production.rb +79 -0
- data/spec/fixtures/rails4_users_app/config/environments/test.rb +42 -0
- data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +11 -0
- data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +4 -0
- data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/fixtures/rails4_users_app/config/locales/en.yml +23 -0
- data/spec/fixtures/rails4_users_app/config/routes.rb +12 -0
- data/spec/fixtures/rails4_users_app/config/secrets.yml +22 -0
- data/spec/fixtures/rails4_users_app/create_app +23 -0
- data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +10 -0
- data/spec/fixtures/rails4_users_app/db/schema.rb +26 -0
- data/spec/fixtures/rails4_users_app/db/seeds.rb +7 -0
- data/spec/fixtures/rails4_users_app/docker-compose.yml +26 -0
- data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
- data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails4_users_app/log/.keep +0 -0
- data/spec/fixtures/rails4_users_app/public/404.html +67 -0
- data/spec/fixtures/rails4_users_app/public/422.html +67 -0
- data/spec/fixtures/rails4_users_app/public/500.html +66 -0
- data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
- data/spec/fixtures/rails4_users_app/public/robots.txt +5 -0
- data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +49 -0
- data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +95 -0
- data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +96 -0
- data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +9 -0
- data/spec/fixtures/rails_users_app/.dockerignore +1 -0
- data/spec/fixtures/rails_users_app/.gitignore +39 -0
- data/spec/fixtures/rails_users_app/.rspec +1 -0
- data/spec/fixtures/rails_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails_users_app/Dockerfile +29 -0
- data/spec/fixtures/rails_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails_users_app/Gemfile +52 -0
- data/spec/fixtures/rails_users_app/Rakefile +6 -0
- data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +27 -0
- data/spec/fixtures/rails_users_app/app/controllers/application_controller.rb +2 -0
- data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails_users_app/app/controllers/health_controller.rb +5 -0
- data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +5 -0
- data/spec/fixtures/rails_users_app/app/models/activerecord/user.rb +18 -0
- data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails_users_app/app/models/sequel/user.rb +25 -0
- data/spec/fixtures/rails_users_app/app/views/layouts/application.html.haml +7 -0
- data/spec/fixtures/rails_users_app/app/views/users/index.html.haml +7 -0
- data/spec/fixtures/rails_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails_users_app/bin/appmap +29 -0
- data/spec/fixtures/rails_users_app/bin/byebug +29 -0
- data/spec/fixtures/rails_users_app/bin/gli +29 -0
- data/spec/fixtures/rails_users_app/bin/htmldiff +29 -0
- data/spec/fixtures/rails_users_app/bin/ldiff +29 -0
- data/spec/fixtures/rails_users_app/bin/nokogiri +29 -0
- data/spec/fixtures/rails_users_app/bin/rackup +29 -0
- data/spec/fixtures/rails_users_app/bin/rails +4 -0
- data/spec/fixtures/rails_users_app/bin/rake +29 -0
- data/spec/fixtures/rails_users_app/bin/rspec +29 -0
- data/spec/fixtures/rails_users_app/bin/ruby-parse +29 -0
- data/spec/fixtures/rails_users_app/bin/ruby-rewrite +29 -0
- data/spec/fixtures/rails_users_app/bin/sequel +29 -0
- data/spec/fixtures/rails_users_app/bin/setup +25 -0
- data/spec/fixtures/rails_users_app/bin/sprockets +29 -0
- data/spec/fixtures/rails_users_app/bin/thor +29 -0
- data/spec/fixtures/rails_users_app/bin/update +25 -0
- data/spec/fixtures/rails_users_app/config.ru +5 -0
- data/spec/fixtures/rails_users_app/config/application.rb +51 -0
- data/spec/fixtures/rails_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails_users_app/config/credentials.yml.enc +1 -0
- data/spec/fixtures/rails_users_app/config/database.yml +18 -0
- data/spec/fixtures/rails_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails_users_app/config/environments/development.rb +40 -0
- data/spec/fixtures/rails_users_app/config/environments/production.rb +68 -0
- data/spec/fixtures/rails_users_app/config/environments/test.rb +36 -0
- data/spec/fixtures/rails_users_app/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/fixtures/rails_users_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails_users_app/config/initializers/cors.rb +16 -0
- data/spec/fixtures/rails_users_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails_users_app/config/initializers/mime_types.rb +4 -0
- data/spec/fixtures/rails_users_app/config/initializers/record_button.rb +3 -0
- data/spec/fixtures/rails_users_app/config/initializers/wrap_parameters.rb +9 -0
- data/spec/fixtures/rails_users_app/config/locales/en.yml +33 -0
- data/spec/fixtures/rails_users_app/config/routes.rb +11 -0
- data/spec/fixtures/rails_users_app/create_app +27 -0
- data/spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb +9 -0
- data/spec/fixtures/rails_users_app/db/schema.rb +23 -0
- data/spec/fixtures/rails_users_app/docker-compose.yml +28 -0
- data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
- data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
- data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
- data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
- data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails_users_app/log/.keep +0 -0
- data/spec/fixtures/rails_users_app/public/robots.txt +1 -0
- data/spec/fixtures/rails_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
- data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +39 -0
- data/spec/fixtures/rails_users_app/spec/rails_helper.rb +66 -0
- data/spec/fixtures/rails_users_app/spec/spec_helper.rb +96 -0
- data/spec/fixtures/rails_users_app/users_app/.gitignore +20 -0
- data/spec/hook_spec.rb +576 -0
- data/spec/rails_spec_helper.rb +60 -0
- data/spec/railtie_spec.rb +44 -0
- data/spec/record_sql_rails4_pg_spec.rb +76 -0
- data/spec/record_sql_rails_pg_spec.rb +68 -0
- data/spec/remote_recording_spec.rb +117 -0
- data/spec/rspec_feature_metadata_spec.rb +32 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/util_spec.rb +21 -0
- data/test/cli_test.rb +116 -0
- data/test/cucumber_test.rb +72 -0
- data/test/fixtures/cli_record_test/appmap.yml +3 -0
- data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +9 -0
- data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
- data/test/fixtures/cucumber_recorder/Gemfile +5 -0
- data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
- data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
- data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
- data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
- data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
- data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
- data/test/fixtures/minitest_recorder/Gemfile +5 -0
- data/test/fixtures/minitest_recorder/appmap.yml +3 -0
- data/test/fixtures/minitest_recorder/lib/hello.rb +5 -0
- data/test/fixtures/minitest_recorder/test/hello_test.rb +12 -0
- data/test/fixtures/process_recorder/appmap.yml +3 -0
- data/test/fixtures/process_recorder/hello.rb +9 -0
- data/test/fixtures/rspec_recorder/Gemfile +5 -0
- data/test/fixtures/rspec_recorder/appmap.yml +3 -0
- data/test/fixtures/rspec_recorder/lib/hello.rb +5 -0
- data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +21 -0
- data/test/fixtures/rspec_recorder/spec/labeled_hello_spec.rb +9 -0
- data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +9 -0
- data/test/minitest_test.rb +38 -0
- data/test/record_process_test.rb +35 -0
- data/test/rspec_test.rb +82 -0
- data/test/test_helper.rb +4 -0
- metadata +525 -0
data/lib/appmap.rb
ADDED
|
@@ -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
|