appmap 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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