appmap 0.94.1 → 0.95.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.nvmrc +1 -0
- data/.ruby-version +1 -0
- data/.rufo +2 -0
- data/.travis.yml +18 -11
- data/CHANGELOG.md +24 -0
- data/lib/appmap/builtin_hooks/activesupport.yml +1 -1
- data/lib/appmap/builtin_hooks/ruby.yml +2 -2
- data/lib/appmap/config.rb +6 -1
- data/lib/appmap/gem_hooks/rails.yml +1 -1
- data/lib/appmap/gem_hooks/railties.yml +1 -1
- data/lib/appmap/hook/method.rb +7 -8
- data/lib/appmap/hook.rb +125 -113
- data/lib/appmap/hook_log.rb +99 -0
- data/lib/appmap/util.rb +2 -1
- data/lib/appmap/version.rb +1 -1
- data/yarn.lock +1206 -417
- metadata +6 -3
- data/lib/appmap/gem_hooks/activerecord.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '097f96c8d929329dfaca50fcfca01f5328ca883750e68105ad67a9c2dcfc682d'
|
4
|
+
data.tar.gz: 52f7f9fc9eacb747dda7fe3c6c8c159c93ffea33898b5e7bbb5777babd5c2472
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fc3c30f6eb7516e434682b759b06e6f77130c10fe4cd40bce1d1dfba8c91946b8923d3dd757b9723cd33b6b6e5a36ce55e6930d9a983f07cb36e1b3d54208ae
|
7
|
+
data.tar.gz: c5688f98106a05c3faecfd2cd2b58efe484291a3134b965ed650219023e312c11c941de032112a72a32d68716d83afadee0388a0cd446b2dbe8bcc9c22073bf1
|
data/.gitignore
CHANGED
data/.nvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
14
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.2
|
data/.rufo
ADDED
data/.travis.yml
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
language: ruby
|
2
|
-
dist:
|
2
|
+
dist: focal
|
3
3
|
cache:
|
4
4
|
- bundle
|
5
5
|
- yarn
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
-
|
10
|
-
|
11
|
-
|
7
|
+
# Travis pre-installs some Ruby versions. You can see the current list
|
8
|
+
# by clicking on "Build system information" on line 7 which reveals a
|
9
|
+
# section called "Pre-installed Ruby versions". Use versions from
|
10
|
+
# that list to not install rvm and ruby each time.
|
11
|
+
rvm:
|
12
|
+
- 2.6.9
|
13
|
+
- 2.7.6
|
14
|
+
- 3.0.1 # doesn't show in pre-installed list
|
15
|
+
- 3.1.2
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
addons:
|
18
|
+
postgresql: "13"
|
19
|
+
apt:
|
20
|
+
packages:
|
21
|
+
- postgresql-13
|
22
|
+
- postgresql-client-13
|
17
23
|
|
18
24
|
before_deploy:
|
25
|
+
- nvm install lts/*
|
19
26
|
- |
|
20
27
|
npm i -g \
|
21
28
|
semantic-release \
|
@@ -28,4 +35,4 @@ deploy:
|
|
28
35
|
script: ./release.sh
|
29
36
|
on:
|
30
37
|
branch: master
|
31
|
-
condition: "$TRAVIS_RUBY_VERSION
|
38
|
+
condition: "$TRAVIS_RUBY_VERSION =~ ^3.0"
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## [0.95.1](https://github.com/getappmap/appmap-ruby/compare/v0.95.0...v0.95.1) (2023-01-26)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Don't match ending ) in swaggerize_path ([4bba178](https://github.com/getappmap/appmap-ruby/commit/4bba178e13b95274f590f07a9df8e529c0d1f836))
|
7
|
+
|
8
|
+
# [0.95.0](https://github.com/getappmap/appmap-ruby/compare/v0.94.1...v0.95.0) (2022-12-15)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Disable active_record hooks ([239986d](https://github.com/getappmap/appmap-ruby/commit/239986deded12e65384b441033180c30a3ffe698))
|
14
|
+
* Fix Array#pack hook ([2a8b2ed](https://github.com/getappmap/appmap-ruby/commit/2a8b2ed4d9fb047213e6a3fd427d846c5d312e2f))
|
15
|
+
* Fix name of rails7 test database ([6581b65](https://github.com/getappmap/appmap-ruby/commit/6581b65c088ee8415fd150d11c4ab6793173f0e2))
|
16
|
+
* Typo in label configuration ([999b25b](https://github.com/getappmap/appmap-ruby/commit/999b25b75057a7f6061cfbc9cdc04616f5aeb998))
|
17
|
+
|
18
|
+
|
19
|
+
### Features
|
20
|
+
|
21
|
+
* Enable deserialize.safe labels ([62be78a](https://github.com/getappmap/appmap-ruby/commit/62be78a0502d83c9b5a2b7d97abd17d144bd41c5))
|
22
|
+
* Enhance the hook log a lot ([c93d91c](https://github.com/getappmap/appmap-ruby/commit/c93d91c5be86b9195e14ef40741600c633638b40))
|
23
|
+
* Enhancing hook logging and profiling ([01dbe4b](https://github.com/getappmap/appmap-ruby/commit/01dbe4bb973b71fea0885f55d17b80d6b6b8fb6e))
|
24
|
+
|
1
25
|
## [0.94.1](https://github.com/getappmap/appmap-ruby/compare/v0.94.0...v0.94.1) (2022-11-23)
|
2
26
|
|
3
27
|
|
@@ -19,6 +19,6 @@
|
|
19
19
|
- method: ActiveSupport::MessageEncryptor#encrypt_and_sign
|
20
20
|
require_name: active_support/message_encryptor
|
21
21
|
force: true
|
22
|
-
- method: ActiveSupport::MessageEncryptor#
|
22
|
+
- method: ActiveSupport::MessageEncryptor#decrypt_and_verify
|
23
23
|
require_name: active_support/message_encryptor
|
24
24
|
force: true
|
data/lib/appmap/config.rb
CHANGED
@@ -8,6 +8,7 @@ require 'appmap/handler'
|
|
8
8
|
require 'appmap/service/guesser'
|
9
9
|
require 'appmap/swagger/configuration'
|
10
10
|
require 'appmap/depends/configuration'
|
11
|
+
require_relative './hook_log'
|
11
12
|
|
12
13
|
module AppMap
|
13
14
|
class Config
|
@@ -499,7 +500,11 @@ module AppMap
|
|
499
500
|
|
500
501
|
def never_hook?(cls, method)
|
501
502
|
_, separator, = ::AppMap::Hook.qualify_method_name(method)
|
502
|
-
|
503
|
+
if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
|
504
|
+
HookLog.log "Hooking of #{method} disabled by configuration" if HookLog.enabled?
|
505
|
+
return true
|
506
|
+
end
|
507
|
+
false
|
503
508
|
end
|
504
509
|
end
|
505
510
|
end
|
@@ -1,2 +1,2 @@
|
|
1
1
|
- method: Rails::Application#config_for
|
2
|
-
|
2
|
+
label: deserialize.safe
|
@@ -1,2 +1,2 @@
|
|
1
1
|
- method: Rails::Application::Configuration#database_configuration
|
2
|
-
|
2
|
+
label: deserialize.safe
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -38,13 +38,13 @@ module AppMap
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def activate
|
41
|
-
if
|
41
|
+
if HookLog.enabled?
|
42
42
|
msg = if method_display_name
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
"#{method_display_name}"
|
44
|
+
else
|
45
|
+
"#{hook_method.name} (class resolution deferred)"
|
46
|
+
end
|
47
|
+
HookLog.log "Hooking #{msg} at line #{(hook_method.source_location || []).join(':')}"
|
48
48
|
end
|
49
49
|
|
50
50
|
hook_method_parameters = hook_method.parameters.dup.freeze
|
@@ -65,8 +65,7 @@ module AppMap
|
|
65
65
|
protected
|
66
66
|
|
67
67
|
def defining_class(hook_class)
|
68
|
-
cls =
|
69
|
-
if RUBY_MAJOR_VERSION == 2 && RUBY_MINOR_VERSION <= 5
|
68
|
+
cls = if RUBY_MAJOR_VERSION == 2 && RUBY_MINOR_VERSION <= 5
|
70
69
|
hook_class
|
71
70
|
.ancestors
|
72
71
|
.select { |cls| cls.method_defined?(hook_method.name) }
|
data/lib/appmap/hook.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'English'
|
4
|
+
require_relative './hook_log'
|
4
5
|
|
5
6
|
module AppMap
|
6
7
|
class Hook
|
7
|
-
LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
8
|
-
LOG_HOOK = (ENV['DEBUG_HOOK'] == 'true')
|
9
|
-
|
10
8
|
OBJECT_INSTANCE_METHODS = %i[! != !~ <=> == === =~ __id__ __send__ class clone define_singleton_method display dup
|
11
9
|
enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods taint tainted? tap then to_enum to_s to_h to_a trust untaint untrust untrusted? yield_self].freeze
|
12
10
|
OBJECT_STATIC_METHODS = %i[! != !~ < <= <=> == === =~ > >= __id__ __send__ alias_method allocate ancestors attr
|
13
11
|
attr_accessor attr_reader attr_writer autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_method define_singleton_method deprecate_constant display dup enum_for eql? equal? extend freeze frozen? hash include include? included_modules inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method method_defined? methods module_eval module_exec name new nil? object_id prepend private_class_method private_constant private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_constant public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable remove_instance_variable remove_method respond_to? send singleton_class singleton_class? singleton_method singleton_methods superclass taint tainted? tap then to_enum to_s trust undef_method untaint untrust untrusted? yield_self].freeze
|
14
|
-
SLOW_PACKAGE_THRESHOLD = 0.
|
12
|
+
SLOW_PACKAGE_THRESHOLD = 0.001
|
15
13
|
|
16
14
|
@unbound_method_arity = ::UnboundMethod.instance_method(:arity)
|
17
15
|
@method_arity = ::Method.instance_method(:arity)
|
@@ -21,7 +19,7 @@ module AppMap
|
|
21
19
|
Mutex.new.synchronize do
|
22
20
|
@hook_builtins = true if @hook_builtins.nil?
|
23
21
|
|
24
|
-
|
22
|
+
next false unless @hook_builtins
|
25
23
|
|
26
24
|
@hook_builtins = false
|
27
25
|
true
|
@@ -41,9 +39,9 @@ module AppMap
|
|
41
39
|
def qualify_method_name(method)
|
42
40
|
if method.owner.singleton_class?
|
43
41
|
class_name = singleton_method_owner_name(method)
|
44
|
-
[
|
42
|
+
[class_name, '.', method.name]
|
45
43
|
else
|
46
|
-
[
|
44
|
+
[method.owner.name, '#', method.name]
|
47
45
|
end
|
48
46
|
end
|
49
47
|
end
|
@@ -69,19 +67,23 @@ module AppMap
|
|
69
67
|
@slow_packages = Set.new
|
70
68
|
|
71
69
|
if ENV['APPMAP_PROFILE_HOOK'] == 'true'
|
70
|
+
dump_times = lambda do
|
71
|
+
@module_load_times
|
72
|
+
.keys
|
73
|
+
.select { |key| !@slow_packages.member?(key) }
|
74
|
+
.each do |key|
|
75
|
+
elapsed = @module_load_times[key]
|
76
|
+
if elapsed >= SLOW_PACKAGE_THRESHOLD
|
77
|
+
@slow_packages.add(key)
|
78
|
+
warn "AppMap: Package #{key} took #{@module_load_times[key]} seconds to hook"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
at_exit &dump_times
|
72
84
|
Thread.new do
|
73
|
-
sleep 1
|
74
85
|
while true
|
75
|
-
|
76
|
-
.keys
|
77
|
-
.select { |key| !@slow_packages.member?(key) }
|
78
|
-
.each do |key|
|
79
|
-
elapsed = @module_load_times[key]
|
80
|
-
if elapsed >= SLOW_PACKAGE_THRESHOLD
|
81
|
-
@slow_packages.add(key)
|
82
|
-
warn "AppMap: Package #{key} took #{@module_load_times[key]} seconds to hook"
|
83
|
-
end
|
84
|
-
end
|
86
|
+
dump_times.call
|
85
87
|
sleep 5
|
86
88
|
end
|
87
89
|
end
|
@@ -99,45 +101,51 @@ module AppMap
|
|
99
101
|
hook_loaded_code = lambda do |hooks_by_class, builtin|
|
100
102
|
hooks_by_class.each do |class_name, hooks|
|
101
103
|
Array(hooks).each do |hook|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
104
|
+
HookLog.builtin class_name do
|
105
|
+
if builtin && hook.package.require_name && hook.package.require_name != 'ruby'
|
106
|
+
begin
|
107
|
+
require hook.package.require_name
|
108
|
+
rescue
|
109
|
+
HookLog.load_error hook.package.require_name, "Unable to require #{hook.package.require_name}: #{$!}" if HookLog.enabled?
|
110
|
+
next
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
111
114
|
begin
|
112
115
|
base_cls = Object.const_get class_name
|
113
116
|
rescue NameError
|
117
|
+
HookLog.load_error class_name, "Class #{class_name} not found in global scope" if HookLog.enabled?
|
114
118
|
next
|
115
119
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
methods << [
|
134
|
-
methods << [
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
methods.
|
120
|
+
|
121
|
+
Array(hook.method_names).each do |method_name|
|
122
|
+
method_name = method_name.to_sym
|
123
|
+
|
124
|
+
hook_method = lambda do |entry|
|
125
|
+
cls, method = entry
|
126
|
+
next if config.never_hook?(cls, method)
|
127
|
+
|
128
|
+
hook.package.handler_class.new(hook.package, cls, method).activate
|
129
|
+
end
|
130
|
+
|
131
|
+
methods = []
|
132
|
+
# irb(main):001:0> Kernel.public_instance_method(:system)
|
133
|
+
# (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError)
|
134
|
+
if base_cls == Kernel
|
135
|
+
methods << [base_cls, base_cls.instance_method(method_name)] rescue nil
|
136
|
+
end
|
137
|
+
methods << [base_cls, base_cls.public_instance_method(method_name)] rescue nil
|
138
|
+
methods << [base_cls, base_cls.protected_instance_method(method_name)] rescue nil
|
139
|
+
if base_cls.respond_to?(:singleton_class)
|
140
|
+
methods << [base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name)] rescue nil
|
141
|
+
methods << [base_cls.singleton_class, base_cls.singleton_class.protected_instance_method(method_name)] rescue nil
|
142
|
+
end
|
143
|
+
methods.compact!
|
144
|
+
if methods.empty?
|
145
|
+
HookLog.load_error [ base_cls.name, method_name ].join('[#.]'), "Method #{method_name} not found on #{base_cls.name}" if HookLog.enabled?
|
146
|
+
else
|
147
|
+
methods.each(&hook_method)
|
148
|
+
end
|
141
149
|
end
|
142
150
|
end
|
143
151
|
end
|
@@ -145,89 +153,93 @@ module AppMap
|
|
145
153
|
end
|
146
154
|
|
147
155
|
hook_loaded_code.(config.builtin_hooks, true)
|
148
|
-
hook_loaded_code.(config.gem_hooks, false)
|
149
156
|
end
|
150
157
|
|
151
158
|
protected
|
152
159
|
|
153
160
|
def trace_location(trace_point)
|
154
|
-
[
|
161
|
+
[trace_point.path, trace_point.lineno].join(':')
|
155
162
|
end
|
156
163
|
|
157
164
|
def trace_end(trace_point)
|
158
165
|
location = trace_location(trace_point)
|
159
|
-
warn "Class or module ends at location #{location}" if Hook::LOG || Hook::LOG_HOOK
|
160
166
|
return unless @trace_locations.add?(location)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
cls = trace_point.self
|
171
|
-
|
172
|
-
instance_methods = cls.public_instance_methods(false) + cls.protected_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
173
|
-
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
174
|
-
class_methods = begin
|
175
|
-
if cls.respond_to?(:singleton_class)
|
176
|
-
cls.singleton_class.public_instance_methods(false) + cls.singleton_class.protected_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
177
|
-
else
|
178
|
-
[]
|
167
|
+
HookLog.on_load location do
|
168
|
+
path = trace_point.path
|
169
|
+
enabled = !@notrace_paths.member?(path) && config.path_enabled?(path)
|
170
|
+
unless enabled
|
171
|
+
HookLog.log 'Not hooking - path is not enabled' if HookLog.enabled?
|
172
|
+
@notrace_paths << path
|
173
|
+
next
|
179
174
|
end
|
180
|
-
rescue NameError
|
181
|
-
[]
|
182
|
-
end
|
183
175
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
176
|
+
cls = trace_point.self
|
177
|
+
|
178
|
+
instance_methods = cls.public_instance_methods(false) + cls.protected_instance_methods(false) - OBJECT_INSTANCE_METHODS
|
179
|
+
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
180
|
+
class_methods = begin
|
181
|
+
if cls.respond_to?(:singleton_class)
|
182
|
+
cls.singleton_class.public_instance_methods(false) + cls.singleton_class.protected_instance_methods(false) - instance_methods - OBJECT_STATIC_METHODS
|
183
|
+
else
|
184
|
+
[]
|
192
185
|
end
|
186
|
+
rescue NameError
|
187
|
+
[]
|
188
|
+
end
|
193
189
|
|
194
|
-
|
195
|
-
|
196
|
-
|
190
|
+
hook = lambda do |hook_cls|
|
191
|
+
lambda do |method_id|
|
192
|
+
method = begin
|
193
|
+
hook_cls.instance_method(method_id)
|
194
|
+
rescue NameError
|
195
|
+
HookLog.load_error [ hook_cls, method_id ].join('[#.]'), "Method #{hook_cls} #{method_id} is not accessible: #{$!}" if HookLog.enabled?
|
196
|
+
next
|
197
|
+
end
|
197
198
|
|
198
|
-
|
199
|
-
|
200
|
-
|
199
|
+
package = config.lookup_package(hook_cls, method)
|
200
|
+
# doing this check first returned early in 98.7% of cases in sample_app_6th_ed
|
201
|
+
next unless package
|
201
202
|
|
202
|
-
|
203
|
+
# Don't try and trace the AppMap methods or there will be
|
204
|
+
# a stack overflow in the defined hook method.
|
205
|
+
next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
|
203
206
|
|
204
|
-
|
207
|
+
next if method_id == :call
|
205
208
|
|
206
|
-
|
209
|
+
next if self.class.already_hooked?(method)
|
207
210
|
|
208
|
-
|
209
|
-
# Skip methods that have no instruction sequence, as they are either have no body or they are or native.
|
210
|
-
# TODO: Figure out how to tell the difference?
|
211
|
-
next unless disasm
|
211
|
+
HookLog.log "Examining #{hook_cls} #{method.name}" if HookLog.enabled?
|
212
212
|
|
213
|
-
|
213
|
+
disasm = RubyVM::InstructionSequence.disasm(method)
|
214
|
+
# Skip methods that have no instruction sequence, as they are either have no body or they are or native.
|
215
|
+
# TODO: Figure out how to tell the difference?
|
216
|
+
next unless disasm
|
217
|
+
|
218
|
+
package.handler_class.new(package, hook_cls, method).activate
|
219
|
+
end
|
214
220
|
end
|
215
|
-
end
|
216
221
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
222
|
+
start = Time.now
|
223
|
+
instance_methods.each(&hook.(cls))
|
224
|
+
begin
|
225
|
+
# NoMethodError: private method `singleton_class' called for Rack::MiniProfiler:Class
|
226
|
+
class_methods.each(&hook.(cls.singleton_class)) if cls.respond_to?(:singleton_class)
|
227
|
+
rescue NameError
|
228
|
+
# NameError:
|
229
|
+
# uninitialized constant Faraday::Connection
|
230
|
+
warn "NameError in #{__FILE__}: #{$!.message}"
|
231
|
+
end
|
232
|
+
elapsed = Time.now - start
|
233
|
+
if location.index(Bundler.bundle_path.to_s) == 0
|
234
|
+
package_tokens = location[Bundler.bundle_path.to_s.length + 1..-1].split('/')
|
235
|
+
@module_load_times[package_tokens[1]] += elapsed
|
236
|
+
else
|
237
|
+
file_path = location[Dir.pwd.length + 1..-1]
|
238
|
+
if file_path
|
239
|
+
location = file_path.split('/', 3)[0..1].join('/')
|
240
|
+
@module_load_times[location] += elapsed
|
241
|
+
end
|
242
|
+
end
|
231
243
|
end
|
232
244
|
end
|
233
245
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module AppMap
|
2
|
+
class HookLog
|
3
|
+
LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
|
4
|
+
LOG_HOOK = (ENV['DEBUG_HOOK'] == 'true' || ENV['APPMAP_LOG_HOOK'] == 'true')
|
5
|
+
LOG_HOOK_FILE = (ENV['APPMAP_LOG_HOOK_FILE'] || 'appmap_hook.log')
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@file_handle = self.class.send :open_log_file
|
9
|
+
@elapsed = Hash.new { |h, k| h[k] = [] }
|
10
|
+
|
11
|
+
at_exit do
|
12
|
+
@file_handle.puts 'Elapsed time:'
|
13
|
+
@elapsed.keys.each do |k|
|
14
|
+
@file_handle.puts "#{k}:\t#{@elapsed[k].sum}"
|
15
|
+
end
|
16
|
+
@file_handle.flush
|
17
|
+
@file_handle.close
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_time(timer)
|
22
|
+
@elapsed[timer] << [Util.gettime]
|
23
|
+
end
|
24
|
+
|
25
|
+
def end_time(timer)
|
26
|
+
unless @elapsed[timer].last.is_a?(Array)
|
27
|
+
warn "AppMap: Unbalanced timing data in hook log"
|
28
|
+
@elapsed[timer].pop
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
@elapsed[timer][-1] = Util.gettime - @elapsed[timer].last[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def enabled?
|
37
|
+
LOG || LOG_HOOK
|
38
|
+
end
|
39
|
+
|
40
|
+
def builtin(class_name, &block)
|
41
|
+
return yield unless enabled?
|
42
|
+
|
43
|
+
begin
|
44
|
+
log "eager\tbegin\tInitiating eager hook for #{class_name}"
|
45
|
+
@hook_log.start_time :eager
|
46
|
+
|
47
|
+
yield
|
48
|
+
ensure
|
49
|
+
@hook_log.end_time :eager
|
50
|
+
log "eager\tend\tCompleted eager hook for #{class_name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_load(location, &block)
|
55
|
+
return yield unless enabled?
|
56
|
+
|
57
|
+
begin
|
58
|
+
log "on-load\tbegin\tInitiating on-load hook for class or module defined at location #{location}"
|
59
|
+
@hook_log.start_time :on_load
|
60
|
+
|
61
|
+
yield
|
62
|
+
ensure
|
63
|
+
@hook_log.end_time :on_load
|
64
|
+
log "on-load\tend\tCompleted on-load hook for location #{location}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_error(name, msg)
|
69
|
+
log "load_error\t#{name}\t#{msg}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def log(msg)
|
73
|
+
unless HookLog.enabled?
|
74
|
+
warn "AppMap: HookLog is not enabled. Disregarding message #{msg}"
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
@hook_log ||= HookLog.new
|
79
|
+
@hook_log.log msg
|
80
|
+
end
|
81
|
+
|
82
|
+
protected def open_log_file
|
83
|
+
if LOG_HOOK_FILE == 'stderr'
|
84
|
+
$stderr
|
85
|
+
else
|
86
|
+
File.open(LOG_HOOK_FILE, 'w')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def log(msg)
|
92
|
+
if LOG_HOOK_FILE == 'stderr'
|
93
|
+
msg = "AppMap: #{msg}"
|
94
|
+
end
|
95
|
+
msg = "#{Util.gettime}\t#{msg}"
|
96
|
+
@file_handle.puts(msg)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/appmap/util.rb
CHANGED