|
| 1 | +# Used to quickly run benchmark under RSpec as part of the usual test suite, to validate it didn't bitrot |
| 2 | +VALIDATE_BENCHMARK_MODE = ENV['VALIDATE_BENCHMARK'] == 'true' |
| 3 | + |
| 4 | +return unless __FILE__ == $PROGRAM_NAME || VALIDATE_BENCHMARK_MODE |
| 5 | + |
| 6 | +require_relative 'benchmarks_helper' |
| 7 | + |
| 8 | +if RUBY_VERSION < '3.2' |
| 9 | + if VALIDATE_BENCHMARK_MODE |
| 10 | + # To simplify things, we allow this benchmark to be run in VALIDATE_BENCHMARK_MODE even though it's a no-op |
| 11 | + $stderr.puts "Skipping benchmark because it requires Ruby 3.2 or newer" |
| 12 | + return |
| 13 | + else |
| 14 | + raise 'This benchmark requires Ruby 3.2 or newer' |
| 15 | + end |
| 16 | +end |
| 17 | + |
| 18 | +# This benchmark measures the performance of the main stack sampling loop of the profiler |
| 19 | + |
| 20 | +class ProfilerSampleGvlBenchmark |
| 21 | + # This is needed because we're directly invoking the collector through a testing interface; in normal |
| 22 | + # use a profiler thread is automatically used. |
| 23 | + PROFILER_OVERHEAD_STACK_THREAD = Thread.new { sleep } |
| 24 | + |
| 25 | + def initialize |
| 26 | + create_profiler |
| 27 | + @target_thread = thread_with_very_deep_stack |
| 28 | + |
| 29 | + # Sample once to trigger thread context creation for all threads (including @target_thread) |
| 30 | + Datadog::Profiling::Collectors::ThreadContext::Testing._native_sample(@collector, PROFILER_OVERHEAD_STACK_THREAD) |
| 31 | + end |
| 32 | + |
| 33 | + def create_profiler |
| 34 | + @recorder = Datadog::Profiling::StackRecorder.new( |
| 35 | + cpu_time_enabled: true, |
| 36 | + alloc_samples_enabled: false, |
| 37 | + heap_samples_enabled: false, |
| 38 | + heap_size_enabled: false, |
| 39 | + heap_sample_every: 1, |
| 40 | + timeline_enabled: true, |
| 41 | + ) |
| 42 | + @collector = Datadog::Profiling::Collectors::ThreadContext.for_testing( |
| 43 | + recorder: @recorder, |
| 44 | + waiting_for_gvl_threshold_ns: 0, |
| 45 | + timeline_enabled: true, |
| 46 | + ) |
| 47 | + end |
| 48 | + |
| 49 | + def thread_with_very_deep_stack(depth: 200) |
| 50 | + deep_stack = proc do |n| |
| 51 | + if n > 0 |
| 52 | + deep_stack.call(n - 1) |
| 53 | + else |
| 54 | + sleep |
| 55 | + end |
| 56 | + end |
| 57 | + |
| 58 | + Thread.new { deep_stack.call(depth) }.tap { |t| t.name = "Deep stack #{depth}" } |
| 59 | + end |
| 60 | + |
| 61 | + def run_benchmark |
| 62 | + Benchmark.ips do |x| |
| 63 | + benchmark_time = VALIDATE_BENCHMARK_MODE ? { time: 0.01, warmup: 0 } : { time: 20, warmup: 2 } |
| 64 | + x.config( |
| 65 | + **benchmark_time, |
| 66 | + ) |
| 67 | + |
| 68 | + x.report("gvl benchmark samples") do |
| 69 | + Datadog::Profiling::Collectors::ThreadContext::Testing._native_on_gvl_waiting(@target_thread) |
| 70 | + Datadog::Profiling::Collectors::ThreadContext::Testing._native_on_gvl_running(@target_thread) |
| 71 | + Datadog::Profiling::Collectors::ThreadContext::Testing._native_sample_after_gvl_running(@collector, @target_thread) |
| 72 | + end |
| 73 | + |
| 74 | + x.save! "#{File.basename(__FILE__)}-results.json" unless VALIDATE_BENCHMARK_MODE |
| 75 | + x.compare! |
| 76 | + end |
| 77 | + |
| 78 | + @recorder.serialize! |
| 79 | + end |
| 80 | +end |
| 81 | + |
| 82 | +puts "Current pid is #{Process.pid}" |
| 83 | + |
| 84 | +ProfilerSampleGvlBenchmark.new.instance_exec do |
| 85 | + run_benchmark |
| 86 | +end |
0 commit comments