appmap 0.40.0 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b0943dadde84e67780d724918fa26377b869791eaab6edfb714e5081ecb5c80
4
- data.tar.gz: c492323d80541a913b800924d04a7c24a6c8682263ba3ec4deabfa8d7ade3908
3
+ metadata.gz: d747c0e65dc1efb45769ae9118a7b97015b0378d79e20eec855e74c20b622af2
4
+ data.tar.gz: dcc3b91f32246aaefd02fbcf47672bfdf13acf6cadb715fbc5ff0040dbcfbf72
5
5
  SHA512:
6
- metadata.gz: 2a27e5b9bc3dafbba8aafd7035732b6ffcb813d57bb216d0e544af30260c0e26f23a920d1b2d6d35882046e74a57f8b492b12b9869de5d820ff6a4e09cafd716
7
- data.tar.gz: f1bdc22fda7a6fdf0a37fc1d5bd419947e0fffa16511873dbe8a1549f30bf862566fe74fa4c9952c4338f5263414c99f3d598daec0a599b467e52ebac374b4f7
6
+ metadata.gz: 5f82a6a0cf8075537b0835e783779c827c5afed81e83e242bc97cbfd5b6ef0320fe352235436f57009a63db321223b9f7f82e8bcdced558d9b935a2ce4e4cbf3
7
+ data.tar.gz: aaa4676d24b6bb1bcbd34fb7f0e3a0244d005c36dd89782b774b342b7e30e1c9637795d0d492522b2a5e525de413e3adb9da54bd5ace2699373ffdfadf3a8157
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v0.41.0
2
+
3
+ * Adjust some label names to match `provider.*`, `format.*`.
4
+ * Add global `exclude` list to *appmap.yml* which can be used to definitively exclude specific classes and methods.
5
+
1
6
  # v0.40.0
2
7
 
3
8
  * Parse source code comments into function labels.
data/README.md CHANGED
@@ -53,15 +53,23 @@ Support for new versions is added frequently, please check back regularly for up
53
53
  <a href="https://www.loom.com/share/78ab32a312ff4b85aa8827a37f1cb655"> <p>Quick and easy setup of the AppMap gem for Rails - Watch Video</p> <img style="max-width:300px;" src="https://cdn.loom.com/sessions/thumbnails/78ab32a312ff4b85aa8827a37f1cb655-with-play.gif"> </a>
54
54
 
55
55
 
56
- Add `gem 'appmap'` to your Gemfile just as you would any other dependency. We recommend that the Gem be added to the `:development, :test` section.
56
+ Add `gem 'appmap'` to **beginning** of your Gemfile. We recommend that you add the `appmap` gem to the `:development, :test` group. Your Gemfile should look something like this:
57
57
 
58
58
  ```
59
+ source 'https://rubygems.org'
60
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
61
+
62
+ # Optional rubRuby version
63
+ # ruby '2.7.2'
64
+
59
65
  group :development, :test do
60
66
  gem 'appmap'
61
67
  end
62
68
  ```
63
69
 
64
- Then install with `bundle`.
70
+ Install with `bundle install`, as usual.
71
+
72
+ It's important to add the `appmap` gem before any other gems that you may want to instrument. There is more about this in the section on adding gems to the *appmap.yml*.
65
73
 
66
74
  **Railtie**
67
75
 
@@ -79,7 +87,8 @@ When you run your program, the `appmap` gem reads configuration settings from `a
79
87
  file for a typical Rails project:
80
88
 
81
89
  ```yaml
82
- name: MyProject
90
+ # 'name' should generally be the same as the code repo name.
91
+ name: my_project
83
92
  packages:
84
93
  - path: app/controllers
85
94
  - path: app/models
@@ -91,10 +100,15 @@ packages:
91
100
  - gem: devise
92
101
  - gem: aws-sdk
93
102
  - gem: will_paginate
