appmap 0.79.0 → 0.80.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|