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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-xray-sdk/configuration.rb +10 -7
  3. data/lib/aws-xray-sdk/daemon_config.rb +59 -0
  4. data/lib/aws-xray-sdk/emitter/default_emitter.rb +10 -21
  5. data/lib/aws-xray-sdk/emitter/emitter.rb +1 -3
  6. data/lib/aws-xray-sdk/facets/aws_sdk.rb +14 -6
  7. data/lib/aws-xray-sdk/facets/helper.rb +4 -11
  8. data/lib/aws-xray-sdk/facets/net_http.rb +4 -0
  9. data/lib/aws-xray-sdk/facets/rack.rb +8 -6
  10. data/lib/aws-xray-sdk/facets/rails/railtie.rb +1 -1
  11. data/lib/aws-xray-sdk/model/cause.rb +2 -2
  12. data/lib/aws-xray-sdk/model/dummy_entities.rb +4 -0
  13. data/lib/aws-xray-sdk/model/entity.rb +2 -2
  14. data/lib/aws-xray-sdk/model/metadata.rb +2 -2
  15. data/lib/aws-xray-sdk/model/segment.rb +8 -1
  16. data/lib/aws-xray-sdk/plugins/elastic_beanstalk.rb +2 -2
  17. data/lib/aws-xray-sdk/recorder.rb +7 -4
  18. data/lib/aws-xray-sdk/sampling/connector.rb +72 -0
  19. data/lib/aws-xray-sdk/sampling/default_sampler.rb +72 -78
  20. data/lib/aws-xray-sdk/sampling/lead_poller.rb +72 -0
  21. data/lib/aws-xray-sdk/sampling/local/reservoir.rb +35 -0
  22. data/lib/aws-xray-sdk/sampling/local/sampler.rb +110 -0
  23. data/lib/aws-xray-sdk/sampling/local/sampling_rule.rb +63 -0
  24. data/lib/aws-xray-sdk/sampling/reservoir.rb +68 -24
  25. data/lib/aws-xray-sdk/sampling/rule_cache.rb +86 -0
  26. data/lib/aws-xray-sdk/sampling/rule_poller.rb +39 -0
  27. data/lib/aws-xray-sdk/sampling/sampler.rb +3 -3
  28. data/lib/aws-xray-sdk/sampling/sampling_decision.rb +8 -0
  29. data/lib/aws-xray-sdk/sampling/sampling_rule.rb +102 -35
  30. data/lib/aws-xray-sdk/version.rb +1 -1
  31. 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
- # Keeps track of the number of sampled segments within
3
- # a single second. This class is implemented to be
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
- # @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
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
- # Returns `true` if there are segments 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?
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 = Time.now.to_i
22
- # refresh time frame
23
- if now != @this_sec
24
- @used_this_sec = 0
25
- @this_sec = now
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
- # 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
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?(service_name:, url_path:, http_method:)
9
+ def sample_request?(sampling_req:)
10
10
  raise 'Not implemented'
11
11
  end
12
12
 
13
- # Decides if a segment should be sampled merely based on internal
14
- # sampling rules.
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
@@ -0,0 +1,8 @@
1
+ module XRay
2
+ # Stores the enum style sampling decisions for default sampler
3
+ module SamplingDecision
4
+ TAKE = 'take'.freeze
5
+ BORROW = 'borrow'.freeze
6
+ NOT_SAMPLE = 'no'.freeze
7
+ end
8
+ 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
- # One SamplingRule object represents one rule defined from the rules hash definition.
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 :fixed_target, :rate, :service_name,
10
- :method, :path, :reservoir, :default
8
+ attr_reader :name, :priority,
9
+ :request_count, :borrow_count, :sampled_count
10
+ attr_accessor :reservoir, :rate
11
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]
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
- @service_name = rule_definition[:service_name]
19
- @method = rule_definition[:http_method]
20
- @path = rule_definition[:url_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
- @default = default
23
- validate
24
- @reservoir = Reservoir.new traces_per_sec: @fixed_target
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 None parameters provided will be considered an implicit match.
30
- def applies?(target_name:, target_path:, target_method:)
31
- name_match = !target_name || SearchPattern.wildcard_match?(pattern: @service_name, text: target_name)
32
- path_match = !target_path || SearchPattern.wildcard_match?(pattern: @path, text: target_path)
33
- method_match = !target_method || SearchPattern.wildcard_match?(pattern: @method, text: target_method)
34
- name_match && path_match && method_match
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
- private
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 validate
40
- if @fixed_target < 0 || @rate < 0
41
- raise InvalidSamplingConfigError, 'All rules must have non-negative values for fixed_target and rate.'
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
- if @default
45
- # validate default rule
46
- if @service_name || @method || @path
47
- raise InvalidSamplingConfigError, 'The default rule must not specify values for url_path, service_name, or http_method.'
48
- end
49
- else
50
- # validate custom rule
51
- unless @service_name && @method && @path
52
- raise InvalidSamplingConfigError, 'All non-default rules must have values for url_path, service_name, and http_method.'
53
- end
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