103
+ exclude:
104
+ - MyClass
105
+ - MyClass#my_instance_method
106
+ - MyClass.my_class_method
94
107
  ```
95
108
 
96
109
  * **name** Provides the project name (required)
97
- * **packages** A list of source code directories which should be instrumented.
110
+ * **packages** A list of source code directories which should be recorded.
111
+ * **exclude** A list of classes and/or methods to definitively exclude from recording.
98
112
 
99
113
  **packages**
100
114
 
@@ -102,13 +116,18 @@ Each entry in the `packages` list is a YAML object which has the following keys:
102
116
 
103
117
  * **path** The path to the source code directory. The path may be relative to the current working directory, or it may
104
118
  be an absolute path.
105
- * **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`.
119
+ * **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`. In your `Gemfile`, the `appmap` gem **must** be listed **before** any gem that you specify in your *appmap.yml*.
106
120
  * **exclude** A list of files and directories which will be ignored. By default, all modules, classes and public
107
- functions are inspected.
121
+ functions are inspected. See also: global `exclude` list.
108
122
  * **shallow** When set to `true`, only the first function call entry into a package will be recorded. Subsequent function calls within
109
123
  the same package are not recorded unless code execution leaves the package and re-enters it. Default: `true` when using `gem`,
110
124
  `false` when using `path`.
111
125
 
126
+ **exclude**
127
+
128
+ Optional list of fully qualified class and method names. Separate class and method names with period (`.`) for class methods and hash (`#`) for instance methods.
129
+
130
+
112
131
  # Labels
113
132
 
114
133
  The [AppMap data format](https://github.com/applandinc/appmap) provides for class and function `labels`, which can be used to enhance the AppMap visualizations, and to programatically analyze the data.
data/lib/appmap/config.rb CHANGED
@@ -57,32 +57,32 @@ module AppMap
57
57
  # Methods that should always be hooked, with their containing
58
58
  # package and labels that should be applied to them.
59
59
  HOOKED_METHODS = {
60
- 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[security crypto])),
61
- 'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[view]))
60
+ 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[provider.secure_compare])),
61
+ 'ActionView::Renderer' => Hook.new(:render, Package.build_from_path('action_view', package_name: 'action_view', labels: %w[mvc.view]))
62
62
  }.freeze
63
63
 
64
64
  BUILTIN_METHODS = {
65
65
  'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
66
- 'Digest::Instance' => Hook.new(:digest, OPENSSL_PACKAGES),
67
66
  'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
68
67
  'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
69
68
  'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
70
69
  'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
71
- 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[http io])),
72
- 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[smtp email io])),
73
- 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[pop pop3 email io])),
74
- 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[imap email io])),
75
- 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[serialization marshal])),
76
- 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[serialization yaml])),
77
- 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[serialization json])),
78
- 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[serialization json])),
70
+ 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http io])),
71
+ 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.smtp protocol.email io])),
72
+ 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.pop protocol.email io])),
73
+ 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.imap protocol.email io])),
74
+ 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[format.marshal provider.serialization marshal])),
75
+ 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[format.yaml provider.serialization])),
76
+ 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
77
+ 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json provider.serialization])),
79
78
  }.freeze
80
79
 
81
- attr_reader :name, :packages
80
+ attr_reader :name, :packages, :exclude
82
81
 
83
- def initialize(name, packages = [])
82
+ def initialize(name, packages = [], exclude = [])
84
83
  @name = name
85
84
  @packages = packages
85
+ @exclude = exclude
86
86
  end
87
87
 
88
88
  class << self
@@ -108,22 +108,30 @@ module AppMap
108
108
  [ Package.build_from_path(path, exclude: package['exclude'] || [], shallow: package['shallow']) ]
109
109
  end
110
110
  end.flatten
111
- Config.new config_data['name'], packages
111
+ Config.new config_data['name'], packages, config_data['exclude'] || []
112
112
  end
113
113
  end
114
114
 
115
115
  def to_h
116
116
  {
117
117
  name: name,
118
- packages: packages.map(&:to_h)
118
+ packages: packages.map(&:to_h),
119
+ exclude: exclude
119
120
  }
120
121
  end
121
122
 
123
+ # package_for_method finds the Package, if any, which configures the hook
124
+ # for a method.
122
125
  def package_for_method(method)
126
+ package_hooked_by_class(method) || package_hooked_by_source_location(method)
127
+ end
128
+
129
+ def package_hooked_by_class(method)
123
130
  defined_class, _, method_name = ::AppMap::Hook.qualify_method_name(method)
