Skip to content

Commit

Permalink
Merge pull request #165 from solarwinds/NH-94756
Browse files Browse the repository at this point in the history
NH-94756: dbo for mysql2
  • Loading branch information
xuan-cao-swi authored Nov 18, 2024
2 parents e35e8ec + 57ee15b commit 44e712f
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 28 deletions.
2 changes: 1 addition & 1 deletion ext/oboe_metal/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
dir_config('oboe', 'src', 'lib')

# create Makefile
if have_library('oboe', 'oboe_config_get_revision', 'oboe.h')
if have_library('oboe')
$libs = append_library($libs, 'oboe')
$libs = append_library($libs, 'stdc++')

Expand Down
6 changes: 6 additions & 0 deletions lib/solarwinds_apm/oboe_init_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,14 @@ def appoptics_collector?
(allowed_uri.include? ENV.fetch('SW_APM_COLLECTOR', nil))
end

def java_collector?(uri)
java_collector_regex = /java-collector:\d+/
uri.match?(java_collector_regex)
end

def sanitize_collector_uri(uri)
return uri if uri.nil? || uri.empty?
return uri if java_collector?(uri)

begin
sanitized_uri = ::URI.parse("http://#{uri}").host
Expand Down
4 changes: 2 additions & 2 deletions lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(txn_manager)
# started span.
def on_start(span, parent_context)
SolarWindsAPM.logger.debug do
"[#{self.class}/#{__method__}] processor on_start span: #{span.inspect}, parent_context: #{parent_context.inspect}"
"[#{self.class}/#{__method__}] processor on_start span: #{span.to_span_data.inspect}, parent_context: #{parent_context.inspect}"
end

return if non_entry_span(parent_context: parent_context)
Expand All @@ -47,7 +47,7 @@ def on_start(span, parent_context)
#
# @param [Span] span the {Span} that just ended.
def on_finish(span)
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.inspect}" }
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.to_span_data.inspect}" }

