appmap 0.78.0 → 0.80.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -16
  3. data/CHANGELOG.md +30 -0
  4. data/{spec/fixtures/rails5_users_app/Dockerfile.pg → Dockerfile.pg} +0 -0
  5. data/README.md +14 -44
  6. data/README_CI.md +0 -7
  7. data/Rakefile +12 -150
  8. data/appmap.gemspec +3 -2
  9. data/docker-compose.yml +10 -0
  10. data/lib/appmap/agent.rb +8 -0
  11. data/lib/appmap/event.rb +26 -12
  12. data/lib/appmap/gem_hooks/actionpack.yml +6 -0
  13. data/lib/appmap/handler/rails/render_handler.rb +29 -0
  14. data/lib/appmap/handler/rails/request_handler.rb +13 -11
  15. data/lib/appmap/handler/rails/sql_handler.rb +43 -1
  16. data/lib/appmap/handler.rb +3 -0
  17. data/lib/appmap/hook/method.rb +0 -1
  18. data/lib/appmap/version.rb +1 -1
  19. data/spec/config_spec.rb +1 -1
  20. data/spec/depends/api_spec.rb +13 -5
  21. data/spec/depends/spec_helper.rb +0 -9
  22. data/spec/fixtures/database.yml +11 -0
  23. data/spec/fixtures/rails5_users_app/config/database.yml +1 -0
  24. data/spec/fixtures/rails6_users_app/Gemfile +1 -25
  25. data/spec/fixtures/rails6_users_app/config/database.yml +1 -0
  26. data/spec/fixtures/rails7_users_app/Gemfile +1 -25
  27. data/spec/fixtures/rails7_users_app/config/database.yml +1 -0
  28. data/spec/handler/eval_spec.rb +1 -1
  29. data/spec/hook_spec.rb +6 -1
  30. data/spec/rails_recording_spec.rb +7 -21
  31. data/spec/rails_spec_helper.rb +76 -63
  32. data/spec/rails_test_spec.rb +7 -17
  33. data/spec/railtie_spec.rb +4 -18
  34. data/spec/record_sql_rails_pg_spec.rb +44 -75
  35. data/spec/remote_recording_spec.rb +18 -30
  36. data/spec/spec_helper.rb +1 -0
  37. data/spec/swagger/swagger_spec.rb +1 -16
  38. data/spec/util_spec.rb +1 -1
  39. data/test/expectations/openssl_test_key_sign2-3.1.json +2 -1
  40. metadata +22 -21
  41. data/Dockerfile.appmap +0 -5
  42. data/spec/fixtures/rack_users_app/Dockerfile +0 -32
  43. data/spec/fixtures/rack_users_app/docker-compose.yml +0 -9
  44. data/spec/fixtures/rails5_users_app/Dockerfile +0 -29
  45. data/spec/fixtures/rails5_users_app/config/database.yml +0 -18
  46. data/spec/fixtures/rails5_users_app/create_app +0 -33
  47. data/spec/fixtures/rails5_users_app/docker-compose.yml +0 -31
  48. data/spec/fixtures/rails6_users_app/.ruby-version +0 -1
  49. data/spec/fixtures/rails6_users_app/Dockerfile +0 -44
  50. data/spec/fixtures/rails6_users_app/Dockerfile.pg +0 -3
  51. data/spec/fixtures/rails6_users_app/config/database.yml +0 -18
  52. data/spec/fixtures/rails6_users_app/create_app +0 -33
  53. data/spec/fixtures/rails6_users_app/docker-compose.yml +0 -31
  54. data/spec/fixtures/rails7_users_app/.ruby-version +0 -1
  55. data/spec/fixtures/rails7_users_app/Dockerfile +0 -30
  56. data/spec/fixtures/rails7_users_app/Dockerfile.pg +0 -3
  57. data/spec/fixtures/rails7_users_app/config/database.yml +0 -86
  58. data/spec/fixtures/rails7_users_app/create_app +0 -31
  59. 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: 8dce970e998105e2e322410b204af7a0ff48790cd177225468e9b49c75651fdb
4
- data.tar.gz: d93b8f4f0690bf5ade69b2c08e4354d3043567fc4bf6018d6e68084eece08be9
3
+ metadata.gz: de1e5a636c3d6e807f11c54a29563e338804f3d101dc4779229596455cd60682
4
+ data.tar.gz: cf2aba805fdb427da2a642826f490b0002eb1892dd57a07693b119582a8cd7e1
5
5
  SHA512:
