ddtrace 0.53.0 → 0.54.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -11
  3. data/ddtrace.gemspec +5 -2
  4. data/docs/GettingStarted.md +40 -3
  5. data/docs/ProfilingDevelopment.md +2 -2
  6. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +86 -0
  7. data/ext/ddtrace_profiling_native_extension/clock_id.h +4 -0
  8. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +52 -0
  9. data/ext/ddtrace_profiling_native_extension/clock_id_noop.c +14 -0
  10. data/ext/ddtrace_profiling_native_extension/extconf.rb +144 -6
  11. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +35 -0
  12. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
  13. data/ext/ddtrace_profiling_native_extension/profiling.c +6 -1
  14. data/lib/datadog/ci/contrib/cucumber/formatter.rb +1 -0
  15. data/lib/datadog/ci/contrib/rspec/example.rb +1 -0
  16. data/lib/datadog/ci/ext/environment.rb +26 -21
  17. data/lib/datadog/ci/ext/test.rb +1 -0
  18. data/lib/datadog/ci/test.rb +5 -1
  19. data/lib/ddtrace/buffer.rb +28 -16
  20. data/lib/ddtrace/configuration/agent_settings_resolver.rb +27 -16
  21. data/lib/ddtrace/context.rb +10 -2
  22. data/lib/ddtrace/contrib/delayed_job/plugin.rb +2 -2
  23. data/lib/ddtrace/contrib/mongodb/instrumentation.rb +1 -1
  24. data/lib/ddtrace/contrib/mongodb/integration.rb +5 -0
  25. data/lib/ddtrace/contrib/rails/configuration/settings.rb +7 -0
  26. data/lib/ddtrace/contrib/rails/framework.rb +3 -2
  27. data/lib/ddtrace/contrib/redis/instrumentation.rb +90 -0
  28. data/lib/ddtrace/contrib/redis/patcher.rb +2 -84
  29. data/lib/ddtrace/contrib/resque/integration.rb +1 -5
  30. data/lib/ddtrace/ext/priority.rb +6 -4
  31. data/lib/ddtrace/ext/profiling.rb +1 -1
  32. data/lib/ddtrace/metrics.rb +2 -2
  33. data/lib/ddtrace/profiling/collectors/stack.rb +45 -45
  34. data/lib/ddtrace/profiling/encoding/profile.rb +1 -1
  35. data/lib/ddtrace/profiling/events/stack.rb +8 -8
  36. data/lib/ddtrace/profiling/native_extension.rb +23 -1
  37. data/lib/ddtrace/profiling/pprof/builder.rb +8 -2
  38. data/lib/ddtrace/profiling/pprof/stack_sample.rb +13 -16
  39. data/lib/ddtrace/profiling/pprof/template.rb +2 -2
  40. data/lib/ddtrace/profiling/tasks/setup.rb +21 -12
  41. data/lib/ddtrace/profiling/trace_identifiers/ddtrace.rb +9 -8
  42. data/lib/ddtrace/profiling/trace_identifiers/helper.rb +2 -2
  43. data/lib/ddtrace/profiling.rb +0 -2
  44. data/lib/ddtrace/sampler.rb +18 -8
  45. data/lib/ddtrace/sampling/rule_sampler.rb +13 -1
  46. data/lib/ddtrace/utils/time.rb +6 -0
  47. data/lib/ddtrace/version.rb +2 -2
  48. metadata +14 -9
  49. data/lib/ddtrace/profiling/ext/cpu.rb +0 -67
  50. data/lib/ddtrace/profiling/ext/cthread.rb +0 -156
@@ -40,6 +40,12 @@ module Datadog
40
40
  after = get_time
41
41
  after - before
42
42
  end
43
+
44
+ def as_utc_epoch_ns(time)
45
+ # we use #to_r instead of #to_f because Float doesn't have enough precision to represent exact nanoseconds, see
46
+ # https://rubyapi.org/3.0/o/time#method-i-to_f
47
+ (time.to_r * 1_000_000_000).to_i
48
+ end
43
49
  end
44
50
  end
45
51
  end
@@ -2,8 +2,8 @@
2
2
  module Datadog
3
3
  module VERSION
4
4
  MAJOR = 0
