appmap 0.42.1 → 0.46.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 +4 -4
- data/.releaserc.yml +11 -0
- data/.travis.yml +33 -2
- data/CHANGELOG.md +44 -0
- data/README.md +74 -16
- data/README_CI.md +29 -0
- data/Rakefile +4 -2
- data/appmap.gemspec +5 -3
- data/lib/appmap.rb +3 -7
- data/lib/appmap/class_map.rb +11 -22
- data/lib/appmap/command/record.rb +1 -1
- data/lib/appmap/config.rb +180 -67
- data/lib/appmap/cucumber.rb +1 -1
- data/lib/appmap/event.rb +46 -27
- data/lib/appmap/handler/function.rb +19 -0
- data/lib/appmap/handler/net_http.rb +107 -0
- data/lib/appmap/handler/rails/request_handler.rb +124 -0
- data/lib/appmap/handler/rails/sql_handler.rb +152 -0
- data/lib/appmap/handler/rails/template.rb +149 -0
- data/lib/appmap/hook.rb +111 -70
- data/lib/appmap/hook/method.rb +6 -8
- data/lib/appmap/middleware/remote_recording.rb +1 -1
- data/lib/appmap/minitest.rb +22 -20
- data/lib/appmap/railtie.rb +5 -5
- data/lib/appmap/record.rb +1 -1
- data/lib/appmap/rspec.rb +22 -21
- data/lib/appmap/trace.rb +47 -6
- data/lib/appmap/util.rb +47 -2
- data/lib/appmap/version.rb +2 -2
- data/package-lock.json +3 -3
- data/release.sh +17 -0
- data/spec/abstract_controller_base_spec.rb +140 -34
- data/spec/class_map_spec.rb +5 -13
- data/spec/config_spec.rb +33 -1
- data/spec/fixtures/hook/custom_instance_method.rb +11 -0
- data/spec/fixtures/hook/method_named_call.rb +11 -0
- data/spec/fixtures/rails5_users_app/Gemfile +7 -3
- data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
- data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails5_users_app/create_app +8 -2
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
- data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
- data/spec/fixtures/rails6_users_app/Gemfile +5 -4
- data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
- data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
- data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
- data/spec/fixtures/rails6_users_app/create_app +8 -2
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +13 -0
- data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
- data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
- data/spec/hook_spec.rb +143 -22
- data/spec/record_net_http_spec.rb +160 -0
- data/spec/record_sql_rails_pg_spec.rb +1 -1
- data/spec/spec_helper.rb +16 -0
- data/test/expectations/openssl_test_key_sign1.json +2 -4
- data/test/gem_test.rb +1 -1
- data/test/rspec_test.rb +0 -13
- metadata +20 -14
- data/exe/appmap +0 -154
- data/lib/appmap/rails/request_handler.rb +0 -109
- data/lib/appmap/rails/sql_handler.rb +0 -150
- data/test/cli_test.rb +0 -116
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'appmap/event'
|
4
|
-
require 'appmap/hook'
|
5
|
-
|
6
|
-
module AppMap
|
7
|
-
module Rails
|
8
|
-
module RequestHandler
|
9
|
-
class HTTPServerRequest < AppMap::Event::MethodEvent
|
10
|
-
attr_accessor :normalized_path_info, :request_method, :path_info, :params
|
11
|
-
|
12
|
-
def initialize(request)
|
13
|
-
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
14
|
-
|
15
|
-
@request_method = request.request_method
|
16
|
-
@normalized_path_info = normalized_path request
|
17
|
-
@path_info = request.path_info.split('?')[0]
|
18
|
-
# ActionDispatch::Http::ParameterFilter is deprecated
|
19
|
-
parameter_filter_cls = \
|
20
|
-
if defined?(ActiveSupport::ParameterFilter)
|
21
|
-
ActiveSupport::ParameterFilter
|
22
|
-
else
|
23
|
-
ActionDispatch::Http::ParameterFilter
|
24
|
-
end
|
25
|
-
@params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
|
26
|
-
end
|
27
|
-
|
28
|
-
def to_h
|
29
|
-
super.tap do |h|
|
30
|
-
h[:http_server_request] = {
|
31
|
-
request_method: request_method,
|
32
|
-
path_info: path_info,
|
33
|
-
normalized_path_info: normalized_path_info
|
34
|
-
}.compact
|
35
|
-
|
36
|
-
h[:message] = params.keys.map do |key|
|
37
|
-
val = params[key]
|
38
|
-
{
|
39
|
-
name: key,
|
40
|
-
class: val.class.name,
|
41
|
-
value: self.class.display_string(val),
|
42
|
-
object_id: val.__id__
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def normalized_path(request, router = ::Rails.application.routes.router)
|
51
|
-
router.recognize request do |route, _|
|
52
|
-
app = route.app
|
53
|
-
next unless app.matches? request
|
54
|
-
return normalized_path request, app.rack_app.routes.router if app.engine?
|
55
|
-
|
56
|
-
return route.path.spec.to_s
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
|
62
|
-
attr_accessor :status, :mime_type
|
63
|
-
|
64
|
-
def initialize(response, parent_id, elapsed)
|
65
|
-
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
66
|
-
|
67
|
-
self.status = response.status
|
68
|
-
self.mime_type = response.headers['Content-Type']
|
69
|
-
self.parent_id = parent_id
|
70
|
-
self.elapsed = elapsed
|
71
|
-
end
|
72
|
-
|
73
|
-
def to_h
|
74
|
-
super.tap do |h|
|
75
|
-
h[:http_server_response] = {
|
76
|
-
status: status,
|
77
|
-
mime_type: mime_type
|
78
|
-
}.compact
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class HookMethod < AppMap::Hook::Method
|
84
|
-
def initialize
|
85
|
-
# ActionController::Instrumentation has issued start_processing.action_controller and
|
86
|
-
# process_action.action_controller since Rails 3. Therefore it's a stable place to hook
|
87
|
-
# the request. Rails controller notifications can't be used directly because they don't
|
88
|
-
# provide response headers, and we want the Content-Type.
|
89
|
-
super(nil, ActionController::Instrumentation, ActionController::Instrumentation.instance_method(:process_action))
|
90
|
-
end
|
91
|
-
|
92
|
-
protected
|
93
|
-
|
94
|
-
def before_hook(receiver, defined_class, _) # args
|
95
|
-
call_event = HTTPServerRequest.new(receiver.request)
|
96
|
-
# http_server_request events are i/o and do not require a package name.
|
97
|
-
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
98
|
-
[ call_event, TIME_NOW.call ]
|
99
|
-
end
|
100
|
-
|
101
|
-
def after_hook(receiver, call_event, start_time, _, _) # return_value, exception
|
102
|
-
elapsed = TIME_NOW.call - start_time
|
103
|
-
return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
|
104
|
-
AppMap.tracing.record_event return_event
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
@@ -1,150 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'appmap/event'
|
4
|
-
|
5
|
-
module AppMap
|
6
|
-
module Rails
|
7
|
-
class SQLHandler
|
8
|
-
class SQLCall < AppMap::Event::MethodCall
|
9
|
-
attr_accessor :payload
|
10
|
-
|
11
|
-
def initialize(payload)
|
12
|
-
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
13
|
-
|
14
|
-
self.payload = payload
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_h
|
18
|
-
super.tap do |h|
|
19
|
-
h[:sql_query] = {
|
20
|
-
sql: payload[:sql],
|
21
|
-
database_type: payload[:database_type]
|
22
|
-
}.tap do |sql_query|
|
23
|
-
%i[server_version].each do |attribute|
|
24
|
-
sql_query[attribute] = payload[attribute] if payload[attribute]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class SQLReturn < AppMap::Event::MethodReturnIgnoreValue
|
32
|
-
def initialize(parent_id, elapsed)
|
33
|
-
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
34
|
-
|
35
|
-
self.parent_id = parent_id
|
36
|
-
self.elapsed = elapsed
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
module SQLExaminer
|
41
|
-
class << self
|
42
|
-
def examine(payload, sql:)
|
43
|
-
return unless (examiner = build_examiner)
|
44
|
-
|
45
|
-
payload[:server_version] = examiner.server_version
|
46
|
-
payload[:database_type] = examiner.database_type.to_s
|
47
|
-
end
|
48
|
-
|
49
|
-
protected
|
50
|
-
|
51
|
-
def build_examiner
|
52
|
-
if defined?(Sequel)
|
53
|
-
SequelExaminer.new
|
54
|
-
elsif defined?(ActiveRecord)
|
55
|
-
ActiveRecordExaminer.new
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
class SequelExaminer
|
61
|
-
def server_version
|
62
|
-
Sequel::Model.db.server_version
|
63
|
-
end
|
64
|
-
|
65
|
-
def database_type
|
66
|
-
Sequel::Model.db.database_type.to_sym
|
67
|
-
end
|
68
|
-
|
69
|
-
def execute_query(sql)
|
70
|
-
Sequel::Model.db[sql].all
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class ActiveRecordExaminer
|
75
|
-
@@db_version_warning_issued = {}
|
76
|
-
|
77
|
-
def issue_warning
|
78
|
-
db_type = database_type
|
79
|
-
return if @@db_version_warning_issued[db_type]
|
80
|
-
warn("AppMap: Unable to determine database version for #{db_type.inspect}")
|
81
|
-
@@db_version_warning_issued[db_type] = true
|
82
|
-
end
|
83
|
-
|
84
|
-
def server_version
|
85
|
-
ActiveRecord::Base.connection.try(:database_version) || issue_warning
|
86
|
-
end
|
87
|
-
|
88
|
-
def database_type
|
89
|
-
type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
90
|
-
type = :postgres if type == :postgresql
|
91
|
-
|
92
|
-
type
|
93
|
-
end
|
94
|
-
|
95
|
-
def execute_query(sql)
|
96
|
-
ActiveRecord::Base.connection.execute(sql).inject([]) { |memo, r| memo << r; memo }
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
|
102
|
-
return if AppMap.tracing.empty?
|
103
|
-
|
104
|
-
reentry_key = "#{self.class.name}#call"
|
105
|
-
return if Thread.current[reentry_key] == true
|
106
|
-
|
107
|
-
Thread.current[reentry_key] = true
|
108
|
-
begin
|
109
|
-
sql = payload[:sql].strip
|
110
|
-
|
111
|
-
# Detect whether a function call within a specified filename is present in the call stack.
|
112
|
-
find_in_backtrace = lambda do |file_name, function_name = nil|
|
113
|
-
Thread.current.backtrace.find do |line|
|
114
|
-
tokens = line.split(':')
|
115
|
-
matches_file = tokens.find { |t| t.rindex(file_name) == (t.length - file_name.length) }
|
116
|
-
matches_function = function_name.nil? || tokens.find { |t| t == "in `#{function_name}'" }
|
117
|
-
matches_file && matches_function
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Ignore SQL calls which are made while establishing a new connection.
|
122
|
-
#
|
123
|
-
# Example:
|
124
|
-
# /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/connection_pool.rb:122:in `make_new'
|
125
|
-
return if find_in_backtrace.call('lib/sequel/connection_pool.rb', 'make_new')
|
126
|
-
# lib/active_record/connection_adapters/abstract/connection_pool.rb:811:in `new_connection'
|
127
|
-
return if find_in_backtrace.call('lib/active_record/connection_adapters/abstract/connection_pool.rb', 'new_connection')
|
128
|
-
|
129
|
-
# Ignore SQL calls which are made while inspecting the DB schema.
|
130
|
-
#
|
131
|
-
# Example:
|
132
|
-
# /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/model/base.rb:812:in `get_db_schema'
|
133
|
-
return if find_in_backtrace.call('lib/sequel/model/base.rb', 'get_db_schema')
|
134
|
-
# /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/model_schema.rb:466:in `load_schema!'
|
135
|
-
return if find_in_backtrace.call('lib/active_record/model_schema.rb', 'load_schema!')
|
136
|
-
return if find_in_backtrace.call('lib/active_model/attribute_methods.rb', 'define_attribute_methods')
|
137
|
-
return if find_in_backtrace.call('lib/active_record/connection_adapters/schema_cache.rb')
|
138
|
-
|
139
|
-
SQLExaminer.examine payload, sql: sql
|
140
|
-
|
141
|
-
call = SQLCall.new(payload)
|
142
|
-
AppMap.tracing.record_event(call)
|
143
|
-
AppMap.tracing.record_event(SQLReturn.new(call.id, finished - started))
|
144
|
-
ensure
|
145
|
-
Thread.current[reentry_key] = nil
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
data/test/cli_test.rb
DELETED
@@ -1,116 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'test_helper'
|
5
|
-
require 'English'
|
6
|
-
|
7
|
-
class CLITest < Minitest::Test
|
8
|
-
OUTPUT_FILENAME = File.expand_path('../tmp/appmap.json', __dir__)
|
9
|
-
STATS_OUTPUT_FILENAME = File.expand_path('../tmp/stats.txt', __dir__)
|
10
|
-
|
11
|
-
def setup
|
12
|
-
FileUtils.rm_f OUTPUT_FILENAME
|
13
|
-
FileUtils.rm_f STATS_OUTPUT_FILENAME
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_record
|
17
|
-
output = Dir.chdir 'test/fixtures/cli_record_test' do
|
18
|
-
`#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
19
|
-
end
|
20
|
-
|
21
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
22
|
-
assert File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} does not exist"
|
23
|
-
assert_equal 'Hello', output
|
24
|
-
output = JSON.parse(File.read(OUTPUT_FILENAME))
|
25
|
-
assert output['classMap'], 'Output should contain classMap'
|
26
|
-
assert output['events'], 'Output should contain events'
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_stats_to_file
|
30
|
-
Dir.chdir 'test/fixtures/cli_record_test' do
|
31
|
-
`#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
32
|
-
end
|
33
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
34
|
-
|
35
|
-
output = Dir.chdir 'test/fixtures/cli_record_test' do
|
36
|
-
`#{File.expand_path '../exe/appmap', __dir__} stats -o #{STATS_OUTPUT_FILENAME} #{OUTPUT_FILENAME}`.strip
|
37
|
-
end
|
38
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
39
|
-
assert_equal '', output
|
40
|
-
assert File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} does not exist"
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
def test_stats_text
|
45
|
-
Dir.chdir 'test/fixtures/cli_record_test' do
|
46
|
-
`#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
47
|
-
end
|
48
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
49
|
-
|
50
|
-
output = Dir.chdir 'test/fixtures/cli_record_test' do
|
51
|
-
`#{File.expand_path '../exe/appmap', __dir__} stats -o - #{OUTPUT_FILENAME}`.strip
|
52
|
-
end
|
53
|
-
|
54
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
55
|
-
assert_equal <<~OUTPUT.strip, output.strip
|
56
|
-
Class frequency:
|
57
|
-
----------------
|
58
|
-
1 Main
|
59
|
-
|
60
|
-
Method frequency:
|
61
|
-
----------------
|
62
|
-
1 Main.say_hello
|
63
|
-
OUTPUT
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_stats_json
|
67
|
-
Dir.chdir 'test/fixtures/cli_record_test' do
|
68
|
-
`#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
|
69
|
-
end
|
70
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
71
|
-
|
72
|
-
output = Dir.chdir 'test/fixtures/cli_record_test' do
|
73
|
-
`#{File.expand_path '../exe/appmap', __dir__} stats -f json -o - #{OUTPUT_FILENAME}`.strip
|
74
|
-
end
|
75
|
-
|
76
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
77
|
-
assert_equal <<~OUTPUT.strip, output.strip
|
78
|
-
{
|
79
|
-
"class_frequency": [
|
80
|
-
{
|
81
|
-
"name": "Main",
|
82
|
-
"count": 1
|
83
|
-
}
|
84
|
-
],
|
85
|
-
"method_frequency": [
|
86
|
-
{
|
87
|
-
"name": "Main.say_hello",
|
88
|
-
"count": 1
|
89
|
-
}
|
90
|
-
]
|
91
|
-
}
|
92
|
-
OUTPUT
|
93
|
-
end
|
94
|
-
|
95
|
-
def test_record_to_default_location
|
96
|
-
Dir.chdir 'test/fixtures/cli_record_test' do
|
97
|
-
system({ 'APPMAP_FILE' => OUTPUT_FILENAME }, "#{File.expand_path '../exe/appmap', __dir__} record ./lib/cli_record_test/main.rb")
|
98
|
-
end
|
99
|
-
|
100
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
101
|
-
assert File.file?(OUTPUT_FILENAME), 'appmap.json does not exist'
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_record_to_stdout
|
105
|
-
output = Dir.chdir 'test/fixtures/cli_record_test' do
|
106
|
-
`#{File.expand_path '../exe/appmap', __dir__} record -o - ./lib/cli_record_test/main.rb`
|
107
|
-
end
|
108
|
-
|
109
|
-
assert_equal 0, $CHILD_STATUS.exitstatus
|
110
|
-
# Event path
|
111
|
-
assert_includes output, %("path":"lib/cli_record_test/main.rb")
|
112
|
-
# Function location
|
113
|
-
assert_includes output, %("location":"lib/cli_record_test/main.rb:3")
|
114
|
-
assert !File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} should not exist"
|
115
|
-
end
|
116
|
-
end
|