6
- metadata.gz: bea7fecb6bbae7feb68fa649ecba6276b3d497312ff99054c527d2f74117b584dd7119fa8e4a346b0091089786e90a0a2f2f375f1bc94a0e5437dfa8cff99e20
7
- data.tar.gz: 913ab70d04467c584ecf5edfe4c8d60807974cf3d01eb9f2107ceaaa8677c157dc3b0e4130b6e90dcdaba3ba055b4a5268b40e94d0054eb6c41d1146205b0eff
6
+ metadata.gz: 6056dda497e69c32a6e7550c76b4b4e40a5cd5405d7244748af4f5f34ed7d6790eafa206470ac8c9a98019fec802944b4cec0cc274451028cf2c5d6f405bd626
7
+ data.tar.gz: '039ef96a6ae64cf641e58fb364a2777a65dc507ded48027752ca766c65204659b844cb6408d17a077947968d1fbf006f7c9b29e2fdfe03fc392d90c4495361ff'
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.1](https://github.com/applandinc/appmap-ruby/compare/v0.80.0...v0.80.1) (2022-04-08)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Don't call #size on complex objects ([3f19d1e](https://github.com/applandinc/appmap-ruby/commit/3f19d1e67288379570dfa14d8758a0624d2c6c34))
7
+
8
+ # [0.80.0](https://github.com/applandinc/appmap-ruby/compare/v0.79.0...v0.80.0) (2022-04-08)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Don't record SQL within an existing event ([ff37a69](https://github.com/applandinc/appmap-ruby/commit/ff37a69af1af02263df216e49aea0d0954b93925))
14
+
15
+
16
+ ### Features
17
+
18
+ * Env var to EXPLAIN queries ([740be75](https://github.com/applandinc/appmap-ruby/commit/740be75c2bc59e343d67ecf86b7715e61cddadba))
19
+ * Optionally record parameter schema ([b7f41b1](https://github.com/applandinc/appmap-ruby/commit/b7f41b15a4556a0ce78650a6a77301d365632bb8))
20
+ * query_plan is available whether a current transaction exists or not ([6edf774](https://github.com/applandinc/appmap-ruby/commit/6edf774fea858d825c4b971be2c4c15db1652446))
21
+ * Record parameter and return value size ([6e69754](https://github.com/applandinc/appmap-ruby/commit/6e697543cb421378832492e286f972dc4cb1e1aa))
22
+ * Save render return value to a thread-local ([f9d1e3f](https://github.com/applandinc/appmap-ruby/commit/f9d1e3f0aa9972482ff77233d38220104515b1d6))
23
+
24
+ # [0.79.0](https://github.com/applandinc/appmap-ruby/compare/v0.78.0...v0.79.0) (2022-04-06)
25
+
26
+
27
+ ### Features
28
+
29
+ * Use a more unique test database name ([0eed036](https://github.com/applandinc/appmap-ruby/commit/0eed036460f0384698ff91c1112a4a9c3214f7f4))
30
+
1
31
  # [0.78.0](https://github.com/applandinc/appmap-ruby/compare/v0.77.4...v0.78.0) (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/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,25 @@ 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_size(param, value)
64
+ # Don't risk calling #size on things like data-access objects, which can and will issue queries for this information.
65
+ if value.is_a?(Array) || value.is_a?(Hash)
66
+ param[:size] = value.size
67
+ end
68
+ end
69
+
70
+ def add_schema(param, value)
71
+ begin
72
+ if value.respond_to?(:keys)
73
+ param[:properties] = value.keys.map { |key| { name: key, class: best_class_name(value[key]) } }
74
+ elsif value.respond_to?(:first) && value.first
75
+ if value.first != value
76
+ add_schema param, value.first
77
+ end
78
+ end
79
+ rescue
80
+ warn "Error in add_schema(#{value.class})", $!
70
81
  end
71
- rescue
72
- nil
73
82
  end
74
83
 
75
84
  # Heuristic for dynamically defined class whose name can be nil
@@ -221,7 +230,9 @@ module AppMap
221
230
  object_id: value.__id__,
222
231
  value: display_string(value),
223
232
  kind: param_type
224
- }
233
+ }.tap do |param|
234
+ add_size param, value
235
+ end
225
236
  end
226
237
  event.receiver = {
227
238
  class: best_class_name(receiver),
@@ -276,7 +287,7 @@ module AppMap
276
287
  attr_accessor :return_value, :exceptions
277
288
 
278
289
  class << self
279
- def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new)
290
+ def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new, parameter_schema: false)
280
291
  event ||= MethodReturn.new
281
292
  event.tap do |_|
282
293
  if return_value
@@ -284,7 +295,10 @@ module AppMap
284
295
  class: best_class_name(return_value),
285
296
  value: display_string(return_value),
286
297
  object_id: return_value.__id__
287
- }
298
+ }.tap do |param|
299
+ add_size param, return_value
300
+ add_schema param, return_value if parameter_schema && !exception
301
+ end
288
302
  end
289
303
  if exception
290
304
  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
- properties = object_properties(val)
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::MethodReturnIgnoreValue
70
+ class HTTPServerResponse < AppMap::Event::MethodReturn
71
71
  attr_accessor :status, :headers
72
72
 
73
- def initialize(response, parent_id, elapsed)
74
- super AppMap::Event.next_id_counter, :return, Thread.current.object_id
75
-
76
- self.status = response.status
77
- self.parent_id = parent_id
78
- self.elapsed = elapsed
79
- self.headers = response.headers.dup
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
- return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
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).inject([]) { |memo, r| memo << r; memo }
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