appmap 0.79.0 → 0.80.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/CHANGELOG.md +16 -0
- data/lib/appmap/agent.rb +8 -0
- data/lib/appmap/event.rb +19 -12
- data/lib/appmap/gem_hooks/actionpack.yml +6 -0
- data/lib/appmap/handler/rails/render_handler.rb +29 -0
- data/lib/appmap/handler/rails/request_handler.rb +13 -11
- data/lib/appmap/handler/rails/sql_handler.rb +43 -1
- data/lib/appmap/handler.rb +3 -0
- data/lib/appmap/hook/method.rb +0 -1
- data/lib/appmap/version.rb +1 -1
- data/spec/handler/eval_spec.rb +1 -1
- data/spec/hook_spec.rb +5 -0
- data/spec/rails_recording_spec.rb +3 -1
- data/test/expectations/openssl_test_key_sign2-3.1.json +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb003b96b494282db035877377124bc3736d78320a64e479b7caa0b8df49063a
|
4
|
+
data.tar.gz: 3aab36a97c90485e1c7f77bed373c4c572b46a5c8c93ac4aa012db3becf201f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8637610caacf145c5badb4de3eb399858a231d3de9a794f86e8fcb3d14801d3b5309453da7e9b799d47ddb9c4eed55bbd97bf2f77eabe7e605e95089b560c5bf
|
7
|
+
data.tar.gz: bba8d4f2f8ffa5b0e5d8b113657324561dfa71e7b7128273631d62d426c8e12339e305d9fb0fe0d05244685a1ddc1b4be4bfbe5a4b44607a2df96e5d60749557
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# [0.80.0](https://github.com/applandinc/appmap-ruby/compare/v0.79.0...v0.80.0) (2022-04-08)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Don't record SQL within an existing event ([ff37a69](https://github.com/applandinc/appmap-ruby/commit/ff37a69af1af02263df216e49aea0d0954b93925))
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* Env var to EXPLAIN queries ([740be75](https://github.com/applandinc/appmap-ruby/commit/740be75c2bc59e343d67ecf86b7715e61cddadba))
|
12
|
+
* Optionally record parameter schema ([b7f41b1](https://github.com/applandinc/appmap-ruby/commit/b7f41b15a4556a0ce78650a6a77301d365632bb8))
|
13
|
+
* query_plan is available whether a current transaction exists or not ([6edf774](https://github.com/applandinc/appmap-ruby/commit/6edf774fea858d825c4b971be2c4c15db1652446))
|
14
|
+
* Record parameter and return value size ([6e69754](https://github.com/applandinc/appmap-ruby/commit/6e697543cb421378832492e286f972dc4cb1e1aa))
|
15
|
+
* Save render return value to a thread-local ([f9d1e3f](https://github.com/applandinc/appmap-ruby/commit/f9d1e3f0aa9972482ff77233d38220104515b1d6))
|
16
|
+
|
1
17
|
# [0.79.0](https://github.com/applandinc/appmap-ruby/compare/v0.78.0...v0.79.0) (2022-04-06)
|
2
18
|
|
3
19
|
|
data/lib/appmap/agent.rb
CHANGED
@@ -100,5 +100,13 @@ module AppMap
|
|
100
100
|
@metadata ||= Metadata.detect.freeze
|
101
101
|
Util.deep_dup(@metadata)
|
102
102
|
end
|
103
|
+
|
104
|
+
def parameter_schema?
|
105
|
+
ENV['APPMAP_PARAMETER_SCHEMA'] == 'true'
|
106
|
+
end
|
107
|
+
|
108
|
+
def explain_queries?
|
109
|
+
ENV['APPMAP_EXPLAIN_QUERIES'] == 'true'
|
110
|
+
end
|
103
111
|
end
|
104
112
|
end
|
data/lib/appmap/event.rb
CHANGED
@@ -60,16 +60,18 @@ module AppMap
|
|
60
60
|
final ? value_string : encode_display_string(value_string)
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
def add_schema(param, value)
|
64
|
+
begin
|
65
|
+
if value.respond_to?(:keys)
|
66
|
+
param[:properties] = value.keys.map { |key| { name: key, class: best_class_name(value[key]) } }
|
67
|
+
elsif value.respond_to?(:first) && value.first
|
68
|
+
if value.first != value
|
69
|
+
add_schema param, value.first
|
70
|
+
end
|
71
|
+
end
|
72
|
+
rescue
|
73
|
+
warn "Error in add_schema(#{value.class})", $!
|
70
74
|
end
|
71
|
-
rescue
|
72
|
-
nil
|
73
75
|
end
|
74
76
|
|
75
77
|
# Heuristic for dynamically defined class whose name can be nil
|
@@ -221,7 +223,9 @@ module AppMap
|
|
221
223
|
object_id: value.__id__,
|
222
224
|
value: display_string(value),
|
223
225
|
kind: param_type
|
224
|
-
}
|
226
|
+
}.tap do |param|
|
227
|
+
param[:size] = value.size if value.respond_to?(:size) && value.is_a?(Enumerable)
|
228
|
+
end
|
225
229
|
end
|
226
230
|
event.receiver = {
|
227
231
|
class: best_class_name(receiver),
|
@@ -276,7 +280,7 @@ module AppMap
|
|
276
280
|
attr_accessor :return_value, :exceptions
|
277
281
|
|
278
282
|
class << self
|
279
|
-
def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new)
|
283
|
+
def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new, parameter_schema: false)
|
280
284
|
event ||= MethodReturn.new
|
281
285
|
event.tap do |_|
|
282
286
|
if return_value
|
@@ -284,7 +288,10 @@ module AppMap
|
|
284
288
|
class: best_class_name(return_value),
|
285
289
|
value: display_string(return_value),
|
286
290
|
object_id: return_value.__id__
|
287
|
-
}
|
291
|
+
}.tap do |param|
|
292
|
+
param[:size] = return_value.size if return_value.respond_to?(:size) && return_value.is_a?(Enumerable)
|
293
|
+
add_schema param, return_value if parameter_schema && !exception
|
294
|
+
end
|
288
295
|
end
|
289
296
|
if exception
|
290
297
|
next_exception = exception
|
@@ -43,3 +43,9 @@
|
|
43
43
|
- ActionController::Instrumentation#redirect_to
|
44
44
|
label: mvc.controller
|
45
45
|
require_name: action_controller
|
46
|
+
- methods:
|
47
|
+
- AbstractController::Rendering#render_to_body
|
48
|
+
- ActionController::Renderers#render_to_body
|
49
|
+
label: mvc.render
|
50
|
+
handler_class: AppMap::Handler::Rails::RenderHandler
|
51
|
+
require_name: action_controller
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'appmap/handler/function'
|
2
|
+
|
3
|
+
module AppMap
|
4
|
+
module Handler
|
5
|
+
module Rails
|
6
|
+
class RenderHandler < AppMap::Handler::Function
|
7
|
+
def handle_call(receiver, args)
|
8
|
+
options, _ = args
|
9
|
+
# TODO: :file, :xml
|
10
|
+
# https://guides.rubyonrails.org/v5.1/layouts_and_rendering.html
|
11
|
+
if options[:json]
|
12
|
+
Thread.current[TEMPLATE_RENDER_FORMAT] = :json
|
13
|
+
end
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
19
|
+
if Thread.current[TEMPLATE_RENDER_FORMAT] == :json
|
20
|
+
Thread.current[TEMPLATE_RENDER_VALUE] = JSON.parse(return_value) rescue nil
|
21
|
+
end
|
22
|
+
Thread.current[TEMPLATE_RENDER_FORMAT] = nil
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -7,6 +7,7 @@ require 'appmap/util'
|
|
7
7
|
module AppMap
|
8
8
|
module Handler
|
9
9
|
module Rails
|
10
|
+
|
10
11
|
module RequestHandler
|
11
12
|
class HTTPServerRequest < AppMap::Event::MethodEvent
|
12
13
|
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :headers
|
@@ -46,8 +47,7 @@ module AppMap
|
|
46
47
|
value: self.class.display_string(val),
|
47
48
|
object_id: val.__id__,
|
48
49
|
}.tap do |message|
|
49
|
-
|
50
|
-
message[:properties] = properties if properties
|
50
|
+
AppMap::Event::MethodEvent.add_schema message, val
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -67,16 +67,16 @@ module AppMap
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
class HTTPServerResponse < AppMap::Event::
|
70
|
+
class HTTPServerResponse < AppMap::Event::MethodReturn
|
71
71
|
attr_accessor :status, :headers
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
class << self
|
74
|
+
def build_from_invocation(parent_id, return_value, elapsed, response, event: HTTPServerResponse.new)
|
75
|
+
event ||= HTTPServerResponse.new
|
76
|
+
event.status = response.status
|
77
|
+
event.headers = response.headers.dup
|
78
|
+
AppMap::Event::MethodReturn.build_from_invocation parent_id, return_value, nil, elapsed: elapsed, event: event, parameter_schema: true
|
79
|
+
end
|
80
80
|
end
|
81
81
|
|
82
82
|
def to_h
|
@@ -108,7 +108,9 @@ module AppMap
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def after_hook(receiver, call_event, elapsed, *)
|
111
|
-
|
111
|
+
return_value = Thread.current[TEMPLATE_RENDER_VALUE]
|
112
|
+
Thread.current[TEMPLATE_RENDER_VALUE] = nil
|
113
|
+
return_event = HTTPServerResponse.build_from_invocation call_event.id, return_value, elapsed, receiver.response
|
112
114
|
AppMap.tracing.record_event return_event
|
113
115
|
end
|
114
116
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'appmap/event'
|
4
|
+
require 'appmap/hook/method'
|
4
5
|
|
5
6
|
module AppMap
|
6
7
|
module Handler
|
@@ -21,6 +22,7 @@ module AppMap
|
|
21
22
|
sql: payload[:sql],
|
22
23
|
database_type: payload[:database_type]
|
23
24
|
}.tap do |sql_query|
|
25
|
+
sql_query[:query_plan] = payload[:query_plan] if payload[:query_plan]
|
24
26
|
%i[server_version].each do |attribute|
|
25
27
|
sql_query[attribute] = payload[attribute] if payload[attribute]
|
26
28
|
end
|
@@ -43,6 +45,36 @@ module AppMap
|
|
43
45
|
def examine(payload, sql:)
|
44
46
|
return unless (examiner = build_examiner)
|
45
47
|
|
48
|
+
in_transaction = examiner.in_transaction?
|
49
|
+
|
50
|
+
if AppMap.explain_queries? && examiner.database_type == :postgres
|
51
|
+
if sql =~ /\A(SELECT|INSERT|UPDATE|DELETE|WITH)/i
|
52
|
+
savepoint_established = \
|
53
|
+
begin
|
54
|
+
tx_query = in_transaction ? 'SAVEPOINT appmap_sql_examiner' : 'BEGIN TRANSACTION'
|
55
|
+
examiner.execute_query tx_query
|
56
|
+
true
|
57
|
+
rescue
|
58
|
+
# Probably: Sequel::DatabaseError: PG::InFailedSqlTransaction
|
59
|
+
warn $!
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
if savepoint_established
|
64
|
+
plan = nil
|
65
|
+
begin
|
66
|
+
plan = examiner.execute_query(%(EXPLAIN #{sql}))
|
67
|
+
payload[:query_plan] = plan.map { |line| line[:'QUERY PLAN'] }.join("\n")
|
68
|
+
rescue
|
69
|
+
warn "(appmap) Error explaining query: #{$!}"
|
70
|
+
ensure
|
71
|
+
tx_query = in_transaction ? 'ROLLBACK TO SAVEPOINT appmap_sql_examiner' : 'ROLLBACK'
|
72
|
+
examiner.execute_query tx_query
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
46
78
|
payload[:server_version] = examiner.server_version
|
47
79
|
payload[:database_type] = examiner.database_type.to_s
|
48
80
|
end
|
@@ -67,6 +99,10 @@ module AppMap
|
|
67
99
|
Sequel::Model.db.database_type.to_sym
|
68
100
|
end
|
69
101
|
|
102
|
+
def in_transaction?
|
103
|
+
Sequel::Model.db.in_transaction?
|
104
|
+
end
|
105
|
+
|
70
106
|
def execute_query(sql)
|
71
107
|
Sequel::Model.db[sql].all
|
72
108
|
end
|
@@ -93,8 +129,12 @@ module AppMap
|
|
93
129
|
type
|
94
130
|
end
|
95
131
|
|
132
|
+
def in_transaction?
|
133
|
+
ActiveRecord::Base.connection.open_transactions > 0
|
134
|
+
end
|
135
|
+
|
96
136
|
def execute_query(sql)
|
97
|
-
ActiveRecord::Base.connection.execute(sql).
|
137
|
+
ActiveRecord::Base.connection.execute(sql).to_a
|
98
138
|
end
|
99
139
|
end
|
100
140
|
end
|
@@ -102,6 +142,8 @@ module AppMap
|
|
102
142
|
def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
|
103
143
|
return if AppMap.tracing.empty?
|
104
144
|
|
145
|
+
return if Thread.current[AppMap::Hook::Method::HOOK_DISABLE_KEY] == true
|
146
|
+
|
105
147
|
reentry_key = "#{self.class.name}#call"
|
106
148
|
return if Thread.current[reentry_key] == true
|
107
149
|
|
data/lib/appmap/handler.rb
CHANGED
@@ -5,6 +5,9 @@ require 'active_support/inflector/methods'
|
|
5
5
|
module AppMap
|
6
6
|
# Specific hook handler classes and general related utilities.
|
7
7
|
module Handler
|
8
|
+
TEMPLATE_RENDER_FORMAT = 'appmap.handler.template.return_value_format'
|
9
|
+
TEMPLATE_RENDER_VALUE = 'appmap.handler.template.return_value'
|
10
|
+
|
8
11
|
# Try to find handler module with a given name.
|
9
12
|
#
|
10
13
|
# If the module is not loaded, tries to require the appropriate file
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -24,7 +24,6 @@ module AppMap
|
|
24
24
|
attr_reader :hook_package, :hook_class, :hook_method, :parameters, :arity
|
25
25
|
|
26
26
|
HOOK_DISABLE_KEY = 'AppMap::Hook.disable'
|
27
|
-
private_constant :HOOK_DISABLE_KEY
|
28
27
|
|
29
28
|
def initialize(hook_package, hook_class, hook_method)
|
30
29
|
@hook_package = hook_package
|
data/lib/appmap/version.rb
CHANGED
data/spec/handler/eval_spec.rb
CHANGED
@@ -32,7 +32,7 @@ describe 'AppMap::Handler::Eval' do
|
|
32
32
|
expect(events[0]).to match hash_including \
|
33
33
|
defined_class: 'Kernel',
|
34
34
|
method_id: 'eval',
|
35
|
-
parameters: [{ class: 'Array', kind: :rest, name: 'arg', value: '[12]' }]
|
35
|
+
parameters: [{ class: 'Array', kind: :rest, name: 'arg', size: 1, value: '[12]' }]
|
36
36
|
end
|
37
37
|
|
38
38
|
# a la Ruby 2.6.3 ruby-token.rb
|
data/spec/hook_spec.rb
CHANGED
@@ -491,6 +491,7 @@ describe 'AppMap class Hooking' do
|
|
491
491
|
:class: Array
|
492
492
|
:value: "[4, 5]"
|
493
493
|
:kind: :rest
|
494
|
+
:size: 2
|
494
495
|
- :name: :kw1
|
495
496
|
:class: String
|
496
497
|
:value: one
|
@@ -503,6 +504,7 @@ describe 'AppMap class Hooking' do
|
|
503
504
|
:class: Hash
|
504
505
|
:value: "{:kw3=>:three}"
|
505
506
|
:kind: :keyrest
|
507
|
+
:size: 1
|
506
508
|
:receiver:
|
507
509
|
:class: InstanceMethod
|
508
510
|
:value: Instance Method fixture
|
@@ -1139,6 +1141,7 @@ describe 'AppMap class Hooking' do
|
|
1139
1141
|
:class: Array
|
1140
1142
|
:value: "[foo]"
|
1141
1143
|
:kind: :rest
|
1144
|
+
:size: 1
|
1142
1145
|
- :name: :kw1
|
1143
1146
|
:class: String
|
1144
1147
|
:value: kw1
|
@@ -1151,6 +1154,7 @@ describe 'AppMap class Hooking' do
|
|
1151
1154
|
:class: Hash
|
1152
1155
|
:value: "{}"
|
1153
1156
|
:kind: :keyrest
|
1157
|
+
:size: 0
|
1154
1158
|
:receiver:
|
1155
1159
|
:class: ReportParameters
|
1156
1160
|
:value: ReportParameters
|
@@ -1160,6 +1164,7 @@ describe 'AppMap class Hooking' do
|
|
1160
1164
|
:return_value:
|
1161
1165
|
:class: Array
|
1162
1166
|
:value: "[[:rest, :args], [:keyreq, :kw1], [:key, :kw2], [:keyrest, :kws]]"
|
1167
|
+
:size: 4
|
1163
1168
|
YAML
|
1164
1169
|
parameters = [[:rest, :args], [:keyreq, :kw1], [:key, :kw2], [:keyrest, :kws]]
|
1165
1170
|
test_hook_behavior 'spec/fixtures/hook/report_parameters.rb', events do
|
@@ -54,7 +54,8 @@ describe 'Rails' do
|
|
54
54
|
'http_server_response' => hash_including(
|
55
55
|
'status_code' => 201,
|
56
56
|
'headers' => hash_including('Content-Type' => 'application/json; charset=utf-8'),
|
57
|
-
)
|
57
|
+
),
|
58
|
+
'return_value' => hash_including('class' => 'Hash', 'object_id' => Integer, 'properties' => include({'name' => 'login', 'class' => 'String'})),
|
58
59
|
)
|
59
60
|
)
|
60
61
|
end
|
@@ -72,6 +73,7 @@ describe 'Rails' do
|
|
72
73
|
'name' => 'params',
|
73
74
|
'class' => 'ActiveSupport::HashWithIndifferentAccess',
|
74
75
|
'object_id' => Integer,
|
76
|
+
'size' => 1,
|
75
77
|
'value' => '{login=>alice}',
|
76
78
|
'kind' => 'req'
|
77
79
|
),
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appmap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.80.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -394,6 +394,7 @@ files:
|
|
394
394
|
- lib/appmap/handler/eval.rb
|
395
395
|
- lib/appmap/handler/function.rb
|
396
396
|
- lib/appmap/handler/net_http.rb
|
397
|
+
- lib/appmap/handler/rails/render_handler.rb
|
397
398
|
- lib/appmap/handler/rails/request_handler.rb
|
398
399
|
- lib/appmap/handler/rails/sql_handler.rb
|
399
400
|
- lib/appmap/handler/rails/template.rb
|