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,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Jaeger
6
+ class HttpSender
7
+ def initialize(url:, headers: {}, encoder:, logger: Logger.new(STDOUT))
8
+ @encoder = encoder
9
+ @logger = logger
10
+
11
+ @uri = URI(url)
12
+ @uri.query = 'format=jaeger.thrift'
13
+
14
+ @transport = ::Thrift::HTTPClientTransport.new(@uri.to_s)
15
+ @transport.add_headers(headers)
16
+
17
+ @serializer = ::Thrift::Serializer.new
18
+ end
19
+
20
+ def send_spans(spans)
21
+ batch = @encoder.encode(spans)
22
+ @transport.write(@serializer.serialize(batch))
23
+ @transport.flush
24
+ rescue StandardError => error
25
+ @logger.error("Failure while sending a batch of spans: #{error}")
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Injectors
5
+ def self.context_as_jaeger_string(span_context)
6
+ [
7
+ span_context.trace_id.to_s(16),
8
+ span_context.span_id.to_s(16),
9
+ span_context.parent_id.to_s(16),
10
+ span_context.flags.to_s(16)
11
+ ].join(':')
12
+ end
13
+
14
+ class JaegerTextMapCodec
15
+ def self.inject(span_context, carrier)
16
+ carrier['uber-trace-id'] = Injectors.context_as_jaeger_string(span_context)
17
+ span_context.baggage.each do |key, value|
18
+ carrier["uberctx-#{key}"] = value
19
+ end
20
+ end
21
+ end
22
+
23
+ class JaegerRackCodec
24
+ def self.inject(span_context, carrier)
25
+ carrier['uber-trace-id'] =
26
+ CGI.escape(Injectors.context_as_jaeger_string(span_context))
27
+ span_context.baggage.each do |key, value|
28
+ carrier["uberctx-#{key}"] = CGI.escape(value)
29
+ end
30
+ end
31
+ end
32
+
33
+ class JaegerBinaryCodec
34
+ def self.inject(_span_context, _carrier)
35
+ warn 'Jaeger::Client with binary format is not supported yet'
36
+ end
37
+ end
38
+
39
+ class B3RackCodec
40
+ def self.inject(span_context, carrier)
41
+ carrier['x-b3-traceid'] = TraceId.to_hex(span_context.trace_id)
42
+ carrier['x-b3-spanid'] = TraceId.to_hex(span_context.span_id)
43
+ carrier['x-b3-parentspanid'] = TraceId.to_hex(span_context.parent_id)
44
+
45
+ # flags (for debug) and sampled headers are mutually exclusive
46
+ if span_context.flags == Jaeger::SpanContext::Flags::DEBUG
47
+ carrier['x-b3-flags'] = '1'
48
+ else
49
+ carrier['x-b3-sampled'] = span_context.flags.to_s(16)
50
+ end
51
+ end
52
+ end
53
+
54
+ class TraceContextRackCodec
55
+ def self.inject(span_context, carrier)
56
+ flags = span_context.sampled? || span_context.debug? ? 1 : 0
57
+
58
+ carrier['traceparent'] = format(
59
+ '%<version>s-%<trace_id>s-%<span_id>s-%<flags>s',
60
+ version: '00',
61
+ trace_id: span_context.trace_id.to_s(16).rjust(32, '0'),
62
+ span_id: span_context.span_id.to_s(16).rjust(16, '0'),
63
+ flags: flags.to_s(16).rjust(2, '0')
64
+ )
65
+ end
66
+ end
67
+
68
+ DEFAULT_INJECTORS = {
69
+ OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
70
+ OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
71
+ OpenTracing::FORMAT_RACK => JaegerRackCodec
72
+ }.freeze
73
+
74
+ def self.prepare(injectors)
75
+ DEFAULT_INJECTORS.reduce(injectors) do |acc, (format, default)|
76
+ provided_injectors = Array(injectors[format])
77
+ provided_injectors += [default] if provided_injectors.empty?
78
+
79
+ acc.merge(format => provided_injectors)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ # RateLimiter is based on leaky bucket algorithm, formulated in terms of a
5
+ # credits balance that is replenished every time check_credit() method is
6
+ # called (tick) by the amount proportional to the time elapsed since the
7
+ # last tick, up to the max_balance. A call to check_credit() takes a cost
8
+ # of an item we want to pay with the balance. If the balance exceeds the
9
+ # cost of the item, the item is "purchased" and the balance reduced,
10
+ # indicated by returned value of true. Otherwise the balance is unchanged
11
+ # and return false.
12
+ #
13
+ # This can be used to limit a rate of messages emitted by a service by
14
+ # instantiating the Rate Limiter with the max number of messages a service
15
+ # is allowed to emit per second, and calling check_credit(1.0) for each
16
+ # message to determine if the message is within the rate limit.
17
+ #
18
+ # It can also be used to limit the rate of traffic in bytes, by setting
19
+ # credits_per_second to desired throughput as bytes/second, and calling
20
+ # check_credit() with the actual message size.
21
+ class RateLimiter
22
+ def initialize(credits_per_second:, max_balance:)
23
+ @credits_per_second = credits_per_second
24
+ @max_balance = max_balance
25
+ @balance = max_balance
26
+ @last_tick = Time.now
27
+ end
28
+
29
+ def check_credit(item_cost)
30
+ update_balance
31
+
32
+ return false if @balance < item_cost
33
+
34
+ @balance -= item_cost
35
+ true
36
+ end
37
+
38
+ def update(credits_per_second:, max_balance:)
39
+ update_balance
40
+
41
+ @credits_per_second = credits_per_second
42
+
43
+ # The new balance should be proportional to the old balance
44
+ @balance = max_balance * @balance / @max_balance
45
+ @max_balance = max_balance
46
+ end
47
+
48
+ private
49
+
50
+ def update_balance
51
+ current_time = Time.now
52
+ elapsed_time = current_time - @last_tick
53
+ @last_tick = current_time
54
+
55
+ @balance += elapsed_time * @credits_per_second
56
+ return if @balance <= @max_balance
57
+
58
+ @balance = @max_balance
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ # Executes a given block periodically. The block will be executed only once
5
+ # when interval is set to 0.
6
+ class RecurringExecutor
7
+ def initialize(interval:)
8
+ @interval = interval
9
+ end
10
+
11
+ def start(&block)
12
+ raise 'Already running' if @thread
13
+
14
+ @thread = Thread.new do
15
+ if @interval <= 0
16
+ yield
17
+ else
18
+ loop do
19
+ yield
20
+ sleep @interval
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def running?
27
+ @thread && @thread.alive?
28
+ end
29
+
30
+ def stop
31
+ @thread.kill
32
+ @thread = nil
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'reporters/composite_reporter'
4
+ require_relative 'reporters/in_memory_reporter'
5
+ require_relative 'reporters/logging_reporter'
6
+ require_relative 'reporters/null_reporter'
7
+ require_relative 'reporters/remote_reporter'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Reporters
5
+ class CompositeReporter
6
+ def initialize(reporters:)
7
+ @reporters = reporters
8
+ end
9
+
10
+ def report(span)
11
+ @reporters.each do |reporter|
12
+ reporter.report(span)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Reporters
5
+ class InMemoryReporter
6
+ def initialize
7
+ @spans = []
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def report(span)
12
+ @mutex.synchronize do
13
+ @spans << span
14
+ end
15
+ end
16
+
17
+ def spans
18
+ @mutex.synchronize do
19
+ @spans
20
+ end
21
+ end
22
+
23
+ def clear
24
+ @mutex.synchronize do
25
+ @spans.clear
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Reporters
5
+ class LoggingReporter
6
+ def initialize(logger: Logger.new($stdout))
7
+ @logger = logger
8
+ end
9
+
10
+ def report(span)
11
+ span_info = {
12
+ operation_name: span.operation_name,
13
+ start_time: span.start_time.iso8601,
14
+ end_time: span.end_time.iso8601,
15
+ trace_id: span.context.to_trace_id,
16
+ span_id: span.context.to_span_id
17
+ }
18
+ @logger.info "Span reported: #{span_info}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Reporters
5
+ class NullReporter
6
+ def report(_span)
7
+ # no-op
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,28 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thread'
4
-
5
- require_relative './async_reporter/buffer'
3
+ require_relative './remote_reporter/buffer'
6
4
 
