From 31ee2e273644bf098dfb142f06e462b6a3257f8b Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Mon, 16 Dec 2024 17:09:00 -0800 Subject: [PATCH 1/6] Update backtrace frame regex to support Ruby 3.4+ --- .../agent/threading/backtrace_node.rb | 11 +- .../agent/threading/backtrace_node_test.rb | 117 +++++++++++++----- 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/lib/new_relic/agent/threading/backtrace_node.rb b/lib/new_relic/agent/threading/backtrace_node.rb index d074f718ca..7b962e3032 100644 --- a/lib/new_relic/agent/threading/backtrace_node.rb +++ b/lib/new_relic/agent/threading/backtrace_node.rb @@ -125,7 +125,16 @@ def dump_string(indent = 0) # Returns [filename, method, line number] def parse_backtrace_frame(frame) - frame =~ /([^:]*)(\:(\d+))?\:in `(.*)'/ + # TODO: OLD RUBIES - Ruby 3.3 + # The (?:`|') non-capturing group can be removed when the agent + # drops support for Ruby 3.3 + # This group is used to capture the pre-Ruby 3.4.0 backtrace syntax. + # Example frame: + # Ruby 3.3.0 and below + # "irb.rb:69:in `catch'" + # Ruby 3.4.0+ + # "irb.rb:69:in 'Kernel#catch'" + frame =~ /([^:]*)(\:(\d+))?\:in (?:`|')(.*)'/ [$1, $4, $3] # sic end end diff --git a/test/new_relic/agent/threading/backtrace_node_test.rb b/test/new_relic/agent/threading/backtrace_node_test.rb index 4ac31c90df..473dbdfb50 100644 --- a/test/new_relic/agent/threading/backtrace_node_test.rb +++ b/test/new_relic/agent/threading/backtrace_node_test.rb @@ -7,15 +7,22 @@ module NewRelic::Agent::Threading class BacktraceNodeTest < Minitest::Test - SINGLE_LINE = "irb.rb:69:in `catch'" - def setup + @single_line = ruby_3_4_0_or_above? ? "irb.rb:69:in 'Kernel#catch'" : "irb.rb:69:in `catch'" @node = BacktraceRoot.new - @single_trace = [ - "irb.rb:69:in `catch'", - "irb.rb:69:in `start'", - "irb:12:in `
'" - ] + @single_trace = if ruby_3_4_0_or_above? + [ + "irb.rb:69:in 'Kernel#catch'", + "irb.rb:69:in 'Object#start'", + "irb:12:in '
'" + ] + else + [ + "irb.rb:69:in `catch'", + "irb.rb:69:in `start'", + "irb:12:in `
'" + ] + end end def assert_backtrace_trees_equal(a, b, original_a = a, original_b = b) @@ -43,12 +50,18 @@ def convert_nodes_to_array(nodes) end def test_single_node_converts_to_array - line = "irb.rb:69:in `catch'" + line = ruby_3_4_0_or_above? ? "irb.rb:69:in 'Kernel#catch'" : "irb.rb:69:in `catch'" node = BacktraceNode.new(line) convert_nodes_to_array([node]) + expected = if ruby_3_4_0_or_above? + ['irb.rb', 'Kernel#catch', 69] + else + ['irb.rb', 'catch', 69] + end + assert_equal([ - ['irb.rb', 'catch', 69], + expected, 0, 0, [] ], @@ -56,18 +69,30 @@ def test_single_node_converts_to_array end def test_multiple_nodes_converts_to_array - line = "irb.rb:69:in `catch'" - child_line = "bacon.rb:42:in `yum'" + line = ruby_3_4_0_or_above? ? "irb.rb:69:in 'Kernel#catch'" : "irb.rb:69:in `catch'" + child_line = ruby_3_4_0_or_above? ? "bacon.rb:42:in 'Bacon#yum'" : "bacon.rb:42:in `yum'" node = create_node(line) child_node = create_node(child_line, node) convert_nodes_to_array([node, child_node]) + expected_1 = if ruby_3_4_0_or_above? + ['irb.rb', 'Kernel#catch', 69] + else + ['irb.rb', 'catch', 69] + end + + expected_2 = if ruby_3_4_0_or_above? + ['bacon.rb', 'Bacon#yum', 42] + else + ['bacon.rb', 'yum', 42] + end + assert_equal([ - ['irb.rb', 'catch', 69], + expected_1, 0, 0, [ [ - ['bacon.rb', 'yum', 42], + expected_2, 0, 0, [] ] @@ -76,13 +101,22 @@ def test_multiple_nodes_converts_to_array node.as_array) end + def test_nodes_in_ruby_3_4_work_with_class_methods + end + def test_nodes_without_line_numbers - line = "transaction_sample_buffer.rb:in `visit_node'" + line = ruby_3_4_0_or_above? ? "transaction_sample_buffer.rb:in 'TransactionSampleBuffer#visit_node'" : "transaction_sample_buffer.rb:in `visit_node'" node = create_node(line) convert_nodes_to_array([node]) + expected = if ruby_3_4_0_or_above? + ['transaction_sample_buffer.rb', 'TransactionSampleBuffer#visit_node', -1] + else + ['transaction_sample_buffer.rb', 'visit_node', -1] + end + assert_equal([ - ['transaction_sample_buffer.rb', 'visit_node', -1], + expected, 0, 0, [] ], @@ -90,7 +124,7 @@ def test_nodes_without_line_numbers end def test_gracefully_handle_bad_values_in_to_array - node = BacktraceNode.new(SINGLE_LINE) + node = BacktraceNode.new(@single_line) node.stubs(:parse_backtrace_frame).returns(['irb.rb', 'catch', 'blarg']) node.runnable_count = Rational(10, 1) convert_nodes_to_array([node]) @@ -104,8 +138,8 @@ def test_gracefully_handle_bad_values_in_to_array end def test_add_child_twice - parent = BacktraceNode.new(SINGLE_LINE) - child = BacktraceNode.new(SINGLE_LINE) + parent = BacktraceNode.new(@single_line) + child = BacktraceNode.new(@single_line) parent.add_child_unless_present(child) parent.add_child_unless_present(child) @@ -137,17 +171,33 @@ def test_aggregate_builds_tree_from_overlapping_traces end def test_aggregate_builds_tree_from_diverging_traces - backtrace1 = [ - "baz.rb:3:in `baz'", - "bar.rb:2:in `bar'", - "foo.rb:1:in `foo'" - ] - - backtrace2 = [ - "wiggle.rb:3:in `wiggle'", - "qux.rb:2:in `qux'", - "foo.rb:1:in `foo'" - ] + backtrace1 = if ruby_3_4_0_or_above? + [ + "baz.rb:3:in 'Object#baz'", + "bar.rb:2:in 'Object#bar'", + "foo.rb:1:in 'Object#foo'" + ] + else + [ + "baz.rb:3:in `baz'", + "bar.rb:2:in `bar'", + "foo.rb:1:in `foo'" + ] + end + + backtrace2 = if ruby_3_4_0_or_above? + [ + "wiggle.rb:3:in 'Object#wiggle'", + "qux.rb:2:in 'Object#qux'", + "foo.rb:1:in 'Object#foo'" + ] + else + [ + "wiggle.rb:3:in `wiggle'", + "qux.rb:2:in `qux'", + "foo.rb:1:in `foo'" + ] + end @node.aggregate(backtrace1) @node.aggregate(backtrace2) @@ -178,12 +228,19 @@ def test_aggregate_doesnt_create_duplicate_children end def test_aggregate_limits_recorded_depth - deep_backtrace = (0..2000).to_a.map { |i| "foo.rb:#{i}:in `foo'" } + + deep_backtrace = (0..2000).to_a.map { |i| ruby_3_4_0_or_above? ? "foo.rb:#{i}:in 'Foo#foo'" : "foo.rb:#{i}:in `foo'" } root = BacktraceRoot.new root.aggregate(deep_backtrace) assert_equal(MAX_THREAD_PROFILE_DEPTH, root.flattened.size) end + + private + + def ruby_3_4_0_or_above? + Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0') + end end end From 5791d07ee81134edac861c22fccd5ff3fdf25e45 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Tue, 17 Dec 2024 14:27:05 -0800 Subject: [PATCH 2/6] Rubocop --- test/new_relic/agent/threading/backtrace_node_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/new_relic/agent/threading/backtrace_node_test.rb b/test/new_relic/agent/threading/backtrace_node_test.rb index 473dbdfb50..4029a8de69 100644 --- a/test/new_relic/agent/threading/backtrace_node_test.rb +++ b/test/new_relic/agent/threading/backtrace_node_test.rb @@ -228,7 +228,6 @@ def test_aggregate_doesnt_create_duplicate_children end def test_aggregate_limits_recorded_depth - deep_backtrace = (0..2000).to_a.map { |i| ruby_3_4_0_or_above? ? "foo.rb:#{i}:in 'Foo#foo'" : "foo.rb:#{i}:in `foo'" } root = BacktraceRoot.new From 4557633bd92f3ed40e9744579336e0e982b6206e Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 18 Dec 2024 12:26:47 -0800 Subject: [PATCH 3/6] Add 3.4 changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 310ffab922..7f7cbbddb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## dev +- **Feature: Support Ruby 3.4.0** + + The agent now supports Ruby 3.4.0. We've made incremental changes throughout the preview stage to reach compatibility. This release includes an update to the Thread Profiler for compatibility with Ruby 3.4.0's new backtrace format. [Issue#2992](https://github.com/newrelic/newrelic-ruby-agent/issues/2992) [PR#2997](https://github.com/newrelic/newrelic-ruby-agent/pull/2997) + - **Bugfix: Do not attempt to decorate logs with `nil` messages** The agent no longer attempts to add New Relic linking metadata to logs with `nil` messages. Thank you, [@arlando](https://github.com/arlando) for bringing this to our attention! [Issue#2985](https://github.com/newrelic/newrelic-ruby-agent/issues/2985) [PR#2986](https://github.com/newrelic/newrelic-ruby-agent/pull/2986) From f251289e38465c71695b24fc117ab584af77e422 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 18 Dec 2024 12:27:07 -0800 Subject: [PATCH 4/6] Update thread profile tests for 3.4 output --- .../agent/threading/thread_profile_test.rb | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/test/new_relic/agent/threading/thread_profile_test.rb b/test/new_relic/agent/threading/thread_profile_test.rb index 459abc208e..95fa5a21fc 100644 --- a/test/new_relic/agent/threading/thread_profile_test.rb +++ b/test/new_relic/agent/threading/thread_profile_test.rb @@ -16,11 +16,19 @@ class ThreadProfileTest < Minitest::Test def setup setup_fake_threads - @single_trace = [ - "irb.rb:69:in `catch'", - "irb.rb:69:in `start'", - "irb:12:in `
'" - ] + @single_trace = if ruby_3_4_0_or_above? + [ + "irb.rb:69:in 'Kernel#catch'", + "irb.rb:69:in 'Object#start'", + "irb:12:in '
'" + ] + else + [ + "irb.rb:69:in `catch'", + "irb.rb:69:in `start'", + "irb:12:in `
'" + ] + end @profile = ThreadProfile.new @@ -240,6 +248,10 @@ def rec_count_tree_nodes(tree) count end + + def ruby_3_4_0_or_above? + Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0') + end end end end From 03be511b399d73e767962c48ed6e787ca9d4feff Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Thu, 19 Dec 2024 14:33:20 -0800 Subject: [PATCH 5/6] Add TODO for ruby version condition --- test/new_relic/agent/threading/backtrace_node_test.rb | 4 ++++ test/new_relic/agent/threading/thread_profile_test.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/new_relic/agent/threading/backtrace_node_test.rb b/test/new_relic/agent/threading/backtrace_node_test.rb index 4029a8de69..1e466943f1 100644 --- a/test/new_relic/agent/threading/backtrace_node_test.rb +++ b/test/new_relic/agent/threading/backtrace_node_test.rb @@ -238,6 +238,10 @@ def test_aggregate_limits_recorded_depth private + # TODO: OLD RUBIES < 3.4 + # Ruby 3.4 introduced a new format for backtraces + # These tests have examples with the old the and new formats + # When we drop support for Ruby 3.3, we can remove the condition def ruby_3_4_0_or_above? Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0') end diff --git a/test/new_relic/agent/threading/thread_profile_test.rb b/test/new_relic/agent/threading/thread_profile_test.rb index 95fa5a21fc..f47ba54254 100644 --- a/test/new_relic/agent/threading/thread_profile_test.rb +++ b/test/new_relic/agent/threading/thread_profile_test.rb @@ -249,6 +249,10 @@ def rec_count_tree_nodes(tree) count end + # TODO: OLD RUBIES < 3.4 + # Ruby 3.4 introduced a new format for backtraces + # These tests have examples with the old the and new formats + # When we drop support for Ruby 3.3, we can remove the condition def ruby_3_4_0_or_above? Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0') end From 858be612b4e888eb163c2b7d8bbd7ec1c6c487b3 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Thu, 19 Dec 2024 14:33:31 -0800 Subject: [PATCH 6/6] Typo --- .rubocop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3cc6cc5d2d..e87611acce 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -877,7 +877,7 @@ Naming/VariableNumber: Performance/BindCall: Enabled: false -# TODO: OLD RUBIES - Requites 2.5 +# TODO: OLD RUBIES - Requires 2.5 Performance/DeletePrefix: Enabled: false