jaeger-client-with-ruby-32-support 2.0.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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +33 -0
- data/.gitignore +12 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +61 -0
- data/.rubocop_todo.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/Makefile +1 -0
- data/README.md +210 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/crossdock/Dockerfile +31 -0
- data/crossdock/Gemfile +6 -0
- data/crossdock/Gemfile.lock +37 -0
- data/crossdock/docker-compose.yml +68 -0
- data/crossdock/jaeger-docker-compose.yml +53 -0
- data/crossdock/rules.mk +35 -0
- data/crossdock/server +175 -0
- data/jaeger-client.gemspec +37 -0
- data/lib/jaeger/client/version.rb +7 -0
- data/lib/jaeger/client.rb +77 -0
- data/lib/jaeger/encoders/thrift_encoder.rb +173 -0
- data/lib/jaeger/extractors.rb +173 -0
- data/lib/jaeger/http_sender.rb +28 -0
- data/lib/jaeger/injectors.rb +83 -0
- data/lib/jaeger/rate_limiter.rb +61 -0
- data/lib/jaeger/recurring_executor.rb +35 -0
- data/lib/jaeger/reporters/composite_reporter.rb +17 -0
- data/lib/jaeger/reporters/in_memory_reporter.rb +30 -0
- data/lib/jaeger/reporters/logging_reporter.rb +22 -0
- data/lib/jaeger/reporters/null_reporter.rb +11 -0
- data/lib/jaeger/reporters/remote_reporter/buffer.rb +29 -0
- data/lib/jaeger/reporters/remote_reporter.rb +42 -0
- data/lib/jaeger/reporters.rb +7 -0
- data/lib/jaeger/samplers/const.rb +24 -0
- data/lib/jaeger/samplers/guaranteed_throughput_probabilistic.rb +47 -0
- data/lib/jaeger/samplers/per_operation.rb +77 -0
- data/lib/jaeger/samplers/probabilistic.rb +40 -0
- data/lib/jaeger/samplers/rate_limiting.rb +51 -0
- data/lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb +34 -0
- data/lib/jaeger/samplers/remote_controlled.rb +119 -0
- data/lib/jaeger/samplers.rb +8 -0
- data/lib/jaeger/scope.rb +39 -0
- data/lib/jaeger/scope_manager/scope_identifier.rb +13 -0
- data/lib/jaeger/scope_manager/scope_stack.rb +33 -0
- data/lib/jaeger/scope_manager.rb +48 -0
- data/lib/jaeger/span/thrift_log_builder.rb +18 -0
- data/lib/jaeger/span.rb +97 -0
- data/lib/jaeger/span_context.rb +57 -0
- data/lib/jaeger/thrift_tag_builder.rb +42 -0
- data/lib/jaeger/trace_id.rb +48 -0
- data/lib/jaeger/tracer.rb +214 -0
- data/lib/jaeger/udp_sender/transport.rb +41 -0
- data/lib/jaeger/udp_sender.rb +26 -0
- data/script/create_follows_from_trace +51 -0
- data/script/create_trace +52 -0
- data/thrift/agent.thrift +32 -0
- data/thrift/gen-rb/jaeger/thrift/agent/agent.rb +118 -0
- data/thrift/gen-rb/jaeger/thrift/agent/agent_constants.rb +15 -0
- data/thrift/gen-rb/jaeger/thrift/agent/agent_types.rb +17 -0
- data/thrift/gen-rb/jaeger/thrift/agent.rb +116 -0
- data/thrift/gen-rb/jaeger/thrift/agent_constants.rb +13 -0
- data/thrift/gen-rb/jaeger/thrift/agent_types.rb +15 -0
- data/thrift/gen-rb/jaeger/thrift/collector.rb +82 -0
- data/thrift/gen-rb/jaeger/thrift/jaeger_constants.rb +13 -0
- data/thrift/gen-rb/jaeger/thrift/jaeger_types.rb +211 -0
- data/thrift/gen-rb/jaeger/thrift/zipkin/zipkin_collector.rb +84 -0
- data/thrift/gen-rb/jaeger/thrift/zipkin/zipkincore_constants.rb +41 -0
- data/thrift/gen-rb/jaeger/thrift/zipkin/zipkincore_types.rb +220 -0
- data/thrift/jaeger.thrift +88 -0
- data/thrift/zipkincore.thrift +300 -0
- metadata +260 -0
| @@ -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&.alive?
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def stop
         | 