7
5
  module Jaeger
8
- module Client
9
- class AsyncReporter
10
- def self.create(sender:, flush_interval:)
11
- reporter = new(sender)
12
-
13
- # start flush thread
14
- Thread.new do
15
- loop do
16
- reporter.flush
17
- sleep(flush_interval)
18
- end
19
- end
20
-
21
- reporter
22
- end
23
-
24
- def initialize(sender)
6
+ module Reporters
7
+ class RemoteReporter
8
+ def initialize(sender:, flush_interval:)
25
9
  @sender = sender
10
+ @flush_interval = flush_interval
26
11
  @buffer = Buffer.new
27
12
  end
28
13
 
@@ -34,8 +19,24 @@ module Jaeger
34
19
 
35
20
  def report(span)
36
21
  return if !span.context.sampled? && !span.context.debug?
22
+
23
+ init_reporter_thread
37
24
  @buffer << span
38
25
  end
26
+
27
+ private
28
+
29
+ def init_reporter_thread
30
+ return if @initializer_pid == Process.pid
31
+
32
+ @initializer_pid = Process.pid
33
+ Thread.new do
34
+ loop do
35
+ flush
36
+ sleep(@flush_interval)
37
+ end
38
+ end
39
+ end
39
40
  end
40
41
  end
41
42
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jaeger
4
- module Client
5
- class AsyncReporter
4
+ module Reporters
5
+ class RemoteReporter
6
6
  class Buffer
