Skip to content

WIP: Add RubyVmStat spec helper

Peter Leitzen requested to merge pl-spec-ruby-vm-stat into master

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:

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

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

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
Edited by Peter Leitzen

Merge request reports