5
- MINOR = 53
6
- PATCH = 0
5
+ MINOR = 54
6
+ PATCH = 1
7
7
  PRE = nil
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
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.53.0
4
+ version: 0.54.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-06 00:00:00.000000000 Z
11
+ date: 2021-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: ffi
28
+ name: debase-ruby_core_source
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '1.0'
33
+ version: 0.10.12
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '1.0'
40
+ version: 0.10.12
41
41
  description: |
42
42
  ddtrace is Datadog’s tracing client for Ruby. It is used to trace requests
43
43
  as they flow across web servers, databases and microservices so that developers
@@ -66,7 +66,13 @@ files:
66
66
  - docs/DevelopmentGuide.md
67
67
  - docs/GettingStarted.md
68
68
  - docs/ProfilingDevelopment.md
69
+ - ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md
70
+ - ext/ddtrace_profiling_native_extension/clock_id.h
71
+ - ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c
72
+ - ext/ddtrace_profiling_native_extension/clock_id_noop.c
69
73
  - ext/ddtrace_profiling_native_extension/extconf.rb
74
+ - ext/ddtrace_profiling_native_extension/private_vm_api_access.c
75
+ - ext/ddtrace_profiling_native_extension/private_vm_api_access.h
70
76
  - ext/ddtrace_profiling_native_extension/profiling.c
71
77
  - lib/datadog/ci.rb
72
78
  - lib/datadog/ci/configuration/components.rb
@@ -374,6 +380,7 @@ files:
374
380
  - lib/ddtrace/contrib/redis/configuration/resolver.rb
375
381
  - lib/ddtrace/contrib/redis/configuration/settings.rb
376
382
  - lib/ddtrace/contrib/redis/ext.rb
383
+ - lib/ddtrace/contrib/redis/instrumentation.rb
377
384
  - lib/ddtrace/contrib/redis/integration.rb
378
385
  - lib/ddtrace/contrib/redis/patcher.rb
379
386
  - lib/ddtrace/contrib/redis/quantize.rb
@@ -505,8 +512,6 @@ files:
505
512
  - lib/ddtrace/profiling/event.rb
506
513
  - lib/ddtrace/profiling/events/stack.rb
507
514
  - lib/ddtrace/profiling/exporter.rb
508
- - lib/ddtrace/profiling/ext/cpu.rb
509
- - lib/ddtrace/profiling/ext/cthread.rb
510
515
  - lib/ddtrace/profiling/ext/forking.rb
511
516
  - lib/ddtrace/profiling/flush.rb
512
517
  - lib/ddtrace/profiling/native_extension.rb