| 31 | 
            +
                  @thread.kill
         | 
| 32 | 
            +
                  @thread = nil
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -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,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Jaeger
         | 
| 4 | 
            +
              module Reporters
         | 
| 5 | 
            +
                class RemoteReporter
         | 
| 6 | 
            +
                  class Buffer
         | 
| 7 | 
            +
                    def initialize
         | 
| 8 | 
            +
                      @buffer = []
         | 
| 9 | 
            +
                      @mutex = Mutex.new
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    def <<(element)
         | 
| 13 | 
            +
                      @mutex.synchronize do
         | 
| 14 | 
            +
                        @buffer << element
         | 
| 15 | 
            +
                        true
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def retrieve
         | 
| 20 | 
            +
                      @mutex.synchronize do
         | 
| 21 | 
            +
                        elements = @buffer.dup
         | 
| 22 | 
            +
                        @buffer.clear
         | 
| 23 | 
            +
                        elements
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative './remote_reporter/buffer'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Jaeger
         | 
| 6 | 
            +
              module Reporters
         | 
| 7 | 
            +
                class RemoteReporter
         | 
| 8 | 
            +
                  def initialize(sender:, flush_interval:)
         | 
| 9 | 
            +
                    @sender = sender
         | 
| 10 | 
            +
                    @flush_interval = flush_interval
         | 
| 11 | 
            +
                    @buffer = Buffer.new
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def flush
         | 
| 15 | 
            +
                    spans = @buffer.retrieve
         | 
| 16 | 
            +
                    @sender.send_spans(spans) if spans.any?
         | 
| 17 | 
            +
                    spans
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def report(span)
         | 
| 21 | 
            +
                    return if !span.context.sampled? && !span.context.debug?
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    init_reporter_thread
         | 
| 24 | 
            +
                    @buffer << span
         | 
| 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
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            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,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(...)
         | 
| 34 | 
            +
                    is_sampled, probabilistic_tags = @probabilistic_sampler.sample(...)
         | 
| 35 | 
            +
                    if is_sampled
         | 
| 36 | 
            +
                      # We still call lower_bound_sampler to update the rate limiter budget
         | 
| 37 | 
            +
                      @lower_bound_sampler.sample(...)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      return [is_sampled, probabilistic_tags]
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    is_sampled, _tags = @lower_bound_sampler.sample(...)
         | 
| 43 | 
            +
                    [is_sampled, @lower_bound_tags]
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 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 | 
            +
                    update_operation_strategies(strategies) || is_updated
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def sample(opts)
         | 
| 39 | 
            +
                    operation_name = opts.fetch(:operation_name)
         | 
| 40 | 
            +
                    sampler = @samplers[operation_name]
         | 
| 41 | 
            +
                    return sampler.sample(opts) if sampler
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    return @default_sampler.sample(opts) if @samplers.length >= @max_operations
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    sampler = GuaranteedThroughputProbabilistic.new(
         | 
| 46 | 
            +
                      lower_bound: @lower_bound,
         | 
| 47 | 
            +
                      rate: @default_sampling_probability
         | 
| 48 | 
            +
                    )
         | 
| 49 | 
            +
                    @samplers[operation_name] = sampler
         | 
| 50 | 
            +
                    sampler.sample(opts)
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  private
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def update_operation_strategies(strategies)
         | 
| 56 | 
            +
                    is_updated = false
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    (strategies[:per_operation_strategies] || []).each do |strategy|
         | 
| 59 | 
            +
                      operation = strategy.fetch(:operation)
         | 
| 60 | 
            +
                      rate = strategy.fetch(:probabilistic_sampling).fetch(:sampling_rate)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                      if (sampler = @samplers[operation])
         | 
| 63 | 
            +
                        is_updated = sampler.update(lower_bound: @lower_bound, rate: rate) || is_updated
         | 
| 64 | 
            +
                      else
         | 
| 65 | 
            +
                        @samplers[operation] = GuaranteedThroughputProbabilistic.new(
         | 
| 66 | 
            +
                          lower_bound: @lower_bound,
         | 
| 67 | 
            +
                          rate: rate
         | 
| 68 | 
            +
                        )
         | 
| 69 | 
            +
                        is_updated = true
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    is_updated
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 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(opts)
         | 
| 34 | 
            +
                    trace_id = opts.fetch(:trace_id)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    [@boundary >= trace_id, @tags]
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            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,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
         |