ddtrace 0.30.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +28 -0
  3. data/.circleci/images/primary/Dockerfile-2.7.0 +73 -0
  4. data/Appraisals +111 -2
  5. data/CHANGELOG.md +33 -1
  6. data/Rakefile +69 -1
  7. data/ddtrace.gemspec +1 -0
  8. data/docker-compose.yml +30 -0
  9. data/docs/GettingStarted.md +28 -3
  10. data/lib/ddtrace.rb +6 -0
  11. data/lib/ddtrace/buffer.rb +3 -3
  12. data/lib/ddtrace/configuration/base.rb +2 -1
  13. data/lib/ddtrace/configuration/settings.rb +15 -2
  14. data/lib/ddtrace/context.rb +62 -57
  15. data/lib/ddtrace/context_flush.rb +51 -114
  16. data/lib/ddtrace/context_provider.rb +45 -0
  17. data/lib/ddtrace/contrib/action_cable/configuration/settings.rb +25 -0
  18. data/lib/ddtrace/contrib/action_cable/event.rb +65 -0
  19. data/lib/ddtrace/contrib/action_cable/events.rb +33 -0
  20. data/lib/ddtrace/contrib/action_cable/events/broadcast.rb +49 -0
  21. data/lib/ddtrace/contrib/action_cable/events/perform_action.rb +52 -0
  22. data/lib/ddtrace/contrib/action_cable/events/transmit.rb +50 -0
  23. data/lib/ddtrace/contrib/action_cable/ext.rb +23 -0
  24. data/lib/ddtrace/contrib/action_cable/instrumentation.rb +31 -0
  25. data/lib/ddtrace/contrib/action_cable/integration.rb +36 -0
  26. data/lib/ddtrace/contrib/action_cable/patcher.rb +27 -0
  27. data/lib/ddtrace/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  28. data/lib/ddtrace/contrib/action_view/events/render_partial.rb +5 -3
  29. data/lib/ddtrace/contrib/action_view/events/render_template.rb +5 -3
  30. data/lib/ddtrace/contrib/action_view/instrumentation/partial_renderer.rb +2 -1
  31. data/lib/ddtrace/contrib/action_view/instrumentation/template_renderer.rb +4 -2
  32. data/lib/ddtrace/contrib/action_view/patcher.rb +1 -1
  33. data/lib/ddtrace/contrib/active_record/events/instantiation.rb +1 -1
  34. data/lib/ddtrace/contrib/active_record/events/sql.rb +1 -1
  35. data/lib/ddtrace/contrib/active_support/cache/instrumentation.rb +2 -2
  36. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +2 -2
  37. data/lib/ddtrace/contrib/dalli/patcher.rb +1 -1
  38. data/lib/ddtrace/contrib/dalli/quantize.rb +1 -1
  39. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +1 -1
  40. data/lib/ddtrace/contrib/excon/middleware.rb +3 -3
  41. data/lib/ddtrace/contrib/faraday/connection.rb +18 -0
  42. data/lib/ddtrace/contrib/faraday/integration.rb +1 -1
  43. data/lib/ddtrace/contrib/faraday/patcher.rb +3 -3
  44. data/lib/ddtrace/contrib/grape/endpoint.rb +5 -5
  45. data/lib/ddtrace/contrib/grape/patcher.rb +1 -1
  46. data/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +1 -1
  47. data/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb +2 -2
  48. data/lib/ddtrace/contrib/grpc/patcher.rb +1 -1
  49. data/lib/ddtrace/contrib/http/instrumentation.rb +1 -1
  50. data/lib/ddtrace/contrib/mongodb/subscribers.rb +2 -2
  51. data/lib/ddtrace/contrib/patchable.rb +1 -1
  52. data/lib/ddtrace/contrib/patcher.rb +1 -1
  53. data/lib/ddtrace/contrib/rack/middlewares.rb +2 -2
  54. data/lib/ddtrace/contrib/rack/patcher.rb +2 -2
  55. data/lib/ddtrace/contrib/rack/request_queue.rb +1 -1
  56. data/lib/ddtrace/contrib/rails/configuration/settings.rb +1 -0
  57. data/lib/ddtrace/contrib/rails/framework.rb +12 -0
  58. data/lib/ddtrace/contrib/rake/instrumentation.rb +2 -2
  59. data/lib/ddtrace/contrib/redis/quantize.rb +1 -1
  60. data/lib/ddtrace/contrib/sinatra/tracer.rb +1 -1
  61. data/lib/ddtrace/ext/forced_tracing.rb +1 -1
  62. data/lib/ddtrace/ext/sampling.rb +3 -0
  63. data/lib/ddtrace/logger.rb +42 -0
  64. data/lib/ddtrace/metrics.rb +5 -5
  65. data/lib/ddtrace/monkey.rb +1 -1
  66. data/lib/ddtrace/pin.rb +1 -1
  67. data/lib/ddtrace/pipeline.rb +1 -1
  68. data/lib/ddtrace/propagation/http_propagator.rb +2 -2
  69. data/lib/ddtrace/runtime/cgroup.rb +1 -1
  70. data/lib/ddtrace/runtime/container.rb +1 -1
  71. data/lib/ddtrace/runtime/metrics.rb +1 -1
  72. data/lib/ddtrace/sampler.rb +1 -1
  73. data/lib/ddtrace/sampling/rule.rb +1 -1
  74. data/lib/ddtrace/sampling/rule_sampler.rb +4 -4
  75. data/lib/ddtrace/span.rb +24 -6
  76. data/lib/ddtrace/sync_writer.rb +4 -3
  77. data/lib/ddtrace/tracer.rb +37 -77
  78. data/lib/ddtrace/transport/http/client.rb +2 -2
  79. data/lib/ddtrace/utils.rb +1 -1
  80. data/lib/ddtrace/version.rb +2 -2
  81. data/lib/ddtrace/workers.rb +3 -3
  82. data/lib/ddtrace/writer.rb +3 -2
  83. metadata +44 -6
  84. data/lib/ddtrace/contrib/faraday/rack_builder.rb +0 -18
  85. data/lib/ddtrace/provider.rb +0 -21
