ddtrace 0.40.0 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -1
  3. data/Rakefile +0 -23
  4. data/ddtrace.gemspec +1 -0
  5. data/lib/ddtrace/buffer.rb +154 -43
  6. data/lib/ddtrace/configuration.rb +4 -1
  7. data/lib/ddtrace/configuration/options.rb +3 -1
  8. data/lib/ddtrace/configuration/settings.rb +5 -1
  9. data/lib/ddtrace/contrib/active_record/events/sql.rb +4 -0
  10. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +2 -2
  11. data/lib/ddtrace/contrib/aws/instrumentation.rb +4 -0
  12. data/lib/ddtrace/contrib/dalli/instrumentation.rb +4 -0
  13. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +4 -0
  14. data/lib/ddtrace/contrib/ethon/easy_patch.rb +4 -2
  15. data/lib/ddtrace/contrib/ethon/multi_patch.rb +4 -0
  16. data/lib/ddtrace/contrib/excon/middleware.rb +4 -0
  17. data/lib/ddtrace/contrib/faraday/middleware.rb +4 -0
  18. data/lib/ddtrace/contrib/grape/endpoint.rb +6 -4
  19. data/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +4 -0
  20. data/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb +4 -0
  21. data/lib/ddtrace/contrib/http/instrumentation.rb +4 -0
  22. data/lib/ddtrace/contrib/httprb/instrumentation.rb +3 -0
  23. data/lib/ddtrace/contrib/mongodb/subscribers.rb +4 -0
  24. data/lib/ddtrace/contrib/mysql2/instrumentation.rb +4 -0
  25. data/lib/ddtrace/contrib/presto/instrumentation.rb +3 -0
  26. data/lib/ddtrace/contrib/racecar/event.rb +4 -0
  27. data/lib/ddtrace/contrib/redis/tags.rb +4 -0
  28. data/lib/ddtrace/contrib/rest_client/request_patch.rb +4 -0
  29. data/lib/ddtrace/contrib/sequel/database.rb +3 -1
  30. data/lib/ddtrace/contrib/sequel/dataset.rb +3 -2
  31. data/lib/ddtrace/contrib/sequel/ext.rb +1 -0
  32. data/lib/ddtrace/contrib/sequel/utils.rb +16 -5
  33. data/lib/ddtrace/ext/integration.rb +8 -0
  34. data/lib/ddtrace/ext/runtime.rb +1 -0
  35. data/lib/ddtrace/opentracer/distributed_headers.rb +1 -1
  36. data/lib/ddtrace/propagation/grpc_propagator.rb +2 -2
  37. data/lib/ddtrace/runtime/metrics.rb +6 -2
  38. data/lib/ddtrace/sampler.rb +2 -2
  39. data/lib/ddtrace/span.rb +152 -27
  40. data/lib/ddtrace/tracer.rb +3 -4
  41. data/lib/ddtrace/transport/http/adapters/net.rb +8 -2
  42. data/lib/ddtrace/transport/http/statistics.rb +14 -1
  43. data/lib/ddtrace/transport/traces.rb +7 -2
  44. data/lib/ddtrace/utils.rb +7 -3
  45. data/lib/ddtrace/version.rb +1 -1
  46. metadata +17 -2
@@ -1,3 +1,4 @@
1
+ require 'ddtrace/ext/integration'
1
2
  require 'ddtrace/ext/runtime'
2
3
 
3
4
  require 'ddtrace/metrics'
@@ -25,8 +26,11 @@ module Datadog
25
26
  # Register service as associated with metrics
26
27
  register_service(span.service) unless span.service.nil?
27
28
 
28
- # Tag span with language and runtime ID for association with metrics
29
- span.set_tag(Ext::Runtime::TAG_LANG, Runtime::Identity.lang)
29
+ # Tag span with language and runtime ID for association with metrics.
30
+ # We only tag spans that performed internal application work.
31
+ unless span.get_tag(Datadog::Ext::Integration::TAG_PEER_SERVICE)
32
+ span.set_tag(Ext::Runtime::TAG_LANG, Runtime::Identity.lang)
33
+ end
30
34
  end
31
35
 
32
36
  # Associate service with runtime metrics
@@ -61,11 +61,11 @@ module Datadog
61
61
 
62
62
  def sample_rate=(sample_rate)
63
63
  @sample_rate = sample_rate
64
- @sampling_id_threshold = sample_rate * Span::MAX_ID
64
+ @sampling_id_threshold = sample_rate * Span::EXTERNAL_MAX_ID
65
65
  end
