jaeger-client 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7de16fd42664a20ad343e3fdc701f057b93df9e9c8ac6e76d4ea6b36895a7733
4
- data.tar.gz: 32421b715a39b2710cae5bf9f7c6ca075872717ac53964de98429f063ebc93ba
3
+ metadata.gz: 8f4b7e773d00ccb0b54390db7b973e674733017ed4c1253bab10931ffdc6358f
4
+ data.tar.gz: b03393cae166cc9a189f3629cfa2a6eb5eb06fe975e2225aa3c2cc1ffb6ebe36
5
5
  SHA512:
6
- metadata.gz: dacf1771b57e8f12074a0426b75b09b6a19610c13d4cb86b687e9930fc24a7e4a20f5c5e2819760e02a88baf01a8438f2175688be57dff2dffd80e5d45303522
7
- data.tar.gz: b8bdaa192c6c2109b7711431ec9eebe3e4352e5e71426c65355abd5aad358cf0f4ceaaecb25dec2cf3a622d0982294ea578858234c71682a841f7bb3bb4090c0
6
+ metadata.gz: 2fa33017d53652e38133d4dab7d134b0d7ff1ee262e07a70b9c9114d80831eaad2f88df16b467ebe7c167c80798e34c39a70b33472d1cf204314d922a43b98d1
7
+ data.tar.gz: 3a7b325ee92c532bf2b1c50c40fd483cf42458d51e9148f63dc98f0c56513e3d0dc3c2a36ec04f07c8953cff91e162b430d1ebb0d9ccc50f51796db9dea3c62d
@@ -48,3 +48,6 @@ Style/FrozenStringLiteralComment:
48
48
 
49
49
  Metrics/LineLength:
50
50
  Max: 120
