dox-jaeger-client 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
|