return if non_entry_span(span: span)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ def extract(carrier, context: ::OpenTelemetry::Context.current, getter: ::OpenTe
def inject(carrier, context: ::OpenTelemetry::Context.current,
setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
span_context = ::OpenTelemetry::Trace.current_span(context).context
SolarWindsAPM.logger.debug do
"[#{self.class}/#{__method__}] context: #{context.inspect}; span_context: #{span_context.inspect}"
end

SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] context current_span: #{context.instance_variable_get(:@entries)&.values&.first.inspect}" }
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context: #{span_context.inspect}" }

return unless span_context&.valid?

x_trace = Utils.traceparent_from_context(span_context)
Expand Down
10 changes: 10 additions & 0 deletions lib/solarwinds_apm/otel_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ def self.initialize
ENV['OTEL_LOG_LEVEL'] = SolarWindsAPM::Config::SW_LOG_LEVEL_MAPPING.dig(log_level, :otel)
end

# for dbo, traceparent injection as comments
require_relative 'patch/tag_sql_patch' if SolarWindsAPM::Config[:tag_sql]

::OpenTelemetry::SDK.configure { |c| c.use_all(@@config_map) }

validate_propagator(::OpenTelemetry.propagation.instance_variable_get(:@propagators))
Expand All @@ -126,6 +129,13 @@ def self.initialize

# configure sampler afterwards
::OpenTelemetry.tracer_provider.sampler = @@config[:sampler]

if ENV['SW_APM_AUTO_CONFIGURE'] == 'false'
SolarWindsAPM.logger.info '==================================================================='
SolarWindsAPM.logger.info "\e[1mSolarWindsAPM manual initialization was successful.\e[0m"
SolarWindsAPM.logger.info '==================================================================='
end

nil
end

Expand Down
36 changes: 36 additions & 0 deletions lib/solarwinds_apm/patch/tag_sql/sw_mysql2_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

module SolarWindsAPM
module Patch
module TagSql
module SWOMysql2Patch
def query(sql, options = {})
current_span = ::OpenTelemetry::Trace.current_span

annotated_sql = ''
if current_span.context.trace_flags.sampled?
traceparent = SolarWindsAPM::Utils.traceparent_from_context(current_span.context)
annotated_traceparent = "/*traceparent='#{traceparent}'*/"
current_span.add_attributes({ 'sw.query_tag' => annotated_traceparent })
annotated_sql = "#{sql} #{annotated_traceparent}"
else
annotated_sql = sql
end

super(annotated_sql, options)
end
end
end
end
end

# need to prepend before mysql2 instrumentation prepend the original function
# after entire process, the call sequence will be:
# upstream instrumentation -> our patch -> original function
Mysql2::Client.prepend(SolarWindsAPM::Patch::TagSql::SWOMysql2Patch) if defined?(Mysql2::Client)
9 changes: 9 additions & 0 deletions lib/solarwinds_apm/patch/tag_sql_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

require_relative 'tag_sql/sw_mysql2_patch'
14 changes: 0 additions & 14 deletions lib/solarwinds_apm/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,3 @@
require_relative 'support/utils'
require_relative 'support/x_trace_options'
require_relative 'support/support_report'

if SolarWindsAPM::Config[:tag_sql]
if defined?(Rails)
if Rails.version < '7'
require_relative 'support/swomarginalia/railtie'
else
require_relative 'support/swomarginalia/comment'
require_relative 'support/swomarginalia/formatter' if Rails.version <= '7.1'
end
elsif defined?(ActiveRecord)
require_relative 'support/swomarginalia/load_swomarginalia'
SolarWindsAPM::SWOMarginalia::LoadSWOMarginalia.insert
end
end
2 changes: 1 addition & 1 deletion lib/solarwinds_apm/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module SolarWindsAPM
module Version
MAJOR = 6 # breaking,
MINOR = 1 # feature,
PATCH = 0 # fix => BFF
PATCH = 3 # fix => BFF
PRE = nil

STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
Expand Down
15 changes: 15 additions & 0 deletions test/minitest_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,18 @@ def stub_for_mkmf_test
end
end
end

# For dbo patching test
module Mysql2
class Client
attr_reader :query_options

def initialize(_opts = {})
@query_options = {}
end

def query(sql, _options = {})
sql
end
end
end
52 changes: 52 additions & 0 deletions test/patch/sw_mysql2_patch_integrate_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

# Copyright (c) 2023 SolarWinds, LLC.
# All rights reserved.

require 'minitest_helper'
require './lib/solarwinds_apm/config'
require './lib/solarwinds_apm/opentelemetry'
require './lib/solarwinds_apm/support/txn_name_manager'
require './lib/solarwinds_apm/otel_config'
require './lib/solarwinds_apm/api'
require './lib/solarwinds_apm/support'
require './lib/solarwinds_apm/constants'
require './lib/solarwinds_apm/oboe_init_options'

# rubocop:disable Naming/MethodName
module SolarWindsAPM
module Span
def self.createSpan(trans_name, domain, span_time, has_error); end
end
end
# rubocop:enable Naming/MethodName

describe 'mysql2 patch test' do
puts "\n\033[1m=== TEST RUN: #{RUBY_VERSION} #{File.basename(__FILE__)} #{Time.now.strftime('%Y-%m-%d %H:%M')} ===\033[0m\n"

let(:sdk) { OpenTelemetry::SDK }
let(:exporter) { sdk::Trace::Export::InMemorySpanExporter.new }
let(:span_processor) { sdk::Trace::Export::SimpleSpanProcessor.new(exporter) }
let(:finished_spans) { exporter.finished_spans }

it 'tag_sql_mysql2_integrate_test' do
require './lib/solarwinds_apm/patch/tag_sql/sw_mysql2_patch'

OpenTelemetry::SDK.configure(&:use_all)
OpenTelemetry.tracer_provider.add_span_processor(span_processor)

client_ancestors = Mysql2::Client.ancestors
_(client_ancestors[0]).must_equal OpenTelemetry::Instrumentation::Mysql2::Patches::Client
_(client_ancestors[1]).must_equal SolarWindsAPM::Patch::TagSql::SWOMysql2Patch
_(client_ancestors[2]).must_equal Mysql2::Client

mysql2_client = Mysql2::Client.new
sql = mysql2_client.query('SELECT * FROM ABC;')

pattern = %r{/\*traceparent='[\da-f-]+'*\*/$}
assert_match pattern, finished_spans[0].attributes['sw.query_tag'], "Doesn't match sw.query_tag"

pattern = %r{^SELECT \* FROM ABC;\s+/\*traceparent='[\da-f-]+'*\*/$}
assert_match pattern, sql, "Sql doesn't contain traceparent"
end
end
23 changes: 23 additions & 0 deletions test/patch/sw_mysql2_patch_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

# Copyright (c) 2023 SolarWinds, LLC.
# All rights reserved.

require 'minitest_helper'
require './lib/solarwinds_apm/config'
require './lib/solarwinds_apm/opentelemetry'
require './lib/solarwinds_apm/support/txn_name_manager'
require './lib/solarwinds_apm/otel_config'

describe 'mysql2 patch test' do
puts "\n\033[1m=== TEST RUN: #{RUBY_VERSION} #{File.basename(__FILE__)} #{Time.now.strftime('%Y-%m-%d %H:%M')} ===\033[0m\n"

it 'mysql_patch_order_test_when_tag_sql_is_false' do
SolarWindsAPM::Config[:tag_sql] = false
SolarWindsAPM::OTelConfig.initialize

client_ancestors = Mysql2::Client.ancestors
_(client_ancestors[0]).must_equal OpenTelemetry::Instrumentation::Mysql2::Patches::Client
_(client_ancestors[1]).must_equal Mysql2::Client
end
end
25 changes: 18 additions & 7 deletions test/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
# -g gemfile - restrict the tests to the ones associated with this gemfile (path from gem-root)
#

check_status() {
status=$?
[[ $status -gt $exit_status ]] && exit_status=$status
[[ $status -ne 0 ]] && echo "!!! Test suite failed for $check_file_name with Ruby $ruby_version !!!"
}

gemfiles=(
"gemfiles/unit.gemfile"
"gemfiles/rails_6x.gemfile"
Expand Down Expand Up @@ -61,9 +67,8 @@ for gemfile in "${gemfiles[@]}" ; do
fi
# and here we are finally running the tests!!!
BUNDLE_GEMFILE=$gemfile bundle exec rake test
status=$?
[[ $status -gt $exit_status ]] && exit_status=$status
[[ $status -ne 0 ]] && echo "!!! Test suite failed for $gemfile with Ruby $ruby_version !!!"
check_file_name=$gemfile
check_status
done

# explicitly test for solarwinds initialization
Expand All @@ -73,6 +78,14 @@ if ! BUNDLE_GEMFILE=gemfiles/test_gems.gemfile bundle update; then
continue
fi

# for dbo patch test
PATCH_TEST_FILE=$(find test/patch/*_test.rb -type f)
for file in $PATCH_TEST_FILE; do
BUNDLE_GEMFILE=gemfiles/test_gems.gemfile bundle exec ruby -I test $file
check_file_name=$file
check_status
done

# create fake libsolarwinds_apm.so for testing
cd test/clib
ruby solarwinds_apm.rb
Expand All @@ -83,12 +96,10 @@ echo "Fake libsolarwinds_apm.so created"
NUMBER_FILE=$(find test/solarwinds_apm/init_test/*_test.rb -type f | wc -l)
for ((i = 1; i <= $NUMBER_FILE; i++)); do
BUNDLE_GEMFILE=gemfiles/test_gems.gemfile bundle exec ruby -I test test/solarwinds_apm/init_test/init_${i}_test.rb
status=$?
[[ $status -gt $exit_status ]] && exit_status=$status
[[ $status -ne 0 ]] && echo "!!! Test suite failed for init_${i}_test.rb with Ruby $ruby_version !!!"
check_file_name=init_${i}_test.rb
check_status
done


echo ""
echo "--- SUMMARY ------------------------------"
grep -E '===|failures|FAIL|ERROR' "$TEST_RUNS_FILE_NAME"
Expand Down
18 changes: 18 additions & 0 deletions test/solarwinds_apm/oboe_init_options_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,24 @@
_(sanitized_uri).must_equal 'google.ca.appoptics'
end

it 'return_original_uri_for_java_collector' do
uri = 'java-collector:12224'
sanitized_uri = SolarWindsAPM::OboeInitOptions.instance.send(:sanitize_collector_uri, uri)
_(sanitized_uri).must_equal 'java-collector:12224'

uri = 'java-collector:1'
sanitized_uri = SolarWindsAPM::OboeInitOptions.instance.send(:sanitize_collector_uri, uri)
_(sanitized_uri).must_equal 'java-collector:1'

uri = 'java-collector'
sanitized_uri = SolarWindsAPM::OboeInitOptions.instance.send(:sanitize_collector_uri, uri)
_(sanitized_uri).must_equal 'java-collector'

uri = 'java-collector:regexregex'
sanitized_uri = SolarWindsAPM::OboeInitOptions.instance.send(:sanitize_collector_uri, uri)
_(sanitized_uri).must_equal ''
end

it 'test_when_otel_service_name_exist' do
ENV['SW_APM_REPORTER'] = 'ssl'
ENV['OTEL_SERVICE_NAME'] = 'abcdef'
Expand Down

0 comments on commit 44e712f

Please sign in to comment.