appmap 0.77.4 → 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/.rubocop.yml +1 -0
- data/.travis.yml +4 -16
- data/CHANGELOG.md +30 -0
- data/{spec/fixtures/rails5_users_app/Dockerfile.pg → Dockerfile.pg} +0 -0
- data/README.md +14 -44
- data/README_CI.md +0 -7
- data/Rakefile +12 -150
- data/appmap.gemspec +3 -2
- data/docker-compose.yml +10 -0
- data/ext/appmap/appmap.c +21 -2
- data/lib/appmap/agent.rb +8 -0
- data/lib/appmap/builtin_hooks/ruby.yml +6 -3
- data/lib/appmap/event.rb +19 -12
- data/lib/appmap/gem_hooks/actionpack.yml +6 -0
- data/lib/appmap/handler/eval.rb +41 -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/config_spec.rb +1 -1
- data/spec/depends/api_spec.rb +13 -5
- data/spec/depends/spec_helper.rb +0 -9
- data/spec/fixtures/database.yml +11 -0
- data/spec/fixtures/rails5_users_app/config/database.yml +1 -0
- data/spec/fixtures/rails6_users_app/Gemfile +1 -25
- data/spec/fixtures/rails6_users_app/config/database.yml +1 -0
- data/spec/fixtures/rails7_users_app/Gemfile +1 -25
- data/spec/fixtures/rails7_users_app/config/database.yml +1 -0
- data/spec/handler/eval_spec.rb +66 -0
- data/spec/hook_spec.rb +6 -1
- data/spec/rails_recording_spec.rb +7 -21
- data/spec/rails_spec_helper.rb +76 -63
- data/spec/rails_test_spec.rb +7 -17
- data/spec/railtie_spec.rb +4 -18
- data/spec/record_sql_rails_pg_spec.rb +44 -75
- data/spec/remote_recording_spec.rb +18 -30
- data/spec/spec_helper.rb +1 -0
- data/spec/swagger/swagger_spec.rb +1 -16
- data/spec/util_spec.rb +1 -1
- data/test/expectations/openssl_test_key_sign2-3.1.json +2 -1
- metadata +24 -21
- data/Dockerfile.appmap +0 -5
- data/spec/fixtures/rack_users_app/Dockerfile +0 -32
- data/spec/fixtures/rack_users_app/docker-compose.yml +0 -9
- data/spec/fixtures/rails5_users_app/Dockerfile +0 -29
- data/spec/fixtures/rails5_users_app/config/database.yml +0 -18
- data/spec/fixtures/rails5_users_app/create_app +0 -33
- data/spec/fixtures/rails5_users_app/docker-compose.yml +0 -31
- data/spec/fixtures/rails6_users_app/.ruby-version +0 -1
- data/spec/fixtures/rails6_users_app/Dockerfile +0 -44
- data/spec/fixtures/rails6_users_app/Dockerfile.pg +0 -3
- data/spec/fixtures/rails6_users_app/config/database.yml +0 -18
- data/spec/fixtures/rails6_users_app/create_app +0 -33
- data/spec/fixtures/rails6_users_app/docker-compose.yml +0 -31
- data/spec/fixtures/rails7_users_app/.ruby-version +0 -1
- data/spec/fixtures/rails7_users_app/Dockerfile +0 -30
- data/spec/fixtures/rails7_users_app/Dockerfile.pg +0 -3
- data/spec/fixtures/rails7_users_app/config/database.yml +0 -86
- data/spec/fixtures/rails7_users_app/create_app +0 -31
- data/spec/fixtures/rails7_users_app/docker-compose.yml +0 -31
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/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
language: ruby
|
2
2
|
dist: bionic
|
3
|
+
cache:
|
4
|
+
- bundle
|
5
|
+
- yarn
|
3
6
|
|
4
7
|
# NB: if you update the ruby versions here, you also need to update
|
5
8
|
# them in the Rakefile.
|
@@ -9,25 +12,10 @@ rbenv:
|
|
9
12
|
- 3.0
|
10
13
|
- 3.1
|
11
14
|
|
12
|
-
addons:
|
13
|
-
apt:
|
14
|
-
packages:
|
15
|
-
# https://docs.travis-ci.com/user/docker/#installing-a-newer-docker-version
|
16
|
-
- docker-ce
|
17
|
-
|
18
15
|
services:
|
19
|
-
-
|
16
|
+
- postgresql
|
20
17
|
|
21
18
|
before_install:
|
22
|
-
- sudo apt-get update && sudo apt-get install apt-transport-https ca-certificates -y && sudo update-ca-certificates
|
23
|
-
# see https://blog.travis-ci.com/docker-rate-limits
|
24
|
-
# and also https://www.docker.com/blog/what-you-need-to-know-about-upcoming-docker-hub-rate-limiting/
|
25
|
-
# if we do not use authorized account,
|
26
|
-
# the pulls-per-IP quota is shared with other Travis users
|
27
|
-
- >
|
28
|
-
if [ ! -z "$DOCKERHUB_PASSWORD" ] && [ ! -z "$DOCKERHUB_USERNAME" ]; then
|
29
|
-
echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin ;
|
30
|
-
fi
|
31
19
|
- |
|
32
20
|
nvm install --lts \
|
33
21
|
&& nvm use --lts \
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
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
|
+
|
17
|
+
# [0.79.0](https://github.com/applandinc/appmap-ruby/compare/v0.78.0...v0.79.0) (2022-04-06)
|
18
|
+
|
19
|
+
|
20
|
+
### Features
|
21
|
+
|
22
|
+
* Use a more unique test database name ([0eed036](https://github.com/applandinc/appmap-ruby/commit/0eed036460f0384698ff91c1112a4a9c3214f7f4))
|
23
|
+
|
24
|
+
# [0.78.0](https://github.com/applandinc/appmap-ruby/compare/v0.77.4...v0.78.0) (2022-04-04)
|
25
|
+
|
26
|
+
|
27
|
+
### Features
|
28
|
+
|
29
|
+
* Hook and label Kernel#eval ([e0c151d](https://github.com/applandinc/appmap-ruby/commit/e0c151d1371f5bed5597ffd0d3bfebb8bca247c2))
|
30
|
+
|
1
31
|
## [0.77.4](https://github.com/applandinc/appmap-ruby/compare/v0.77.3...v0.77.4) (2022-04-04)
|
2
32
|
|
3
33
|
|
File without changes
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ Visit the [AppMap for Ruby](https://appland.com/docs/reference/appmap-ruby.html)
|
|
28
28
|
|
29
29
|
**Configuration**
|
30
30
|
|
31
|
-
*appmap.yml* is loaded into an `AppMap::Config`.
|
31
|
+
*appmap.yml* is loaded into an `AppMap::Config`.
|
32
32
|
|
33
33
|
**Hooking**
|
34
34
|
|
@@ -39,7 +39,7 @@ method with calls that record the parameters and return value.
|
|
39
39
|
**Builtins**
|
40
40
|
|
41
41
|
`Hook` begins by iterating over builtin classes and modules defined in the `Config`. Builtins include code
|
42
|
-
like `openssl` and `net/http`. This code is not dependent on any external libraries being present, and
|
42
|
+
like `openssl` and `net/http`. This code is not dependent on any external libraries being present, and
|
43
43
|
`appmap` cannot guarantee that it will be loaded before builtins. Therefore, it's necessary to require it and
|
44
44
|
hook it by looking up the classes and modules as constants in the `Object` namespace.
|
45
45
|
|
@@ -81,50 +81,20 @@ The fixture apps in `test/fixtures` are plain Ruby projects that exercise the ba
|
|
81
81
|
|
82
82
|
The fixture apps in `spec/fixtures` are simple Rack, Rails5, and Rails6 apps.
|
83
83
|
You can use them to interactively develop and test the recording features of the `appmap` gem.
|
84
|
-
These fixture apps are more sophisticated than `test/fixtures`, because they include additional
|
85
|
-
resources such as a PostgreSQL database.
|
84
|
+
These fixture apps are more sophisticated than `test/fixtures`, because they include additional
|
85
|
+
resources such as a PostgreSQL database. Still, you can simply enter the fixture directory and `bundle`.
|
86
86
|
|
87
|
-
|
87
|
+
If you don't have PostgreSQL on the local (default) socket, you can export `DATABASE_URL` to
|
88
|
+
point to the database server you want to use.
|
88
89
|
|
89
|
-
|
90
|
-
$ bundle exec rake build:fixtures:all
|
91
|
-
```
|
92
|
-
|
93
|
-
This will build the `appmap.gem`, along with a Docker image for each fixture app.
|
94
|
-
|
95
|
-
Then move to the directory of the fixture you want to use, and provision the environment.
|
96
|
-
In this example, we use Ruby 2.6.
|
97
|
-
|
98
|
-
```sh-session
|
99
|
-
$ export RUBY_VERSION=2.6
|
100
|
-
$ docker-compose up -d pg
|
101
|
-
$ sleep 10s # Or some reasonable amount of time
|
102
|
-
$ docker-compose run --rm app ./create_app
|
103
|
-
```
|
90
|
+
You can launch a database like this:
|
104
91
|
|
105
|
-
Now you can start a development container.
|
106
|
-
|
107
|
-
```sh-session
|
108
|
-
$ docker-compose run --rm -v $PWD:/app -v $PWD/../../..:/src/appmap-ruby app bash
|
109
|
-
Starting rails_users_app_pg_1 ... done
|
110
|
-
root@6fab5f89125f:/app# cd /src/appmap-ruby
|
111
|
-
root@6fab5f89125f:/src/appmap-ruby# rm ext/appmap/*.so ext/appmap/*.o
|
112
|
-
root@6fab5f89125f:/src/appmap-ruby# bundle
|
113
|
-
root@6fab5f89125f:/src/appmap-ruby# bundle exec rake compile
|
114
|
-
root@6fab5f89125f:/src/appmap-ruby# cd /src/app
|
115
|
-
root@6fab5f89125f:/src/app# bundle config local.appmap /src/appmap-ruby
|
116
|
-
root@6fab5f89125f:/src/app# bundle
|
117
92
|
```
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
Configuring AppMap from path appmap.yml
|
126
|
-
....
|
127
|
-
|
128
|
-
Finished in 0.07357 seconds (files took 2.1 seconds to load)
|
129
|
-
4 examples, 0 failures
|
93
|
+
➜ docker-compose -p appmap-ruby up -d
|
94
|
+
... stuff
|
95
|
+
➜ docker-compose ps pg
|
96
|
+
Name Command State Ports
|
97
|
+
-----------------------------------------------------------------------------------------
|
98
|
+
appmap-ruby_pg_1 docker-entrypoint.sh postgres Up (healthy) 0.0.0.0:59134->5432/tcp
|
99
|
+
➜ export DATABASE_URL=postgres://postgres@localhost:59134
|
130
100
|
```
|
data/README_CI.md
CHANGED
@@ -4,13 +4,6 @@
|
|
4
4
|
* `GEM_HOST_API_KEY`: rubygems API key
|
5
5
|
* `GEM_ALTERNATIVE_NAME` (optional): used for testing of CI flows,
|
6
6
|
to avoid publication of test releases under official package name
|
7
|
-
* `DOCKERHUB\_USERNAME`, `DOCKERHUB_PASSWORD`: optional dockerhub credentials,
|
8
|
-
to avoid throttling of dockerhub anonymous pulls
|
9
|
-
|
10
|
-
Note: for security reasons, it's better to use dedicated (not personal)
|
11
|
-
Dockerhub account,
|
12
|
-
and also use [access tokens](https://docs.docker.com/docker-hub/access-tokens/)
|
13
|
-
instead of primary password
|
14
7
|
|
15
8
|
# Release command
|
16
9
|
|
data/Rakefile
CHANGED
@@ -1,26 +1,8 @@
|
|
1
1
|
$: << File.join(__dir__, 'lib')
|
2
|
-
require 'appmap/version'
|
3
|
-
GEM_VERSION = AppMap::VERSION
|
4
|
-
|
5
|
-
# Make sure the local version is not behind the one on
|
6
|
-
# rubygems.org (it's ok if they're the same).
|
7
|
-
#
|
8
|
-
# If it is behind, the fixture images won't get updated with the gem
|
9
|
-
# built from the local source, so you'll wind up testing the rubygems
|
10
|
-
# version instead.
|
11
|
-
unless ENV['SKIP_VERSION_CHECK']
|
12
|
-
require 'json'
|
13
|
-
require 'net/http'
|
14
|
-
rubygems_version = JSON.parse(Net::HTTP.get(URI.parse('https://rubygems.org/api/v1/gems/appmap.json')))['version']
|
15
|
-
if Gem::Version.new(GEM_VERSION) < Gem::Version.new(rubygems_version)
|
16
|
-
raise "#{GEM_VERSION} < #{rubygems_version}. Rebase to avoid build issues."
|
17
|
-
end
|
18
|
-
end
|
19
2
|
|
3
|
+
require 'rspec/core/rake_task'
|
20
4
|
require 'rake/testtask'
|
21
5
|
require 'rdoc/task'
|
22
|
-
|
23
|
-
require 'open3'
|
24
6
|
require 'rake/extensiontask'
|
25
7
|
|
26
8
|
desc 'build the native extension'
|
@@ -28,138 +10,20 @@ Rake::ExtensionTask.new("appmap") do |ext|
|
|
28
10
|
ext.lib_dir = "lib/appmap"
|
29
11
|
end
|
30
12
|
|
31
|
-
RUBY_VERSIONS=%w[2.6 2.7 3.0 3.1].select do |version|
|
32
|
-
travis_ruby_version = ENV['TRAVIS_RUBY_VERSION']
|
33
|
-
next true unless travis_ruby_version
|
34
|
-
|
35
|
-
if travis_ruby_version.index(version) == 0
|
36
|
-
warn "Testing Ruby version #{version}, since it matches TRAVIS_RUBY_VERSION=#{travis_ruby_version}"
|
37
|
-
next true
|
38
|
-
end
|
39
|
-
|
40
|
-
false
|
41
|
-
end
|
42
|
-
FIXTURE_APPS=[:rack_users_app, :rails6_users_app, :rails5_users_app, :rails7_users_app => {:ruby_version => '>= 2.7'}]
|
43
|
-
|
44
|
-
def run_cmd(*cmd)
|
45
|
-
$stderr.puts "Running: #{cmd}"
|
46
|
-
out, s = Open3.capture2e(*cmd)
|
47
|
-
unless s.success?
|
48
|
-
$stderr.puts <<-END
|
49
|
-
Command failed:
|
50
|
-
<<< Output:
|
51
|
-
#{out}
|
52
|
-
>>> End of output
|
53
|
-
END
|
54
|
-
raise 'Docker build failed'
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def build_base_image(ruby_version)
|
59
|
-
run_cmd "docker build" \
|
60
|
-
" --build-arg RUBY_VERSION=#{ruby_version}" \
|
61
|
-
" --build-arg GEM_VERSION=#{GEM_VERSION}" \
|
62
|
-
" -t appmap:#{GEM_VERSION} -f Dockerfile.appmap ."
|
63
|
-
end
|
64
|
-
|
65
|
-
def build_app_image(app, ruby_version)
|
66
|
-
Dir.chdir "spec/fixtures/#{app}" do
|
67
|
-
env = {"RUBY_VERSION" => ruby_version, "GEM_VERSION" => GEM_VERSION}
|
68
|
-
run_cmd(env,
|
69
|
-
"docker-compose build" \
|
70
|
-
" --build-arg RUBY_VERSION=#{ruby_version}" \
|
71
|
-
" --build-arg GEM_VERSION=#{GEM_VERSION}" )
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
13
|
desc 'Install non-Ruby dependencies'
|
76
14
|
task :install do
|
77
15
|
system 'yarn install' or raise 'yarn install failed'
|
78
16
|
end
|
79
17
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
build_base_image(ruby_version)
|
90
|
-
end.tap do |t|
|
91
|
-
desc "Build all images"
|
92
|
-
task all: t
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
namespace :fixtures do
|
98
|
-
RUBY_VERSIONS.each do |ruby_version|
|
99
|
-
namespace ruby_version do
|
100
|
-
desc "build:fixtures:#{ruby_version}"
|
101
|
-
FIXTURE_APPS.each do |app_spec|
|
102
|
-
app = if app_spec.instance_of?(Hash)
|
103
|
-
app_spec = app_spec.flatten
|
104
|
-
version_rqt = Gem::Requirement.create(app_spec[1][:ruby_version])
|
105
|
-
next unless version_rqt =~ (Gem::Version.new(ruby_version))
|
106
|
-
app = app_spec[0]
|
107
|
-
else
|
108
|
-
app = app_spec
|
109
|
-
end.to_s
|
110
|
-
|
111
|
-
|
112
|
-
desc app
|
113
|
-
task app => ["base:#{ruby_version}"] do
|
114
|
-
build_app_image(app, ruby_version)
|
115
|
-
end.tap do |t|
|
116
|
-
desc "Build all fixture images for #{ruby_version}"
|
117
|
-
task all: t
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
desc "Build all fixture images"
|
123
|
-
task all: ["#{ruby_version}:all"]
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
task all: ["fixtures:all"]
|
128
|
-
end
|
129
|
-
|
130
|
-
def run_specs(ruby_version, task_args)
|
131
|
-
require 'rspec/core/rake_task'
|
132
|
-
require 'climate_control'
|
133
|
-
# Define an rspec rake task for the specified Ruby version. It's hidden (i.e. doesn't have a
|
134
|
-
# description), because it's not intended to be invoked directly
|
135
|
-
RSpec::Core::RakeTask.new("rspec_#{ruby_version}", [:specs]) do |task, args|
|
136
|
-
task.exclude_pattern = 'spec/fixtures/**/*_spec.rb'
|
137
|
-
task.rspec_opts = '-f doc'
|
138
|
-
if args.count > 0
|
139
|
-
# There doesn't appear to be a value for +pattern+ that will
|
140
|
-
# cause it to be ignored. Setting it to '' or +nil+ causes an
|
141
|
-
# empty argument to get passed to rspec, which confuses it.
|
142
|
-
task.pattern = 'never match this'
|
143
|
-
task.rspec_opts += " " + args.to_a.join(' ')
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# Set up the environment, then execute the rspec task we
|
148
|
-
# created above.
|
149
|
-
ClimateControl.modify(RUBY_VERSION: ruby_version) do
|
150
|
-
Rake::Task["rspec_#{ruby_version}"].execute(task_args)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
namespace :spec do
|
155
|
-
RUBY_VERSIONS.each do |ruby_version|
|
156
|
-
desc ruby_version
|
157
|
-
task ruby_version, [:specs] => ["install", "compile", "build:fixtures:#{ruby_version}:all"] do |_, task_args|
|
158
|
-
run_specs(ruby_version, task_args)
|
159
|
-
end.tap do |t|
|
160
|
-
desc "Run all specs"
|
161
|
-
task :all, [:specs] => t
|
162
|
-
end
|
18
|
+
RSpec::Core::RakeTask.new spec: %i[compile install] do |task, args|
|
19
|
+
task.exclude_pattern = 'spec/fixtures/**/*_spec.rb'
|
20
|
+
task.rspec_opts = '-f doc'
|
21
|
+
if args.count > 0
|
22
|
+
# There doesn't appear to be a value for +pattern+ that will
|
23
|
+
# cause it to be ignored. Setting it to '' or +nil+ causes an
|
24
|
+
# empty argument to get passed to rspec, which confuses it.
|
25
|
+
task.pattern = 'never match this'
|
26
|
+
task.rspec_opts += [nil, *args].join(' ')
|
163
27
|
end
|
164
28
|
end
|
165
29
|
|
@@ -169,14 +33,12 @@ Rake::RDocTask.new do |rd|
|
|
169
33
|
rd.title = 'AppMap'
|
170
34
|
end
|
171
35
|
|
172
|
-
Rake::TestTask.new(minitest:
|
36
|
+
Rake::TestTask.new(minitest: :compile) do |t|
|
173
37
|
t.libs << 'test'
|
174
38
|
t.libs << 'lib'
|
175
39
|
t.test_files = FileList['test/*_test.rb']
|
176
40
|
end
|
177
41
|
|
178
|
-
task
|
179
|
-
|
180
|
-
task test: %i[spec:all minitest]
|
42
|
+
task test: %i[spec minitest]
|
181
43
|
|
182
44
|
task default: :test
|
data/appmap.gemspec
CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require 'appmap/version'
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
|
-
# ability to parameterize gem name is added intentionally,
|
8
|
+
# ability to parameterize gem name is added intentionally,
|
9
9
|
# to support the possibility of unofficial releases, e.g. during CI tests
|
10
10
|
spec.name = (ENV['GEM_ALTERNATIVE_NAME'].to_s.empty? ? 'appmap' : ENV["GEM_ALTERNATIVE_NAME"] )
|
11
11
|
spec.version = AppMap::VERSION
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_dependency 'reverse_markdown'
|
33
33
|
|
34
34
|
spec.add_development_dependency 'bundler', '>= 1.16'
|
35
|
-
spec.add_development_dependency 'minitest', '~> 5.
|
35
|
+
spec.add_development_dependency 'minitest', '~> 5.15'
|
36
36
|
spec.add_development_dependency 'pry-byebug'
|
37
37
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
38
38
|
spec.add_development_dependency 'rdoc'
|
@@ -48,5 +48,6 @@ Gem::Specification.new do |spec|
|
|
48
48
|
spec.add_development_dependency 'webdrivers', '~> 4.0'
|
49
49
|
spec.add_development_dependency 'timecop'
|
50
50
|
spec.add_development_dependency 'hashie'
|
51
|
+
spec.add_development_dependency 'random-port', '~> 0.5.1'
|
51
52
|
spec.add_development_dependency 'webrick'
|
52
53
|
end
|
data/docker-compose.yml
ADDED
data/ext/appmap/appmap.c
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
+
#include <ruby/debug.h>
|
2
3
|
#include <ruby/intern.h>
|
3
4
|
|
4
5
|
// Seems like CLASS_OR_MODULE_P should really be in a header file in
|
@@ -39,7 +40,7 @@ am_define_method_with_arity(VALUE mod, VALUE name, VALUE arity, VALUE proc)
|
|
39
40
|
{
|
40
41
|
VALUE arities_key = rb_intern(ARITIES_KEY);
|
41
42
|
VALUE arities = rb_ivar_get(mod, arities_key);
|
42
|
-
|
43
|
+
|
43
44
|
if (arities == Qundef || NIL_P(arities)) {
|
44
45
|
arities = rb_hash_new();
|
45
46
|
rb_ivar_set(mod, arities_key, arities);
|
@@ -62,7 +63,7 @@ am_get_method_arity(VALUE method, VALUE orig_arity_method)
|
|
62
63
|
}
|
63
64
|
// Didn't find one, call the original method.
|
64
65
|
if (NIL_P(arity)) {
|
65
|
-
VALUE bound_method = rb_funcall(orig_arity_method, rb_intern("bind"), 1, method);
|
66
|
+
VALUE bound_method = rb_funcall(orig_arity_method, rb_intern("bind"), 1, method);
|
66
67
|
arity = rb_funcall(bound_method, rb_intern("call"), 0);
|
67
68
|
}
|
68
69
|
|
@@ -83,11 +84,29 @@ am_method_arity(VALUE method)
|
|
83
84
|
return am_get_method_arity(method, orig_method_arity);
|
84
85
|
}
|
85
86
|
|
87
|
+
static VALUE
|
88
|
+
bindings_callback(const rb_debug_inspector_t *dbg_context, void *level)
|
89
|
+
{
|
90
|
+
const int i = NUM2INT((VALUE) level);
|
91
|
+
if (i >= RARRAY_LEN(rb_debug_inspector_backtrace_locations(dbg_context))) {
|
92
|
+
return Qnil;
|
93
|
+
} else {
|
94
|
+
return rb_debug_inspector_frame_binding_get(dbg_context, i);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
static VALUE
|
99
|
+
am_previous_bindings(VALUE self, VALUE level)
|
100
|
+
{
|
101
|
+
return rb_debug_inspector_open(bindings_callback, (void *) level);
|
102
|
+
}
|
103
|
+
|
86
104
|
void Init_appmap() {
|
87
105
|
VALUE appmap = rb_define_module("AppMap");
|
88
106
|
am_AppMapHook = rb_define_class_under(appmap, "Hook", rb_cObject);
|
89
107
|
|
90
108
|
rb_define_singleton_method(am_AppMapHook, "singleton_method_owner_name", singleton_method_owner_name, 1);
|
109
|
+
rb_define_module_function(appmap, "caller_binding", am_previous_bindings, 1);
|
91
110
|
|
92
111
|
rb_define_method(rb_cModule, "define_method_with_arity", am_define_method_with_arity, 3);
|
93
112
|
rb_define_method(rb_cUnboundMethod, "arity", am_unbound_method_arity, 0);
|
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
|
@@ -14,14 +14,17 @@
|
|
14
14
|
- String#unpack1
|
15
15
|
require_name: ruby
|
16
16
|
label: string.unpack
|
17
|
-
|
17
|
+
- methods:
|
18
|
+
- Kernel#eval
|
19
|
+
require_name: ruby
|
20
|
+
label: lang.eval
|
21
|
+
handler_class: AppMap::Handler::Eval
|
18
22
|
# TODO: eval does not happen in the right context, and therefore any new constants
|
19
23
|
# which are defined are placed on the wrong module/class.
|
20
|
-
# - Kernel#eval
|
21
24
|
# - Binding#eval
|
22
25
|
# - BasicObject#instance_eval
|
23
26
|
# These methods cannot be hooked as far as I can tell.
|
24
|
-
# Why? When calling one of these functions, the context at the point of
|
27
|
+
# Why? When calling one of these functions, the context at the point of
|
25
28
|
# definition is used. It's not possible to bind class_eval to a new context.
|
26
29
|
# - Module#class_eval
|
27
30
|
# - Module#module_eval
|
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,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/handler/function'
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
module Handler
|
7
|
+
# Handler class for Kernel#eval.
|
8
|
+
#
|
9
|
+
# Second argument to eval is a Binding, which despite the name (and
|
10
|
+
# the accessible methods) in addition to locals and receiver also
|
11
|
+
# encapsulates the entire execution context, in particular including
|
12
|
+
# the lexical scope. This is especially important for constant lookup
|
13
|
+
# and definition.
|
14
|
+
#
|
15
|
+
# If the binding is not provided, by default eval will run in the
|
16
|
+
# current frame. Since we call it here, this will mean the #do_call
|
17
|
+
# frame, which would make AppMap::Handler::Eval the lexical scope
|
18
|
+
# for constant lookup and definition; as a consequence
|
19
|
+
# eg. `eval "class Foo; end"` would define
|
20
|
+
# AppMap::Handler::Eval::Foo instead of defining it in
|
21
|
+
# the module where the original call was made.
|
22
|
+
#
|
23
|
+
# To avoid this, we explicitly substitute the correct execution
|
24
|
+
# context, up several stack frames.
|
25
|
+
class Eval < Function
|
26
|
+
# The depth of the frame we need to pluck out:
|
27
|
+
# 1. Hook::Method#do_call
|
28
|
+
# 2. Hook::Method#trace_call
|
29
|
+
# 3. Hook::Method#call
|
30
|
+
# 4. proc generated by Hook::Method#hook_method_def
|
31
|
+
# 5. the (intended) frame of the original eval that we hooked
|
32
|
+
# Note it needs to be adjusted if this call sequence changes.
|
33
|
+
FRAME_DEPTH = 5
|
34
|
+
|
35
|
+
def do_call(receiver, src = nil, context = nil, *rest)
|
36
|
+
context ||= AppMap.caller_binding FRAME_DEPTH
|
37
|
+
hook_method.bind(receiver).call(src, context, *rest)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -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
|