jaeger-client 0.7.1 → 1.1.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 (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