aws-xray-sdk 0.10.2 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|