@@ -26,6 +26,7 @@ To contribute, check out the [contribution guidelines][contribution docs] and [d
26
26
  - [Quickstart for OpenTracing](#quickstart-for-opentracing)
27
27
  - [Manual instrumentation](#manual-instrumentation)
28
28
  - [Integration instrumentation](#integration-instrumentation)
29
+ - [Action Cable](#action-cable)
29
30
  - [Action View](#action-view)
30
31
  - [Active Model Serializers](#active-model-serializers)
31
32
  - [Action Pack](#action-pack)
@@ -80,7 +81,8 @@ To contribute, check out the [contribution guidelines][contribution docs] and [d
80
81
 
81
82
  | Type | Documentation | Version | Support type | Gem version support |
82
83
  | ----- | -------------------------- | ----- | ------------------------------------ | ------------------- |
83
- | MRI | https://www.ruby-lang.org/ | 2.6 | Full | Latest |
84
+ | MRI | https://www.ruby-lang.org/ | 2.7 | Full | Latest |
85
+ | | | 2.6 | Full | Latest |
84
86
  | | | 2.5 | Full | Latest |
85
87
  | | | 2.4 | Full | Latest |
86
88
  | | | 2.3 | Full | Latest |
@@ -324,6 +326,7 @@ For a list of available integrations, and their configuration options, please re
324
326
 
325
327
  | Name | Key | Versions Supported | How to configure | Gem source |
326
328
  | ------------------------ | -------------------------- | ------------------------ | ----------------------------------- | ------------------------------------------------------------------------------ |
329
+ | Action Cable | `action_cable` | `>= 5.0` | *[Link](#action-cable)* | *[Link](https://github.com/rails/rails/tree/master/actioncable)* |
327
330
  | Action View | `action_view` | `>= 3.2` | *[Link](#action-view)* | *[Link](https://github.com/rails/rails/tree/master/actionview)* |
328
331
  | Active Model Serializers | `active_model_serializers` | `>= 0.9` | *[Link](#active-model-serializers)* | *[Link](https://github.com/rails-api/active_model_serializers)* |
329
332
  | Action Pack | `action_pack` | `>= 3.2` | *[Link](#action-pack)* | *[Link](https://github.com/rails/rails/tree/master/actionpack)* |
@@ -356,6 +359,28 @@ For a list of available integrations, and their configuration options, please re
356
359
  | Sinatra | `sinatra` | `>= 1.4.5` | *[Link](#sinatra)* | *[Link](https://github.com/sinatra/sinatra)* |
357
360
  | Sucker Punch | `sucker_punch` | `>= 2.0` | *[Link](#sucker-punch)* | *[Link](https://github.com/brandonhilkert/sucker_punch)* |
358
361
 
362
+ ### Action Cable
363
+
364
+ The Action Cable integration traces broadcast messages and channel actions.
365
+
366
+ You can enable it through `Datadog.configure`:
367
+
368
+ ```ruby
369
+ require 'ddtrace'
370
+
371
+ Datadog.configure do |c|
372
+ c.use :action_cable, options
373
+ end
374
+ ```
375
+
376
+ Where `options` is an optional `Hash` that accepts the following parameters:
377
+
378
+ | Key | Description | Default |
379
+ | --- | ----------- | ------- |
380
+ | `analytics_enabled` | Enable analytics for spans produced by this integration. `true` for on, `nil` to defer to global setting, `false` for off. | `false` |
381
+ | `service_name` | Service name used for `action_cable` instrumentation | `'action_cable'` |
382
+ | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` |
383
+
359
384
  ### Action View
360
385
 
361
386
  Most of the time, Active Support is set up as part of Rails, but it can be activated separately:
@@ -1105,7 +1130,7 @@ Where `options` is an optional `Hash` that accepts the following parameters:
1105
1130
  | 2.2 - 2.3 | 3.0 - 5.2 |
1106
1131
  | 2.4 | 4.2.8 - 5.2 |
1107
1132
  | 2.5 | 4.2.8 - 6.0 |
1108
- | 2.6 | 5.0 - 6.0 |
1133
+ | 2.6 - 2.7 | 5.0 - 6.0 |
1109
1134
 
1110
1135
  ### Rake
1111
1136
 
@@ -1450,7 +1475,7 @@ Datadog.configure do |c|
1450
1475
  c.tracer log: Logger.new(f) # Overriding the default tracer
1451
1476
  end
1452
1477
 
1453
- Datadog::Tracer.log.info { "this is typically called by tracing code" }
1478
+ Datadog::Logger.log.info { "this is typically called by tracing code" }
1454
1479
  ```
1455
1480
 
1456
1481
  ### Environment and tags
@@ -26,8 +26,14 @@ module Datadog
26
26
  # Load and extend Contrib by default
27
27
  require 'ddtrace/contrib/extensions'
28
28
  extend Contrib::Extensions
29
+
30
+ # Add shutdown hook:
31
+ # Ensures the tracer has an opportunity to flush traces
32
+ # and cleanup before terminating the process.
33
+ at_exit { Datadog.tracer.shutdown! }
29
34
  end
30
35
 
36
+ require 'ddtrace/contrib/action_cable/integration'
31
37
  require 'ddtrace/contrib/action_pack/integration'
32
38
  require 'ddtrace/contrib/action_view/integration'
33
39
  require 'ddtrace/contrib/active_model_serializers/integration'
@@ -83,7 +83,7 @@ module Datadog
83
83
  @buffer_accepted += 1
84
84
  @buffer_accepted_lengths += trace.length
85
85
  rescue StandardError => e
86
- Datadog::Tracer.log.debug("Failed to measure queue accept. Cause: #{e.message} Source: #{e.backtrace.first}")
86
+ Datadog::Logger.log.debug("Failed to measure queue accept. Cause: #{e.message} Source: #{e.backtrace.first}")
87
87
  end
88
88
 
89
89
  def measure_drop(trace)
@@ -91,7 +91,7 @@ module Datadog
91
91
  @buffer_spans -= trace.length
92
92
  @buffer_accepted_lengths -= trace.length
93
93
  rescue StandardError => e
94
- Datadog::Tracer.log.debug("Failed to measure queue drop. Cause: #{e.message} Source: #{e.backtrace.first}")
94
+ Datadog::Logger.log.debug("Failed to measure queue drop. Cause: #{e.message} Source: #{e.backtrace.first}")
95
95
  end
96
96
 
97
97
  def measure_pop(traces)
@@ -113,7 +113,7 @@ module Datadog
113
113
  @buffer_dropped = 0
114
114
  @buffer_spans = 0
115
115
  rescue StandardError => e
116
- Datadog::Tracer.log.debug("Failed to measure queue. Cause: #{e.message} Source: #{e.backtrace.first}")
116
+ Datadog::Logger.log.debug("Failed to measure queue. Cause: #{e.message} Source: #{e.backtrace.first}")
117
117
  end
118
118
  end
119
119
  end
@@ -23,7 +23,8 @@ module Datadog
23
23
  settings_class = new_settings_class(&block)
24
24
 
25
25
  option(name) do |o|
26
- o.default settings_class.new
26
+ o.default -> { settings_class.new }
27
+ o.lazy
27
28
  o.resetter do |value|
28
29
  value.reset! if value.respond_to?(:reset!)
29
30
  value
@@ -3,6 +3,7 @@ require 'ddtrace/configuration/base'
3
3
  require 'ddtrace/ext/analytics'
4
4
  require 'ddtrace/ext/distributed'
5
5
  require 'ddtrace/ext/runtime'
6
+ require 'ddtrace/ext/sampling'
6
7
 
7
8
  require 'ddtrace/tracer'
8
9
  require 'ddtrace/metrics'
@@ -56,6 +57,18 @@ module Datadog
56
57
  end
57
58
  end
58
59
 
60
+ settings :sampling do
61
+ option :default_rate do |o|
62
+ o.default { env_to_float(Ext::Sampling::ENV_SAMPLE_RATE, nil) }
63
+ o.lazy
64
+ end
65
+
66
+ option :rate_limit do |o|
67
+ o.default { env_to_float(Ext::Sampling::ENV_RATE_LIMIT, 100) }
68
+ o.lazy
69
+ end
70
+ end
71
+
59
72
  settings :diagnostics do
60
73
  option :health_metrics do |o|
61
74
  o.default do
@@ -85,10 +98,10 @@ module Datadog
85
98
  tracer.tap do |t|
86
99
  unless options.nil?
87
100
  t.configure(options)
88
- t.class.log = options[:log] if options[:log]
101
+ Datadog::Logger.log = options[:log] if options[:log]
89
102
  t.set_tags(options[:tags]) if options[:tags]
90
103
  t.set_tags(env: options[:env]) if options[:env]
91
- t.class.debug_logging = options.fetch(:debug, false)
104
+ Datadog::Logger.debug_logging = options.fetch(:debug, false)
92
105
  end
93
106
  end
94
107
  end
@@ -1,6 +1,9 @@
1
1
  require 'thread'
2
2
  require 'ddtrace/diagnostics/health'
3
3
 
4
+ require 'ddtrace/context_flush'
5
+ require 'ddtrace/context_provider'
6
+
4
7
  module Datadog
5
8
  # \Context is used to keep track of a hierarchy of spans for the current
6
9
  # execution flow. During each logical execution, the same \Context is
@@ -92,7 +95,7 @@ module Datadog
92
95
  if @max_length > 0 && @trace.length >= @max_length
93
96
  # Detach the span from any context, it's being dropped and ignored.
94
97
  span.context = nil
95
- Datadog::Tracer.log.debug("context full, ignoring span #{span.name}")
98
+ Datadog::Logger.log.debug("context full, ignoring span #{span.name}")
96
99
 
97
100
  # If overflow has already occurred, don't send this metric.
98
101
  # Prevents metrics spam if buffer repeatedly overflows for the same trace.
@@ -121,13 +124,13 @@ module Datadog
121
124
  set_current_span(span.parent)
122
125
  return if span.tracer.nil?
123
126
  if span.parent.nil? && !all_spans_finished?
124
- if Datadog::Tracer.debug_logging
127
+ if Datadog::Logger.debug_logging
125
128
  opened_spans = @trace.length - @finished_spans
126
- Datadog::Tracer.log.debug("root span #{span.name} closed but has #{opened_spans} unfinished spans:")
129
+ Datadog::Logger.log.debug("root span #{span.name} closed but has #{opened_spans} unfinished spans:")
127
130
  end
128
131
 
129
132
  @trace.reject(&:finished?).group_by(&:name).each do |unfinished_span_name, unfinished_spans|
130
- Datadog::Tracer.log.debug("unfinished span: #{unfinished_spans.first}") if Datadog::Tracer.debug_logging
133
+ Datadog::Logger.log.debug("unfinished span: #{unfinished_spans.first}") if Datadog::Logger.debug_logging
131
134
  Diagnostics::Health.metrics.error_unfinished_spans(
132
135
  unfinished_spans.length,
133
136
  tags: ["name:#{unfinished_span_name}"]
@@ -145,6 +148,13 @@ module Datadog
145
148
  end
146
149
  end
147
150
 
151
+ # @@return [Numeric] numbers of finished spans
152
+ def finished_span_count
153
+ @mutex.synchronize do
154
+ @finished_spans
155
+ end
156
+ end
157
+
148
158
  # Returns true if the context is sampled, that is, if it should be kept
149
159
  # and sent to the trace agent.
150
160
  def sampled?
@@ -154,27 +164,66 @@ module Datadog
154
164
  end
155
165
 
156
166
  # Returns both the trace list generated in the current context and
157
- # if the context is sampled or not. It returns nil, nil if the ``Context`` is
158
- # not finished. If a trace is returned, the \Context will be reset so that it
167
+ # if the context is sampled or not.
168
+ #
169
+ # It returns +[nil,@sampled]+ if the \Context is
170
+ # not finished.
171
+ #
172
+ # If a trace is returned, the \Context will be reset so that it
159
173
  # can be re-used immediately.
160
174
  #
161
175
  # This operation is thread-safe.
176
+ #
177
+ # @return [Array<Array<Span>, Boolean>] finished trace and sampled flag
162
178
  def get
163
179
  @mutex.synchronize do
164
180
  trace = @trace
165
181
  sampled = @sampled
166
182
 
167
- attach_sampling_priority if sampled && @sampling_priority
168
- attach_origin if @origin
169
-
170
183
  # still return sampled attribute, even if context is not finished
171
184
  return nil, sampled unless all_spans_finished?
172
185
 
186
+ # Root span is finished at this point, we can configure it
187
+ annotate_for_flush!(@current_root_span)
188
+
173
189
  reset
174
190
  [trace, sampled]
175
191
  end
176
192
  end
177
193
 
194
+ # Delete any span matching the condition. This is thread safe.
195
+ #
196
+ # @return [Array<Span>] deleted spans
197
+ def delete_span_if
198
+ @mutex.synchronize do
199
+ [].tap do |deleted_spans|
200
+ @trace.delete_if do |span|
201
+ finished = span.finished?
202
+
203
+ next unless yield span
204
+
205
+ deleted_spans << span
206
+
207
+ # We need to detach the span from the context, else, some code
208
+ # finishing it afterwards would mess up with the number of
209
+ # finished_spans and possibly cause other side effects.
210
+ span.context = nil
211
+ # Acknowledge there's one span less to finish, if needed.
212
+ # It's very important to keep this balanced.
213
+ @finished_spans -= 1 if finished
214
+
215
+ true
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ # Set tags to root span required for flush
222
+ def annotate_for_flush!(span)
223
+ attach_sampling_priority(span) if @sampled && @sampling_priority
224
+ attach_origin(span) if @origin
225
+ end
226
+
178
227
  # Return a string representation of the context.
179
228
  def to_s
180
229
  @mutex.synchronize do
@@ -215,15 +264,15 @@ module Datadog
215
264
  @finished_spans > 0 && @trace.length == @finished_spans
216
265
  end
217
266
 
218
- def attach_sampling_priority
219
- @current_root_span.set_metric(
267
+ def attach_sampling_priority(span)
268
+ span.set_metric(
220
269
  Ext::DistributedTracing::SAMPLING_PRIORITY_KEY,
221
270
  @sampling_priority
222
271
  )
223
272
  end
224
273
 
225
- def attach_origin
226
- @current_root_span.set_tag(
274
+ def attach_origin(span)
275
+ span.set_tag(
227
276
  Ext::DistributedTracing::ORIGIN_KEY,
228
277
  @origin
229
278
  )
@@ -252,49 +301,5 @@ module Datadog
252
301
  end
253
302
  end
254
303
  end
255
-
256
- # Delete any span matching the condition. This is thread safe.
257
- def delete_span_if
258
- @mutex.synchronize do
259
- @trace.delete_if do |span|
260
- finished = span.finished?
261
- delete_span = yield span
262
- if delete_span
263
- # We need to detach the span from the context, else, some code
264
- # finishing it afterwards would mess up with the number of
265
- # finished_spans and possibly cause other side effects.
266
- span.context = nil
267
- # Acknowledge there's one span less to finish, if needed.
268
- # It's very important to keep this balanced.
269
- @finished_spans -= 1 if finished
270
- end
271
- delete_span
272
- end
273
- end
274
- end
275
- end
276
-
277
- # ThreadLocalContext can be used as a tracer global reference to create
278
- # a different \Context for each thread. In synchronous tracer, this
279
- # is required to prevent multiple threads sharing the same \Context
280
- # in different executions.
281
- class ThreadLocalContext
282
- # ThreadLocalContext can be used as a tracer global reference to create
283
- # a different \Context for each thread. In synchronous tracer, this
284
- # is required to prevent multiple threads sharing the same \Context
285
- # in different executions.
286
- def initialize
287
- self.local = Datadog::Context.new
288
- end
289
-
290
- # Override the thread-local context with a new context.
291
- def local=(ctx)
292
- Thread.current[:datadog_context] = ctx
293
- end
294
-
295
- # Return the thread-local context.
296
- def local
297
- Thread.current[:datadog_context] ||= Datadog::Context.new
298
- end
299
304
  end
300
305
  end
@@ -1,132 +1,69 @@
1
- require 'set'
2
-
3
- require 'ddtrace/context'
4
-
5
1
  module Datadog
6
- # \ContextFlush is used to cap context size and avoid it using too much memory.
7
- # It performs memory flushes when required.
8
- class ContextFlush
9
- # by default, soft and hard limits are the same
10
- DEFAULT_MAX_SPANS_BEFORE_PARTIAL_FLUSH = Datadog::Context::DEFAULT_MAX_LENGTH
11
- # by default, never do a partial flush
12
- DEFAULT_MIN_SPANS_BEFORE_PARTIAL_FLUSH = Datadog::Context::DEFAULT_MAX_LENGTH
13
- # timeout should be lower than the trace agent window
14
- DEFAULT_PARTIAL_FLUSH_TIMEOUT = 10
15
-
16
- private_constant :DEFAULT_MAX_SPANS_BEFORE_PARTIAL_FLUSH
17
- private_constant :DEFAULT_MIN_SPANS_BEFORE_PARTIAL_FLUSH
18
- private_constant :DEFAULT_PARTIAL_FLUSH_TIMEOUT
19
-
20
- def initialize(options = {})
21
- # max_spans_before_partial_flush is the amount of spans collected before
22
- # the context starts to partially flush parts of traces. With a setting of 10k,
23
- # the memory overhead is about 10Mb per thread/context (depends on spans metadata,
24
- # this is just an order of magnitude).
25
- @max_spans_before_partial_flush = options.fetch(:max_spans_before_partial_flush,
26
- DEFAULT_MAX_SPANS_BEFORE_PARTIAL_FLUSH)
27
- # min_spans_before_partial_flush is the minimum number of spans required
28
- # for a partial flush to happen on a timeout. This is to prevent partial flush
29
- # of traces which last a very long time but yet have few spans.
30
- @min_spans_before_partial_flush = options.fetch(:min_spans_before_partial_flush,
31
- DEFAULT_MIN_SPANS_BEFORE_PARTIAL_FLUSH)
32
- # partial_flush_timeout is the limit (in seconds) above which the context
33
- # considers flushing parts of the trace. Partial flushes should not be done too
34
- # late else the agent rejects them with a "too far in the past" error.
35
- @partial_flush_timeout = options.fetch(:partial_flush_timeout,
36
- DEFAULT_PARTIAL_FLUSH_TIMEOUT)
37
- @partial_traces = []
2
+ module ContextFlush
3
+ # Consumes only completed traces (where all spans have finished)
4
+ class Finished
5
+ # Consumes and returns completed traces (where all spans have finished)
6
+ # from the provided +context+, if any.
7
+ #
8
+ # Any traces consumed are removed from +context+ as a side effect.
9
+ #
10
+ # @return [Array<Span>] trace to be flushed, or +nil+ if the trace is not finished
11
+ def consume!(context)
12
+ trace, sampled = context.get
13
+ trace if sampled
14
+ end
38
15
  end
39
16
 
40
- def add_children(m, spans, ids, leaf)
41
- spans << leaf
42
- ids.add(leaf.span_id)
17
+ # Performs partial trace flushing to avoid large traces residing in memory for too long
18
+ class Partial
19
+ # Start flushing partial trace after this many active spans in one trace
20
+ DEFAULT_MIN_SPANS_FOR_PARTIAL_FLUSH = 500
43
21
 
44
- if m[leaf.span_id]
45
- m[leaf.span_id].each do |sub|
46
- add_children(m, spans, ids, sub)
47
- end
22
+ def initialize(options = {})
23
+ @min_spans_for_partial = options.fetch(:min_spans_before_partial_flush, DEFAULT_MIN_SPANS_FOR_PARTIAL_FLUSH)
48
24
  end
49
- end
50
25
 
51
- def partial_traces(context)
52
- # 1st step, taint all parents of an unfinished span as unflushable
53
- unflushable_ids = Set.new
26
+ # Consumes and returns completed or partially completed
27
+ # traces from the provided +context+, if any.
28
+ #
29
+ # Partially completed traces, where not all spans have finished,
30
+ # will only be returned if there are at least
31
+ # +@min_spans_for_partial+ finished spans.
32
+ #
33
+ # Any spans consumed are removed from +context+ as a side effect.
34
+ #
35
+ # @return [Array<Span>] partial or complete trace to be flushed, or +nil+ if no spans are finished
36
+ def consume!(context)
37
+ trace, sampled = context.get
54
38
 
55
- context.send(:each_span) do |span|
56
- next if span.finished? || unflushable_ids.include?(span.span_id)
57
- unflushable_ids.add span.span_id
58
- while span.parent
59
- span = span.parent
60
- unflushable_ids.add span.span_id
61
- end
62
- end
39
+ return nil unless sampled
40
+ return trace if trace && !trace.empty?
63
41
 
64
- # 2nd step, find all spans which are at the border between flushable and unflushable
65
- # Along the road, collect a reverse-tree which allows direct walking from parents to
66
- # children but only for the ones we're interested it.
67
- roots = []
68
- children_map = {}
69
- context.send(:each_span) do |span|
70
- # There's no point in trying to put the real root in those partial roots, if
71
- # it's flushable, the default algorithm would figure way more quickly.
72
- if span.parent && !unflushable_ids.include?(span.span_id)
73
- if unflushable_ids.include?(span.parent.span_id)
74
- # span is flushable but is parent is not
75
- roots << span
76
- else
77
- # span is flushable and its parent is too, build the reverse
78
- # parent to child map for this one, it will be useful
79
- children_map[span.parent.span_id] ||= []
80
- children_map[span.parent.span_id] << span
81
- end
82
- end
42
+ partial_trace(context)
83
43
  end
84
44
 
85
- # 3rd step, find all children, as this can be costly, only perform it for partial roots
86
- partial_traces = []
87
- all_ids = Set.new
88
- roots.each do |root|
89
- spans = []
90
- add_children(children_map, spans, all_ids, root)
91
- partial_traces << spans
92
- end
45
+ private
93
46
 
94
- return [nil, nil] if partial_traces.empty?
95
- [partial_traces, all_ids]
96
- end
47
+ def partial_trace(context)
48
+ return nil if context.finished_span_count < @min_spans_for_partial
49
+
50
+ finished_spans(context)
51
+ end
97
52
 
98
- def partial_flush(context)
99
- traces, flushed_ids = partial_traces(context)
100
- return nil unless traces && flushed_ids
53
+ def finished_spans(context)
54
+ trace = context.delete_span_if(&:finished?)
101
55
 
102
- # We need to reject by span ID and not by value, because a span
103
- # value may be altered (typical example: it's finished by some other thread)
104
- # since we lock only the context, not all the spans which belong to it.
105
- context.send(:delete_span_if) { |span| flushed_ids.include? span.span_id }
106
- traces
107
- end
56
+ # Ensure that the first span in a partial trace has
57
+ # sampling and origin information.
58
+ if trace[0]
59
+ context.annotate_for_flush!(trace[0])
60
+ else
61
+ Datadog::Tracer.log.debug('Tried to retrieve trace from context, but got nothing. ' \
62
+ "Is there another consumer for this context? #{context.trace_id}")
63
+ end
108
64
 
109
- # Performs an operation which each partial trace it can get from the context.
110
- def each_partial_trace(context)
111
- start_time = context.send(:start_time)
112
- length = context.send(:length)
113
- # Stop and do not flush anything if there are not enough spans.
114
- return if length <= @min_spans_before_partial_flush
115
- # If there are enough spans, but not too many, check for start time.
116
- # If timeout is not given or 0, then wait
117
- return if length <= @max_spans_before_partial_flush &&
118
- (@partial_flush_timeout.nil? || @partial_flush_timeout <= 0 ||
119
- (start_time && start_time > Time.now.utc - @partial_flush_timeout))
120
- # Here, either the trace is old or we have too many spans, flush it.
121
- traces = partial_flush(context)
122
- return unless traces
123
- traces.each do |trace|
124
- yield trace
65
+ trace unless trace.empty?
125
66
  end
126
67
  end
127
-
128
- private :add_children
129
- private :partial_traces
130
- private :partial_flush
131
68
  end
132
69
  end