66
66
 
67
67
  def sample?(span)
68
- ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::MAX_ID) <= @sampling_id_threshold
68
+ ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::EXTERNAL_MAX_ID) <= @sampling_id_threshold
69
69
  end
70
70
 
71
71
  def sample!(span)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'thread'
3
5
 
@@ -8,6 +10,7 @@ require 'ddtrace/environment'
8
10
  require 'ddtrace/analytics'
9
11
  require 'ddtrace/forced_tracing'
10
12
  require 'ddtrace/diagnostics/health'
13
+ require 'ddtrace/utils/time'
11
14
 
12
15
  module Datadog
13
16
  # Represents a logical unit of work in the system. Each trace consists of one or more spans.
@@ -24,24 +27,26 @@ module Datadog
24
27
  # The max value for a \Span identifier.
25
28
  # Span and trace identifiers should be strictly positive and strictly inferior to this limit.
26
29
  #
27
- # Limited to 63-bit positive integers, as some other languages might be limited to this,
28
- # and IDs need to be easy to port across various languages and platforms.
29
- MAX_ID = 2**63
30
+ # Limited to +2<<62-1+ positive integers, as Ruby is able to represent such numbers "inline",
31
+ # inside a +VALUE+ scalar, thus not requiring memory allocation.
32
+ #
33
+ # The range of IDs also has to consider portability across different languages and platforms.
34
+ RUBY_MAX_ID = (1 << 62) - 1
30
35
 
31
36
  # While we only generate 63-bit integers due to limitations in other languages, we support
32
37
  # parsing 64-bit integers for distributed tracing since an upstream system may generate one
33
- EXTERNAL_MAX_ID = 2**64
38
+ EXTERNAL_MAX_ID = 1 << 64
34
39
 
35
40
  # This limit is for numeric tags because uint64 could end up rounded.
36
- NUMERIC_TAG_SIZE_RANGE = (-2**53..2**53)
41
+ NUMERIC_TAG_SIZE_RANGE = (-1 << 53..1 << 53)
37
42
 
38
43
  attr_accessor :name, :service, :resource, :span_type,
39
- :start_time, :end_time,
40
44
  :span_id, :trace_id, :parent_id,
41
45
  :status, :sampled,
42
- :tracer, :context
46
+ :tracer, :context, :duration, :start_time, :end_time
43
47
 
44
48
  attr_reader :parent
49
+
45
50
  # Create a new span linked to the given tracer. Call the \Tracer method <tt>start_span()</tt>
46
51
  # and then <tt>finish()</tt> once the tracer operation is over.
47
52
  #
@@ -72,11 +77,20 @@ module Datadog
72
77
  @parent = nil
73
78
  @sampled = true
74
79
 
75
- @start_time = nil # set by Tracer.start_span
76
- @end_time = nil # set by Span.finish
77
-
78
80
  @allocation_count_start = now_allocations
79
81
  @allocation_count_finish = @allocation_count_start
82
+
83
+ # start_time and end_time track wall clock. In Ruby, wall clock
84
+ # has less accuracy than monotonic clock, so if possible we look to only use wall clock
85
+ # to measure duration when a time is supplied by the user, or if monotonic clock
86
+ # is unsupported.
87
+ @start_time = nil
88
+ @end_time = nil
89
+
90
+ # duration_start and duration_end track monotonic clock, and may remain nil in cases where it
91
+ # is known that we have to use wall clock to measure duration.
92
+ @duration_start = nil
93
+ @duration_end = nil
80
94
  end
81
95
 
82
96
  # Set the given key / value tag pair on the span. Keys and values
@@ -160,6 +174,28 @@ module Datadog
160
174
  set_tag(Ext::Errors::STACK, e.backtrace) unless e.backtrace.empty?
161
175
  end
162
176
 
177
+ # Mark the span started at the current time.
178
+ def start(start_time = nil)
179
+ # A span should not be started twice. However, this is existing
180
+ # behavior and so we maintain it for backward compatibility for those
181
+ # who are using async manual instrumentation that may rely on this
182
+
183
+ @start_time = start_time || Time.now.utc
184
+ @duration_start = start_time.nil? ? duration_marker : nil
185
+
186
+ self
187
+ end
188
+
189
+ # for backwards compatibility
190
+ def start_time=(time)
191
+ time.tap { start(time) }
192
+ end
193
+
194
+ # for backwards compatibility
195
+ def end_time=(time)
196
+ time.tap { finish(time) }
197
+ end
198
+
163
199
  # Mark the span finished at the current time and submit it.
