appmap 0.77.4 → 0.80.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.travis.yml +4 -16
  4. data/CHANGELOG.md +30 -0
  5. data/{spec/fixtures/rails5_users_app/Dockerfile.pg → Dockerfile.pg} +0 -0
  6. data/README.md +14 -44
  7. data/README_CI.md +0 -7
  8. data/Rakefile +12 -150
  9. data/appmap.gemspec +3 -2
  10. data/docker-compose.yml +10 -0
  11. data/ext/appmap/appmap.c +21 -2
  12. data/lib/appmap/agent.rb +8 -0
  13. data/lib/appmap/builtin_hooks/ruby.yml +6 -3
  14. data/lib/appmap/event.rb +19 -12
  15. data/lib/appmap/gem_hooks/actionpack.yml +6 -0
  16. data/lib/appmap/handler/eval.rb +41 -0
  17. data/lib/appmap/handler/rails/render_handler.rb +29 -0
  18. data/lib/appmap/handler/rails/request_handler.rb +13 -11
  19. data/lib/appmap/handler/rails/sql_handler.rb +43 -1
  20. data/lib/appmap/handler.rb +3 -0
  21. data/lib/appmap/hook/method.rb +0 -1
  22. data/lib/appmap/version.rb +1 -1
  23. data/spec/config_spec.rb +1 -1
  24. data/spec/depends/api_spec.rb +13 -5
  25. data/spec/depends/spec_helper.rb +0 -9
  26. data/spec/fixtures/database.yml +11 -0
  27. data/spec/fixtures/rails5_users_app/config/database.yml +1 -0
  28. data/spec/fixtures/rails6_users_app/Gemfile +1 -25
  29. data/spec/fixtures/rails6_users_app/config/database.yml +1 -0
  30. data/spec/fixtures/rails7_users_app/Gemfile +1 -25
  31. data/spec/fixtures/rails7_users_app/config/database.yml +1 -0
  32. data/spec/handler/eval_spec.rb +66 -0
  33. data/spec/hook_spec.rb +6 -1
  34. data/spec/rails_recording_spec.rb +7 -21
  35. data/spec/rails_spec_helper.rb +76 -63
  36. data/spec/rails_test_spec.rb +7 -17
  37. data/spec/railtie_spec.rb +4 -18
  38. data/spec/record_sql_rails_pg_spec.rb +44 -75
  39. data/spec/remote_recording_spec.rb +18 -30
  40. data/spec/spec_helper.rb +1 -0
  41. data/spec/swagger/swagger_spec.rb +1 -16
  42. data/spec/util_spec.rb +1 -1
  43. data/test/expectations/openssl_test_key_sign2-3.1.json +2 -1
  44. metadata +24 -21
  45. data/Dockerfile.appmap +0 -5
  46. data/spec/fixtures/rack_users_app/Dockerfile +0 -32
  47. data/spec/fixtures/rack_users_app/docker-compose.yml +0 -9
  48. data/spec/fixtures/rails5_users_app/Dockerfile +0 -29
  49. data/spec/fixtures/rails5_users_app/config/database.yml +0 -18
  50. data/spec/fixtures/rails5_users_app/create_app +0 -33
  51. data/spec/fixtures/rails5_users_app/docker-compose.yml +0 -31
  52. data/spec/fixtures/rails6_users_app/.ruby-version +0 -1
  53. data/spec/fixtures/rails6_users_app/Dockerfile +0 -44
  54. data/spec/fixtures/rails6_users_app/Dockerfile.pg +0 -3
  55. data/spec/fixtures/rails6_users_app/config/database.yml +0 -18
  56. data/spec/fixtures/rails6_users_app/create_app +0 -33
  57. data/spec/fixtures/rails6_users_app/docker-compose.yml +0 -31
  58. data/spec/fixtures/rails7_users_app/.ruby-version +0 -1
  59. data/spec/fixtures/rails7_users_app/Dockerfile +0 -30
  60. data/spec/fixtures/rails7_users_app/Dockerfile.pg +0 -3
  61. data/spec/fixtures/rails7_users_app/config/database.yml +0 -86
  62. data/spec/fixtures/rails7_users_app/create_app +0 -31
  63. 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: 4a3775abc9bff6d949fa377e2da5121414500735c37d9c5b9d6f03a7a2ae31a7
4
- data.tar.gz: 7254e2e5c3f25f431cdb25e8d0512d55a155afd8b64647b255b3a8223b663f3f
3
+ metadata.gz: eb003b96b494282db035877377124bc3736d78320a64e479b7caa0b8df49063a
4
+ data.tar.gz: 3aab36a97c90485e1c7f77bed373c4c572b46a5c8c93ac4aa012db3becf201f4
5
5
  SHA512:
6
- metadata.gz: cf54fd237e40750eff85fb4723651a747ba6e69214ea04acae1a95964a09e4a800ea1de31dbc4f7dec7d26c585ef7fb9167a3e97eb36e9b0c14003240696d6d6
7
- data.tar.gz: 81f413c5e21379b145a99f9882db9faea3d27e75ef56df7a8a928ce0b74f141c26025300cf0449fbf00128eb4991d99c94316f4fb522c5bd25042fb6ad2cfff3
6
+ metadata.gz: 8637610caacf145c5badb4de3eb399858a231d3de9a794f86e8fcb3d14801d3b5309453da7e9b799d47ddb9c4eed55bbd97bf2f77eabe7e605e95089b560c5bf
7
+ data.tar.gz: bba8d4f2f8ffa5b0e5d8b113657324561dfa71e7b7128273631d62d426c8e12339e305d9fb0fe0d05244685a1ddc1b4be4bfbe5a4b44607a2df96e5d60749557
data/.rubocop.yml CHANGED
@@ -19,6 +19,7 @@ Layout/LineLength:
19
19
 
20
20
  Metrics/BlockLength:
21
21
  ExcludedMethods:
22
+ - describe
22
23
  - it
23
24
  - context
24
25
 
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
- - docker
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
 
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
- To build the fixture container images, first run:
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
- ```sh-session
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
- At this point, the bundle is built with the `appmap` gem located in `/src/appmap`, which is volume-mounted from the host.
120
- So you can edit the fixture code and the appmap code and run test commands such as `rspec` in the container.
121
- For example:
122
-
123
- ```sh-session
124
- root@6fab5f89125f:/src/app# APPMAP=true bundle exec rspec
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
- namespace :build do
81
- namespace :base do
82
- RUBY_VERSIONS.each do |ruby_version|
83
- desc ruby_version
84
- task ruby_version do
85
- run_system = ->(cmd) { system(cmd) or raise "Command failed: #{cmd}" }
86
-
87
- run_system.call 'mkdir -p pkg'
88
- run_system.call "gem build appmap.gemspec --output pkg/appmap-#{GEM_VERSION}.gem"
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: 'compile') do |t|
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 spec: %i[spec:all]
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.14'
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
@@ -0,0 +1,10 @@
1
+ version: "3"
2
+ services:
3
+ pg:
4
+ build:
5
+ context: .
6
+ dockerfile: Dockerfile.pg
7
+ ports:
8
+ - "5432"
9
+ environment:
10
+ POSTGRES_HOST_AUTH_METHOD: trust
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
- #- methods:
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 object_properties(hash_like)
64
- hash = hash_like.to_h
65
- hash.keys.map do |key|
66
- {
67
- name: key,
68
- class: hash[key].class.name,
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