51
+
52
+ Style/SingleLineMethods:
53
+ Enabled: false
data/README.md CHANGED
@@ -109,8 +109,8 @@ Set `sampler` to
109
109
  Jaeger::Samplers::PerOperation.new(
110
110
  strategies: {
111
111
  per_operation_strategies: [
112
- { operation: 'GET /articles', probabilistic_sampling: 0.5 },
113
- { operation: 'POST /articles', probabilistic_sampling: 1.0 }
112
+ { operation: 'GET /articles', probabilistic_sampling: { sampling_rate: 0.5 } },
113
+ { operation: 'POST /articles', probabilistic_sampling: { sampling_rate: 1.0 } }
114
114
  ],
115
115
  default_sampling_probability: 0.001,
116
116
  default_lower_bound_traces_per_second: 1.0 / (10.0 * 60.0)
@@ -119,6 +119,39 @@ Set `sampler` to
119
119
  )
120
120
  ```
121
121
 
122
+ #### RemoteControlled sampler
123
+
124
+ `RemoteControlled` sampler is a sampler that is controller by jaeger agent. It starts out with `Probabilistic` sampler. It polls the jaeger-agent and changes sampling strategy accordingly. Set `sampler` to `Jaeger::Client::Samplers::RemoteControlled.new(service_name: 'service_name')`.
125
+
126
+ RemoteControlled sampler options:
127
+
128
+ | Param | Required | Description |
129
+ |-------------------|----------|-------------|
130
+ | service_name | x | name of the current service / application, same as given to Tracer |
131
+ | sampler | | initial sampler to use prior to retrieving strategies from Agent |
132
+ | refresh_interval | | interval in seconds before sampling strategy refreshes (0 to not refresh, defaults to 60) |
133
+ | host | | host for jaeger-agent (defaults to 'localhost') |
134
+ | port | | port for jaeger-agent for SamplingManager endpoint (defaults to 5778) |
135
+ | logger | | logger for communication between jaeger-agent (default to $stdout logger) |
136
+
137
+ ### TraceContext compatible header propagation
138
+
139
+ It is possible to use [W3C Trace Context](https://www.w3.org/TR/trace-context/#overview) headers to propagate the tracing information.
140
+
141
+ To set it up you need to change FORMAT_RACK injector and extractor.
142
+
143
+ ```ruby
144
+ OpenTracing.global_tracer = Jaeger::Client.build(
145
+ service_name: 'service_name',
146
+ injectors: {
147
+ OpenTracing::FORMAT_RACK => [Jaeger::Injectors::TraceContextRackCodec]
148
+ },
149
+ extractors: {
150
+ OpenTracing::FORMAT_RACK => [Jaeger::Extractors::TraceContextRackCodec]
151
+ }
152
+ )
153
+ ```
154
+
122
155
  ### Zipkin HTTP B3 compatible header propagation
123
156
 
124
157
  Jaeger Tracer supports Zipkin B3 Propagation HTTP headers, which are used by a lot of Zipkin tracers. This means that you can use Jaeger in conjunction with OpenZipkin tracers.
@@ -141,6 +174,20 @@ It's also possible to set up multiple injectors and extractors. Each injector wi
141
174
 
142
175
  If multiple extractors is used then the span context from the first match will be returned.
143
176
 
177
+ ### Process Tags
178
+
179
+ Jaeger Tracer allows you to define process level tags. By default the tracer provides `jaeger.version`, `ip` and `hostname`. You may want to overwrite `ip` or `hostname` if the tracer cannot auto-detect them.
180
+
181
+ ```ruby
182
+ OpenTracing.global_tracer = Jaeger::Client.build(
183
+ service_name: 'service_name',
184
+ tags: {
185
+ 'hostname' => 'custom-hostname',
186
+ 'custom_tag' => 'custom-tag-value'
187
+ }
188
+ )
189
+ ```
190
+
144
191
  ## Development
145
192
 
146
193
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -20,12 +20,13 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_development_dependency 'bundler', '~> 1.14'
23
+ spec.add_development_dependency 'bundler'
24
24
  spec.add_development_dependency 'rake', '~> 10.0'
25
25
  spec.add_development_dependency 'rspec', '~> 3.0'
26
26
  spec.add_development_dependency 'rubocop', '~> 0.54.0'
27
27
  spec.add_development_dependency 'rubocop-rspec', '~> 1.24.0'
28
28
  spec.add_development_dependency 'timecop', '~> 0.9'
29
+ spec.add_development_dependency 'webmock', '~> 3.4.2'
29
30
 
30
31
  spec.add_dependency 'opentracing', '~> 0.3'
31
32
  spec.add_dependency 'thrift'
@@ -5,6 +5,10 @@ $LOAD_PATH.push(File.dirname(__FILE__) + '/../../thrift/gen-rb')
5
5
  require 'opentracing'
6
6
  require 'jaeger/thrift/agent'
7
7
  require 'logger'
8
+ require 'time'
9
+ require 'net/http'
10
+ require 'cgi'
11
+ require 'json'
8
12
 
9
13
  require_relative 'tracer'
10
14
  require_relative 'span'
@@ -21,6 +25,8 @@ require_relative 'encoders/thrift_encoder'
21
25
  require_relative 'injectors'
22
26
  require_relative 'extractors'
23
27
  require_relative 'rate_limiter'
28
+ require_relative 'thrift_tag_builder'
29
+ require_relative 'recurring_executor'
24
30
 
25
31
  module Jaeger
26
32
  module Client
@@ -46,8 +52,9 @@ module Jaeger
46
52
  sender: nil,
47
53
  reporter: nil,
48
54
  injectors: {},
49
- extractors: {})
50
- encoder = Encoders::ThriftEncoder.new(service_name: service_name)
55
+ extractors: {},
56
+ tags: {})
57
+ encoder = Encoders::ThriftEncoder.new(service_name: service_name, tags: tags)
51
58
 
52
59
  if sender
53
60
  warn '[DEPRECATION] Passing `sender` directly to Jaeger::Client.build is deprecated.' \
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jaeger
4
4
  module Client
5
- VERSION = '0.10.0'.freeze
5
+ VERSION = '1.0.0'.freeze
6
6
  end
7
7
  end
@@ -3,49 +3,46 @@
3
3
  module Jaeger
4
4
  module Encoders
5
5
  class ThriftEncoder
6
- def initialize(service_name:)
6
+ def initialize(service_name:, tags: {})
7
7
  @service_name = service_name
8
- @tags = [
9
- Jaeger::Thrift::Tag.new(
10
- 'key' => 'jaeger.version',
11
- 'vType' => Jaeger::Thrift::TagType::STRING,
12
- 'vStr' => 'Ruby-' + Jaeger::Client::VERSION
13
- ),
14
- Jaeger::Thrift::Tag.new(
15
- 'key' => 'hostname',
16
- 'vType' => Jaeger::Thrift::TagType::STRING,
17
- 'vStr' => Socket.gethostname
18
- )
19
- ]
20
- ipv4 = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
21
- unless ipv4.nil? # rubocop:disable Style/GuardClause
22
- @tags << Jaeger::Thrift::Tag.new(
23
- 'key' => 'ip',
24
- 'vType' => Jaeger::Thrift::TagType::STRING,
25
- 'vStr' => ipv4.ip_address
26
- )
27
- end
8
+ @tags = prepare_tags(tags)
9
+ @process = Jaeger::Thrift::Process.new('serviceName' => @service_name, 'tags' => @tags)
28
10
  end
29
11
 
30
12
  def encode(spans)
31
- Jaeger::Thrift::Batch.new(
32
- 'process' => Jaeger::Thrift::Process.new(
33
- 'serviceName' => @service_name,
34
- 'tags' => @tags
35
- ),
36
- 'spans' => spans.map(&method(:encode_span))
37
- )
13
+ encode_batch(spans.map(&method(:encode_span)))
14
+ end
15
+
16
+ def encode_limited_size(spans, protocol_class, max_message_length)
17
+ batches = []
18
+ current_batch = []
19
+ transport.flush
20
+ spans.each do |span|
21
+ encoded_span = encode_span(span)
22
+ if aggregated_span_size(encoded_span, protocol_class) > max_message_length && !current_batch.empty?
23
+ batches << encode_batch(current_batch)
24
+ current_batch = []
25
+ transport.flush
26
+ end
27
+ current_batch << encoded_span
28
+ end
29
+ batches << encode_batch(current_batch) unless current_batch.empty?
30
+ batches
38
31
  end
39
32
 
40
33
  private
41
34
 
35
+ def encode_batch(encoded_spans)
36
+ Jaeger::Thrift::Batch.new('process' => @process, 'spans' => encoded_spans)
37
+ end
38
+
42
39
  def encode_span(span)
43
40
  context = span.context
44
41
  start_ts, duration = build_timestamps(span)
45
42
 
46
43
  Jaeger::Thrift::Span.new(
47
- 'traceIdLow' => TraceId.uint64_id_to_int64(context.trace_id),
48
- 'traceIdHigh' => 0,
44
+ 'traceIdLow' => TraceId.uint64_id_to_int64(trace_id_to_low(context.trace_id)),
45
+ 'traceIdHigh' => TraceId.uint64_id_to_int64(trace_id_to_high(context.trace_id)),
49
46
  'spanId' => TraceId.uint64_id_to_int64(context.span_id),
50
47
  'parentSpanId' => TraceId.uint64_id_to_int64(context.parent_id),
51
48
  'operationName' => span.operation_name,
@@ -87,6 +84,59 @@ module Jaeger
87
84
  nil
88
85
  end
89
86
  end
87
+
88
+ def prepare_tags(tags)
89
+ with_default_tags = tags.dup
90
+ with_default_tags['jaeger.version'] = 'Ruby-' + Jaeger::Client::VERSION
91
+ with_default_tags['hostname'] ||= Socket.gethostname
92
+
93
+ unless with_default_tags['ip']
94
+ ipv4 = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
95
+ with_default_tags['ip'] = ipv4.ip_address unless ipv4.nil?
96
+ end
97
+
98
+ with_default_tags.map do |key, value|
99
+ ThriftTagBuilder.build(key, value)
100
+ end
101
+ end
102
+
103
+ # Returns the right most 64 bits of trace id
104
+ def trace_id_to_low(trace_id)
105
+ trace_id & TraceId::MAX_64BIT_UNSIGNED_INT
106
+ end
107
+
108
+ # Returns the left most 64 bits of trace id
109
+ def trace_id_to_high(trace_id)
110
+ (trace_id >> 64) & TraceId::MAX_64BIT_UNSIGNED_INT
111
+ end
112
+
113
+ class DummyTransport
114
+ attr_accessor :size
115
+
116
+ def initialize
117
+ @size = 0
118
+ end
119
+
120
+ def write(buf)
121
+ @size += buf.size
122
+ end
123
+
124
+ def flush
125
+ @size = 0
126
+ end
127
+
128
+ def close; end
129
+ end
130
+
131
+ def aggregated_span_size(span, protocol_class)
132
+ @protocol ||= protocol_class.new(transport)
133
+ span.write(@protocol)
134
+ transport.size
135
+ end
136
+
137
+ def transport
138
+ @transport ||= DummyTransport.new
139
+ end
90
140
  end
91
141
  end
92
142
  end
@@ -6,10 +6,12 @@ module Jaeger
6
6
  def self.parse(trace)
7
7
  return nil if !trace || trace == ''
8
8
 
9
- trace_arguments = trace.split(':').map(&TraceId.method(:base16_hex_id_to_uint64))
9
+ trace_arguments = trace.split(':')
10
10
  return nil if trace_arguments.size != 4
11
11
 
12
- trace_id, span_id, parent_id, flags = trace_arguments
12
+ trace_id = TraceId.base16_hex_id_to_uint128(trace_arguments[0])
13
+ span_id, parent_id, flags = trace_arguments[1..3].map(&TraceId.method(:base16_hex_id_to_uint64))
14
+
13
15
  return nil if trace_id.zero? || span_id.zero?
14
16
 
15
17
  SpanContext.new(
@@ -63,11 +65,39 @@ module Jaeger
63
65
  end
64
66
 
65
67
  class B3RackCodec
68
+ class Keys
69
+ TRACE_ID = 'HTTP_X_B3_TRACEID'.freeze
70
+ SPAN_ID = 'HTTP_X_B3_SPANID'.freeze
71
+ PARENT_SPAN_ID = 'HTTP_X_B3_PARENTSPANID'.freeze
72
+ FLAGS = 'HTTP_X_B3_FLAGS'.freeze
73
+ SAMPLED = 'HTTP_X_B3_SAMPLED'.freeze
74
+ end.freeze
75
+
66
76
  def self.extract(carrier)
67
- trace_id = TraceId.base16_hex_id_to_uint64(carrier['HTTP_X_B3_TRACEID'])
68
- span_id = TraceId.base16_hex_id_to_uint64(carrier['HTTP_X_B3_SPANID'])
69
- parent_id = TraceId.base16_hex_id_to_uint64(carrier['HTTP_X_B3_PARENTSPANID'])
70
- flags = parse_flags(carrier['HTTP_X_B3_FLAGS'], carrier['HTTP_X_B3_SAMPLED'])
77
+ B3CodecCommon.extract(carrier, Keys)
78
+ end
79
+ end
80
+
81
+ class B3TextMapCodec
82
+ class Keys
83
+ TRACE_ID = 'x-b3-traceid'.freeze
84
+ SPAN_ID = 'x-b3-spanid'.freeze
85
+ PARENT_SPAN_ID = 'x-b3-parentspanid'.freeze
86
+ FLAGS = 'x-b3-flags'.freeze
87
+ SAMPLED = 'x-b3-sampled'.freeze
88
+ end.freeze
89
+
90
+ def self.extract(carrier)
91
+ B3CodecCommon.extract(carrier, Keys)
92
+ end
93
+ end
94
+
95
+ class B3CodecCommon
96
+ def self.extract(carrier, keys)
97
+ trace_id = TraceId.base16_hex_id_to_uint64(carrier[keys::TRACE_ID])
98
+ span_id = TraceId.base16_hex_id_to_uint64(carrier[keys::SPAN_ID])
99
+ parent_id = TraceId.base16_hex_id_to_uint64(carrier[keys::PARENT_SPAN_ID])
100
+ flags = parse_flags(carrier[keys::FLAGS], carrier[keys::SAMPLED])
71
101
 
72
102
  return nil if span_id.nil? || trace_id.nil?
73
103
  return nil if span_id.zero? || trace_id.zero?
@@ -91,6 +121,34 @@ module Jaeger
91
121
  private_class_method :parse_flags
92
122
  end
93
123
 
124
+ class TraceContextRackCodec
125
+ # Internal regex used to identify the TraceContext version
126
+ VERSION_PATTERN = /^([0-9a-fA-F]{2})-(.+)$/
127
+
128
+ # Internal regex used to parse fields in version 0
129
+ HEADER_V0_PATTERN = /^([0-9a-fA-F]{32})-([0-9a-fA-F]{16})(-([0-9a-fA-F]{2}))?$/
130
+
131
+ def self.extract(carrier)
132
+ header_value = carrier['HTTP_TRACEPARENT']
133
+
134
+ version_match = VERSION_PATTERN.match(header_value)
135
+ return nil unless version_match
136
+
137
+ # We currently only support version 0
138
+ return nil if version_match[1].to_i(16) != 0
139
+
140
+ match = HEADER_V0_PATTERN.match(version_match[2])
141
+ return nil unless match
142
+
143
+ trace_id = TraceId.base16_hex_id_to_uint128(match[1])
144
+ span_id = TraceId.base16_hex_id_to_uint64(match[2])
145
+ flags = TraceId.base16_hex_id_to_uint64(match[4])
146
+ return nil if trace_id.zero? || span_id.zero?
147
+
148
+ SpanContext.new(trace_id: trace_id, span_id: span_id, flags: flags)
149
+ end
150
+ end
151
+
94
152
  DEFAULT_EXTRACTORS = {
95
153
  OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
96
154
  OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
@@ -51,18 +51,32 @@ module Jaeger
51
51
  end
52
52
  end
53
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
+
54
68
  DEFAULT_INJECTORS = {
55
69
  OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
56
70
  OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
57
71
  OpenTracing::FORMAT_RACK => JaegerRackCodec
58
72
  }.freeze
59
73
 
60
- def self.prepare(extractors)
61
- DEFAULT_INJECTORS.reduce(extractors) do |acc, (format, default)|
62
- provided_extractors = Array(extractors[format])
63
- provided_extractors += [default] if provided_extractors.empty?
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?
64
78
 
65
- acc.merge(format => provided_extractors)
79
+ acc.merge(format => provided_injectors)
66
80
  end
67
81
  end
68
82
  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
@@ -5,3 +5,4 @@ require_relative 'samplers/guaranteed_throughput_probabilistic'
5
5
  require_relative 'samplers/per_operation'
6
6
  require_relative 'samplers/probabilistic'
7
7
  require_relative 'samplers/rate_limiting'
8
+ require_relative 'samplers/remote_controlled'
@@ -16,7 +16,7 @@ module Jaeger
16
16
  }
17
17
  end
18
18
 
19
- def sample?(*)
19
+ def sample(*)
20
20
  [@decision, @tags]
21
21
  end
22
22
  end
@@ -12,27 +12,34 @@ module Jaeger
12
12
  # emitted, ie. if is_sampled() for both samplers return true, the tags
13
13
  # for Probabilistic sampler will be used.
14
14
  class GuaranteedThroughputProbabilistic
15
- attr_reader :tags
15
+ attr_reader :tags, :probabilistic_sampler, :lower_bound_sampler
16
16
 
17
17
  def initialize(lower_bound:, rate:, lower_bound_sampler: nil)
18
18
  @probabilistic_sampler = Probabilistic.new(rate: rate)
19
19
  @lower_bound_sampler = lower_bound_sampler || RateLimiting.new(max_traces_per_second: lower_bound)
20
20
  @lower_bound_tags = {
21
21
  'sampler.type' => 'lowerbound',
22
- 'sampler.param' => lower_bound
22
+ 'sampler.param' => rate
23
23
  }
24
24
  end
25
25
 
26
- def sample?(*args)
27
- is_sampled, probabilistic_tags = @probabilistic_sampler.sample?(*args)
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)
28
35
  if is_sampled
29
36
  # We still call lower_bound_sampler to update the rate limiter budget
30
- @lower_bound_sampler.sample?(*args)
37
+ @lower_bound_sampler.sample(*args)
31
38
 
32
39
  return [is_sampled, probabilistic_tags]
33
40
  end
34
41
 
35
- is_sampled, _tags = @lower_bound_sampler.sample?(*args)
42
+ is_sampled, _tags = @lower_bound_sampler.sample(*args)
36
43
  [is_sampled, @lower_bound_tags]
37
44
  end
38
45
  end
@@ -10,37 +10,69 @@ module Jaeger
10
10
  DEFAULT_SAMPLING_PROBABILITY = 0.001
11
11
  DEFAULT_LOWER_BOUND = 1.0 / (10.0 * 60.0) # sample once every 10 minutes'
12
12
 
13
+ attr_reader :default_sampling_probability, :lower_bound, :samplers
14
+
13
15
  def initialize(strategies:, max_operations:)
14
16
  @max_operations = max_operations
17
+ @samplers = {}
18
+ update(strategies: strategies)
19
+ end
20
+
21
+ def update(strategies:)
22
+ is_updated = false
23
+
15
24
  @default_sampling_probability =
16
25
  strategies[:default_sampling_probability] || DEFAULT_SAMPLING_PROBABILITY
17
- @lower_bound = strategies[:default_lower_bound_traces_per_second] || DEFAULT_LOWER_BOUND
26
+ @lower_bound =
27
+ strategies[:default_lower_bound_traces_per_second] || DEFAULT_LOWER_BOUND
18
28
 
19
- @default_sampler = Probabilistic.new(rate: @default_sampling_probability)
20
- @samplers = (strategies[:per_operation_strategies] || []).reduce({}) do |acc, strategy|
21
- operation = strategy.fetch(:operation)
22
- rate = strategy.fetch(:probabilistic_sampling)
23
- sampler = GuaranteedThroughputProbabilistic.new(
24
- lower_bound: @lower_bound,
25
- rate: rate
26
- )
27
- acc.merge(operation => sampler)
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)
28
33
  end
34
+
35
+ is_updated = update_operation_strategies(strategies) || is_updated
36
+
37
+ is_updated
29
38
  end
30
39
 
31
- def sample?(opts)
40
+ def sample(opts)
32
41
  operation_name = opts.fetch(:operation_name)
33
42
  sampler = @samplers[operation_name]
34
- return sampler.sample?(opts) if sampler
43
+ return sampler.sample(opts) if sampler
35
44
 
36
- return @default_sampler.sample?(opts) if @samplers.length >= @max_operations
45
+ return @default_sampler.sample(opts) if @samplers.length >= @max_operations
37
46
 
38
47
  sampler = GuaranteedThroughputProbabilistic.new(
39
48
  lower_bound: @lower_bound,
40
49
  rate: @default_sampling_probability
41
50
  )
42
51
  @samplers[operation_name] = sampler
43
- sampler.sample?(opts)
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
44
76
  end
45
77
  end
46
78
  end
@@ -6,19 +6,31 @@ module Jaeger
6
6
  #
7
7
  # Sample a portion of traces using trace_id as the random decision
8
8
  class Probabilistic
9
+ attr_reader :rate
10
+
9
11
  def initialize(rate: 0.001)
12
+ update(rate: rate)
13
+ end
14
+
15
+ def update(rate:)
10
16
  if rate < 0.0 || rate > 1.0
11
17
  raise "Sampling rate must be between 0.0 and 1.0, got #{rate.inspect}"
12
18
  end
13
19
 
20
+ new_boundary = TraceId::TRACE_ID_UPPER_BOUND * rate
21
+ return false if @boundary == new_boundary
22
+
23
+ @rate = rate
14
24
  @boundary = TraceId::TRACE_ID_UPPER_BOUND * rate
15
25
  @tags = {
16
26
  'sampler.type' => 'probabilistic',
17
27
  'sampler.param' => rate
18
28
  }
29
+
30
+ true
19
31
  end
20
32
 
21
- def sample?(trace_id:, **)
33
+ def sample(trace_id:, **)
22
34
  [@boundary >= trace_id, @tags]
23
35
  end
24
36
  end
@@ -8,24 +8,42 @@ module Jaeger
8
8
  # well, but if requests are bursty, especially sub-second, then a number
9
9
  # of sequential requests can be sampled each second.
10
10
  class RateLimiting
11
- attr_reader :tags
11
+ attr_reader :tags, :max_traces_per_second
12
12
 
13
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:)
14
18
  if max_traces_per_second < 0.0
15
19
  raise "max_traces_per_second must not be negative, got #{max_traces_per_second}"
16
20
  end
17
21
 
18
- @rate_limiter = RateLimiter.new(
19
- credits_per_second: max_traces_per_second,
20
- max_balance: [max_traces_per_second, 1.0].max
21
- )
22
+ return false if max_traces_per_second == @max_traces_per_second
23
+
22
24
  @tags = {
23
25
  'sampler.type' => 'ratelimiting',
24
26
  'sampler.param' => max_traces_per_second
25
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
26
44
  end
27
45
 
28
- def sample?(*)
46
+ def sample(*)
29
47
  [@rate_limiter.check_credit(1.0), @tags]
30
48
  end
31
49
  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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'span/thrift_tag_builder'
4
3
  require_relative 'span/thrift_log_builder'
5
4
 
6
5
  module Jaeger
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ class ThriftTagBuilder
5
+ FIELDS = Jaeger::Thrift::Tag::FIELDS
6
+ KEY = FIELDS[Jaeger::Thrift::Tag::KEY].fetch(:name)
7
+ VTYPE = FIELDS[Jaeger::Thrift::Tag::VTYPE].fetch(:name)
8
+ VLONG = FIELDS[Jaeger::Thrift::Tag::VLONG].fetch(:name)
9
+ VDOUBLE = FIELDS[Jaeger::Thrift::Tag::VDOUBLE].fetch(:name)
10
+ VBOOL = FIELDS[Jaeger::Thrift::Tag::VBOOL].fetch(:name)
11
+ VSTR = FIELDS[Jaeger::Thrift::Tag::VSTR].fetch(:name)
12
+
13
+ def self.build(key, value)
14
+ if value.is_a?(Integer)
15
+ Jaeger::Thrift::Tag.new(
16
+ KEY => key.to_s,
17
+ VTYPE => Jaeger::Thrift::TagType::LONG,
18
+ VLONG => value
19
+ )
20
+ elsif value.is_a?(Float)
21
+ Jaeger::Thrift::Tag.new(
22
+ KEY => key.to_s,
23
+ VTYPE => Jaeger::Thrift::TagType::DOUBLE,
24
+ VDOUBLE => value
25
+ )
26
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
27
+ Jaeger::Thrift::Tag.new(
28
+ KEY => key.to_s,
29
+ VTYPE => Jaeger::Thrift::TagType::BOOL,
30
+ VBOOL => value
31
+ )
32
+ else
33
+ Jaeger::Thrift::Tag.new(
34
+ KEY => key.to_s,
35
+ VTYPE => Jaeger::Thrift::TagType::STRING,
36
+ VSTR => value.to_s
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -4,6 +4,7 @@ module Jaeger
4
4
  module TraceId
5
5
  MAX_64BIT_SIGNED_INT = (1 << 63) - 1
6
6
  MAX_64BIT_UNSIGNED_INT = (1 << 64) - 1
7
+ MAX_128BIT_UNSIGNED_INT = (1 << 128) - 1
7
8
  TRACE_ID_UPPER_BOUND = MAX_64BIT_UNSIGNED_INT + 1
8
9
 
9
10
  def self.generate
@@ -12,10 +13,18 @@ module Jaeger
12
13
 
13
14
  def self.base16_hex_id_to_uint64(id)
14
15
  return nil unless id
16
+
15
17
  value = id.to_i(16)
16
18
  value > MAX_64BIT_UNSIGNED_INT || value < 0 ? 0 : value
17
19
  end
18
20
 
21
+ def self.base16_hex_id_to_uint128(id)
22
+ return nil unless id
23
+
24
+ value = id.to_i(16)
25
+ value > MAX_128BIT_UNSIGNED_INT || value < 0 ? 0 : value
26
+ end
27
+
19
28
  # Thrift defines ID fields as i64, which is signed, therefore we convert
20
29
  # large IDs (> 2^63) to negative longs
21
30
  def self.uint64_id_to_int64(id)
@@ -41,7 +41,13 @@ module Jaeger
41
41
  # @param ignore_active_scope [Boolean] whether to create an implicit
42
42
  # References#CHILD_OF reference to the ScopeManager#active.
43
43
  #
44
- # @return [Span] The newly-started Span
44
+ # @yield [Span] If passed an optional block, start_span will yield the
45
+ # newly-created span to the block. The span will be finished automatically
46
+ # after the block is executed.
47
+ # @return [Span, Object] If passed an optional block, start_span will return
48
+ # the block's return value, otherwise it returns the newly-started Span
49
+ # instance, which has not been automatically registered via the
50
+ # ScopeManager
45
51
  def start_span(operation_name,
46
52
  child_of: nil,
47
53
  references: nil,
@@ -55,7 +61,7 @@ module Jaeger
55
61
  references: references,
56
62
  ignore_active_scope: ignore_active_scope
57
63
  )
58
- Span.new(
64
+ span = Span.new(
59
65
  context,
60
66
  operation_name,
61
67
  @reporter,
@@ -63,6 +69,16 @@ module Jaeger
63
69
  references: references,
64
70
  tags: tags.merge(sampler_tags)
65
71
  )
72
+
73
+ if block_given?
74
+ begin
75
+ yield(span)
76
+ ensure
77
+ span.finish
78
+ end
79
+ else
80
+ span
81
+ end
66
82
  end
67
83
 
68
84
  # Creates a newly started and activated Scope
@@ -87,10 +103,12 @@ module Jaeger
87
103
  # References#CHILD_OF reference to the ScopeManager#active.
88
104
  # @param finish_on_close [Boolean] whether span should automatically be
89
105
  # finished when Scope#close is called
90
- # @yield [Scope] If an optional block is passed to start_active it will
91
- # yield the newly-started Scope. If `finish_on_close` is true then the
106
+ # @yield [Scope] If an optional block is passed to start_active_span it
107
+ # will yield the newly-started Scope. If `finish_on_close` is true then the
92
108
  # Span will be finished automatically after the block is executed.
93
- # @return [Scope] The newly-started and activated Scope
109
+ # @return [Scope, Object] If passed an optional block, start_active_span
110
+ # returns the block's return value, otherwise it returns the newly-started
111
+ # and activated Scope
94
112
  def start_active_span(operation_name,
95
113
  child_of: nil,
96
114
  references: nil,
@@ -115,9 +133,9 @@ module Jaeger
115
133
  ensure
116
134
  scope.close
117
135
  end
136
+ else
137
+ scope
118
138
  end
119
-
120
- scope
121
139
  end
122
140
 
123
141
  # Inject a SpanContext into the given carrier
@@ -157,7 +175,7 @@ module Jaeger
157
175
  [SpanContext.create_from_parent_context(context), {}]
158
176
  else
159
177
  trace_id = TraceId.generate
160
- is_sampled, tags = @sampler.sample?(
178
+ is_sampled, tags = @sampler.sample(
161
179
  trace_id: trace_id,
162
180
  operation_name: operation_name
163
181
  )
@@ -5,18 +5,20 @@ require 'socket'
5
5
 
6
6
  module Jaeger
7
7
  class UdpSender
8
- def initialize(host:, port:, encoder:, logger:)
8
+ def initialize(host:, port:, encoder:, logger:, max_packet_size: 65_000)
9
9
  @encoder = encoder
10
10
  @logger = logger
11
11
 
12
12
  transport = Transport.new(host, port)
13
- protocol = ::Thrift::CompactProtocol.new(transport)
13
+ @protocol_class = ::Thrift::CompactProtocol
14
+ protocol = @protocol_class.new(transport)
14
15
  @client = Jaeger::Thrift::Agent::Client.new(protocol)
16
+ @max_packet_size = max_packet_size
15
17
  end
16
18
 
17
19
  def send_spans(spans)
18
- batch = @encoder.encode(spans)
19
- @client.emitBatch(batch)
20
+ batches = @encoder.encode_limited_size(spans, @protocol_class, @max_packet_size)
21
+ batches.each { |batch| @client.emitBatch(batch) }
20
22
  rescue StandardError => error
21
23
  @logger.error("Failure while sending a batch of spans: #{error}")
22
24
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jaeger-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SaleMove TechMovers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-25 00:00:00.000000000 Z
11
+ date: 2019-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.14'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.14'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.4.2
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.4.2
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: opentracing
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -156,6 +170,7 @@ files:
156
170
  - lib/jaeger/http_sender.rb
157
171
  - lib/jaeger/injectors.rb
158
172
  - lib/jaeger/rate_limiter.rb
173
+ - lib/jaeger/recurring_executor.rb
159
174
  - lib/jaeger/reporters.rb
160
175
  - lib/jaeger/reporters/composite_reporter.rb
161
176
  - lib/jaeger/reporters/in_memory_reporter.rb
@@ -169,14 +184,16 @@ files:
169
184
  - lib/jaeger/samplers/per_operation.rb
170
185
  - lib/jaeger/samplers/probabilistic.rb
171
186
  - lib/jaeger/samplers/rate_limiting.rb
187
+ - lib/jaeger/samplers/remote_controlled.rb
188
+ - lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb
172
189
  - lib/jaeger/scope.rb
173
190
  - lib/jaeger/scope_manager.rb
174
191
  - lib/jaeger/scope_manager/scope_identifier.rb
175
192
  - lib/jaeger/scope_manager/scope_stack.rb
176
193
  - lib/jaeger/span.rb
177
194
  - lib/jaeger/span/thrift_log_builder.rb
178
- - lib/jaeger/span/thrift_tag_builder.rb
179
195
  - lib/jaeger/span_context.rb
196
+ - lib/jaeger/thrift_tag_builder.rb
180
197
  - lib/jaeger/trace_id.rb
181
198
  - lib/jaeger/tracer.rb
182
199
  - lib/jaeger/udp_sender.rb
@@ -217,8 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
234
  - !ruby/object:Gem::Version
218
235
  version: '0'
219
236
  requirements: []
220
- rubyforge_project:
221
- rubygems_version: 2.7.7
237
+ rubygems_version: 3.0.6
222
238
  signing_key:
223
239
  specification_version: 4
224
240
  summary: OpenTracing Tracer implementation for Jaeger in Ruby
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Jaeger
4
- class Span
5
- class ThriftTagBuilder
6
- FIELDS = Jaeger::Thrift::Tag::FIELDS
7
- KEY = FIELDS[Jaeger::Thrift::Tag::KEY].fetch(:name)
8
- VTYPE = FIELDS[Jaeger::Thrift::Tag::VTYPE].fetch(:name)
9
- VLONG = FIELDS[Jaeger::Thrift::Tag::VLONG].fetch(:name)
10
- VDOUBLE = FIELDS[Jaeger::Thrift::Tag::VDOUBLE].fetch(:name)
11
- VBOOL = FIELDS[Jaeger::Thrift::Tag::VBOOL].fetch(:name)
12
- VSTR = FIELDS[Jaeger::Thrift::Tag::VSTR].fetch(:name)
13
-
14
- def self.build(key, value)
15
- if value.is_a?(Integer)
16
- Jaeger::Thrift::Tag.new(
17
- KEY => key.to_s,
18
- VTYPE => Jaeger::Thrift::TagType::LONG,
19
- VLONG => value
20
- )
21
- elsif value.is_a?(Float)
22
- Jaeger::Thrift::Tag.new(
23
- KEY => key.to_s,
24
- VTYPE => Jaeger::Thrift::TagType::DOUBLE,
25
- VDOUBLE => value
26
- )
27
- elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
28
- Jaeger::Thrift::Tag.new(
29
- KEY => key.to_s,
30
- VTYPE => Jaeger::Thrift::TagType::BOOL,
31
- VBOOL => value
32
- )
33
- else
34
- Jaeger::Thrift::Tag.new(
35
- KEY => key.to_s,
36
- VTYPE => Jaeger::Thrift::TagType::STRING,
37
- VSTR => value.to_s
38
- )
39
- end
40
- end
41
- end
42
- end
43
- end