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.
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