124
- package = find_package(defined_class, method_name)
125
- return package if package
131
+ return find_package(defined_class, method_name)
132
+ end
126
133
 
134
+ def package_hooked_by_source_location(method)
127
135
  location = method.source_location
128
136
  location_file, = location
129
137
  return unless location_file
@@ -135,14 +143,22 @@ module AppMap
135
143
  end
136
144
  end
137
145
 
138
- def included_by_location?(method)
139
- !!package_for_method(method)
146
+ def never_hook?(method)
147
+ defined_class, separator, method_name = ::AppMap::Hook.qualify_method_name(method)
148
+ return true if exclude.member?(defined_class) || exclude.member?([ defined_class, separator, method_name ].join)
140
149
  end
141
150
 
151
+ # always_hook? indicates a method that should always be hooked.
142
152
  def always_hook?(defined_class, method_name)
143
153
  !!find_package(defined_class, method_name)
144
154
  end
145
155
 
156
+ # included_by_location? indicates a method whose source location matches a method definition that has been
157
+ # configured for inclusion.
158
+ def included_by_location?(method)
159
+ !!package_for_method(method)
160
+ end
161
+
146
162
  def find_package(defined_class, method_name)
147
163
  hook = find_hook(defined_class)
148
164
  return nil unless hook
data/lib/appmap/hook.rb CHANGED
@@ -63,6 +63,8 @@ module AppMap
63
63
  # Skip methods that have no instruction sequence, as they are obviously trivial.
64
64
  next unless disasm
65
65
 
66
+ next if config.never_hook?(method)
67
+
66
68
  next unless \
67
69
  config.always_hook?(hook_cls, method.name) ||
68
70
  config.included_by_location?(method)
@@ -84,6 +86,8 @@ module AppMap
84
86
  tp.enable(&block)
85
87
  end
86
88
 
89
+ # hook_builtins builds hooks for code that is built in to the Ruby standard library.
90
+ # No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
87
91
  def hook_builtins
88
92
  return unless self.class.lock_builtins
89
93
 
@@ -97,6 +101,7 @@ module AppMap
97
101
  require hook.package.package_name if hook.package.package_name
98
102
  Array(hook.method_names).each do |method_name|
99
103
  method_name = method_name.to_sym
104
+
100
105
  cls = class_from_string.(class_name)
101
106
  method = \
102
107
  begin
@@ -105,6 +110,8 @@ module AppMap
105
110
  cls.method(method_name) rescue nil
106
111
  end
107
112
 
113
+ next if config.never_hook?(method)
114
+
108
115
  if method
109
116
  Hook::Method.new(hook.package, cls, method).activate
110
117
  else
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.40.0'
6
+ VERSION = '0.41.0'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.4'
9
9
  end
@@ -8,7 +8,7 @@ describe 'AbstractControllerBase' do
8
8
  FileUtils.rm_rf tmpdir
9
9
  FileUtils.mkdir_p tmpdir
10
10
  cmd = <<~CMD.gsub "\n", ' '
11
- docker-compose run --rm -e APPMAP=true
11
+ docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true
12
12
  -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec #{spec_name}
13
13
  CMD
14
14
  run_cmd cmd, chdir: fixture_dir
@@ -137,7 +137,7 @@ describe 'AbstractControllerBase' do
137
137
  'name' => 'Renderer',
138
138
  'children' => include(hash_including(
139
139
  'name' => 'render',
140
- 'labels' => ['view']
140
+ 'labels' => ['mvc.view']
141
141
  ))
142
142
  ))
143
143
  ))
data/spec/config_spec.rb CHANGED
@@ -7,6 +7,7 @@ require 'appmap/config'
7
7
  describe AppMap::Config, docker: false do
8
8
  it 'loads from a Hash' do
