aws-xray-sdk 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/aws-xray-sdk/configuration.rb +10 -7
- data/lib/aws-xray-sdk/daemon_config.rb +59 -0
- data/lib/aws-xray-sdk/emitter/default_emitter.rb +10 -21
- data/lib/aws-xray-sdk/emitter/emitter.rb +1 -3
- data/lib/aws-xray-sdk/facets/aws_sdk.rb +14 -6
- data/lib/aws-xray-sdk/facets/helper.rb +4 -11
- data/lib/aws-xray-sdk/facets/net_http.rb +4 -0
- data/lib/aws-xray-sdk/facets/rack.rb +8 -6
- data/lib/aws-xray-sdk/facets/rails/railtie.rb +1 -1
- data/lib/aws-xray-sdk/model/cause.rb +2 -2
- data/lib/aws-xray-sdk/model/dummy_entities.rb +4 -0
- data/lib/aws-xray-sdk/model/entity.rb +2 -2
- data/lib/aws-xray-sdk/model/metadata.rb +2 -2
- data/lib/aws-xray-sdk/model/segment.rb +8 -1
- data/lib/aws-xray-sdk/plugins/elastic_beanstalk.rb +2 -2
- data/lib/aws-xray-sdk/recorder.rb +7 -4
- data/lib/aws-xray-sdk/sampling/connector.rb +72 -0
- data/lib/aws-xray-sdk/sampling/default_sampler.rb +72 -78
- data/lib/aws-xray-sdk/sampling/lead_poller.rb +72 -0
- data/lib/aws-xray-sdk/sampling/local/reservoir.rb +35 -0
- data/lib/aws-xray-sdk/sampling/local/sampler.rb +110 -0
- data/lib/aws-xray-sdk/sampling/local/sampling_rule.rb +63 -0
- data/lib/aws-xray-sdk/sampling/reservoir.rb +68 -24
- data/lib/aws-xray-sdk/sampling/rule_cache.rb +86 -0
- data/lib/aws-xray-sdk/sampling/rule_poller.rb +39 -0
- data/lib/aws-xray-sdk/sampling/sampler.rb +3 -3
- data/lib/aws-xray-sdk/sampling/sampling_decision.rb +8 -0
- data/lib/aws-xray-sdk/sampling/sampling_rule.rb +102 -35
- data/lib/aws-xray-sdk/version.rb +1 -1
- metadata +34 -11
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'aws-xray-sdk/sampling/sampling_rule'
|
3
|
+
require 'aws-xray-sdk/logger'
|
4
|
+
|
5
|
+
module XRay
|
6
|
+
# Connector class that translates Sampling poller functions to
|
7
|
+
# actual X-Ray back-end APIs and communicates with X-Ray daemon
|
8
|
+
# as the signing proxy.
|
9
|
+
class ServiceConnector
|
10
|
+
include Logging
|
11
|
+
attr_accessor :xray_client
|
12
|
+
@@client_id = SecureRandom.hex(12)
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
update_xray_client
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch_sampling_rules
|
19
|
+
rules = []
|
20
|
+
records = @xray_client.get_sampling_rules.sampling_rule_records
|
21
|
+
records.each { |record| rules << SamplingRule.new(record.sampling_rule) if rule_valid?(record.sampling_rule) }
|
22
|
+
rules
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_sampling_targets(rules)
|
26
|
+
now = Time.now.to_i
|
27
|
+
reports = generate_reports(rules, now)
|
28
|
+
resp = @xray_client.get_sampling_targets({ sampling_statistics_documents: reports })
|
29
|
+
{
|
30
|
+
last_modified: resp.last_rule_modification,
|
31
|
+
documents: resp.sampling_target_documents
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_xray_client(ip: '127.0.0.1', port: 2000)
|
36
|
+
require 'aws-sdk-xray'
|
37
|
+
@xray_client = Aws::XRay::Client.new(
|
38
|
+
endpoint: %(http://#{ip}:#{port}),
|
39
|
+
access_key_id: 'dummy', # AWS Ruby SDK doesn't support unsigned request
|
40
|
+
secret_access_key: 'dummy',
|
41
|
+
region: 'us-west-2' # not used
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def daemon_config=(v)
|
46
|
+
update_xray_client ip: v.tcp_ip, port: v.tcp_port
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def generate_reports(rules, now)
|
52
|
+
reports = []
|
53
|
+
rules.each do |rule|
|
54
|
+
report = rule.snapshot_statistics
|
55
|
+
report[:rule_name] = rule.name
|
56
|
+
report[:timestamp] = now
|
57
|
+
report[:client_id] = @@client_id
|
58
|
+
reports << report
|
59
|
+
end
|
60
|
+
reports
|
61
|
+
end
|
62
|
+
|
63
|
+
def rule_valid?(rule)
|
64
|
+
return false if rule.version != 1
|
65
|
+
# rules has resource ARN and attributes configured
|
66
|
+
# doesn't apply to this SDK
|
67
|
+
return false unless rule.resource_arn == '*'
|
68
|
+
return false unless rule.attributes.nil? || rule.attributes.empty?
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,105 +1,99 @@
|
|
1
|
+
require 'aws-xray-sdk/logger'
|
2
|
+
require 'aws-xray-sdk/sampling/local/sampler'
|
3
|
+
require 'aws-xray-sdk/sampling/lead_poller'
|
4
|
+
require 'aws-xray-sdk/sampling/rule_cache'
|
1
5
|
require 'aws-xray-sdk/sampling/sampler'
|
2
6
|
require 'aws-xray-sdk/sampling/sampling_rule'
|
3
|
-
require 'aws-xray-sdk/
|
7
|
+
require 'aws-xray-sdk/sampling/sampling_decision'
|
4
8
|
|
5
9
|
module XRay
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# An example definition:
|
10
|
-
# {
|
11
|
-
# version: 1,
|
12
|
-
# rules: [
|
13
|
-
# {
|
14
|
-
# description: 'Player moves.',
|
15
|
-
# service_name: '*',
|
16
|
-
# http_method: '*',
|
17
|
-
# url_path: '/api/move/*',
|
18
|
-
# fixed_target: 0,
|
19
|
-
# rate: 0.05
|
20
|
-
# }
|
21
|
-
# ],
|
22
|
-
# default: {
|
23
|
-
# fixed_target: 1,
|
24
|
-
# rate: 0.1
|
25
|
-
# }
|
26
|
-
# }
|
27
|
-
# This example defines one custom rule and a default rule.
|
28
|
-
# The custom rule applies a five-percent sampling rate with no minimum
|
29
|
-
# number of requests to trace for paths under /api/move/. The default
|
30
|
-
# rule traces the first request each second and 10 percent of additional requests.
|
31
|
-
# The SDK applies custom rules in the order in which they are defined.
|
32
|
-
# If a request matches multiple custom rules, the SDK applies only the first rule.
|
10
|
+
# Making sampling decisions based on service sampling rules defined
|
11
|
+
# by X-Ray control plane APIs. It will fall back to local sampling rules
|
12
|
+
# if service sampling rules are not available or expired.
|
33
13
|
class DefaultSampler
|
34
14
|
include Sampler
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
fixed_target: 1,
|
39
|
-
rate: 0.05
|
40
|
-
},
|
41
|
-
rules: []
|
42
|
-
}.freeze
|
15
|
+
include Logging
|
16
|
+
attr_reader :cache, :local_sampler, :poller
|
17
|
+
attr_accessor :origin
|
43
18
|
|
44
19
|
def initialize
|
45
|
-
|
20
|
+
@local_sampler = LocalSampler.new
|
21
|
+
@cache = RuleCache.new
|
22
|
+
@poller = LeadPoller.new(@cache)
|
23
|
+
|
24
|
+
@started = false
|
25
|
+
@origin = nil
|
26
|
+
@lock = Mutex.new
|
46
27
|
end
|
47
28
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
29
|
+
# Start background threads to poll sampling rules
|
30
|
+
def start
|
31
|
+
@lock.synchronize do
|
32
|
+
unless @started
|
33
|
+
@poller.start
|
34
|
+
@started = true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the rule name if it decides to sample based on
|
40
|
+
# a service sampling rule matching. If there is no match
|
41
|
+
# it will fallback to local defined sampling rules.
|
42
|
+
def sample_request?(sampling_req)
|
43
|
+
start unless @started
|
44
|
+
now = Time.now.to_i
|
45
|
+
if sampling_req.nil?
|
46
|
+
sampling_req = { service_type: @origin } if @origin
|
47
|
+
elsif !sampling_req.key?(:service_type)
|
48
|
+
sampling_req[:service_type] = @origin if @origin
|
49
|
+
end
|
50
|
+
|
51
|
+
matched_rule = @cache.get_matched_rule(sampling_req, now: now)
|
52
|
+
if !matched_rule.nil?
|
53
|
+
logger.debug %(Rule #{matched_rule.name} is selected to make a sampling decision.')
|
54
|
+
process_matched_rule(matched_rule, now)
|
55
|
+
else
|
56
|
+
logger.warn %(No effective centralized sampling rule match. Fallback to local rules.)
|
57
|
+
@local_sampler.sample_request?(sampling_req)
|
60
58
|
end
|
61
|
-
sample?
|
62
59
|
end
|
63
60
|
|
64
|
-
# Decides if should sample based on non-path-based rule.
|
65
|
-
# Currently only the default rule is not path-based.
|
66
61
|
def sample?
|
67
|
-
|
62
|
+
sample_request? nil
|
68
63
|
end
|
69
64
|
|
70
|
-
# @param [Hash] v
|
65
|
+
# @param [Hash] v Local sampling rules definition.
|
66
|
+
# This configuration has lower priority than service
|
67
|
+
# sampling rules and only has effect when those rules
|
68
|
+
# are not available or expired.
|
71
69
|
def sampling_rules=(v)
|
72
|
-
|
70
|
+
@local_sampler.sampling_rules = v
|
73
71
|
end
|
74
72
|
|
75
|
-
|
76
|
-
|
77
|
-
all_rules = []
|
78
|
-
all_rules << @default_rule
|
79
|
-
all_rules << @custom_rules unless @custom_rules.empty?
|
80
|
-
all_rules
|
73
|
+
def daemon_config=(v)
|
74
|
+
@poller.connector.daemon_config = v
|
81
75
|
end
|
82
76
|
|
83
77
|
private
|
84
78
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
if
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
@custom_rules << SamplingRule.new(rule_definition: d)
|
79
|
+
def process_matched_rule(rule, now)
|
80
|
+
# As long as a rule is matched we increment request counter.
|
81
|
+
rule.increment_request_count
|
82
|
+
reservoir = rule.reservoir
|
83
|
+
sample = true
|
84
|
+
# We check if we can borrow or take from reservoir first.
|
85
|
+
decision = reservoir.borrow_or_take(now, rule.borrowable?)
|
86
|
+
if decision == SamplingDecision::BORROW
|
87
|
+
rule.increment_borrow_count
|
88
|
+
elsif decision == SamplingDecision::TAKE
|
89
|
+
rule.increment_sampled_count
|
90
|
+
# Otherwise we compute based on fixed rate of this sampling rule.
|
91
|
+
elsif rand <= rule.rate
|
92
|
+
rule.increment_sampled_count
|
93
|
+
else
|
94
|
+
sample = false
|
102
95
|
end
|
96
|
+
sample ? rule.name : false
|
103
97
|
end
|
104
98
|
end
|
105
99
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'aws-xray-sdk/logger'
|
2
|
+
require 'aws-xray-sdk/sampling/connector'
|
3
|
+
require 'aws-xray-sdk/sampling/rule_poller'
|
4
|
+
|
5
|
+
module XRay
|
6
|
+
# The poller to report the current statistics of all
|
7
|
+
# sampling rules and retrieve the new allocated
|
8
|
+
# sampling quota and TTL from X-Ray service. It also
|
9
|
+
# controls the rule poller.
|
10
|
+
class LeadPoller
|
11
|
+
include Logging
|
12
|
+
attr_reader :connector
|
13
|
+
@@interval = 10 # working frequency of the lead poller
|
14
|
+
@@rule_interval = 5 * 60 # 5 minutes on polling rules
|
15
|
+
|
16
|
+
def initialize(cache)
|
17
|
+
@cache = cache
|
18
|
+
@connector = ServiceConnector.new
|
19
|
+
@rule_poller = RulePoller.new cache: @cache, connector: @connector
|
20
|
+
@rule_poller_elapsed = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@rule_poller.run
|
25
|
+
Thread.new { worker }
|
26
|
+
end
|
27
|
+
|
28
|
+
def worker
|
29
|
+
loop do
|
30
|
+
sleep_time = @@interval + rand
|
31
|
+
sleep sleep_time
|
32
|
+
@rule_poller_elapsed += sleep_time
|
33
|
+
refresh_cache
|
34
|
+
if @rule_poller_elapsed >= @@rule_interval
|
35
|
+
@rule_poller.run
|
36
|
+
@rule_poller_elapsed = 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def refresh_cache
|
44
|
+
candidates = get_candidates(@cache.rules)
|
45
|
+
if candidates.empty?
|
46
|
+
logger.debug %(No X-Ray sampling rules to report statistics. Skipping.)
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
result = @connector.fetch_sampling_targets(candidates)
|
51
|
+
targets = {}
|
52
|
+
result[:documents].each { |doc| targets[doc.rule_name] = doc }
|
53
|
+
@cache.load_targets(targets)
|
54
|
+
|
55
|
+
return unless @cache.last_updated && result[:last_modified].to_i > @cache.last_updated
|
56
|
+
logger.info 'Performing out-of-band sampling rule polling to fetch updated rules.'
|
57
|
+
@rule_poller.run
|
58
|
+
@rule_poller_elapsed = 0
|
59
|
+
rescue StandardError => e
|
60
|
+
logger.warn %(failed to fetch X-Ray sampling targets due to #{e.message})
|
61
|
+
end
|
62
|
+
|
63
|
+
# Don't report a rule statistics if any of the conditions is met:
|
64
|
+
# 1. The report time hasn't come(some rules might have larger report intervals).
|
65
|
+
# 2. The rule is never matched.
|
66
|
+
def get_candidates(rules)
|
67
|
+
candidates = []
|
68
|
+
rules.each { |rule| candidates << rule if rule.ever_matched? && rule.time_to_report? }
|
69
|
+
candidates
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module XRay
|
2
|
+
# Keeps track of the number of sampled segments within
|
3
|
+
# a single second in the local process. This class is
|
4
|
+
# implemented to be thread-safe to achieve accurate sampling.
|
5
|
+
class LocalReservoir
|
6
|
+
# @param [Integer] traces_per_sec The number of guranteed sampled
|
7
|
+
# segments per second.
|
8
|
+
def initialize(traces_per_sec: 0)
|
9
|
+
@traces_per_sec = traces_per_sec
|
10
|
+
@used_this_sec = 0
|
11
|
+
@this_sec = Time.now.to_i
|
12
|
+
@lock = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns `true` if there are quota left within the
|
16
|
+
# current second, otherwise returns `false`.
|
17
|
+
def take
|
18
|
+
# nothing to provide if reserved is set to 0
|
19
|
+
return false if @traces_per_sec.zero?
|
20
|
+
@lock.synchronize do
|
21
|
+
now = Time.now.to_i
|
22
|
+
# refresh time frame
|
23
|
+
if now != @this_sec
|
24
|
+
@used_this_sec = 0
|
25
|
+
@this_sec = now
|
26
|
+
end
|
27
|
+
# return false if reserved item ran out
|
28
|
+
return false unless @used_this_sec < @traces_per_sec
|
29
|
+
# otherwise increment used counter and return true
|
30
|
+
@used_this_sec += 1
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'aws-xray-sdk/sampling/sampler'
|
2
|
+
require 'aws-xray-sdk/sampling/local/sampling_rule'
|
3
|
+
require 'aws-xray-sdk/exceptions'
|
4
|
+
|
5
|
+
module XRay
|
6
|
+
# The local sampler that uses locally defined
|
7
|
+
# sampling rule and reservoir models to decide sampling decision.
|
8
|
+
# It also uses the default sampling rule.
|
9
|
+
# An example definition:
|
10
|
+
# {
|
11
|
+
# version: 2,
|
12
|
+
# rules: [
|
13
|
+
# {
|
14
|
+
# description: 'Player moves.',
|
15
|
+
# host: '*',
|
16
|
+
# http_method: '*',
|
17
|
+
# url_path: '/api/move/*',
|
18
|
+
# fixed_target: 0,
|
19
|
+
# rate: 0.05
|
20
|
+
# }
|
21
|
+
# ],
|
22
|
+
# default: {
|
23
|
+
# fixed_target: 1,
|
24
|
+
# rate: 0.1
|
25
|
+
# }
|
26
|
+
# }
|
27
|
+
# This example defines one custom rule and a default rule.
|
28
|
+
# The custom rule applies a five-percent sampling rate with no minimum
|
29
|
+
# number of requests to trace for paths under /api/move/. The default
|
30
|
+
# rule traces the first request each second and 10 percent of additional requests.
|
31
|
+
# The SDK applies custom rules in the order in which they are defined.
|
32
|
+
# If a request matches multiple custom rules, the SDK applies only the first rule.
|
33
|
+
class LocalSampler
|
34
|
+
include Sampler
|
35
|
+
DEFAULT_RULES = {
|
36
|
+
version: 2,
|
37
|
+
default: {
|
38
|
+
fixed_target: 1,
|
39
|
+
rate: 0.05
|
40
|
+
},
|
41
|
+
rules: []
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
SUPPORTED_VERSION = [1, 2].freeze
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
load_sampling_rules(DEFAULT_RULES)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return True if the sampler decide to sample based on input
|
51
|
+
# information and sampling rules. It will first check if any
|
52
|
+
# custom rule should be applied, if not it falls back to the
|
53
|
+
# default sampling rule.
|
54
|
+
# All arugments are extracted from incoming requests by
|
55
|
+
# X-Ray middleware to perform path based sampling.
|
56
|
+
def sample_request?(sampling_req)
|
57
|
+
sample = sample?
|
58
|
+
return sample if sampling_req.nil? || sampling_req.empty?
|
59
|
+
@custom_rules ||= []
|
60
|
+
@custom_rules.each do |c|
|
61
|
+
return should_sample?(c) if c.applies?(sampling_req: sampling_req)
|
62
|
+
end
|
63
|
+
# use previously made decision based on default rule
|
64
|
+
# if no path-based rule has been matched
|
65
|
+
sample
|
66
|
+
end
|
67
|
+
|
68
|
+
# Decides if should sample based on non-path-based rule.
|
69
|
+
# Currently only the default rule is non-path-based.
|
70
|
+
def sample?
|
71
|
+
should_sample?(@default_rule)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param [Hash] v The sampling rules definition.
|
75
|
+
def sampling_rules=(v)
|
76
|
+
load_sampling_rules(v)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Array] An array of [SamplingRule]
|
80
|
+
def sampling_rules
|
81
|
+
all_rules = []
|
82
|
+
all_rules << @default_rule
|
83
|
+
all_rules << @custom_rules unless @custom_rules.empty?
|
84
|
+
all_rules
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def should_sample?(rule)
|
90
|
+
return true if rule.reservoir.take
|
91
|
+
Random.rand <= rule.rate
|
92
|
+
end
|
93
|
+
|
94
|
+
def load_sampling_rules(v)
|
95
|
+
version = v[:version]
|
96
|
+
unless SUPPORTED_VERSION.include?(version)
|
97
|
+
raise InvalidSamplingConfigError, %('Sampling rule version #{version} is not supported.')
|
98
|
+
end
|
99
|
+
unless v[:default]
|
100
|
+
raise InvalidSamplingConfigError, 'A default rule must be provided.'
|
101
|
+
end
|
102
|
+
@default_rule = LocalSamplingRule.new rule_definition: v[:default], default: true
|
103
|
+
@custom_rules = []
|
104
|
+
v[:rules].each do |d|
|
105
|
+
d[:host] = d[:service_name] if version == 1
|
106
|
+
@custom_rules << LocalSamplingRule.new(rule_definition: d)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|