164
200
  def finish(finish_time = nil)
165
201
  # A span should not be finished twice. Note that this is not thread-safe,
@@ -170,12 +206,15 @@ module Datadog
170
206
 
171
207
  @allocation_count_finish = now_allocations
172
208
 
173
- # Provide a default start_time if unset, but this should have been set by start_span.
174
- # Using now here causes 0-duration spans, still, this is expected, as we never
175
- # explicitely say when it started.
176
- @start_time ||= Time.now.utc
209
+ now = Time.now.utc
210
+
211
+ # Provide a default start_time if unset.
212
+ # Using `now` here causes duration to be 0; this is expected
213
+ # behavior when start_time is unknown.
214
+ start(finish_time || now) unless started?
177
215
 
178
- @end_time = finish_time.nil? ? Time.now.utc : finish_time # finish this
216
+ @end_time = finish_time || now
217
+ @duration_end = finish_time.nil? ? duration_marker : nil
179
218
 
180
219
  # Finish does not really do anything if the span is not bound to a tracer and a context.
181
220
  return self if @tracer.nil? || @context.nil?
@@ -195,11 +234,6 @@ module Datadog
195
234
  self
196
235
  end
197
236
 
198
- # Return whether the span is finished or not.
199
- def finished?
200
- !@end_time.nil?
201
- end
202
-
203
237
  # Return a string representation of the span.
204
238
  def to_s
205
239
  "Span(name:#{@name},sid:#{@span_id},tid:#{@trace_id},pid:#{@parent_id})"
@@ -246,19 +280,76 @@ module Datadog
246
280
  error: @status
247
281
  }
248
282
 
249
- if !@start_time.nil? && !@end_time.nil?
250
- h[:start] = (@start_time.to_f * 1e9).to_i
251
- h[:duration] = ((@end_time - @start_time) * 1e9).to_i
283
+ if finished?
284
+ h[:start] = start_time_nano
285
+ h[:duration] = duration_nano
252
286
  end
253
287
 
254
288
  h
255
289
  end
256
290
 
291
+ # MessagePack serializer interface. Making this object
292
+ # respond to `#to_msgpack` allows it to be automatically
293
+ # serialized by MessagePack.
294
+ #
295
+ # This is more efficient than doing +MessagePack.pack(span.to_hash)+
296
+ # as we don't have to create an intermediate Hash.
297
+ #
298
+ # @param packer [MessagePack::Packer] serialization buffer, can be +nil+ with JRuby
299
+ def to_msgpack(packer = nil)
300
+ # As of 1.3.3, JRuby implementation doesn't pass an existing packer
301
+ packer ||= MessagePack::Packer.new
302
+
303
+ if finished?
304
+ packer.write_map_header(13) # Set header with how many elements in the map
305
+
306
+ packer.write('start')
307
+ packer.write(start_time_nano)
308
+
309
+ packer.write('duration')
310
+ packer.write(duration_nano)
311
+ else
312
+ packer.write_map_header(11) # Set header with how many elements in the map
313
+ end
314
+
315
+ # DEV: We use strings as keys here, instead of symbols, as
316
+ # DEV: MessagePack will ultimately convert them to strings.
317
+ # DEV: By providing strings directly, we skip this indirection operation.
318
+ packer.write('span_id')
319
+ packer.write(@span_id)
320
+ packer.write('parent_id')
321
+ packer.write(@parent_id)
322
+ packer.write('trace_id')
323
+ packer.write(@trace_id)
324
+ packer.write('name')
325
+ packer.write(@name)
326
+ packer.write('service')
327
+ packer.write(@service)
328
+ packer.write('resource')
329
+ packer.write(@resource)
330
+ packer.write('type')
331
+ packer.write(@span_type)
332
+ packer.write('meta')
333
+ packer.write(@meta)
334
+ packer.write('metrics')
335
+ packer.write(@metrics)
336
+ packer.write('allocations')
337
+ packer.write(allocations)
338
+ packer.write('error')
339
+ packer.write(@status)
340
+ packer
341
+ end
342
+
343
+ # JSON serializer interface.
344
+ # Used by older version of the transport.
345
+ def to_json(*args)
346
+ to_hash.to_json(*args)
347
+ end
348
+
257
349
  # Return a human readable version of the span
258
350
  def pretty_print(q)
