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,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/exceptions'
7
+ require 'aws-xray-sdk/sampling/sampling_decision'
4
8
 
5
9
  module XRay
6
- # The default sampler that uses internally 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: 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
- DEFAULT_RULES = {
36
- version: 1,
37
- default: {
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
- load_sampling_rules(DEFAULT_RULES)
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
- # Return True if the sampler decide to sample based on input
49
- # information and sampling rules. It will first check if any
50
- # custom rule should be applied, if not it falls back to the
51
- # default sampling rule.
52
- # All arugments are extracted from incoming requests by
53
- # X-Ray middleware to perform path based sampling.
54
- def sample_request?(service_name:, url_path:, http_method:)
55
- # directly fallback to non-path-based if all arguments are nil
56
- return sample? unless service_name || url_path || http_method
57
- @custom_rules ||= []
58
- @custom_rules.each do |c|
59
- return should_sample?(c) if c.applies?(target_name: service_name, target_path: url_path, target_method: http_method)
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
- should_sample?(@default_rule)
62
+ sample_request? nil
68
63
  end
69
64
 
70
- # @param [Hash] v The sampling rules definition.
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
- load_sampling_rules(v)
70
+ @local_sampler.sampling_rules = v
73
71
  end
74
72
 
75
- # @return [Array] An array of [SamplingRule]
76
- def sampling_rules
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 should_sample?(rule)
86
- return true if rule.reservoir.take
87
- Random.rand <= rule.rate
88
- end
89
-
90
- def load_sampling_rules(v)
91
- version = v[:version]
92
- if version != 1
93
- raise InvalidSamplingConfigError, %('Sampling rule version #{version} is not supported.')
94
- end
95
- unless v[:default]
96
- raise InvalidSamplingConfigError, 'A default rule must be provided.'
97
- end
98
- @default_rule = SamplingRule.new rule_definition: v[:default], default: true
99
- @custom_rules = []
100
- v[:rules].each do |d|
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