jaeger-client 0.7.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/.rubocop.yml +3 -0
  4. data/.travis.yml +11 -2
  5. data/Makefile +1 -0
  6. data/README.md +127 -13
  7. data/crossdock/Dockerfile +29 -0
  8. data/crossdock/Gemfile +6 -0
  9. data/crossdock/Gemfile.lock +35 -0
  10. data/crossdock/docker-compose.yml +68 -0
  11. data/crossdock/jaeger-docker-compose.yml +48 -0
  12. data/crossdock/rules.mk +35 -0
  13. data/crossdock/server +173 -0
  14. data/jaeger-client.gemspec +4 -2
  15. data/lib/jaeger/client.rb +42 -18
  16. data/lib/jaeger/client/version.rb +1 -1
  17. data/lib/jaeger/encoders/thrift_encoder.rb +142 -0
  18. data/lib/jaeger/extractors.rb +173 -0
  19. data/lib/jaeger/http_sender.rb +28 -0
  20. data/lib/jaeger/injectors.rb +83 -0
  21. data/lib/jaeger/rate_limiter.rb +61 -0
  22. data/lib/jaeger/recurring_executor.rb +35 -0
  23. data/lib/jaeger/reporters.rb +7 -0
  24. data/lib/jaeger/reporters/composite_reporter.rb +17 -0
  25. data/lib/jaeger/reporters/in_memory_reporter.rb +30 -0
  26. data/lib/jaeger/reporters/logging_reporter.rb +22 -0
  27. data/lib/jaeger/reporters/null_reporter.rb +11 -0
  28. data/lib/jaeger/{client/async_reporter.rb → reporters/remote_reporter.rb} +21 -20
  29. data/lib/jaeger/{client/async_reporter → reporters/remote_reporter}/buffer.rb +2 -2
  30. data/lib/jaeger/samplers.rb +8 -0
  31. data/lib/jaeger/samplers/const.rb +24 -0
  32. data/lib/jaeger/samplers/guaranteed_throughput_probabilistic.rb +47 -0
  33. data/lib/jaeger/samplers/per_operation.rb +79 -0
  34. data/lib/jaeger/samplers/probabilistic.rb +38 -0
  35. data/lib/jaeger/samplers/rate_limiting.rb +51 -0
  36. data/lib/jaeger/samplers/remote_controlled.rb +119 -0
  37. data/lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb +34 -0
  38. data/lib/jaeger/scope.rb +38 -0
  39. data/lib/jaeger/scope_manager.rb +47 -0
  40. data/lib/jaeger/scope_manager/scope_identifier.rb +13 -0
  41. data/lib/jaeger/scope_manager/scope_stack.rb +33 -0
  42. data/lib/jaeger/span.rb +97 -0
  43. data/lib/jaeger/span/thrift_log_builder.rb +18 -0
  44. data/lib/jaeger/span_context.rb +57 -0
  45. data/lib/jaeger/thrift_tag_builder.rb +41 -0
  46. data/lib/jaeger/trace_id.rb +48 -0
  47. data/lib/jaeger/tracer.rb +213 -0
  48. data/lib/jaeger/udp_sender.rb +26 -0
  49. data/lib/jaeger/udp_sender/transport.rb +40 -0
  50. metadata +78 -31
  51. data/lib/jaeger/client/carrier.rb +0 -26
  52. data/lib/jaeger/client/encoders/thrift_encoder.rb +0 -94
  53. data/lib/jaeger/client/extractors.rb +0 -88
  54. data/lib/jaeger/client/http_sender.rb +0 -30
  55. data/lib/jaeger/client/injectors.rb +0 -54
  56. data/lib/jaeger/client/samplers.rb +0 -4
  57. data/lib/jaeger/client/samplers/const.rb +0 -29
  58. data/lib/jaeger/client/samplers/probabilistic.rb +0 -30
  59. data/lib/jaeger/client/scope.rb +0 -40
  60. data/lib/jaeger/client/scope_manager.rb +0 -49
  61. data/lib/jaeger/client/scope_manager/scope_identifier.rb +0 -15
  62. data/lib/jaeger/client/scope_manager/scope_stack.rb +0 -35
  63. data/lib/jaeger/client/span.rb +0 -84
  64. data/lib/jaeger/client/span/thrift_log_builder.rb +0 -20
  65. data/lib/jaeger/client/span/thrift_tag_builder.rb +0 -45
  66. data/lib/jaeger/client/span_context.rb +0 -59
  67. data/lib/jaeger/client/trace_id.rb +0 -41
  68. data/lib/jaeger/client/tracer.rb +0 -189
  69. data/lib/jaeger/client/udp_sender.rb +0 -27
  70. data/lib/jaeger/client/udp_sender/transport.rb +0 -42
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Samplers
5
+ # A sampler that leverages both Probabilistic sampler and RateLimiting
6
+ # sampler via the GuaranteedThroughputProbabilistic sampler. This sampler
7
+ # keeps track of all operations and delegates calls the the respective
8
+ # GuaranteedThroughputProbabilistic sampler.
9
+ class PerOperation
10
+ DEFAULT_SAMPLING_PROBABILITY = 0.001
11
+ DEFAULT_LOWER_BOUND = 1.0 / (10.0 * 60.0) # sample once every 10 minutes'
12
+
13
+ attr_reader :default_sampling_probability, :lower_bound, :samplers
14
+
15
+ def initialize(strategies:, max_operations:)
16
+ @max_operations = max_operations
17
+ @samplers = {}
18
+ update(strategies: strategies)
19
+ end
20
+
21
+ def update(strategies:)
22
+ is_updated = false
23
+
24
+ @default_sampling_probability =
25
+ strategies[:default_sampling_probability] || DEFAULT_SAMPLING_PROBABILITY
26
+ @lower_bound =
27
+ strategies[:default_lower_bound_traces_per_second] || DEFAULT_LOWER_BOUND
28
+
29
+ if @default_sampler
30
+ is_updated = @default_sampler.update(rate: @default_sampling_probability)
31
+ else
32
+ @default_sampler = Probabilistic.new(rate: @default_sampling_probability)
33
+ end
34
+
35
+ is_updated = update_operation_strategies(strategies) || is_updated
36
+
37
+ is_updated
38
+ end
39
+
40
+ def sample(opts)
41
+ operation_name = opts.fetch(:operation_name)
42
+ sampler = @samplers[operation_name]
43
+ return sampler.sample(opts) if sampler
44
+
45
+ return @default_sampler.sample(opts) if @samplers.length >= @max_operations
46
+
47
+ sampler = GuaranteedThroughputProbabilistic.new(
48
+ lower_bound: @lower_bound,
49
+ rate: @default_sampling_probability
50
+ )
51
+ @samplers[operation_name] = sampler
52
+ sampler.sample(opts)
53
+ end
54
+
55
+ private
56
+
57
+ def update_operation_strategies(strategies)
58
+ is_updated = false
59
+
60
+ (strategies[:per_operation_strategies] || []).each do |strategy|
61
+ operation = strategy.fetch(:operation)
62
+ rate = strategy.fetch(:probabilistic_sampling).fetch(:sampling_rate)
63
+
64
+ if (sampler = @samplers[operation])
65
+ is_updated = sampler.update(lower_bound: @lower_bound, rate: rate) || is_updated
66
+ else
67
+ @samplers[operation] = GuaranteedThroughputProbabilistic.new(
68
+ lower_bound: @lower_bound,
69
+ rate: rate
70
+ )
71
+ is_updated = true
72
+ end
73
+ end
74
+
75
+ is_updated
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Samplers
5
+ # Probabilistic sampler
6
+ #
7
+ # Sample a portion of traces using trace_id as the random decision
8
+ class Probabilistic
9
+ attr_reader :rate
10
+
11
+ def initialize(rate: 0.001)
12
+ update(rate: rate)
13
+ end
14
+
15
+ def update(rate:)
16
+ if rate < 0.0 || rate > 1.0
17
+ raise "Sampling rate must be between 0.0 and 1.0, got #{rate.inspect}"
18
+ end
19
+
20
+ new_boundary = TraceId::TRACE_ID_UPPER_BOUND * rate
21
+ return false if @boundary == new_boundary
22
+
23
+ @rate = rate
24
+ @boundary = TraceId::TRACE_ID_UPPER_BOUND * rate
25
+ @tags = {
26
+ 'sampler.type' => 'probabilistic',
27
+ 'sampler.param' => rate
28
+ }
29
+
30
+ true
31
+ end
32
+
33
+ def sample(trace_id:, **)
34
+ [@boundary >= trace_id, @tags]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Samplers
5
+ # Samples at most max_traces_per_second. The distribution of sampled
6
+ # traces follows burstiness of the service, i.e. a service with uniformly
7
+ # distributed requests will have those requests sampled uniformly as
8
+ # well, but if requests are bursty, especially sub-second, then a number
9
+ # of sequential requests can be sampled each second.
10
+ class RateLimiting
11
+ attr_reader :tags, :max_traces_per_second
12
+
13
+ def initialize(max_traces_per_second: 10)
14
+ update(max_traces_per_second: max_traces_per_second)
15
+ end
16
+
17
+ def update(max_traces_per_second:)
18
+ if max_traces_per_second < 0.0
19
+ raise "max_traces_per_second must not be negative, got #{max_traces_per_second}"
20
+ end
21
+
22
+ return false if max_traces_per_second == @max_traces_per_second
23
+
24
+ @tags = {
25
+ 'sampler.type' => 'ratelimiting',
26
+ 'sampler.param' => max_traces_per_second
27
+ }
28
+ @max_traces_per_second = max_traces_per_second
29
+ max_balance = [max_traces_per_second, 1.0].max
30
+
31
+ if @rate_limiter
32
+ @rate_limiter.update(
33
+ credits_per_second: max_traces_per_second,
34
+ max_balance: max_balance
35
+ )
36
+ else
37
+ @rate_limiter = RateLimiter.new(
38
+ credits_per_second: max_traces_per_second,
39
+ max_balance: [max_traces_per_second, 1.0].max
40
+ )
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ def sample(*)
47
+ [@rate_limiter.check_credit(1.0), @tags]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'remote_controlled/instructions_fetcher'
4
+
5
+ module Jaeger
6
+ module Samplers
7
+ class RemoteControlled
8
+ DEFAULT_REFRESH_INTERVAL = 60
9
+ DEFAULT_SAMPLING_HOST = 'localhost'.freeze
10
+ DEFAULT_SAMPLING_PORT = 5778
11
+
12
+ attr_reader :sampler
13
+
14
+ def initialize(opts = {})
15
+ @sampler = opts.fetch(:sampler, Probabilistic.new)
16
+ @logger = opts.fetch(:logger, Logger.new($stdout))
17
+
18
+ @poll_executor = opts[:poll_executor] || begin
19
+ refresh_interval = opts.fetch(:refresh_interval, DEFAULT_REFRESH_INTERVAL)
20
+ RecurringExecutor.new(interval: refresh_interval)
21
+ end
22
+
23
+ @instructions_fetcher = opts[:instructions_fetcher] || begin
24
+ service_name = opts.fetch(:service_name)
25
+ host = opts.fetch(:host, DEFAULT_SAMPLING_HOST)
26
+ port = opts.fetch(:port, DEFAULT_SAMPLING_PORT)
27
+ InstructionsFetcher.new(host: host, port: port, service_name: service_name)
28
+ end
29
+ end
30
+
31
+ def sample(*args)
32
+ @poll_executor.start(&method(:poll)) unless @poll_executor.running?
33
+
34
+ @sampler.sample(*args)
35
+ end
36
+
37
+ def poll
38
+ @logger.debug 'Fetching sampling strategy'
39
+
40
+ instructions = @instructions_fetcher.fetch
41
+ handle_instructions(instructions)
42
+ rescue InstructionsFetcher::FetchFailed => e
43
+ @logger.warn "Fetching sampling strategy failed: #{e.message}"
44
+ end
45
+
46
+ private
47
+
48
+ def handle_instructions(instructions)
49
+ if instructions['operationSampling']
50
+ update_per_operation_sampler(instructions['operationSampling'])
51
+ else
52
+ update_rate_limiting_or_probabilistic_sampler(instructions['strategyType'], instructions)
53
+ end
54
+ end
55
+
56
+ def update_per_operation_sampler(instructions)
57
+ strategies = normalize(instructions)
58
+
59
+ if @sampler.is_a?(PerOperation)
60
+ @sampler.update(strategies: strategies)
61
+ else
62
+ @sampler = PerOperation.new(strategies: strategies, max_operations: 2000)
63
+ end
64
+ end
65
+
66
+ def normalize(instructions)
67
+ {
68
+ default_sampling_probability: instructions['defaultSamplingProbability'],
69
+ default_lower_bound_traces_per_second: instructions['defaultLowerBoundTracesPerSecond'],
70
+ per_operation_strategies: instructions['perOperationStrategies'].map do |strategy|
71
+ {
72
+ operation: strategy['operation'],
73
+ probabilistic_sampling: {
74
+ sampling_rate: strategy['probabilisticSampling']['samplingRate']
75
+ }
76
+ }
77
+ end
78
+ }
79
+ end
80
+
81
+ def update_rate_limiting_or_probabilistic_sampler(strategy, instructions)
82
+ case strategy
83
+ when 'PROBABILISTIC'
84
+ update_probabilistic_strategy(instructions['probabilisticSampling'])
85
+ when 'RATE_LIMITING'
86
+ update_rate_limiting_strategy(instructions['rateLimitingSampling'])
87
+ else
88
+ @logger.warn "Unknown sampling strategy #{strategy}"
89
+ end
90
+ end
91
+
92
+ def update_probabilistic_strategy(instructions)
93
+ rate = instructions['samplingRate']
94
+ return unless rate
95
+
96
+ if @sampler.is_a?(Probabilistic)
97
+ @sampler.update(rate: rate)
98
+ @logger.info "Updated Probabilistic sampler (rate=#{rate})"
99
+ else
100
+ @sampler = Probabilistic.new(rate: rate)
101
+ @logger.info "Updated sampler to Probabilistic (rate=#{rate})"
102
+ end
103
+ end
104
+
105
+ def update_rate_limiting_strategy(instructions)
106
+ max_traces_per_second = instructions['maxTracesPerSecond']
107
+ return unless max_traces_per_second
108
+
109
+ if @sampler.is_a?(RateLimiting)
110
+ @sampler.update(max_traces_per_second: max_traces_per_second)
111
+ @logger.info "Updated Ratelimiting sampler (max_traces_per_second=#{max_traces_per_second})"
112
+ else
113
+ @sampler = RateLimiting.new(max_traces_per_second: max_traces_per_second)
114
+ @logger.info "Updated sampler to Ratelimiting (max_traces_per_second=#{max_traces_per_second})"
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Samplers
5
+ class RemoteControlled
6
+ class InstructionsFetcher
7
+ FetchFailed = Class.new(StandardError)
8
+
9
+ def initialize(host:, port:, service_name:)
10
+ @host = host
11
+ @port = port
12
+ @service_name = service_name
13
+ end
14
+
15
+ def fetch
16
+ http = Net::HTTP.new(@host, @port)
17
+ path = "/sampling?service=#{CGI.escape(@service_name)}"
18
+ response =
19
+ begin
20
+ http.request(Net::HTTP::Get.new(path))
21
+ rescue StandardError => e
22
+ raise FetchFailed, e.inspect
23
+ end
24
+
25
+ unless response.is_a?(Net::HTTPSuccess)
26
+ raise FetchFailed, "Unsuccessful response (code=#{response.code})"
27
+ end
28
+
29
+ JSON.parse(response.body)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ # Scope represents an OpenTracing Scope
5
+ #
6
+ # See http://www.opentracing.io for more information.
7
+ class Scope
8
+ def initialize(span, scope_stack, finish_on_close:)
9
+ @span = span
10
+ @scope_stack = scope_stack
11
+ @finish_on_close = finish_on_close
12
+ @closed = false
13
+ end
14
+
15
+ # Return the Span scoped by this Scope
16
+ #
17
+ # @return [Span]
18
+ attr_reader :span
19
+
20
+ # Close scope
21
+ #
22
+ # Mark the end of the active period for the current thread and Scope,
23
+ # updating the ScopeManager#active in the process.
24
+ def close
25
+ raise "Tried to close already closed span: #{inspect}" if @closed
26
+ @closed = true
27
+
28
+ @span.finish if @finish_on_close
29
+ removed_scope = @scope_stack.pop
30
+
31
+ if removed_scope != self # rubocop:disable Style/GuardClause
32
+ raise 'Removed non-active scope, ' \
33
+ "removed: #{removed_scope.inspect}, "\
34
+ "expected: #{inspect}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'scope_manager/scope_stack'
4
+ require_relative 'scope_manager/scope_identifier'
5
+
6
+ module Jaeger
7
+ # ScopeManager represents an OpenTracing ScopeManager
8
+ #
9
+ # See http://www.opentracing.io for more information.
10
+ #
11
+ # The ScopeManager interface abstracts both the activation of Span instances
12
+ # via ScopeManager#activate and access to an active Span/Scope via
13
+ # ScopeManager#active
14
+ #
15
+ class ScopeManager
16
+ def initialize
17
+ @scope_stack = ScopeStack.new
18
+ end
19
+
20
+ # Make a span instance active
21
+ #
22
+ # @param span [Span] the Span that should become active
23
+ # @param finish_on_close [Boolean] whether the Span should automatically be
24
+ # finished when Scope#close is called
25
+ # @return [Scope] instance to control the end of the active period for the
26
+ # Span. It is a programming error to neglect to call Scope#close on the
27
+ # returned instance.
28
+ def activate(span, finish_on_close: true)
29
+ return active if active && active.span == span
30
+ scope = Scope.new(span, @scope_stack, finish_on_close: finish_on_close)
31
+ @scope_stack.push(scope)
32
+ scope
33
+ end
34
+
35
+ # Return active scope
36
+ #
37
+ # If there is a non-null Scope, its wrapped Span becomes an implicit parent
38
+ # (as Reference#CHILD_OF) of any newly-created Span at
39
+ # Tracer#start_active_span or Tracer#start_span time.
40
+ #
41
+ # @return [Scope] the currently active Scope which can be used to access the
42
+ # currently active Span.
43
+ def active
44
+ @scope_stack.peek
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ class ScopeManager
5
+ # @api private
6
+ class ScopeIdentifier
7
+ def self.generate
8
+ # 65..90.chr are characters between A and Z
9
+ "opentracing_#{(0...8).map { rand(65..90).chr }.join}".to_sym
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ class ScopeManager
5
+ # @api private
6
+ class ScopeStack
7
+ def initialize
8
+ # Generate a random identifier to use as the Thread.current key. This is
9
+ # needed so that it would be possible to create multiple tracers in one
10
+ # thread (mostly useful for testing purposes)
11
+ @scope_identifier = ScopeIdentifier.generate
12
+ end
13
+
14
+ def push(scope)
15
+ store << scope
16
+ end
17
+
18
+ def pop
19
+ store.pop
20
+ end
21
+
22
+ def peek
23
+ store.last
24
+ end
25
+
26
+ private
27
+
28
+ def store
29
+ Thread.current[@scope_identifier] ||= []
30
+ end
31
+ end
32
+ end
33
+ end