259
- start_time = (@start_time.to_f * 1e9).to_i rescue '-'
260
- end_time = (@end_time.to_f * 1e9).to_i rescue '-'
261
- duration = ((@end_time - @start_time) * 1e9).to_i rescue 0
351
+ start_time = (self.start_time.to_f * 1e9).to_i
352
+ end_time = (self.end_time.to_f * 1e9).to_i
262
353
  q.group 0 do
263
354
  q.breakable
264
355
  q.text "Name: #{@name}\n"
@@ -271,7 +362,7 @@ module Datadog
271
362
  q.text "Error: #{@status}\n"
272
363
  q.text "Start: #{start_time}\n"
273
364
  q.text "End: #{end_time}\n"
274
- q.text "Duration: #{duration}\n"
365
+ q.text "Duration: #{duration.to_f if finished?}\n"
275
366
  q.text "Allocations: #{allocations}\n"
276
367
  q.group(2, 'Tags: [', "]\n") do
277
368
  q.breakable
@@ -288,8 +379,30 @@ module Datadog
288
379
  end
289
380
  end
290
381
 
382
+ # Return whether the duration is started or not
383
+ def started?
384
+ !@start_time.nil?
385
+ end
386
+
387
+ # Return whether the duration is finished or not.
388
+ def finished?
389
+ !@end_time.nil?
390
+ end
391
+
392
+ def duration
393
+ if @duration_end.nil? || @duration_start.nil?
394
+ @end_time - @start_time
395
+ else
396
+ @duration_end - @duration_start
397
+ end
398
+ end
399
+
291
400
  private
292
401
 
402
+ def duration_marker
403
+ Utils::Time.get_time
404
+ end
405
+
293
406
  if defined?(JRUBY_VERSION) || Gem::Version.new(RUBY_VERSION) < Gem::Version.new(VERSION::MINIMUM_RUBY_VERSION)
294
407
  def now_allocations
295
408
  0
@@ -303,5 +416,17 @@ module Datadog
303
416
  GC.stat(:total_allocated_objects)
304
417
  end
305
418
  end
419
+
420
+ # Used for serialization
421
+ # @return [Integer] in nanoseconds since Epoch
422
+ def start_time_nano
423
+ @start_time.to_i * 1000000000 + @start_time.nsec
424
+ end
425
+
426
+ # Used for serialization
427
+ # @return [Integer] in nanoseconds since Epoch
428
+ def duration_nano
429
+ (duration * 1e9).to_i
430
+ end
306
431
  end
307
432
  end
@@ -1,4 +1,3 @@
1
- require 'pp'
2
1
  require 'thread'
3
2
  require 'logger'
4
3
  require 'pathname'
@@ -186,8 +185,7 @@ module Datadog
186
185
  # * +start_time+: when the span actually starts (defaults to \now)
187
186
  # * +tags+: extra tags which should be added to the span.
188
187
  def start_span(name, options = {})
189
- start_time = options.fetch(:start_time, Time.now.utc)
190
-
188
+ start_time = options[:start_time]
191
189
  tags = options.fetch(:tags, {})
192
190
 
193
191
  span_options = options.select do |k, _v|
@@ -212,9 +210,10 @@ module Datadog
212
210
  # child span
213
211
  span.parent = parent # sets service, trace_id, parent_id, sampled
214
212
  end
213
+
215
214
  span.set_tags(@tags) unless @tags.empty?
216
215
  span.set_tags(tags) unless tags.empty?
217
- span.start_time = start_time
216
+ span.start(start_time)
218
217
 
219
218
  # this could at some point be optional (start_active_span vs start_manual_span)
220
219
  ctx.add_span(span) unless ctx.nil?
@@ -20,8 +20,14 @@ module Datadog
20
20
  end
21
21
 
22
22
  def open
23
- # Open connection
24
- ::Net::HTTP.start(hostname, port, open_timeout: timeout, read_timeout: timeout) do |http|
23
+ # DEV Initializing +Net::HTTP+ directly help us avoid expensive
24
+ # options processing done in +Net::HTTP.start+:
25
+ # https://github.com/ruby/ruby/blob/b2d96abb42abbe2e01f010ffc9ac51f0f9a50002/lib/net/http.rb#L614-L618
26
+ req = ::Net::HTTP.new(hostname, port, nil)
27
+
28
+ req.open_timeout = req.read_timeout = timeout
29
+
30
+ req.start do |http|
25
31
  yield(http)
26
32
  end
27
33
  end
@@ -18,11 +18,24 @@ module Datadog
18
18
  # Add status code tag to api.responses metric
