appmap 0.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +5 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +100 -0
- data/Dockerfile.appmap +5 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +117 -0
- data/appmap.gemspec +41 -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-record-self +49 -0
- data/exe/appmap +168 -0
- data/lib/appmap/algorithm/prune_class_map.rb +65 -0
- data/lib/appmap/command/inspect.rb +11 -0
- data/lib/appmap/command/record.rb +91 -0
- data/lib/appmap/command/upload.rb +103 -0
- data/lib/appmap/config/directory.rb +65 -0
- data/lib/appmap/config/file.rb +13 -0
- data/lib/appmap/config/named_function.rb +21 -0
- data/lib/appmap/config/package_dir.rb +52 -0
- data/lib/appmap/config/path.rb +25 -0
- data/lib/appmap/config.rb +65 -0
- data/lib/appmap/feature.rb +262 -0
- data/lib/appmap/inspect/inspector.rb +99 -0
- data/lib/appmap/inspect/parse_node.rb +170 -0
- data/lib/appmap/inspect/parser.rb +15 -0
- data/lib/appmap/inspect.rb +91 -0
- data/lib/appmap/middleware/remote_recording.rb +122 -0
- data/lib/appmap/parser.rb +60 -0
- data/lib/appmap/rails/action_handler.rb +77 -0
- data/lib/appmap/rails/sql_handler.rb +148 -0
- data/lib/appmap/railtie.rb +32 -0
- data/lib/appmap/rspec/parse_node.rb +41 -0
- data/lib/appmap/rspec/parser.rb +15 -0
- data/lib/appmap/rspec.rb +288 -0
- data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +65 -0
- data/lib/appmap/trace/tracer.rb +347 -0
- data/lib/appmap/version.rb +5 -0
- data/lib/appmap.rb +26 -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 +1066 -0
- data/package.json +24 -0
- data/spec/abstract_controller4_base_spec.rb +58 -0
- data/spec/abstract_controller_base_spec.rb +59 -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/application.rb +26 -0
- data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/database.yml +17 -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/config.ru +4 -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 +24 -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 +51 -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-record-self +29 -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/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 +17 -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/config.ru +5 -0
- data/spec/fixtures/rails_users_app/create_app +11 -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 +24 -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/rack_handler_webrick_spec.rb +59 -0
- data/spec/rails_spec_helper.rb +34 -0
- data/spec/railtie_spec.rb +35 -0
- data/spec/record_sql_rails4_pg_spec.rb +76 -0
- data/spec/record_sql_rails_pg_spec.rb +68 -0
- data/spec/rspec_feature_metadata_spec.rb +30 -0
- data/spec/spec_helper.rb +6 -0
- data/test/cli_test.rb +81 -0
- data/test/config_test.rb +149 -0
- data/test/explict_inspect_test.rb +29 -0
- data/test/fixtures/active_record_like/active_record/aggregations.rb +4 -0
- data/test/fixtures/active_record_like/active_record/association.rb +4 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +8 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +8 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +6 -0
- data/test/fixtures/active_record_like/active_record/caps/caps.rb +4 -0
- data/test/fixtures/active_record_like/active_record.rb +2 -0
- data/test/fixtures/cli_record_test/appmap.yml +2 -0
- data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +7 -0
- data/test/fixtures/ignore_non_ruby_file/class.rb +3 -0
- data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +1 -0
- data/test/fixtures/includes_excludes/lib/a/a_1.rb +6 -0
- data/test/fixtures/includes_excludes/lib/a/a_2.rb +6 -0
- data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +8 -0
- data/test/fixtures/includes_excludes/lib/b/b_1.rb +6 -0
- data/test/fixtures/includes_excludes/lib/root_1.rb +4 -0
- data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_a.rb +2 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b.rb +2 -0
- data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +6 -0
- data/test/fixtures/parse_file/defs_static_function.rb +96 -0
- data/test/fixtures/parse_file/function_within_class.rb +36 -0
- data/test/fixtures/parse_file/include_public_methods.rb +127 -0
- data/test/fixtures/parse_file/instance_function.rb +17 -0
- data/test/fixtures/parse_file/modules.rb +71 -0
- data/test/fixtures/parse_file/sclass_static_function.rb +88 -0
- data/test/fixtures/parse_file/toplevel_class.rb +13 -0
- data/test/fixtures/parse_file/toplevel_function.rb +14 -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/hello_spec.rb +9 -0
- data/test/fixtures/trace_test/trace_program_1.rb +44 -0
- data/test/implicit_inspect_test.rb +33 -0
- data/test/include_exclude_test.rb +48 -0
- data/test/prerecorded_trace_test.rb +76 -0
- data/test/rspec_test.rb +22 -0
- data/test/test_helper.rb +46 -0
- data/test/trace_test.rb +92 -0
- 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,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,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
|