WIP: Add RubyVmStat spec helper
What does this MR do?
This MR allows tracking the RubyVM.stat
changes per spec example adds in the test suite. Each change indicates a purge of Ruby's method cache which might impact the ~performance of an application.
To enable set the environment variable RUBY_VM_STAT={1,verbose}
Example
# Prints a summary (sorted by weight) after suite has run
RUBY_VM_STAT=1 be bin/rspec spec/models/user_spec.rb
# Additionally prints stats per example. Useful for quick feedback.
RUBY_VM_STAT=verbose be bin/rspec spec/models/user_spec.rb
Example output
RUBY_VM_STAT=1 be bin/rspec spec/models/user_spec.rb
....
Summary RubyVM.stat:
-------------------
...
./spec/models/user_spec.rb:3793: {:class_serial=>933}
./spec/models/user_spec.rb:456: {:global_constant_state=>4, :class_serial=>958}
./spec/models/user_spec.rb:2150: {:class_serial=>988}
./spec/models/user_spec.rb:3657: {:global_constant_state=>3, :class_serial=>1038}
./spec/models/user_spec.rb:3294: {:class_serial=>1047}
./spec/models/user_spec.rb:3280: {:class_serial=>1227}
./spec/support/shared_examples/models/email_format_shared_examples.rb:22: {:class_serial=>1245}
./spec/models/user_spec.rb:2583: {:class_serial=>1282}
./spec/models/user_spec.rb:2021: {:class_serial=>1427}
./spec/models/user_spec.rb:1888: {:class_serial=>1516}
./spec/models/user_spec.rb:564: {:class_serial=>1731}
./spec/models/user_spec.rb:59: {:global_constant_state=>16, :class_serial=>1940}
./spec/models/user_spec.rb:877: {:class_serial=>2343}
./spec/models/user_spec.rb:1047: {:class_serial=>2670}
./spec/models/user_spec.rb:2366: {:class_serial=>5673}
./spec/models/user_spec.rb:2676: {:global_constant_state=>20, :class_serial=>8205}
./spec/models/user_spec.rb:81: {:global_constant_state=>5, :class_serial=>10769}
./spec/models/user_spec.rb:91: {:global_constant_state=>4, :class_serial=>10814}
./spec/models/user_spec.rb:1281: {:class_serial=>103194}
Finished in 125.37 seconds (files took 35.43 seconds to load)
123 examples, 0 failures
Why?
See https://mensfeld.pl/2015/04/ruby-global-method-cache-invalidation-impact-on-a-single-and-multithreaded-applications/ for possible implications of busting Ruby's method cache.
Defining a method during "runtime" via e.g. define_singleton_method
prunes Ruby's method cache (see increased :class_serial
in RubyVM.stat
). This can cause slow-downs.
Grepping for e.g. define_singleton_method
gives this list:
define_singleton_method(key) { value }
self.define_singleton_method(:request) { ActionDispatch::Request.new(env) }
self.define_singleton_method(:params) { request.params.symbolize_keys }
define_singleton_method(attribute) do
MinimalTestClass.define_singleton_method(:defaults) do
MinimalTestClass.define_singleton_method(:current_without_cache) do
define_singleton_method("find_by_#{token_field}") do |token|
define_singleton_method(name) do
define_singleton_method(counter) do
define_singleton_method("#{counter}_increment!") do
define_singleton_method(key) { value }
define_singleton_method(key) { value }
Note, not every entry above is bad because some of them are execute during "compile-time". Example: https://gitlab.com/gitlab-org/gitlab/blob/89ff3c243/lib/gitlab/metrics/methods.rb#L27
Counter Example: Both presenter implementations (https://gitlab.com/gitlab-org/gitlab/blob/89ff3c243/lib/gitlab/view/presenter/simple.rb#L13 and https://gitlab.com/gitlab-org/gitlab/blob/89ff3c243/lib/gitlab/view/presenter/delegated.rb#L17) define methods during "runtime". Every time .present(key: :value)
is called with a parameter Ruby's method cache is purged:
require "./spec/support/ruby_vm_stat"
MergeRequest.new # warm-up caches
RubyVmStat.track { MergeRequest.new.present(foo: :bar) } # => eval: {:class_serial=>2}
RubyVmStat.track { Object.new.define_singleton_method(:a) {} } # => eval: {:class_serial=>2}
Solution?
Avoid defining e.g. methods (or classes via Struct.new
) during "runtime".
define_singleton_method
is only one example how the cache is invalidated. There are many more...
Links
- https://mensfeld.pl/2015/04/ruby-global-method-cache-invalidation-impact-on-a-single-and-multithreaded-applications/
- https://github.com/ruby/ruby/pull/2752
- https://github.com/rails/rails/pull/37927
Does this MR meet the acceptance criteria?
Conformity
-
Changelog entry -
Documentation (if required) -
Code review guidelines -
Merge request performance guidelines -
Style guides -
Database guides -
Separation of EE specific content
Availability and Testing
-
Review and add/update tests for this feature/bug. Consider all test levels. See the Test Planning Process. -
Tested in all supported browsers -
Informed Infrastructure department of a default or new setting change, if applicable per definition of done
Security
If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:
-
Label as security and @ mention @gitlab-com/gl-security/appsec
-
The MR includes necessary changes to maintain consistency between UI, API, email, or other methods -
Security reports checked/validated by a reviewer from the AppSec team