7
7
  def initialize
8
8
  @buffer = []
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'samplers/const'
4
+ require_relative 'samplers/guaranteed_throughput_probabilistic'
5
+ require_relative 'samplers/per_operation'
6
+ require_relative 'samplers/probabilistic'
7
+ require_relative 'samplers/rate_limiting'
8
+ require_relative 'samplers/remote_controlled'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Samplers
5
+ # Const sampler
6
+ #
7
+ # A sampler that always makes the same decision for new traces depending
8
+ # on the initialization value. Use `Jaeger::Samplers::Const.new(true)`
9
+ # to mark all new traces as sampled.
10
+ class Const
11
+ def initialize(decision)
12
+ @decision = decision
13
+ @tags = {
14
+ 'sampler.type' => 'const',
15
+ 'sampler.param' => @decision ? 1 : 0
16
+ }
17
+ end
18
+
19
+ def sample(*)
20
+ [@decision, @tags]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Samplers
5
+ # A sampler that leverages both Probabilistic sampler and RateLimiting
6
+ # sampler. The RateLimiting is used as a guaranteed lower bound sampler
7
+ # such that every operation is sampled at least once in a time interval
8
+ # defined by the lower_bound. ie a lower_bound of 1.0 / (60 * 10) will
9
+ # sample an operation at least once every 10 minutes.
10
+ #
11
+ # The Probabilistic sampler is given higher priority when tags are
12
+ # emitted, ie. if is_sampled() for both samplers return true, the tags
13
+ # for Probabilistic sampler will be used.
14
+ class GuaranteedThroughputProbabilistic
15
+ attr_reader :tags, :probabilistic_sampler, :lower_bound_sampler
16
+
17
+ def initialize(lower_bound:, rate:, lower_bound_sampler: nil)
18
+ @probabilistic_sampler = Probabilistic.new(rate: rate)
19
+ @lower_bound_sampler = lower_bound_sampler || RateLimiting.new(max_traces_per_second: lower_bound)
20
+ @lower_bound_tags = {
21
+ 'sampler.type' => 'lowerbound',
22
+ 'sampler.param' => rate
23
+ }
24
+ end
25
+
26
+ def update(lower_bound:, rate:)
27
+ is_updated = @probabilistic_sampler.update(rate: rate)
28
+ is_updated = @lower_bound_sampler.update(max_traces_per_second: lower_bound) || is_updated
29
+ @lower_bound_tags['sampler.param'] = rate
30
+ is_updated
31
+ end
32
+
33
+ def sample(*args)
34
+ is_sampled, probabilistic_tags = @probabilistic_sampler.sample(*args)
35
+ if is_sampled
36
+ # We still call lower_bound_sampler to update the rate limiter budget
37
+ @lower_bound_sampler.sample(*args)
38
+
39
+ return [is_sampled, probabilistic_tags]
40
+ end
41
+
42
+ is_sampled, _tags = @lower_bound_sampler.sample(*args)
43
+ [is_sampled, @lower_bound_tags]
44
+ end
45
+ end
46
+ end
47
+ end