diff --git a/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_remote_sampler.rb b/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_remote_sampler.rb index 13238f9c1a..41f051ca2f 100644 --- a/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_remote_sampler.rb +++ b/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_remote_sampler.rb @@ -8,7 +8,9 @@ require 'json' require 'opentelemetry/sdk' require_relative 'sampling_rule' +require_relative 'fallback_sampler' require_relative 'sampling_rule_applier' +require_relative 'rule_cache' require_relative 'aws_xray_sampling_client' module OpenTelemetry @@ -57,7 +59,9 @@ def initialize(endpoint: '127.0.0.1:2000', polling_interval: DEFAULT_RULES_POLLI @target_polling_jitter_millis = (rand / 10) * 1000 @aws_proxy_endpoint = endpoint || DEFAULT_AWS_PROXY_ENDPOINT + @fallback_sampler = OpenTelemetry::Sampler::XRay::FallbackSampler.new @client_id = self.class.generate_client_id + @rule_cache = OpenTelemetry::Sampler::XRay::RuleCache.new(resource) @sampling_client = OpenTelemetry::Sampler::XRay::AWSXRaySamplingClient.new(@aws_proxy_endpoint) @@ -68,10 +72,25 @@ def initialize(endpoint: '127.0.0.1:2000', polling_interval: DEFAULT_RULES_POLLI end def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) - OpenTelemetry::SDK::Trace::Samplers::Result.new( - decision: OpenTelemetry::SDK::Trace::Samplers::Decision::DROP, - tracestate: tracestate, - attributes: attributes + if @rule_cache.expired? + OpenTelemetry.logger.debug('Rule cache is expired, so using fallback sampling strategy') + return @fallback_sampler.should_sample?( + trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes + ) + end + + matched_rule = @rule_cache.get_matched_rule(attributes) + if matched_rule + return matched_rule.should_sample?( + trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes + ) + end + + OpenTelemetry.logger.debug( + 'Using fallback sampler as no rule match was found. This is likely due to a bug, since default rule should always match' + ) + @fallback_sampler.should_sample?( + trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes ) end @@ -102,6 +121,8 @@ def retrieve_and_update_sampling_rules else OpenTelemetry.logger.error('GetSamplingRules Response is falsy') end + rescue StandardError => e + OpenTelemetry.handle_error(exception: e, message: 'Error occurred when retrieving or updating Sampling Rules') end def update_sampling_rules(response_object) @@ -113,7 +134,7 @@ def update_sampling_rules(response_object) sampling_rules << SamplingRuleApplier.new(sampling_rule) end end - # TODO: Add Sampling Rules to a Rule Cache + @rule_cache.update_rules(sampling_rules) else OpenTelemetry.logger.error('SamplingRuleRecords from GetSamplingRules request is not defined') end diff --git a/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_sampling_client.rb b/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_sampling_client.rb index 84b1323b9c..3660e1a14a 100644 --- a/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_sampling_client.rb +++ b/sampler/xray/lib/opentelemetry/sampler/xray/aws_xray_sampling_client.rb @@ -51,7 +51,7 @@ def parse_endpoint(endpoint) host, port = endpoint.split(':') [host, port.to_i] rescue StandardError => e - OpenTelemetry.logger.error("Invalid endpoint: #{endpoint}") + OpenTelemetry.handle_error(exception: e, message: "Invalid endpoint: #{endpoint}") raise e end end diff --git a/sampler/xray/lib/opentelemetry/sampler/xray/fallback_sampler.rb b/sampler/xray/lib/opentelemetry/sampler/xray/fallback_sampler.rb new file mode 100644 index 0000000000..f25b08052c --- /dev/null +++ b/sampler/xray/lib/opentelemetry/sampler/xray/fallback_sampler.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Sampler + module XRay + # FallbackSampler samples 1 req/sec and additional 5% of requests using TraceIdRatioBasedSampler. + class FallbackSampler + def initialize + @fixed_rate_sampler = OpenTelemetry::SDK::Trace::Samplers::TraceIdRatioBased.new(0.05) + end + + def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) + # TODO: implement and use Rate Limiting Sampler + + @fixed_rate_sampler.should_sample?(trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes) + end + + def description + 'FallbackSampler{fallback sampling with sampling config of 1 req/sec and 5% of additional requests}' + end + end + end + end +end diff --git a/sampler/xray/lib/opentelemetry/sampler/xray/rule_cache.rb b/sampler/xray/lib/opentelemetry/sampler/xray/rule_cache.rb new file mode 100644 index 0000000000..4583fe8397 --- /dev/null +++ b/sampler/xray/lib/opentelemetry/sampler/xray/rule_cache.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# Copyright OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Sampler + module XRay + # RuleCache stores all the Sampling Rule Appliers, each corresponding + # to the user's Sampling Rules that were retrieved from AWS X-Ray + class RuleCache + # The cache expires 1 hour after the last refresh time. + RULE_CACHE_TTL_MILLIS = 60 * 60 * 1000 + + def initialize(sampler_resource) + @rule_appliers = [] + @sampler_resource = sampler_resource + @last_updated_epoch_millis = Time.now.to_i * 1000 + @cache_lock = Mutex.new + end + + def expired? + now_in_millis = Time.now.to_i * 1000 + now_in_millis > @last_updated_epoch_millis + RULE_CACHE_TTL_MILLIS + end + + def get_matched_rule(attributes) + @rule_appliers.find do |rule| + rule.matches?(attributes, @sampler_resource) || rule.sampling_rule.rule_name == 'Default' + end + end + + def update_rules(new_rule_appliers) + old_rule_appliers_map = {} + + @cache_lock.synchronize do + @rule_appliers.each do |rule| + old_rule_appliers_map[rule.sampling_rule.rule_name] = rule + end + + new_rule_appliers.each_with_index do |new_rule, index| + rule_name_to_check = new_rule.sampling_rule.rule_name + next unless old_rule_appliers_map.key?(rule_name_to_check) + + old_rule = old_rule_appliers_map[rule_name_to_check] + new_rule_appliers[index] = old_rule if new_rule.sampling_rule.equals?(old_rule.sampling_rule) + end + + @rule_appliers = new_rule_appliers + sort_rules_by_priority + @last_updated_epoch_millis = Time.now.to_i * 1000 + end + end + + private + + def sort_rules_by_priority + @rule_appliers.sort! do |rule1, rule2| + if rule1.sampling_rule.priority == rule2.sampling_rule.priority + rule1.sampling_rule.rule_name < rule2.sampling_rule.rule_name ? -1 : 1 + else + rule1.sampling_rule.priority - rule2.sampling_rule.priority + end + end + end + end + end + end +end diff --git a/sampler/xray/lib/opentelemetry/sampler/xray/sampling_rule_applier.rb b/sampler/xray/lib/opentelemetry/sampler/xray/sampling_rule_applier.rb index d6f0aac971..507e226c1c 100644 --- a/sampler/xray/lib/opentelemetry/sampler/xray/sampling_rule_applier.rb +++ b/sampler/xray/lib/opentelemetry/sampler/xray/sampling_rule_applier.rb @@ -7,6 +7,7 @@ require 'opentelemetry/sdk' require 'opentelemetry-semantic_conventions' require 'date' +require_relative 'sampling_rule' require_relative 'statistics' require_relative 'utils' @@ -19,16 +20,92 @@ class SamplingRuleApplier attr_reader :sampling_rule MAX_DATE_TIME_SECONDS = Time.at(8_640_000_000_000) + SEMCONV = OpenTelemetry::SemanticConventions def initialize(sampling_rule, statistics = OpenTelemetry::Sampler::XRay::Statistics.new, target = nil) @sampling_rule = sampling_rule + @fixed_rate_sampler = OpenTelemetry::SDK::Trace::Samplers::TraceIdRatioBased.new(@sampling_rule.fixed_rate) + + # TODO: Add Reservoir Sampler (Rate Limiting Sampler) + + @reservoir_expiry_time = MAX_DATE_TIME_SECONDS + @statistics = statistics + end + + def matches?(attributes, resource) + http_target = nil + http_url = nil + http_method = nil + http_host = nil + + unless attributes.nil? + http_target = attributes[SEMCONV::Trace::HTTP_TARGET] + http_url = attributes[SEMCONV::Trace::HTTP_URL] + http_method = attributes[SEMCONV::Trace::HTTP_METHOD] + http_host = attributes[SEMCONV::Trace::HTTP_HOST] + end + + service_type = nil + resource_arn = nil + + resource_hash = resource.attribute_enumerator.to_h + + if resource + service_name = resource_hash[SEMCONV::Resource::SERVICE_NAME] || '' + cloud_platform = resource_hash[SEMCONV::Resource::CLOUD_PLATFORM] + service_type = OpenTelemetry::Sampler::XRay::Utils::CLOUD_PLATFORM_MAPPING[cloud_platform] if cloud_platform.is_a?(String) + resource_arn = get_arn(resource, attributes) + end + + if http_target.nil? && http_url.is_a?(String) + begin + uri = URI(http_url) + http_target = uri.path.empty? ? '/' : uri.path + rescue URI::InvalidURIError + http_target = '/' + end + elsif http_target.nil? && http_url.nil? + http_target = '/' + end + + OpenTelemetry::Sampler::XRay::Utils.attribute_match(attributes, @sampling_rule.attributes) && + OpenTelemetry::Sampler::XRay::Utils.wildcard_match(@sampling_rule.host, http_host) && + OpenTelemetry::Sampler::XRay::Utils.wildcard_match(@sampling_rule.http_method, http_method) && + OpenTelemetry::Sampler::XRay::Utils.wildcard_match(@sampling_rule.service_name, service_name) && + OpenTelemetry::Sampler::XRay::Utils.wildcard_match(@sampling_rule.url_path, http_target) && + OpenTelemetry::Sampler::XRay::Utils.wildcard_match(@sampling_rule.service_type, service_type) && + OpenTelemetry::Sampler::XRay::Utils.wildcard_match(@sampling_rule.resource_arn, resource_arn) end def should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) - OpenTelemetry::SDK::Trace::Samplers::Result.new( + # TODO: Record Sampling Statistics + + result = OpenTelemetry::SDK::Trace::Samplers::Result.new( decision: OpenTelemetry::SDK::Trace::Samplers::Decision::DROP, tracestate: OpenTelemetry::Trace::Tracestate::DEFAULT ) + + # TODO: Apply Reservoir Sampling + + if result.instance_variable_get(:@decision) == OpenTelemetry::SDK::Trace::Samplers::Decision::DROP + result = @fixed_rate_sampler.should_sample?( + trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes + ) + end + + result + end + + private + + def get_arn(resource, attributes) + resource_hash = resource.attribute_enumerator.to_h + arn = resource_hash[SEMCONV::Resource::AWS_ECS_CONTAINER_ARN] || + resource_hash[SEMCONV::Resource::AWS_ECS_CLUSTER_ARN] || + resource_hash[SEMCONV::Resource::AWS_EKS_CLUSTER_ARN] + + arn = attributes[SEMCONV::Trace::AWS_LAMBDA_INVOKED_ARN] || resource_hash[SEMCONV::Resource::FAAS_ID] if arn.nil? + arn end end end diff --git a/sampler/xray/test/aws_xray_remote_sampler_test.rb b/sampler/xray/test/aws_xray_remote_sampler_test.rb index 9dcb5a8c4f..3de4299cb5 100644 --- a/sampler/xray/test/aws_xray_remote_sampler_test.rb +++ b/sampler/xray/test/aws_xray_remote_sampler_test.rb @@ -9,6 +9,7 @@ DATA_DIR_SAMPLING_RULES = File.join(__dir__, 'data/test-remote-sampler_sampling-rules-response-sample.json') DATA_DIR_SAMPLING_TARGETS = File.join(__dir__, 'data/test-remote-sampler_sampling-targets-response-sample.json') TEST_URL = 'localhost:2000' +SEMCONV = OpenTelemetry::SemanticConventions describe OpenTelemetry::Sampler::XRay::AWSXRayRemoteSampler do it 'creates remote sampler with empty resource' do @@ -22,6 +23,7 @@ assert !sampler.instance_variable_get(:@rule_poller).nil? assert_equal(sampler.instance_variable_get(:@rule_polling_interval_millis), 300 * 1000) assert !sampler.instance_variable_get(:@sampling_client).nil? + assert !sampler.instance_variable_get(:@rule_cache).nil? assert_match(/[a-f0-9]{24}/, sampler.instance_variable_get(:@client_id)) end @@ -32,14 +34,16 @@ .to_return(status: 200, body: File.read(DATA_DIR_SAMPLING_TARGETS)) resource = OpenTelemetry::SDK::Resources::Resource.create( - OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => 'test-service-name', - OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM => 'test-cloud-platform' + SEMCONV::Resource::SERVICE_NAME => 'test-service-name', + SEMCONV::Resource::CLOUD_PLATFORM => 'test-cloud-platform' ) sampler = OpenTelemetry::Sampler::XRay::InternalAWSXRayRemoteSampler.new(resource: resource) assert !sampler.instance_variable_get(:@rule_poller).nil? assert_equal(sampler.instance_variable_get(:@rule_polling_interval_millis), 300 * 1000) assert !sampler.instance_variable_get(:@sampling_client).nil? + assert !sampler.instance_variable_get(:@rule_cache).nil? + assert_equal(sampler.instance_variable_get(:@rule_cache).instance_variable_get(:@sampler_resource), resource) assert_match(/[a-f0-9]{24}/, sampler.instance_variable_get(:@client_id)) end @@ -50,8 +54,8 @@ .to_return(status: 200, body: File.read(DATA_DIR_SAMPLING_TARGETS)) resource = OpenTelemetry::SDK::Resources::Resource.create( - OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => 'test-service-name', - OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM => 'test-cloud-platform' + SEMCONV::Resource::SERVICE_NAME => 'test-service-name', + SEMCONV::Resource::CLOUD_PLATFORM => 'test-cloud-platform' ) sampler = OpenTelemetry::Sampler::XRay::InternalAWSXRayRemoteSampler.new( resource: resource, @@ -62,10 +66,34 @@ assert !sampler.instance_variable_get(:@rule_poller).nil? assert_equal(sampler.instance_variable_get(:@rule_polling_interval_millis), 120 * 1000) assert !sampler.instance_variable_get(:@sampling_client).nil? + assert !sampler.instance_variable_get(:@rule_cache).nil? + assert_equal(sampler.instance_variable_get(:@rule_cache).instance_variable_get(:@sampler_resource), resource) assert_equal(sampler.instance_variable_get(:@aws_proxy_endpoint), 'abc.com') assert_match(/[a-f0-9]{24}/, sampler.instance_variable_get(:@client_id)) end + it 'updates sampling rules and targets with pollers and should sample' do + stub_request(:post, "#{TEST_URL}/GetSamplingRules") + .to_return(status: 200, body: File.read(DATA_DIR_SAMPLING_RULES)) + stub_request(:post, "#{TEST_URL}/SamplingTargets") + .to_return(status: 200, body: File.read(DATA_DIR_SAMPLING_TARGETS)) + + resource = OpenTelemetry::SDK::Resources::Resource.create( + SEMCONV::Resource::SERVICE_NAME => 'test-service-name', + SEMCONV::Resource::CLOUD_PLATFORM => 'test-cloud-platform' + ) + rs = OpenTelemetry::Sampler::XRay::AWSXRayRemoteSampler.new(resource: resource) + + attributes = { 'abc' => '1234' } + + test_rule_applier = rs.instance_variable_get(:@root).instance_variable_get(:@root).instance_variable_get(:@rule_cache).instance_variable_get(:@rule_appliers)[0] + assert_equal 'test', test_rule_applier.instance_variable_get(:@sampling_rule).instance_variable_get(:@rule_name) + assert_equal OpenTelemetry::SDK::Trace::Samplers::Decision::DROP, + rs.should_sample?(parent_context: nil, trace_id: '3759e988bd862e3fe1be46a994272793', name: 'name', kind: OpenTelemetry::Trace::SpanKind::SERVER, attributes: attributes, links: []).instance_variable_get(:@decision) + + # TODO: Run more tests after updating Sampling Targets + end + it 'generates valid client id' do client_id = OpenTelemetry::Sampler::XRay::InternalAWSXRayRemoteSampler.generate_client_id assert_match(/[0-9a-z]{24}/, client_id) @@ -81,4 +109,15 @@ expected_string = 'InternalAWSXRayRemoteSampler{aws_proxy_endpoint=127.0.0.1:2000, rule_polling_interval_millis=300000}' assert_equal(sampler.description, expected_string) end + + def create_spans(sampled_array, thread_id, span_attributes, remote_sampler, number_of_spans) + sampled = 0 + number_of_spans.times do + sampled += 1 if remote_sampler.should_sample?(parent_context: nil, trace_id: '3759e988bd862e3fe1be46a994272793', name: 'name', kind: OpenTelemetry::Trace::SpanKind::SERVER, attributes: span_attributes, + links: []).instance_variable_get(:@decision) != OpenTelemetry::SDK::Trace::Samplers::Decision::DROP + end + sampled_array[thread_id] = sampled + end + + # TODO: Run tests for Reservoir Sampling end diff --git a/sampler/xray/test/fallback_sampler_test.rb b/sampler/xray/test/fallback_sampler_test.rb new file mode 100644 index 0000000000..01f5224b71 --- /dev/null +++ b/sampler/xray/test/fallback_sampler_test.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Copyright OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Sampler::XRay::FallbackSampler do + # TODO: Add tests for Fallback sampler when Rate Limiter is implemented + + it 'test_to_string' do + assert_equal( + 'FallbackSampler{fallback sampling with sampling config of 1 req/sec and 5% of additional requests}', + OpenTelemetry::Sampler::XRay::FallbackSampler.new.description + ) + end +end diff --git a/sampler/xray/test/rule_cache_test.rb b/sampler/xray/test/rule_cache_test.rb new file mode 100644 index 0000000000..bad80e3fc0 --- /dev/null +++ b/sampler/xray/test/rule_cache_test.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +# Copyright OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Sampler::XRay::RuleCache do + def create_rule(name, priority, reservoir_size, fixed_rate) + test_sampling_rule = { + 'RuleName' => name, + 'Priority' => priority, + 'ReservoirSize' => reservoir_size, + 'FixedRate' => fixed_rate, + 'ServiceName' => '*', + 'ServiceType' => '*', + 'Host' => '*', + 'HTTPMethod' => '*', + 'URLPath' => '*', + 'ResourceARN' => '*', + 'Version' => 1 + } + OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new(OpenTelemetry::Sampler::XRay::SamplingRule.new(test_sampling_rule)) + end + + it 'test_cache_updates_and_sorts_rules' do + # Set up default rule in rule cache + default_rule = create_rule('Default', 10_000, 1, 0.05) + cache = OpenTelemetry::Sampler::XRay::RuleCache.new(OpenTelemetry::SDK::Resources::Resource.create({})) + cache.update_rules([default_rule]) + + # Expect default rule to exist + assert_equal 1, cache.instance_variable_get(:@rule_appliers).length + + # Set up incoming rules + rule1 = create_rule('low', 200, 0, 0.0) + rule2 = create_rule('abc', 100, 0, 0.0) + rule3 = create_rule('Abc', 100, 0, 0.0) + rule4 = create_rule('ab', 100, 0, 0.0) + rule5 = create_rule('A', 100, 0, 0.0) + rule6 = create_rule('high', 10, 0, 0.0) + rules = [rule1, rule2, rule3, rule4, rule5, rule6] + + cache.update_rules(rules) + + rule_appliers = cache.instance_variable_get(:@rule_appliers) + assert_equal rules.length, rule_appliers.length + assert_equal 'high', rule_appliers[0].sampling_rule.rule_name + assert_equal 'A', rule_appliers[1].sampling_rule.rule_name + assert_equal 'Abc', rule_appliers[2].sampling_rule.rule_name + assert_equal 'ab', rule_appliers[3].sampling_rule.rule_name + assert_equal 'abc', rule_appliers[4].sampling_rule.rule_name + assert_equal 'low', rule_appliers[5].sampling_rule.rule_name + end + + it 'test_rule_cache_expiration_logic' do + Timecop.freeze(Time.now) do + default_rule = create_rule('Default', 10_000, 1, 0.05) + cache = OpenTelemetry::Sampler::XRay::RuleCache.new(OpenTelemetry::SDK::Resources::Resource.create({})) + cache.update_rules([default_rule]) + + Timecop.travel(2 * 60 * 60) # Travel 2 hours into the future + assert cache.expired? + end + end + + it 'test_update_cache_with_only_one_rule_changed' do + cache = OpenTelemetry::Sampler::XRay::RuleCache.new(OpenTelemetry::SDK::Resources::Resource.create({})) + rule1 = create_rule('rule_1', 1, 0, 0.0) + rule2 = create_rule('rule_2', 10, 0, 0.0) + rule3 = create_rule('rule_3', 100, 0, 0.0) + rule_appliers = [rule1, rule2, rule3] + + cache.update_rules(rule_appliers) + rule_appliers_copy = cache.instance_variable_get(:@rule_appliers).dup + + new_rule3 = create_rule('new_rule_3', 5, 0, 0.0) + new_rule_appliers = [rule1, rule2, new_rule3] + cache.update_rules(new_rule_appliers) + + current_appliers = cache.instance_variable_get(:@rule_appliers) + assert_equal 3, current_appliers.length + assert_equal 'rule_1', current_appliers[0].sampling_rule.rule_name + assert_equal 'new_rule_3', current_appliers[1].sampling_rule.rule_name + assert_equal 'rule_2', current_appliers[2].sampling_rule.rule_name + + assert_equal rule_appliers_copy[0], current_appliers[0] + assert_equal rule_appliers_copy[1], current_appliers[2] + refute_equal rule_appliers_copy[2], current_appliers[1] + end + + it 'test_update_rules_removes_older_rule' do + cache = OpenTelemetry::Sampler::XRay::RuleCache.new(OpenTelemetry::SDK::Resources::Resource.create({})) + assert_equal 0, cache.instance_variable_get(:@rule_appliers).length + + rule1 = create_rule('first_rule', 200, 0, 0.0) + cache.update_rules([rule1]) + + rule_appliers = cache.instance_variable_get(:@rule_appliers) + assert_equal 1, rule_appliers.length + assert_equal 'first_rule', rule_appliers[0].sampling_rule.rule_name + + replacement_rule1 = create_rule('second_rule', 200, 0, 0.0) + cache.update_rules([replacement_rule1]) + + rule_appliers = cache.instance_variable_get(:@rule_appliers) + assert_equal 1, rule_appliers.length + assert_equal 'second_rule', rule_appliers[0].sampling_rule.rule_name + end + + # TODO: Add tests for updating Sampling Targets and getting statistics +end diff --git a/sampler/xray/test/sampling_rule_applier_test.rb b/sampler/xray/test/sampling_rule_applier_test.rb index 3509bfa9d2..4fcf49445f 100644 --- a/sampler/xray/test/sampling_rule_applier_test.rb +++ b/sampler/xray/test/sampling_rule_applier_test.rb @@ -8,4 +8,204 @@ require 'json' describe OpenTelemetry::Sampler::XRay::SamplingRuleApplier do + DATA_DIR = File.join(File.dirname(__FILE__), 'data') + + it 'test_applier_attribute_matching_from_xray_response' do + sample_data = JSON.parse(File.read(File.join(DATA_DIR, 'get-sampling-rules-response-sample-2.json'))) + + all_rules = sample_data['SamplingRuleRecords'] + default_rule = OpenTelemetry::Sampler::XRay::SamplingRule.new(all_rules[0]['SamplingRule']) + sampling_rule_applier = OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new(default_rule) + + resource = OpenTelemetry::SDK::Resources::Resource.create({ + 'service.name' => 'test_service_name', + 'cloud.platform' => 'test_cloud_platform' + }) + + attr = { + 'http.target' => '/target', + 'http.method' => 'method', + 'http.url' => 'url', + 'http.host' => 'host', + 'foo' => 'bar', + 'abc' => '1234' + } + + assert sampling_rule_applier.matches?(attr, resource) + end + + it 'test_applier_matches_with_all_attributes' do + rule = OpenTelemetry::Sampler::XRay::SamplingRule.new({ + 'Attributes' => { 'abc' => '123', 'def' => '4?6', 'ghi' => '*89' }, + 'FixedRate' => 0.11, + 'HTTPMethod' => 'GET', + 'Host' => 'localhost', + 'Priority' => 20, + 'ReservoirSize' => 1, + 'ResourceARN' => 'arn:aws:lambda:us-west-2:123456789012:function:my-function', + 'RuleARN' => 'arn:aws:xray:us-east-1:999999999999:sampling-rule/test', + 'RuleName' => 'test', + 'ServiceName' => 'myServiceName', + 'ServiceType' => 'AWS::Lambda::Function', + 'URLPath' => '/helloworld', + 'Version' => 1 + }) + + attributes = { + 'http.host' => 'localhost', + 'http.method' => 'GET', + 'aws.lambda.invoked_arn' => 'arn:aws:lambda:us-west-2:123456789012:function:my-function', + 'http.url' => 'http://127.0.0.1:5000/helloworld', + 'abc' => '123', + 'def' => '456', + 'ghi' => '789' + } + + resource = OpenTelemetry::SDK::Resources::Resource.create({ + 'service.name' => 'myServiceName', + 'cloud.platform' => 'aws_lambda' + }) + + rule_applier = OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new(rule) + + assert rule_applier.matches?(attributes, resource) + + attributes.delete('http.url') + attributes['http.target'] = '/helloworld' + assert rule_applier.matches?(attributes, resource) + end + + it 'test_applier_wild_card_attributes_matches_span_attributes' do + rule = OpenTelemetry::Sampler::XRay::SamplingRule.new({ + 'Attributes' => { + 'attr1' => '*', + 'attr2' => '*', + 'attr3' => 'HelloWorld', + 'attr4' => 'Hello*', + 'attr5' => '*World', + 'attr6' => '?ello*', + 'attr7' => 'Hell?W*d', + 'attr8' => '*.World', + 'attr9' => '*.World' + }, + 'FixedRate' => 0.11, + 'HTTPMethod' => '*', + 'Host' => '*', + 'Priority' => 20, + 'ReservoirSize' => 1, + 'ResourceARN' => '*', + 'RuleARN' => 'arn:aws:xray:us-east-1:999999999999:sampling-rule/test', + 'RuleName' => 'test', + 'ServiceName' => '*', + 'ServiceType' => '*', + 'URLPath' => '*', + 'Version' => 1 + }) + + rule_applier = OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new(rule) + + attributes = { + 'attr1' => '', + 'attr2' => 'HelloWorld', + 'attr3' => 'HelloWorld', + 'attr4' => 'HelloWorld', + 'attr5' => 'HelloWorld', + 'attr6' => 'HelloWorld', + 'attr7' => 'HelloWorld', + 'attr8' => 'Hello.World', + 'attr9' => 'Bye.World' + } + + assert rule_applier.matches?(attributes, OpenTelemetry::SDK::Resources::Resource.create) + end + + it 'test_applier_wild_card_attributes_matches_http_span_attributes' do + rule_applier = OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new( + OpenTelemetry::Sampler::XRay::SamplingRule.new({ + 'Attributes' => {}, + 'FixedRate' => 0.11, + 'HTTPMethod' => '*', + 'Host' => '*', + 'Priority' => 20, + 'ReservoirSize' => 1, + 'ResourceARN' => '*', + 'RuleARN' => 'arn:aws:xray:us-east-1:999999999999:sampling-rule/test', + 'RuleName' => 'test', + 'ServiceName' => '*', + 'ServiceType' => '*', + 'URLPath' => '*', + 'Version' => 1 + }) + ) + + attributes = { + 'http.host' => 'localhost', + 'http.method' => 'GET', + 'http.url' => 'http://127.0.0.1:5000/helloworld' + } + + assert rule_applier.matches?(attributes, OpenTelemetry::SDK::Resources::Resource.create) + end + + it 'test_applier_wild_card_attributes_matches_with_empty_attributes' do + rule_applier = OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new( + OpenTelemetry::Sampler::XRay::SamplingRule.new({ + 'Attributes' => {}, + 'FixedRate' => 0.11, + 'HTTPMethod' => '*', + 'Host' => '*', + 'Priority' => 20, + 'ReservoirSize' => 1, + 'ResourceARN' => '*', + 'RuleARN' => 'arn:aws:xray:us-east-1:999999999999:sampling-rule/test', + 'RuleName' => 'test', + 'ServiceName' => '*', + 'ServiceType' => '*', + 'URLPath' => '*', + 'Version' => 1 + }) + ) + + attributes = {} + resource = OpenTelemetry::SDK::Resources::Resource.create({ + 'service.name' => 'myServiceName', + 'cloud.platform' => 'aws_ec2' + }) + + assert rule_applier.matches?(attributes, resource) + assert rule_applier.matches?({}, resource) + assert rule_applier.matches?(attributes, OpenTelemetry::SDK::Resources::Resource.create) + assert rule_applier.matches?({}, OpenTelemetry::SDK::Resources::Resource.create) + assert rule_applier.matches?(attributes, OpenTelemetry::SDK::Resources::Resource.create({})) + assert rule_applier.matches?({}, OpenTelemetry::SDK::Resources::Resource.create({})) + end + + it 'test_applier_matches_with_http_url_with_http_target_undefined' do + rule_applier = OpenTelemetry::Sampler::XRay::SamplingRuleApplier.new( + OpenTelemetry::Sampler::XRay::SamplingRule.new({ + 'Attributes' => {}, + 'FixedRate' => 0.11, + 'HTTPMethod' => '*', + 'Host' => '*', + 'Priority' => 20, + 'ReservoirSize' => 1, + 'ResourceARN' => '*', + 'RuleARN' => 'arn:aws:xray:us-east-1:999999999999:sampling-rule/test', + 'RuleName' => 'test', + 'ServiceName' => '*', + 'ServiceType' => '*', + 'URLPath' => '/somerandompath', + 'Version' => 1 + }) + ) + + attributes = { + 'http.url' => 'https://somerandomurl.com/somerandompath' + } + resource = OpenTelemetry::SDK::Resources::Resource.create({}) + + assert rule_applier.matches?(attributes, resource) + assert rule_applier.matches?(attributes, OpenTelemetry::SDK::Resources::Resource.create) + assert rule_applier.matches?(attributes, OpenTelemetry::SDK::Resources::Resource.create({})) + end end