@@ -1,67 +0,0 @@
1
- # typed: true
2
- module Datadog
3
- module Profiling
4
- module Ext
5
- # Monkey patches Ruby's `Thread` with our `Ext::CThread` to enable CPU-time profiling
6
- module CPU
7
- # We cannot apply our CPU extension if a broken rollbar is around because that can cause customer apps to fail
8
- # with a SystemStackError: stack level too deep.
9
- #
10
- # This occurs whenever our extensions to Thread are applied BEFORE rollbar applies its own. This happens
11
- # because a loop forms: our extension tries to call Thread#initialize, but it's intercepted by rollbar, which
12
- # then tries to call the original Thread#initialize as well, but instead alls our extension, leading to stack
13
- # exhaustion.
14
- #
15
- # See https://github.com/rollbar/rollbar-gem/pull/1018 for more details on the issue
16
- ROLLBAR_INCOMPATIBLE_VERSIONS = Gem::Requirement.new('<= 3.1.1')
17
-
18
- def self.supported?
19
- unsupported_reason.nil?
20
- end
21
-
22
- def self.apply!
23
- return false unless supported?
24
-
25
- # Applying CThread to Thread will ensure any new threads
26
- # will provide a thread/clock ID for CPU timing.
27
- require 'ddtrace/profiling/ext/cthread'
28
- ::Thread.prepend(Profiling::Ext::CThread)
29
- ::Thread.singleton_class.prepend(Datadog::Profiling::Ext::WrapThreadStartFork)
30
- end
31
-
32
- def self.unsupported_reason
33
- # NOTE: Only the first matching reason is returned, so try to keep a nice order on reasons -- e.g. tell users
34
- # first that they can't use this on macOS before telling them that they have the wrong ffi version
35
-
36
- if RUBY_ENGINE == 'jruby'
37
- 'JRuby is not supported'
38
- elsif RUBY_PLATFORM.include?('darwin')
39
- 'Feature requires Linux; macOS is not supported'
40
- elsif RUBY_PLATFORM =~ /(mswin|mingw)/
41
- 'Feature requires Linux; Windows is not supported'
42
- elsif !RUBY_PLATFORM.include?('linux')
43
- "Feature requires Linux; #{RUBY_PLATFORM} is not supported"
44
- elsif Gem::Specification.find_all_by_name('rollbar', ROLLBAR_INCOMPATIBLE_VERSIONS).any?
45
- 'You have an incompatible rollbar gem version installed; ensure that you have rollbar >= 3.1.2 by ' \
46
- "adding `gem 'rollbar', '>= 3.1.2'` to your Gemfile or gems.rb file. " \
47
- 'See https://github.com/rollbar/rollbar-gem/pull/1018 for details'
48
- elsif Gem::Specification.find_all_by_name('logging').any? && logging_inherit_context_enabled?
49
- 'The `logging` gem is installed and its thread inherit context feature is enabled. ' \
50
- "Please add LOGGING_INHERIT_CONTEXT=false to your application's environment variables to disable the " \
51
- 'conflicting `logging` gem feature. ' \
52
- 'See https://github.com/TwP/logging/pull/230 for details'
53
- end
54
- end
55
-
56
- private_class_method def self.logging_inherit_context_enabled?
57
- # The logging gem provides a mechanism to disable the conflicting behavior, see
58
- # https://github.com/TwP/logging/blob/ae9872d093833b2a5a34cbe1faa4e895a81f6845/lib/logging/diagnostic_context.rb#L418
59
- # Here we check if the behavior is enabled
60
- inherit_context_configuration = ENV['LOGGING_INHERIT_CONTEXT']
61
-
62
- inherit_context_configuration.nil? || !%w[false no 0].include?(inherit_context_configuration.downcase)
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,156 +0,0 @@
1
- # typed: false
2
- require 'ffi'
3
-
4
- module Datadog
5
- module Profiling
6
- module Ext
7
- # C-struct for retrieving clock ID from pthread
8
- class CClockId < FFI::Struct
9
- layout :value, :int
10
- end
11
-
12
- # Enables interfacing with pthread via FFI
13
- module NativePthread
14
- extend FFI::Library
15
- ffi_lib ['pthread', 'libpthread.so.0']
16
- attach_function :pthread_self, [], :ulong
17
- attach_function :pthread_getcpuclockid, [:ulong, CClockId], :int
18
-
19
- # NOTE: Only returns thread ID for thread that evaluates this call.
20
- # a.k.a. evaluating `get_pthread_thread_id(thread_a)` from within
21
- # `thread_b` will return `thread_b`'s thread ID, not `thread_a`'s.
22
- def self.get_pthread_thread_id(thread)
23
- return unless ::Thread.current == thread
24
-
25
- pthread_self
26
- end
27
-
28
- def self.get_clock_id(thread, pthread_id)
29
- return unless ::Thread.current == thread && pthread_id
30
-
31
- clock = CClockId.new
32
- clock[:value] = 0
33
- pthread_getcpuclockid(pthread_id, clock).zero? ? clock[:value] : nil
34
- end
35
- end
36
-
37
- # Extension used to enable CPU-time profiling via use of pthread's `getcpuclockid`.
38
- module CThread
39
- def self.prepended(base)
40
- # Threads that have already been created, will not have resolved
41
- # a thread/clock ID. This is because these IDs can only be resolved
42
- # from within the thread's execution context, which we do not control.
43
- #
44
- # We can mitigate this for the current thread via #update_native_ids,
45
- # since we are currently running within its execution context. We cannot
46
- # do this for any other threads that may have been created already.
47
- # (This is why it's important that CThread is applied before anything else runs.)
48
- base.current.send(:update_native_ids) if base.current.is_a?(CThread)
49
- end
50
-
51
- # Process::Waiter crash workaround:
52
- #
53
- # This is a workaround for a Ruby VM segfault (usually something like
54
- # "[BUG] Segmentation fault at 0x0000000000000008") in the affected Ruby versions.
55
- # See https://bugs.ruby-lang.org/issues/17807 and the regression tests added to this module's specs for details.
56
- #
57
- # In those Ruby versions, there's a very special subclass of `Thread` called `Process::Waiter` that causes VM
58
- # crashes whenever something tries to read its instance variables. This subclass of thread only shows up when
59
- # the `Process.detach` API gets used.
60
- # In this module's specs you can find crash regression tests that include a way of reproducing it.
61
- #
62
- # The workaround is to use `defined?` to check first if the instance variable exists. This seems to be fine
63
- # with Ruby.
64
- # Note that this crash doesn't affect `@foo ||=` nor instance variable writes (after the first write ever of any
65
- # instance variable on a `Process::Waiter`, then further reads and writes to that or any other instance are OK;
66
- # it looks like there's some lazily-created structure that is missing and did not get created).
67
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3') &&
68
- Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
69
- attr_reader :pthread_thread_id
70
- else
71
- def pthread_thread_id
72
- defined?(@pthread_thread_id) && @pthread_thread_id
73
- end
74
- end
75
-
76
- def initialize(*args)
77
- @pid = ::Process.pid
78
- @pthread_thread_id = nil
79
- @clock_id = nil
80
-
81
- # Wrap the work block with our own
82
- # so we can retrieve the native thread ID within the thread's context.
83
- wrapped_block = proc do |*t_args|
84
- # Set native thread ID & clock ID
85
- update_native_ids
86
- yield(*t_args)
87
- end
88
- wrapped_block.ruby2_keywords if wrapped_block.respond_to?(:ruby2_keywords, true)
89
-
90
- super(*args, &wrapped_block)
91
- end
92
- ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
93
-
94
- def cpu_time(unit = :float_second)
95
- ::Process.clock_gettime(clock_id, unit) if clock_id
96
- end
97
-
98
- def cpu_time_instrumentation_installed?
99
- # If this thread was started before this module was added to Thread OR if something caused the initialize
100
- # method above not to be properly called on new threads, this instance variable is never defined (never set to
101
- # any value at all, including nil).
102
- #
103
- # Thus, we can use @clock_id as a canary to detect a thread that has missing instrumentation, because we
104
- # know that in initialize above we always set this variable to nil.
105
- defined?(@clock_id) != nil
106
- end
107
-
108
- private
109
-
110
- def clock_id
111
- update_native_ids if forked?
112
- defined?(@clock_id) && @clock_id
113
- end
114
-
115
- def forked?
116
- ::Process.pid != (@pid ||= nil)
117
- end
118
-
119
- def update_native_ids
120
- # Can only resolve if invoked from same thread
121
- return unless ::Thread.current == self
122
-
123
- @pid = ::Process.pid
124
- @pthread_thread_id = NativePthread.get_pthread_thread_id(self)
125
- @clock_id = NativePthread.get_clock_id(self, @pthread_thread_id)
126
- end
127
- end
128
-
129
- # Threads in Ruby can be started by creating a new instance of `Thread` (or a subclass) OR by calling
130
- # `start`/`fork` on `Thread` (or a subclass).
131
- #
132
- # This module intercepts calls to `start`/`fork`, ensuring that the `update_native_ids` operation is correctly
133
- # called once the new thread starts.
134
- #
135
- # Note that unlike CThread above, this module should be prepended to the `Thread`'s singleton class, not to
136
- # the class.
137
- module WrapThreadStartFork
138
- def start(*args)
139
- # Wrap the work block with our own
140
- # so we can retrieve the native thread ID within the thread's context.
141
- wrapped_block = proc do |*t_args|
142
- # Set native thread ID & clock ID
143
- ::Thread.current.send(:update_native_ids)
144
- yield(*t_args)
145
- end
146
- wrapped_block.ruby2_keywords if wrapped_block.respond_to?(:ruby2_keywords, true)
147
-
148
- super(*args, &wrapped_block)
149
- end
150
- ruby2_keywords :start if respond_to?(:ruby2_keywords, true)
151
-
152
- alias fork start
153
- end
154
- end
155
- end
156
- end