Skip to content

Commit

Permalink
Import interesting_backtrace_helper.rb from backtracie gem and use …
Browse files Browse the repository at this point in the history
…it for testing the stack collector

(As the creator and sole copyright owner of that file in the
`backtracie` gem, taken from
<https://github.com/ivoanjo/backtracie/blob/1f6116ff5b07e87fe3e5be95a22c6a8e1210de8b/spec/unit/interesting_backtrace_helper.rb>
I am relicensing the file and its copyrights to belong to Datadog
similarly to the rest of the dd-trace-rb codebase).

This file is used as a single mega-complex test for the stack
sampling code, and is quite useful to spot regressions.
  • Loading branch information
ivoanjo committed May 20, 2022
1 parent ad5c876 commit 7d1d002
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ AllCops:
- 'integration/apps/*/bin/*'
- 'integration/apps/*/Gemfile'
- 'lib/datadog/profiling/pprof/pprof_pb.rb'
- 'spec/**/**/interesting_backtrace_helper.rb' # This file needs quite a few bizarre code patterns by design
NewCops: disable # Don't allow new cops to be enabled implicitly.

Layout/LineLength:
Expand Down
185 changes: 185 additions & 0 deletions spec/datadog/profiling/collectors/interesting_backtrace_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# typed: ignore

# This (very bizarre file) gets used from `stack_spec.rb`. It tries to reproduce the most interesting (contrived?) call
# stack I can think of, with as many weird variants as possible.
# The objective is to thoroughly test our stack trace sampling abilities.
# Control flow goes from bottom of file to top (e.g. ClassA will be the top of the stack)

# ----

class IbhClassA
def hello
$ibh_ready_queue << true
sleep
end
end

module IbhModuleB
class IbhClassB < IbhClassA
def hello
super
end
end
end

module IbhModuleC
def self.hello
IbhModuleB::IbhClassB.new.hello
end
end

class IbhClassWithStaticMethod
def self.hello
IbhModuleC.hello
end
end

module IbhModuleD
def hello
IbhClassWithStaticMethod.hello
end
end

class IbhClassC
include IbhModuleD
end

$ibh_a_proc = proc { IbhClassC.new.hello }

$ibh_a_lambda = lambda { $ibh_a_proc.call }

class IbhClassD; end

$ibh_class_d_object = IbhClassD.new

def $ibh_class_d_object.hello
$ibh_a_lambda.call
end

class IbhClassE
def hello
$ibh_class_d_object.hello
end
end

class IbhClassG
def hello
raise "This should not be called"
end
end

module IbhContainsRefinement
module RefinesIbhClassG
refine IbhClassG do
def hello
if RUBY_VERSION >= "2.7.0"
IbhClassE.instance_method(:hello).bind_call(IbhClassF.new)
else
IbhClassE.instance_method(:hello).bind(IbhClassF.new).call
end
end
end
end
end

module IbhModuleE
using IbhContainsRefinement::RefinesIbhClassG

def self.hello
IbhClassG.new.hello
end
end

class IbhClassH
def method_missing(name, *_)
super unless name == :hello

IbhModuleE.hello
end
end

class IbhClassF < IbhClassE
def hello(arg1, arg2, test1, test2)
1.times {
IbhClassH.new.hello
}
end
end

IbhClassI = Class.new do
define_method(:hello) do
IbhClassF.new.hello(0, 1, 2, 3)
end
end

$ibh_singleton_class = Object.new.singleton_class

def $ibh_singleton_class.hello
IbhClassI.new.hello
end

$ibh_anonymous_instance = Class.new do
def hello
$ibh_singleton_class.hello
end
end.new

$ibh_anonymous_module = Module.new do
def self.hello
$ibh_anonymous_instance.hello
end
end

def ibh_method_with_complex_parameters(a, b = nil, *c, (d), f:, g: nil, **h, &i)
$ibh_anonymous_module.hello
end

class IbhClassJ
def hello_helper
yield
end

def hello
hello_helper do
hello_helper do
ibh_method_with_complex_parameters(0, 1, 2, [3, 4], f: 5, g: 6, h: 7, &proc {})
end
end
end
end

class IbhClassK
def hello
eval("IbhClassJ.new.hello", binding, __FILE__, __LINE__)
end
end

class IbhClassL
def hello
IbhClassK.new.send(:instance_eval, "hello")
end
end

class IbhClassM
def hello
IbhClassL.new.send(:eval, "hello")
end
end

IbhClassN = Class.new do
define_method(:hello) do
1.times {
IbhClassM.new.hello
}
end
end

def ibh_top_level_hello
IbhClassN.new.hello
end

1.times {
1.times {
eval("ibh_top_level_hello()")
}
}
23 changes: 23 additions & 0 deletions spec/datadog/profiling/collectors/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,29 @@ def call_sleep
end
end
# rubocop:enable Style/EvalWithLocation

context 'when sampling the interesting backtrace helper' do
# rubocop:disable Style/GlobalVars
let(:do_in_background_thread) do
proc do |ready_queue|
$ibh_ready_queue = ready_queue
load("#{__dir__}/interesting_backtrace_helper.rb")
end
end

after do
$ibh_ready_queue = nil
end
# rubocop:enable Style/GlobalVars

it 'matches the Ruby backtrace API' do
expect(gathered_stack).to eq reference_stack
end

it 'has a sleeping frame at the top of the stack' do
expect(reference_stack.first).to match(hash_including(base_label: 'sleep'))
end
end
end

context 'when sampling a thread with a stack that is deeper than the configured max_frames' do
Expand Down

0 comments on commit 7d1d002

Please sign in to comment.