19
19
  if metrics.key?(:api_responses)
20
20
  (metrics[:api_responses].options[:tags] ||= []).tap do |tags|
21
- tags << "status_code:#{response.code}"
21
+ tags << metrics_tag_value(response.code)
22
22
  end
23
23
  end
24
24
  end
25
25
  end
26
+
27
+ private
28
+
29
+ # The most common status code on a healthy tracer
30
+ STATUS_CODE_200 = 'status_code:200'.freeze
31
+
32
+ def metrics_tag_value(status_code)
33
+ if status_code == 200
34
+ STATUS_CODE_200 # DEV Saves string concatenation/creation for common case
35
+ else
36
+ "status_code:#{status_code}"
37
+ end
38
+ end
26
39
  end
27
40
  end
28
41
  end
@@ -57,7 +57,12 @@ module Datadog
57
57
  # @return [Enumerable[Array[Bytes,Integer]]] list of encoded chunks: each containing a byte array and
58
58
  # number of traces
59
59
  def encode_in_chunks(traces)
60
- encoded_traces = traces.map { |t| encode_one(t) }.reject(&:nil?)
60
+ encoded_traces = if traces.respond_to?(:filter_map)
61
+ # DEV Supported since Ruby 2.7, saves an intermediate object creation
62
+ traces.filter_map { |t| encode_one(t) }
63
+ else
64
+ traces.map { |t| encode_one(t) }.reject(&:nil?)
65
+ end
61
66
 
62
67
  Datadog::Chunker.chunk_by_size(encoded_traces, max_size).map do |chunk|
63
68
  [encoder.join(chunk), chunk.size]
@@ -86,7 +91,7 @@ module Datadog
86
91
  module_function
87
92
 
88
93
  def encode_trace(encoder, trace)
89
- encoder.encode(trace.map(&:to_hash))
94
+ encoder.encode(trace)
90
95
  end
91
96
  end
92
97
 
@@ -3,7 +3,7 @@ require 'ddtrace/utils/database'
3
3
  module Datadog
4
4
  # Utils contains low-level utilities, typically to provide pseudo-random trace IDs.
5
5
  module Utils
6
- STRING_PLACEHOLDER = ''.encode(::Encoding::UTF_8).freeze
6
+ EMPTY_STRING = ''.encode(::Encoding::UTF_8).freeze
7
7
  # We use a custom random number generator because we want no interference
8
8
  # with the default one. Using the default prng, we could break code that
9
9
  # would rely on srand/rand sequences.
@@ -12,7 +12,7 @@ module Datadog
12
12
  def self.next_id
13
13
  reset! if was_forked?
14
14
 
15
- @rnd.rand(Datadog::Span::MAX_ID)
15
+ @rnd.rand(Datadog::Span::RUBY_MAX_ID)
16
16
  end
17
17
 
18
18
  def self.reset!
@@ -53,13 +53,17 @@ module Datadog
53
53
  str.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
54
54
  elsif str.encoding == ::Encoding::UTF_8
55
55
  str
56
+ elsif str.empty?
57
+ # DEV Optimization as `nil.to_s` is a very common source for an empty string,
58
+ # DEV but it comes encoded as US_ASCII.
59
+ EMPTY_STRING
56
60
  else
57
61
  str.encode(::Encoding::UTF_8)
58
62
  end
59
63
  rescue => e
60
64
  Datadog.logger.debug("Error encoding string in UTF-8: #{e}")
61
65
 
62
- options.fetch(:placeholder, STRING_PLACEHOLDER)
66
+ options.fetch(:placeholder, EMPTY_STRING)
63
67
  end
64
68
  end
65
69
  end
@@ -1,7 +1,7 @@
1
1
  module Datadog
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 40
4
+ MINOR = 41
5
5
  PATCH = 0
6
6
  PRE = nil
7
7
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ddtrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.40.0
4
+ version: 0.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-08 00:00:00.000000000 Z
11
+ date: 2020-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.4.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: concurrent-ruby
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -716,6 +730,7 @@ files:
716
730
  - lib/ddtrace/ext/errors.rb
717
731
  - lib/ddtrace/ext/forced_tracing.rb
718
732
  - lib/ddtrace/ext/http.rb
733
+ - lib/ddtrace/ext/integration.rb
719
734
  - lib/ddtrace/ext/manual_tracing.rb
720
735
  - lib/ddtrace/ext/metrics.rb
721
736
  - lib/ddtrace/ext/net.rb