9
9
  config_data = {
10
+ exclude: [],
10
11
  name: 'test',
11
12
  packages: [
12
13
  {
@@ -0,0 +1,15 @@
1
+ class ExcludeTest
2
+ def instance_method
3
+ 'instance_method'
4
+ end
5
+
6
+ class << self
7
+ def singleton_method
8
+ 'singleton_method'
9
+ end
10
+ end
11
+
12
+ def self.cls_method
13
+ 'class_method'
14
+ end
15
+ end
@@ -1,4 +1,7 @@
1
1
  name: rails5_users_app
2
2
  packages:
3
- - path: app
3
+ - path: app/models
4
+ labels: [ mvc.model ]
5
+ - path: app/controllers
6
+ labels: [ mvc.controller ]
4
7
  - gem: sequel
@@ -1,5 +1,8 @@
1
1
  name: rails6_users_app
2
2
  packages:
3
- - path: app
3
+ - path: app/models
4
+ labels: [ mvc.model ]
5
+ - path: app/controllers
6
+ labels: [ mvc.controller ]
4
7
  - gem: sequel
5
8
 
data/spec/hook_spec.rb CHANGED
@@ -61,6 +61,16 @@ describe 'AppMap class Hooking', docker: false do
61
61
  AppMap.configuration = nil
62
62
  end
63
63
 
64
+ it 'excludes named classes and methods' do
65
+ load 'spec/fixtures/hook/exclude.rb'
66
+ package = AppMap::Config::Package.build_from_path('spec/fixtures/hook/exclude.rb')
67
+ config = AppMap::Config.new('hook_spec', [ package ], %w[ExcludeTest])
68
+ AppMap.configuration = config
69
+
70
+ expect(config.never_hook?(ExcludeTest.new.method(:instance_method))).to be_truthy
71
+ expect(config.never_hook?(ExcludeTest.method(:cls_method))).to be_truthy
72
+ end
73
+
64
74
  it 'parses labels from comments' do
65
75
  _, tracer = invoke_test_file 'spec/fixtures/hook/labels.rb' do
66
76
  ClassWithLabel.new.fn_with_label
@@ -827,7 +837,7 @@ describe 'AppMap class Hooking', docker: false do
827
837
  entry = cm[1][:children][0][:children][0][:children][0]
828
838
  # Sanity check, make sure we got the right one
829
839
  expect(entry[:name]).to eq('secure_compare')
830
- expect(entry[:labels]).to eq(%w[security crypto])
840
+ expect(entry[:labels]).to eq(%w[provider.secure_compare])
831
841
  end
832
842
  end
833
843
 
@@ -5,7 +5,7 @@ describe 'SQL events' do
5
5
  around(:each) do |example|
6
6
  FileUtils.rm_rf tmpdir
7
7
  FileUtils.mkdir_p tmpdir
8
- cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
8
+ cmd = "docker-compose run --rm -e ORM_MODULE=#{orm_module} -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path tmpdir}:/app/tmp app ./bin/rspec spec/controllers/users_controller_api_spec.rb:#{test_line_number}"
9
9
  run_cmd cmd, chdir: fixture_dir
10
10
 
11
11
  example.run
@@ -7,7 +7,7 @@ describe 'RSpec feature and feature group metadata' do
7
7
  around(:each) do |example|
8
8
  FileUtils.rm_rf tmpdir
9
9
  FileUtils.mkdir_p tmpdir
10
- cmd = "docker-compose run --rm -e APPMAP=true -v #{File.absolute_path(tmpdir).shellescape}:/app/tmp app ./bin/rspec spec/models/user_spec.rb"
10
+ cmd = "docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path(tmpdir).shellescape}:/app/tmp app ./bin/rspec spec/models/user_spec.rb"
11
11
  run_cmd cmd, chdir: fixture_dir
12
12
 
13
13
  example.run
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.40.0
4
+ version: 0.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-29 00:00:00.000000000 Z
11
+ date: 2021-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -386,6 +386,7 @@ files:
386
386
  - spec/fixtures/hook/compare.rb
387
387
  - spec/fixtures/hook/constructor.rb
388
388
  - spec/fixtures/hook/exception_method.rb
389
+ - spec/fixtures/hook/exclude.rb
389
390
  - spec/fixtures/hook/instance_method.rb
390
391
  - spec/fixtures/hook/labels.rb
391
392
  - spec/fixtures/hook/singleton_method.rb