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,63 @@
|
|
1
|
+
require 'aws-xray-sdk/exceptions'
|
2
|
+
require 'aws-xray-sdk/sampling/local/reservoir'
|
3
|
+
require 'aws-xray-sdk/search_pattern'
|
4
|
+
|
5
|
+
module XRay
|
6
|
+
# One SamplingRule object represents one rule defined from the rules hash definition.
|
7
|
+
# It can be either a custom rule or the default rule.
|
8
|
+
class LocalSamplingRule
|
9
|
+
attr_reader :fixed_target, :rate, :host,
|
10
|
+
:method, :path, :reservoir, :default
|
11
|
+
|
12
|
+
# @param [Hash] rule_definition Hash that defines a single rule.
|
13
|
+
# @param default A boolean flag indicates if this rule is the default rule.
|
14
|
+
def initialize(rule_definition:, default: false)
|
15
|
+
@fixed_target = rule_definition[:fixed_target]
|
16
|
+
@rate = rule_definition[:rate]
|
17
|
+
|
18
|
+
@host = rule_definition[:host]
|
19
|
+
@method = rule_definition[:http_method]
|
20
|
+
@path = rule_definition[:url_path]
|
21
|
+
|
22
|
+
@default = default
|
23
|
+
validate
|
24
|
+
@reservoir = LocalReservoir.new traces_per_sec: @fixed_target
|
25
|
+
end
|
26
|
+
|
27
|
+
# Determines whether or not this sampling rule applies to
|
28
|
+
# the incoming request based on some of the request's parameters.
|
29
|
+
# Any None parameters provided will be considered an implicit match.
|
30
|
+
def applies?(sampling_req)
|
31
|
+
return false if sampling_req.nil? || sampling_req.empty?
|
32
|
+
|
33
|
+
host = sampling_req[:host]
|
34
|
+
url_path = sampling_req[:url_path]
|
35
|
+
http_method = sampling_req[:http_method]
|
36
|
+
|
37
|
+
host_match = !host || SearchPattern.wildcard_match?(pattern: @host, text: host)
|
38
|
+
path_match = !url_path || SearchPattern.wildcard_match?(pattern: @path, text: url_path)
|
39
|
+
method_match = !http_method || SearchPattern.wildcard_match?(pattern: @method, text: http_method)
|
40
|
+
host_match && path_match && method_match
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def validate
|
46
|
+
if @fixed_target < 0 || @rate < 0
|
47
|
+
raise InvalidSamplingConfigError, 'All rules must have non-negative values for fixed_target and rate.'
|
48
|
+
end
|
49
|
+
|
50
|
+
if @default
|
51
|
+
# validate default rule
|
52
|
+
if @host || @method || @path
|
53
|
+
raise InvalidSamplingConfigError, 'The default rule must not specify values for url_path, service_name, or http_method.'
|
54
|
+
end
|
55
|
+
else
|
56
|
+
# validate custom rule
|
57
|
+
unless @host && @method && @path
|
58
|
+
raise InvalidSamplingConfigError, 'All non-default rules must have values for url_path, service_name, and http_method.'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,35 +1,79 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'aws-xray-sdk/sampling/sampling_decision'
|
3
|
+
|
1
4
|
module XRay
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# thread-safe to achieve accurate sampling.
|
5
|
+
# Centralized thread-safe reservoir which holds fixed sampling
|
6
|
+
# quota for the current instance, borrowed count and TTL.
|
5
7
|
class Reservoir
|
6
|
-
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@
|
10
|
-
@
|
11
|
-
|
8
|
+
attr_reader :quota, :ttl
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@quota = nil
|
12
|
+
@ttl = nil
|
13
|
+
|
14
|
+
@this_sec = 0
|
15
|
+
@taken_this_sec = 0
|
16
|
+
@borrowed_this_sec = 0
|
17
|
+
|
18
|
+
@report_interval = 1
|
19
|
+
@report_elapsed = 0
|
20
|
+
|
12
21
|
@lock = Mutex.new
|
13
22
|
end
|
14
23
|
|
15
|
-
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
return false if @traces_per_sec.zero?
|
24
|
+
# Decide whether to borrow or take one quota from
|
25
|
+
# the reservoir. Return `false` if it can neither
|
26
|
+
# borrow nor take. This method is thread-safe.
|
27
|
+
def borrow_or_take(now, borrowable)
|
20
28
|
@lock.synchronize do
|
21
|
-
now
|
22
|
-
#
|
23
|
-
if now
|
24
|
-
@
|
25
|
-
@
|
29
|
+
reset_new_sec(now)
|
30
|
+
# Don't borrow if the quota is available and fresh.
|
31
|
+
if quota_fresh?(now)
|
32
|
+
return SamplingDecision::NOT_SAMPLE if @taken_this_sec >= @quota
|
33
|
+
@taken_this_sec += 1
|
34
|
+
return SamplingDecision::TAKE
|
26
35
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
36
|
+
|
37
|
+
# Otherwise try to borrow if the quota is not present or expired.
|
38
|
+
if borrowable
|
39
|
+
return SamplingDecision::NOT_SAMPLE if @borrowed_this_sec >= 1
|
40
|
+
@borrowed_this_sec += 1
|
41
|
+
return SamplingDecision::BORROW
|
42
|
+
end
|
43
|
+
|
44
|
+
# Cannot sample if quota expires and cannot borrow
|
45
|
+
SamplingDecision::NOT_SAMPLE
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_target_info(quota:, ttl:, interval:)
|
50
|
+
@quota = quota unless quota.nil?
|
51
|
+
@ttl = ttl.to_i unless ttl.nil?
|
52
|
+
@interval = interval / 10 unless interval.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def time_to_report?
|
56
|
+
if @report_elapsed + 1 >= @report_interval
|
57
|
+
@report_elapsed = 0
|
58
|
+
true
|
59
|
+
else
|
60
|
+
@report_elapsed += 1
|
61
|
+
false
|
32
62
|
end
|
33
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Reset the counter if now enters a new one-second window
|
68
|
+
def reset_new_sec(now)
|
69
|
+
return if now == @this_sec
|
70
|
+
@taken_this_sec = 0
|
71
|
+
@borrowed_this_sec = 0
|
72
|
+
@this_sec = now
|
73
|
+
end
|
74
|
+
|
75
|
+
def quota_fresh?(now)
|
76
|
+
@quota && @quota >= 0 && @ttl && @ttl >= now
|
77
|
+
end
|
34
78
|
end
|
35
79
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'aws-xray-sdk/logger'
|
2
|
+
|
3
|
+
module XRay
|
4
|
+
# Cache sampling rules and quota retrieved by `TargetPoller`
|
5
|
+
# and `RulePoller`. It will not return anything if it expires.
|
6
|
+
class RuleCache
|
7
|
+
include Logging
|
8
|
+
attr_accessor :last_updated
|
9
|
+
@@TTL = 60 * 60 # 1 hour
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@rules = []
|
13
|
+
@last_updated = nil
|
14
|
+
@lock = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_matched_rule(sampling_req, now: Time.now.to_i)
|
18
|
+
return nil if expired?(now)
|
19
|
+
matched = nil
|
20
|
+
rules.each do |rule|
|
21
|
+
matched = rule if matched.nil? && rule.applies?(sampling_req)
|
22
|
+
matched = rule if matched.nil? && rule.default?
|
23
|
+
end
|
24
|
+
matched
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_rules(new_rules)
|
28
|
+
@lock.synchronize do
|
29
|
+
# Simply assign rules and sort if cache is empty
|
30
|
+
if @rules.empty?
|
31
|
+
@rules = new_rules
|
32
|
+
return sort_rules
|
33
|
+
end
|
34
|
+
|
35
|
+
# otherwise we need to merge new rules and current rules
|
36
|
+
curr_rules = {}
|
37
|
+
@rules.each do |rule|
|
38
|
+
curr_rules[rule.name] = rule
|
39
|
+
end
|
40
|
+
# Update the rules in the cache
|
41
|
+
@rules = new_rules
|
42
|
+
# Transfer state information
|
43
|
+
@rules.each do |rule|
|
44
|
+
curr_rule = curr_rules[rule.name]
|
45
|
+
rule.merge(curr_rule) unless curr_rule.nil?
|
46
|
+
end
|
47
|
+
sort_rules
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_targets(targets_h)
|
52
|
+
@lock.synchronize do
|
53
|
+
@rules.each do |rule|
|
54
|
+
target = targets_h[rule.name]
|
55
|
+
next if target.nil?
|
56
|
+
rule.rate = target.fixed_rate
|
57
|
+
rule.reservoir.load_target_info(
|
58
|
+
quota: target.reservoir_quota,
|
59
|
+
ttl: target.reservoir_quota_ttl,
|
60
|
+
interval: target.interval
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def rules
|
67
|
+
@lock.synchronize do
|
68
|
+
@rules
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# The cache should maintain the order of the rules based on
|
75
|
+
# priority. If priority is the same we sort name by alphabet
|
76
|
+
# as rule name is unique.
|
77
|
+
def sort_rules
|
78
|
+
@rules.sort_by! { |rule| [rule.priority, rule.name] }
|
79
|
+
end
|
80
|
+
|
81
|
+
def expired?(now)
|
82
|
+
# The cache is treated as expired if it is never loaded.
|
83
|
+
@last_updated.nil? || now > @last_updated + @@TTL
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'aws-xray-sdk/logger'
|
2
|
+
|
3
|
+
module XRay
|
4
|
+
# Polls sampling rules from X-Ray service
|
5
|
+
class RulePoller
|
6
|
+
include Logging
|
7
|
+
attr_reader :cache, :connector
|
8
|
+
|
9
|
+
def initialize(cache:, connector:)
|
10
|
+
@cache = cache
|
11
|
+
@connector = connector
|
12
|
+
@worker = Thread.new { poll }
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
@worker.run
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def poll
|
22
|
+
loop do
|
23
|
+
Thread.stop
|
24
|
+
refresh_cache
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def refresh_cache
|
29
|
+
now = Time.now.to_i
|
30
|
+
rules = @connector.fetch_sampling_rules
|
31
|
+
unless rules.nil? || rules.empty?
|
32
|
+
@cache.load_rules(rules)
|
33
|
+
@cache.last_updated = now
|
34
|
+
end
|
35
|
+
rescue StandardError => e
|
36
|
+
logger.warn %(failed to fetch X-Ray sampling rules due to #{e.backtrace})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -6,12 +6,12 @@ module XRay
|
|
6
6
|
module Sampler
|
7
7
|
# Decides if a segment should be sampled for an incoming request.
|
8
8
|
# Used in case of middleware.
|
9
|
-
def sample_request?(
|
9
|
+
def sample_request?(sampling_req:)
|
10
10
|
raise 'Not implemented'
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
14
|
-
#
|
13
|
+
# Sample purely based on cached sampling rules without
|
14
|
+
# any incoming rules matching information.
|
15
15
|
def sample?
|
16
16
|
raise 'Not implemented'
|
17
17
|
end
|
@@ -3,55 +3,122 @@ require 'aws-xray-sdk/sampling/reservoir'
|
|
3
3
|
require 'aws-xray-sdk/search_pattern'
|
4
4
|
|
5
5
|
module XRay
|
6
|
-
#
|
7
|
-
# It can be either a custom rule or the default rule.
|
6
|
+
# Service sampling rule data model
|
8
7
|
class SamplingRule
|
9
|
-
attr_reader :
|
10
|
-
:
|
8
|
+
attr_reader :name, :priority,
|
9
|
+
:request_count, :borrow_count, :sampled_count
|
10
|
+
attr_accessor :reservoir, :rate
|
11
11
|
|
12
|
-
# @param
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
@rate =
|
12
|
+
# @param Struct defined here https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/XRay/Types/SamplingRule.html.
|
13
|
+
def initialize(record)
|
14
|
+
@name = record.rule_name
|
15
|
+
@priority = record.priority
|
16
|
+
@rate = record.fixed_rate
|
17
17
|
|
18
|
-
@
|
19
|
-
@method =
|
20
|
-
@path =
|
18
|
+
@host = record.host
|
19
|
+
@method = record.http_method
|
20
|
+
@path = record.url_path
|
21
|
+
@service = record.service_name
|
22
|
+
@service_type = record.service_type
|
21
23
|
|
22
|
-
@
|
23
|
-
|
24
|
-
|
24
|
+
@reservoir_size = record.reservoir_size
|
25
|
+
@reservoir = Reservoir.new
|
26
|
+
reset_statistics
|
27
|
+
|
28
|
+
@lock = Mutex.new
|
25
29
|
end
|
26
30
|
|
27
31
|
# Determines whether or not this sampling rule applies to
|
28
32
|
# the incoming request based on some of the request's parameters.
|
29
|
-
# Any
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
# Any Nil parameters provided will be considered as implicit matches
|
34
|
+
# as the rule matching is a best effort.
|
35
|
+
def applies?(sampling_req)
|
36
|
+
return false if sampling_req.nil? || sampling_req.empty?
|
37
|
+
|
38
|
+
host = sampling_req[:host]
|
39
|
+
http_method = sampling_req[:http_method]
|
40
|
+
url_path = sampling_req[:url_path]
|
41
|
+
service = sampling_req[:service]
|
42
|
+
|
43
|
+
host_match = !host || SearchPattern.wildcard_match?(pattern: @host, text: host)
|
44
|
+
path_match = !url_path || SearchPattern.wildcard_match?(pattern: @path, text: url_path)
|
45
|
+
method_match = !http_method || SearchPattern.wildcard_match?(pattern: @method, text: http_method)
|
46
|
+
service_match = !service || SearchPattern.wildcard_match?(pattern: @service, text: service)
|
47
|
+
|
48
|
+
# if sampling request contains service type we assmue
|
49
|
+
# the origin (a.k.a AWS plugins are set and effective)
|
50
|
+
if sampling_req.key?(:service_type)
|
51
|
+
service_type = sampling_req[:service_type]
|
52
|
+
service_type_match = SearchPattern.wildcard_match?(pattern: @service_type, text: service_type)
|
53
|
+
else
|
54
|
+
service_type_match = @service_type == '*'
|
55
|
+
end
|
56
|
+
host_match && path_match && method_match && service_match && service_type_match
|
35
57
|
end
|
36
58
|
|
37
|
-
|
59
|
+
def snapshot_statistics
|
60
|
+
@lock.synchronize do
|
61
|
+
report = {
|
62
|
+
request_count: @request_count,
|
63
|
+
borrow_count: @borrow_count,
|
64
|
+
sampled_count: @sampled_count
|
65
|
+
}
|
66
|
+
reset_statistics
|
67
|
+
report
|
68
|
+
end
|
69
|
+
end
|
38
70
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
71
|
+
def merge(rule)
|
72
|
+
@lock.synchronize do
|
73
|
+
@request_count = rule.request_count
|
74
|
+
@borrow_count = rule.borrow_count
|
75
|
+
@sampled_count = rule.sampled_count
|
76
|
+
@reservoir = rule.reservoir
|
77
|
+
rule.reservoir = nil
|
42
78
|
end
|
79
|
+
end
|
43
80
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
81
|
+
def borrowable?
|
82
|
+
@reservoir_size != 0
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return `true` if this rule is the default rule.
|
86
|
+
def default?
|
87
|
+
@name == 'Default'
|
88
|
+
end
|
89
|
+
|
90
|
+
def ever_matched?
|
91
|
+
@request_count > 0
|
92
|
+
end
|
93
|
+
|
94
|
+
def time_to_report?
|
95
|
+
@reservoir.time_to_report?
|
96
|
+
end
|
97
|
+
|
98
|
+
def increment_request_count
|
99
|
+
@lock.synchronize do
|
100
|
+
@request_count += 1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def increment_borrow_count
|
105
|
+
@lock.synchronize do
|
106
|
+
@borrow_count += 1
|
54
107
|
end
|
55
108
|
end
|
109
|
+
|
110
|
+
def increment_sampled_count
|
111
|
+
@lock.synchronize do
|
112
|
+
@sampled_count += 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def reset_statistics
|
119
|
+
@request_count = 0
|
120
|
+
@borrow_count = 0
|
121
|
+
@sampled_count = 0
|
122
|
+
end
|
56
123
|
end
|
57
124
|
end
|