ddtrace 0.52.0 → 0.54.2

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -11
  3. data/ddtrace.gemspec +6 -3
  4. data/docs/DevelopmentGuide.md +1 -6
  5. data/docs/GettingStarted.md +109 -18
  6. data/docs/ProfilingDevelopment.md +2 -2
  7. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +86 -0
  8. data/ext/ddtrace_profiling_native_extension/clock_id.h +4 -0
  9. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +52 -0
  10. data/ext/ddtrace_profiling_native_extension/clock_id_noop.c +14 -0
  11. data/ext/ddtrace_profiling_native_extension/extconf.rb +177 -8
  12. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +35 -0
  13. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
  14. data/ext/ddtrace_profiling_native_extension/profiling.c +6 -1
  15. data/lib/datadog/ci/contrib/cucumber/formatter.rb +1 -0
  16. data/lib/datadog/ci/contrib/rspec/example.rb +1 -0
  17. data/lib/datadog/ci/contrib/rspec/integration.rb +2 -2
  18. data/lib/datadog/ci/ext/environment.rb +64 -22
  19. data/lib/datadog/ci/ext/test.rb +1 -0
  20. data/lib/datadog/ci/test.rb +5 -1
  21. data/lib/datadog/contrib.rb +2 -0
  22. data/lib/datadog/core/environment/vm_cache.rb +46 -0
  23. data/lib/ddtrace/buffer.rb +28 -16
  24. data/lib/ddtrace/configuration/agent_settings_resolver.rb +131 -53
  25. data/lib/ddtrace/configuration/components.rb +1 -1
  26. data/lib/ddtrace/configuration/settings.rb +13 -3
  27. data/lib/ddtrace/context.rb +10 -2
  28. data/lib/ddtrace/contrib/action_cable/instrumentation.rb +46 -0
  29. data/lib/ddtrace/contrib/action_cable/patcher.rb +1 -0
  30. data/lib/ddtrace/contrib/action_mailer/configuration/settings.rb +32 -0
  31. data/lib/ddtrace/contrib/action_mailer/event.rb +50 -0
  32. data/lib/ddtrace/contrib/action_mailer/events/deliver.rb +54 -0
  33. data/lib/ddtrace/contrib/action_mailer/events/process.rb +41 -0
  34. data/lib/ddtrace/contrib/action_mailer/events.rb +31 -0
  35. data/lib/ddtrace/contrib/action_mailer/ext.rb +32 -0
  36. data/lib/ddtrace/contrib/action_mailer/integration.rb +45 -0
  37. data/lib/ddtrace/contrib/action_mailer/patcher.rb +27 -0
  38. data/lib/ddtrace/contrib/active_job/configuration/settings.rb +33 -0
  39. data/lib/ddtrace/contrib/active_job/event.rb +54 -0
  40. data/lib/ddtrace/contrib/active_job/events/discard.rb +46 -0
  41. data/lib/ddtrace/contrib/active_job/events/enqueue.rb +45 -0
  42. data/lib/ddtrace/contrib/active_job/events/enqueue_at.rb +45 -0
  43. data/lib/ddtrace/contrib/active_job/events/enqueue_retry.rb +47 -0
  44. data/lib/ddtrace/contrib/active_job/events/perform.rb +45 -0
  45. data/lib/ddtrace/contrib/active_job/events/retry_stopped.rb +46 -0
  46. data/lib/ddtrace/contrib/active_job/events.rb +39 -0
  47. data/lib/ddtrace/contrib/active_job/ext.rb +32 -0
  48. data/lib/ddtrace/contrib/active_job/integration.rb +46 -0
  49. data/lib/ddtrace/contrib/active_job/log_injection.rb +21 -0
  50. data/lib/ddtrace/contrib/active_job/patcher.rb +33 -0
  51. data/lib/ddtrace/contrib/auto_instrument.rb +0 -1
  52. data/lib/ddtrace/contrib/delayed_job/plugin.rb +2 -2
  53. data/lib/ddtrace/contrib/mongodb/instrumentation.rb +1 -1
  54. data/lib/ddtrace/contrib/mongodb/integration.rb +5 -0
  55. data/lib/ddtrace/contrib/rails/auto_instrument_railtie.rb +0 -1
  56. data/lib/ddtrace/contrib/rails/configuration/settings.rb +7 -0
  57. data/lib/ddtrace/contrib/rails/framework.rb +24 -1
  58. data/lib/ddtrace/contrib/rails/patcher.rb +19 -10
  59. data/lib/ddtrace/contrib/redis/instrumentation.rb +90 -0
  60. data/lib/ddtrace/contrib/redis/patcher.rb +2 -84
  61. data/lib/ddtrace/contrib/registerable.rb +0 -1
  62. data/lib/ddtrace/contrib/resque/integration.rb +1 -5
  63. data/lib/ddtrace/contrib/sidekiq/ext.rb +3 -0
  64. data/lib/ddtrace/contrib/sidekiq/integration.rb +10 -0
  65. data/lib/ddtrace/contrib/sidekiq/patcher.rb +26 -0
  66. data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/heartbeat.rb +30 -0
  67. data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/job_fetch.rb +30 -0
  68. data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/scheduled_push.rb +29 -0
  69. data/lib/ddtrace/contrib/sinatra/env.rb +2 -1
  70. data/lib/ddtrace/contrib/sinatra/tracer.rb +15 -2
  71. data/lib/ddtrace/ext/git.rb +12 -0
  72. data/lib/ddtrace/ext/priority.rb +6 -4
  73. data/lib/ddtrace/ext/profiling.rb +8 -11
  74. data/lib/ddtrace/ext/runtime.rb +3 -0
  75. data/lib/ddtrace/ext/transport.rb +11 -0
  76. data/lib/ddtrace/metrics.rb +2 -2
  77. data/lib/ddtrace/profiling/collectors/stack.rb +112 -72
  78. data/lib/ddtrace/profiling/encoding/profile.rb +10 -2
  79. data/lib/ddtrace/profiling/events/stack.rb +13 -13
  80. data/lib/ddtrace/profiling/native_extension.rb +23 -1
  81. data/lib/ddtrace/profiling/pprof/builder.rb +8 -2
  82. data/lib/ddtrace/profiling/pprof/converter.rb +22 -9
  83. data/lib/ddtrace/profiling/pprof/stack_sample.rb +32 -9
  84. data/lib/ddtrace/profiling/pprof/template.rb +2 -2
  85. data/lib/ddtrace/profiling/scheduler.rb +20 -4
  86. data/lib/ddtrace/profiling/tasks/setup.rb +21 -13
  87. data/lib/ddtrace/profiling/trace_identifiers/ddtrace.rb +10 -9
  88. data/lib/ddtrace/profiling/trace_identifiers/helper.rb +5 -5
  89. data/lib/ddtrace/profiling/transport/http/api/endpoint.rb +8 -15
  90. data/lib/ddtrace/profiling/transport/http.rb +8 -17
  91. data/lib/ddtrace/profiling.rb +0 -2
  92. data/lib/ddtrace/runtime/metrics.rb +14 -0
  93. data/lib/ddtrace/sampler.rb +18 -8
  94. data/lib/ddtrace/sampling/rule_sampler.rb +13 -1
  95. data/lib/ddtrace/span.rb +7 -19
  96. data/lib/ddtrace/tracer.rb +1 -1
  97. data/lib/ddtrace/transport/http/adapters/net.rb +13 -3
  98. data/lib/ddtrace/transport/http/adapters/test.rb +4 -2
  99. data/lib/ddtrace/transport/http/adapters/unix_socket.rb +23 -12
  100. data/lib/ddtrace/transport/http/builder.rb +13 -6
  101. data/lib/ddtrace/transport/http.rb +5 -11
  102. data/lib/ddtrace/utils/time.rb +11 -6
  103. data/lib/ddtrace/version.rb +2 -2
  104. data/lib/ddtrace/workers/{loop.rb → interval_loop.rb} +0 -16
  105. data/lib/ddtrace/workers/polling.rb +1 -1
  106. metadata +40 -10
  107. data/lib/ddtrace/profiling/ext/cpu.rb +0 -67
  108. data/lib/ddtrace/profiling/ext/cthread.rb +0 -156
@@ -97,9 +97,9 @@ The profiler backend links a trace covering a given time interval to the profile
97
97
  whenever they share the same `runtime-id`.
98
98
 
99
99
  To further enable filtering of a profile to show only samples related to a given trace/span, each sample taken by the
100
- profiler is tagged with the trace_id and span_id for the given trace/span.
100
+ profiler is tagged with the `local root span id` and `span id` for the given trace/span.
101
101
 
102
- This is done using the `Datadog::Profiling::TraceIdentifiers::Helper` that retrieves a trace_id and span_id, if
102
+ This is done using the `Datadog::Profiling::TraceIdentifiers::Helper` that retrieves a `root_span_id` and `span_id`, if
103
103
  available, from the supported tracers. This helper is called by the `Collectors::Stack` during sampling.
104
104
 
105
105
  Note that if a given trace executes too fast, it's possible that the profiler will not contain any samples for that
@@ -0,0 +1,86 @@
1
+ # Profiling Native Extension Design
2
+
3
+ The profiling native extension is used to implement features which are expensive (in terms of resources) or otherwise
4
+ impossible to implement using Ruby code.
5
+
6
+ This extension is quite coupled with MRI Ruby ("C Ruby") internals, and is not intended to support other rubies such as
7
+ JRuby or TruffleRuby. When below we say "Ruby", read it as "MRI Ruby".
8
+
9
+ ## Disabling
10
+
11
+ The profiling native extension can be disabled by setting `DD_PROFILING_NO_EXTENSION=true` when installing or running
12
+ the gem. Setting `DD_PROFILING_NO_EXTENSION` at installation time skips compilation of the extension entirely.
13
+
14
+ (If you're a customer and needed to use this, please tell us why on <https://github.com/DataDog/dd-trace-rb/issues/new>.)
15
+
16
+ Currently the profiler can still "limp along" when the native extension is disabled, but the plan is to require it
17
+ in future releases -- e.g. disabling the extension will disable profiling entirely.
18
+
19
+ ## Safety
20
+
21
+ The profiling native extension is (and must always be) designed to **not cause failures** during gem installation, even
22
+ if some features, Ruby versions, or operating systems are not supported.
23
+
24
+ E.g. the extension must cleanly build on Ruby 2.1 (or the oldest Ruby version we support at the time) on Windows,
25
+ even if at run time it will effectively do nothing for such a setup.
26
+
27
+ We have a CI setup to help validate this, but this is really important to keep in mind when adding to or changing the
28
+ existing codebase.
29
+
30
+ ## Usage of private VM headers
31
+
32
+ To implement some of the features below, we sometimes require access to private Ruby header files (that describe VM
33
+ internal types, structures and functions).
34
+
35
+ Because these private header files are not included in regular Ruby installations, we have two different workarounds:
36
+
37
+ 1. for Ruby versions >= 2.6 we make use use the Ruby private MJIT header
38
+ 2. for Ruby versions < 2.6 (legacy Rubies) we make use of the `debase-ruby_core_source` gem
39
+
40
+ Functions which make use of these headers are defined in the <private_vm_api_acccess.c> file.
41
+
42
+ **Important Note**: Our medium/long-term plan is to stop relying on all private Ruby headers, and instead request and
43
+ contribute upstream changes so that they become official public VM APIs.
44
+
45
+ ### Approach 1: Using the Ruby private MJIT header
46
+
47
+ Ruby versions >= 2.6 introduced a JIT compiler called MJIT. This compiler does not directly generate machine code;
48
+ instead it generates C code and uses the system C compiler to turn it into machine code.
49
+
50
+ The generated C code `#include`s a private header -- which we reference as "the MJIT header" everywhere.
51
+ The MJIT header gets shipped with all MJIT-enabled Rubies and includes the layout of many internal VM structures;
52
+ and of course the intention is that it is only used by the Ruby MJIT compiler.
53
+
54
+ This header is placed inside the `include/` directory in a Ruby installation, and is named for that specific Ruby
55
+ version. e.g. `rb_mjit_min_header-2.7.4.h`.
56
+
57
+ ### Approach 2: Using the `debase-ruby_core_source` gem
58
+
59
+ The [`debase-ruby_core_source`](https://github.com/ruby-debug/debase-ruby_core_source) contains almost no code;
60
+ instead, it just contains per-Ruby-version folders with the private VM headers (`.h`) files for that version.
61
+
62
+ Thus, even though a regular Ruby installation does not include these files, we can access the copy inside this gem.
63
+
64
+ ## Feature: Getting thread CPU-time clock_ids
65
+
66
+ * **OS support**: Linux
67
+ * **Ruby support**: 2.6+
68
+
69
+ To enable CPU-time profiling, we use the `pthread_getcpuclockid(pthread_t thread, clockid_t *clockid)` C function to
70
+ obtain a `clockid_t` that can then be used with the `Process.clock_gettime` method (or directly with the
71
+ `clock_gettime()` C function).
72
+
73
+ The challenge with using `pthread_getcpuclockid()` is that we need to get the `pthread_t` for a given Ruby `Thread`
74
+ object. We previously did this with a weird combination of monkey patching and `pthread_self()` (effectively patching
75
+ every `Thread` to run `pthread_self()` at initialization time and stash that value somewhere), but this had a number
76
+ of downsides.
77
+
78
+ The approach we use in the profiling native extension is to reach inside the internal structure of the `Thread` object,
79
+ and extract the `pthread_t` that Ruby itself keeps, but does not expose. This is implemented in the `pthread_id_for()`
80
+ function in `private_vm_api_acccess.c`. Thus, using this trick we can at any point in execution go from a `Thread`
81
+ object into the `clockid_t` that we need.
82
+
83
+ Note that `pthread_getcpuclockid()` is not available on macOS (nor, obviously, on Windows), and hence this feature
84
+ is currently Linux-specific. Thus, in the <clock_id_from_pthread.c> file we implement the feature for supported Ruby
85
+ setups but if something is missing we instead compile in <clock_id_noop.c> that includes a no-op implementation of the
86
+ feature.
@@ -0,0 +1,4 @@
1
+ #pragma once
2
+
3
+ void self_test_clock_id();
4
+ VALUE clock_id_for(VALUE self, VALUE thread);
@@ -0,0 +1,52 @@
1
+ #include "extconf.h"
2
+
3
+ // This file is only compiled on systems where pthread_getcpuclockid() is available;
4
+ // Otherwise we compile clock_id_noop.c
5
+ #ifdef HAVE_PTHREAD_GETCPUCLOCKID
6
+
7
+ #include <pthread.h>
8
+ #include <time.h>
9
+ #include <errno.h>
10
+
11
+ #include <ruby.h>
12
+
13
+ #ifdef RUBY_2_1_WORKAROUND
14
+ #include <thread_native.h>
15
+ #else
16
+ #include <ruby/thread_native.h>
17
+ #endif
18
+
19
+ #include "private_vm_api_access.h"
20
+
21
+ #include "clock_id.h"
22
+
23
+ // Validate that our home-cooked pthread_id_for() matches pthread_self() for the current thread
24
+ void self_test_clock_id() {
25
+ rb_nativethread_id_t expected_pthread_id = pthread_self();
26
+ rb_nativethread_id_t actual_pthread_id = pthread_id_for(rb_thread_current());
27
+
28
+ if (expected_pthread_id != actual_pthread_id) rb_raise(rb_eRuntimeError, "pthread_id_for() self-test failed");
29
+ }
30
+
31
+ VALUE clock_id_for(VALUE self, VALUE thread) {
32
+ rb_nativethread_id_t thread_id = pthread_id_for(thread);
33
+
34
+ clockid_t clock_id;
35
+ int error = pthread_getcpuclockid(thread_id, &clock_id);
36
+
37
+ if (error == 0) {
38
+ return CLOCKID2NUM(clock_id);
39
+ } else {
40
+ switch(error) {
41
+ // The more specific error messages are based on the pthread_getcpuclockid(3) man page
42
+ case ENOENT:
43
+ rb_exc_raise(rb_syserr_new(error, "Failed to get clock_id for given thread: Per-thread CPU time clocks are not supported by the system."));
44
+ case ESRCH:
45
+ rb_exc_raise(rb_syserr_new(error, "Failed to get clock_id for given thread: No thread could be found."));
46
+ default:
47
+ rb_exc_raise(rb_syserr_new(error, "Failed to get clock_id for given thread"));
48
+ }
49
+ }
50
+ }
51
+
52
+ #endif
@@ -0,0 +1,14 @@
1
+ #include "extconf.h"
2
+
3
+ // This file is the dual of clock_id_from_pthread.c for systems where that info
4
+ // is not available.
5
+ #ifndef HAVE_PTHREAD_GETCPUCLOCKID
6
+
7
+ #include <ruby.h>
8
+
9
+ #include "clock_id.h"
10
+
11
+ void self_test_clock_id() { } // Nothing to check
12
+ VALUE clock_id_for(VALUE self, VALUE thread) { return Qnil; } // Nothing to return
13
+
14
+ #endif
@@ -1,28 +1,197 @@
1
- # typed: false
2
- def skip_building_extension?
1
+ # typed: ignore
2
+ # rubocop:disable Style/StderrPuts
3
+
4
+ # Older Rubies don't have the MJIT header, used by the JIT compiler, so we need to use a different approach
5
+ CAN_USE_MJIT_HEADER = RUBY_VERSION >= '2.6'
6
+
7
+ def on_jruby?
3
8
  # We don't support JRuby for profiling, and JRuby doesn't support native extensions, so let's just skip this entire
4
9
  # thing so that JRuby users of dd-trace-rb aren't impacted.
5
- on_jruby = RUBY_ENGINE == 'jruby'
10
+ RUBY_ENGINE == 'jruby'
11
+ end
12
+
13
+ def on_truffleruby?
14
+ # We don't officially support TruffleRuby for dd-trace-rb at all BUT let's not break adventurous customers that
15
+ # want to give it a try.
16
+ RUBY_ENGINE == 'truffleruby'
17
+ end
18
+
19
+ def on_windows?
20
+ # Microsoft Windows is unsupported, so let's not build the extension there.
21
+ Gem.win_platform?
22
+ end
23
+
24
+ def expected_to_use_mjit_but_mjit_is_disabled?
25
+ # On some Rubies, we require the mjit header to be present. If Ruby was installed without MJIT support, we also skip
26
+ # building the extension.
27
+ mjit_disabled = CAN_USE_MJIT_HEADER && RbConfig::CONFIG['MJIT_SUPPORT'] != 'yes'
28
+
29
+ if mjit_disabled
30
+ $stderr.puts(%(
31
+ +------------------------------------------------------------------------------+
32
+ | Your Ruby has been compiled without JIT support (--disable-jit-support). |
33
+ | The profiling native extension requires a Ruby compiled with JIT support, |
34
+ | even if the JIT is not in use by the application itself. |
35
+ | |
36
+ | WARNING: Without the profiling native extension, some profiling features |
37
+ | will not be available. |
38
+ +------------------------------------------------------------------------------+
39
+
40
+ ))
41
+ end
6
42
 
43
+ mjit_disabled
44
+ end
45
+
46
+ def disabled_via_env?
7
47
  # Experimental toggle to disable building the extension.
8
48
  # Disabling the extension will lead to the profiler not working in future releases.
9
49
  # If you needed to use this, please tell us why on <https://github.com/DataDog/dd-trace-rb/issues/new>.
10
- disabled_via_env = ENV['DD_PROFILING_NO_EXTENSION'].to_s.downcase == 'true'
50
+ ENV['DD_PROFILING_NO_EXTENSION'].to_s.downcase == 'true'
51
+ end
11
52
 
12
- on_jruby || disabled_via_env
53
+ def skip_building_extension?
54
+ disabled_via_env? || on_jruby? || on_truffleruby? || on_windows? || expected_to_use_mjit_but_mjit_is_disabled?
13
55
  end
14
56
 
15
- if skip_building_extension?
57
+ # IMPORTANT: When adding flags, remember that our customers compile with a wide range of gcc/clang versions, so
58
+ # doublecheck that what you're adding can be reasonably expected to work on their systems.
59
+ def add_compiler_flag(flag)
60
+ $CFLAGS << ' ' << flag
61
+ end
62
+
63
+ def skip_building_extension!
16
64
  File.write('Makefile', 'all install clean: # dummy makefile that does nothing')
17
- return
65
+ exit
66
+ end
67
+
68
+ if skip_building_extension?
69
+ $stderr.puts(%(
70
+ +------------------------------------------------------------------------------+
71
+ | Skipping build of profiling native extension and replacing it with a no-op |
72
+ | Makefile |
73
+ +------------------------------------------------------------------------------+
74
+
75
+ ))
76
+ skip_building_extension!
18
77
  end
19
78
 
79
+ $stderr.puts(%(
80
+ +------------------------------------------------------------------------------+
81
+ | ** Preparing to build the ddtrace native extension... ** |
82
+ | |
83
+ | If you run into any failures during this step, you can set the |
84
+ | `DD_PROFILING_NO_EXTENSION` environment variable to `true` e.g. |
85
+ | `$ DD_PROFILING_NO_EXTENSION=true bundle install` to skip this step. |
86
+ | |
87
+ | Disabling the extension will lead to the ddtrace profiling features not |
88
+ | working in future releases. |
89
+ | If you needed to use this, please tell us why on |
90
+ | <https://github.com/DataDog/dd-trace-rb/issues/new> so we can fix it :\) |
91
+ | |
92
+ | Thanks for using ddtrace! You rock! |
93
+ +------------------------------------------------------------------------------+
94
+
95
+ ))
96
+
20
97
  # NOTE: we MUST NOT require 'mkmf' before we check the #skip_building_extension? because the require triggers checks
21
98
  # that may fail on an environment not properly setup for building Ruby extensions.
22
99
  require 'mkmf'
23
100
 
101
+ # Gets really noisy when we include the MJIT header, let's omit it
102
+ add_compiler_flag '-Wno-unused-function'
103
+
104
+ # Allow defining variables at any point in a function
105
+ add_compiler_flag '-Wno-declaration-after-statement'
106
+
107
+ # If we forget to include a Ruby header, the function call may still appear to work, but then
108
+ # cause a segfault later. Let's ensure that never happens.
109
+ add_compiler_flag '-Werror-implicit-function-declaration'
110
+
111
+ if RUBY_PLATFORM.include?('linux')
112
+ # Supposedly, the correct way to do this is
113
+ # ```
114
+ # have_library 'pthread'
115
+ # have_func 'pthread_getcpuclockid'
116
+ # ```
117
+ # but it broke the build on Windows and on older Ruby versions (2.1 and 2.2)
118
+ # so instead we just assume that we have the function we need on Linux, and nowhere else
119
+ $defs << '-DHAVE_PTHREAD_GETCPUCLOCKID'
120
+ end
121
+
24
122
  # Tag the native extension library with the Ruby version and Ruby platform.
25
123
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
26
124
  # the wrong library is never loaded.
27
125
  # When requiring, we need to use the exact same string, including the version and the platform.
28
- create_makefile "ddtrace_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
126
+ EXTENSION_NAME = "ddtrace_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}".freeze
127
+
128
+ if CAN_USE_MJIT_HEADER
129
+ mjit_header_file_name = "rb_mjit_min_header-#{RUBY_VERSION}.h"
130
+
131
+ # Validate that the mjit header can actually be compiled on this system. We learned via
132
+ # https://github.com/DataDog/dd-trace-rb/issues/1799 and https://github.com/DataDog/dd-trace-rb/issues/1792
133
+ # that even if the header seems to exist, it may not even compile.
134
+ # `have_macro` actually tries to compile a file that mentions the given macro, so if this passes, we should be good to
135
+ # use the MJIT header.
136
+ # Finally, the `COMMON_HEADERS` conflict with the MJIT header so we need to temporarily disable them for this check.
137
+ original_common_headers = MakeMakefile::COMMON_HEADERS
138
+ MakeMakefile::COMMON_HEADERS = ''.freeze
139
+ unless have_macro('RUBY_MJIT_H', mjit_header_file_name)
140
+ $stderr.puts(%(
141
+ +------------------------------------------------------------------------------+
142
+ | WARNING: Unable to compile a needed component for ddtrace native extension. |
143
+ | Your C compiler or Ruby VM just-in-time compiler seems to be broken. |
144
+ | |
145
+ | You will be NOT be able to use ddtrace profiling features, |
146
+ | but all other features will work fine! |
147
+ | |
148
+ | For help solving this issue, please contact Datadog support at |
149
+ | <https://docs.datadoghq.com/help/>. |
150
+ +------------------------------------------------------------------------------+
151
+
152
+ ))
153
+ skip_building_extension!
154
+ end
155
+ MakeMakefile::COMMON_HEADERS = original_common_headers
156
+
157
+ $defs << '-DUSE_MJIT_HEADER'
158
+
159
+ # NOTE: This needs to come after all changes to $defs
160
+ create_header
161
+
162
+ # The MJIT header is always (afaik?) suffixed with the exact Ruby VM version,
163
+ # including patch (e.g. 2.7.2). Thus, we add to the header file a definition
164
+ # containing the exact file, so that it can be used in a #include in the C code.
165
+ header_contents =
166
+ File.read($extconf_h)
167
+ .sub('#endif',
168
+ <<-EXTCONF_H.strip
169
+ #define RUBY_MJIT_HEADER "#{mjit_header_file_name}"
170
+
171
+ #endif
172
+ EXTCONF_H
173
+ )
174
+ File.open($extconf_h, 'w') { |file| file.puts(header_contents) }
175
+
176
+ create_makefile EXTENSION_NAME
177
+ else
178
+ # On older Rubies, we use the debase-ruby_core_source gem to get access to private VM headers.
179
+ # This gem ships source code copies of these VM headers for the different Ruby VM versions;
180
+ # see https://github.com/ruby-debug/debase-ruby_core_source for details
181
+
182
+ thread_native_for_ruby_2_1 = proc { true }
183
+ if RUBY_VERSION < '2.2'
184
+ # This header became public in Ruby 2.2, but we need to pull it from the private headers folder for 2.1
185
+ thread_native_for_ruby_2_1 = proc { have_header('thread_native.h') }
186
+ $defs << '-DRUBY_2_1_WORKAROUND'
187
+ end
188
+
189
+ create_header
190
+
191
+ require 'debase/ruby_core_source'
192
+ dir_config('ruby') # allow user to pass in non-standard core include directory
193
+
194
+ Debase::RubyCoreSource
195
+ .create_makefile_with_core(proc { have_header('vm_core.h') && thread_native_for_ruby_2_1.call }, EXTENSION_NAME)
196
+ end
197
+ # rubocop:enable Style/StderrPuts
@@ -0,0 +1,35 @@
1
+ #include "extconf.h"
2
+
3
+ // This file exports functions used to access private Ruby VM APIs and internals.
4
+ // To do this, it imports a few VM internal (private) headers.
5
+ //
6
+ // **Important Note**: Our medium/long-term plan is to stop relying on all private Ruby headers, and instead request and
7
+ // contribute upstream changes so that they become official public VM APIs.
8
+ //
9
+ // In the meanwhile, be very careful when changing things here :)
10
+
11
+ #ifdef USE_MJIT_HEADER
12
+ // Pick up internal structures from the private Ruby MJIT header file
13
+ #include RUBY_MJIT_HEADER
14
+ #else
15
+ // On older Rubies, use a copy of the VM internal headers shipped in the debase-ruby_core_source gem
16
+ #include <vm_core.h>
17
+ #endif
18
+
19
+ // MRI has a similar rb_thread_ptr() function which we can't call it directly
20
+ // because Ruby does not expose the thread_data_type publicly.
21
+ // Instead, we have our own version of that function, and we lazily initialize the thread_data_type pointer
22
+ // from a known-correct object: the current thread.
23
+ //
24
+ // Note that beyond returning the rb_thread_struct*, rb_check_typeddata() raises an exception
25
+ // if the argument passed in is not actually a `Thread` instance.
26
+ static inline struct rb_thread_struct *thread_struct_from_object(VALUE thread) {
27
+ static const rb_data_type_t *thread_data_type = NULL;
28
+ if (thread_data_type == NULL) thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
29
+
30
+ return (struct rb_thread_struct *) rb_check_typeddata(thread, thread_data_type);
31
+ }
32
+
33
+ rb_nativethread_id_t pthread_id_for(VALUE thread) {
34
+ return thread_struct_from_object(thread)->thread_id;
35
+ }
@@ -0,0 +1,3 @@
1
+ #pragma once
2
+
3
+ rb_nativethread_id_t pthread_id_for(VALUE thread);
@@ -1,5 +1,6 @@
1
1
  #include <ruby.h>
2
- #include <stdio.h>
2
+
3
+ #include "clock_id.h"
3
4
 
4
5
  static VALUE native_working_p(VALUE self);
5
6
 
@@ -10,8 +11,12 @@ void Init_ddtrace_profiling_native_extension(void) {
10
11
 
11
12
  rb_define_singleton_method(native_extension_module, "native_working?", native_working_p, 0);
12
13
  rb_funcall(native_extension_module, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("native_working?")));
14
+
15
+ rb_define_singleton_method(native_extension_module, "clock_id_for", clock_id_for, 1); // from clock_id.h
13
16
  }
14
17
 
15
18
  static VALUE native_working_p(VALUE self) {
19
+ self_test_clock_id();
20
+
16
21
  return Qtrue;
17
22
  }
@@ -39,6 +39,7 @@ module Datadog
39
39
  service: configuration[:service_name]
40
40
  },
41
41
  framework: Ext::FRAMEWORK,
42
+ framework_version: Datadog::CI::Contrib::Cucumber::Integration.version.to_s,
42
43
  test_name: event.test_case.name,
43
44
  test_suite: event.test_case.location.file,
44
45
  test_type: Ext::TEST_TYPE
@@ -37,6 +37,7 @@ module Datadog
37
37
  service: configuration[:service_name]
38
38
  },
39
39
  framework: Ext::FRAMEWORK,
40
+ framework_version: Datadog::CI::Contrib::RSpec::Integration.version.to_s,
40
41
  test_name: test_name,
41
42
  test_suite: file_path,
42
43
  test_type: Ext::TEST_TYPE
@@ -17,8 +17,8 @@ module Datadog
17
17
  register_as :rspec, auto_patch: true
18
18
 
19
19
  def self.version
20
- Gem.loaded_specs['rspec'] \
21
- && Gem.loaded_specs['rspec'].version
20
+ Gem.loaded_specs['rspec-core'] \
21
+ && Gem.loaded_specs['rspec-core'].version
22
22
  end
23
23
 
24
24
  def self.loaded?
@@ -40,22 +40,27 @@ module Datadog
40
40
  module_function
41
41
 
42
42
  def tags(env)
43
+ # Extract metadata from CI provider environment variables
43
44
  _, extractor = PROVIDERS.find { |provider_env_var, _| env.key?(provider_env_var) }
44
- if extractor
45
- tags = public_send(extractor, env)
46
-
47
- tags[Datadog::Ext::Git::TAG_TAG] = normalize_ref(tags[Datadog::Ext::Git::TAG_TAG])
48
- tags.delete(Datadog::Ext::Git::TAG_BRANCH) unless tags[Datadog::Ext::Git::TAG_TAG].nil?
49
- tags[Datadog::Ext::Git::TAG_BRANCH] = normalize_ref(tags[Datadog::Ext::Git::TAG_BRANCH])
50
- tags[Datadog::Ext::Git::TAG_REPOSITORY_URL] = filter_sensitive_info(tags[Datadog::Ext::Git::TAG_REPOSITORY_URL])
51
-
52
- # Expand ~
53
- workspace_path = tags[TAG_WORKSPACE_PATH]
54
- if !workspace_path.nil? && (workspace_path == '~' || workspace_path.start_with?('~/'))
55
- tags[TAG_WORKSPACE_PATH] = File.expand_path(workspace_path)
56
- end
57
- else
58
- tags = {}
45
+ tags = extractor ? public_send(extractor, env).reject { |_, v| v.nil? || v.strip.empty? } : {}
46
+ tags.delete(Datadog::Ext::Git::TAG_BRANCH) unless tags[Datadog::Ext::Git::TAG_TAG].nil?
47
+
48
+ # If user defined metadata is defined, overwrite
49
+ tags.merge!(extract_user_defined_git(env))
50
+ if !tags[Datadog::Ext::Git::TAG_BRANCH].nil? && tags[Datadog::Ext::Git::TAG_BRANCH].include?('tags/')
51
+ tags[Datadog::Ext::Git::TAG_TAG] = tags[Datadog::Ext::Git::TAG_BRANCH]
52
+ tags.delete(Datadog::Ext::Git::TAG_BRANCH)
53
+ end
54
+
55
+ # Normalize Git references
56
+ tags[Datadog::Ext::Git::TAG_TAG] = normalize_ref(tags[Datadog::Ext::Git::TAG_TAG])
57
+ tags[Datadog::Ext::Git::TAG_BRANCH] = normalize_ref(tags[Datadog::Ext::Git::TAG_BRANCH])
58
+ tags[Datadog::Ext::Git::TAG_REPOSITORY_URL] = filter_sensitive_info(tags[Datadog::Ext::Git::TAG_REPOSITORY_URL])
59
+
60
+ # Expand ~
61
+ workspace_path = tags[TAG_WORKSPACE_PATH]
62
+ if !workspace_path.nil? && (workspace_path == '~' || workspace_path.start_with?('~/'))
63
+ tags[TAG_WORKSPACE_PATH] = File.expand_path(workspace_path)
59
64
  end
60
65
 
61
66
  # Fill out tags from local git as fallback
@@ -131,6 +136,8 @@ module Datadog
131
136
  TAG_PIPELINE_NUMBER => build_id,
132
137
  TAG_PIPELINE_URL => pipeline_url,
133
138
  TAG_JOB_URL => job_url,
139
+ TAG_STAGE_NAME => env['SYSTEM_STAGEDISPLAYNAME'],
140
+ TAG_JOB_NAME => env['SYSTEM_JOBDISPLAYNAME'],
134
141
  Datadog::Ext::Git::TAG_REPOSITORY_URL =>
135
142
  env['SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI'] || env['BUILD_REPOSITORY_URI'],
136
143
  Datadog::Ext::Git::TAG_COMMIT_SHA => env['SYSTEM_PULLREQUEST_SOURCECOMMITID'] || env['BUILD_SOURCEVERSION'],
@@ -206,16 +213,19 @@ module Datadog
206
213
  ref = env['GITHUB_REF'] if ref.nil? || ref.empty?
207
214
  branch, tag = branch_or_tag(ref)
208
215
 
216
+ pipeline_url = "#{env['GITHUB_SERVER_URL']}/#{env['GITHUB_REPOSITORY']}/actions/runs/#{env['GITHUB_RUN_ID']}"
217
+ pipeline_url = "#{pipeline_url}/attempts/#{env['GITHUB_RUN_ATTEMPT']}" if env['GITHUB_RUN_ATTEMPT']
218
+
209
219
  {
210
220
  Datadog::Ext::Git::TAG_BRANCH => branch,
211
221
  Datadog::Ext::Git::TAG_COMMIT_SHA => env['GITHUB_SHA'],
212
- Datadog::Ext::Git::TAG_REPOSITORY_URL => "https://github.com/#{env['GITHUB_REPOSITORY']}.git",
222
+ Datadog::Ext::Git::TAG_REPOSITORY_URL => "#{env['GITHUB_SERVER_URL']}/#{env['GITHUB_REPOSITORY']}.git",
213
223
  Datadog::Ext::Git::TAG_TAG => tag,
214
- TAG_JOB_URL => "https://github.com/#{env['GITHUB_REPOSITORY']}/commit/#{env['GITHUB_SHA']}/checks",
224
+ TAG_JOB_URL => "#{env['GITHUB_SERVER_URL']}/#{env['GITHUB_REPOSITORY']}/commit/#{env['GITHUB_SHA']}/checks",
215
225
  TAG_PIPELINE_ID => env['GITHUB_RUN_ID'],
216
226
  TAG_PIPELINE_NAME => env['GITHUB_WORKFLOW'],
217
227
  TAG_PIPELINE_NUMBER => env['GITHUB_RUN_NUMBER'],
218
- TAG_PIPELINE_URL => "https://github.com/#{env['GITHUB_REPOSITORY']}/commit/#{env['GITHUB_SHA']}/checks",
228
+ TAG_PIPELINE_URL => pipeline_url,
219
229
  TAG_PROVIDER_NAME => 'github',
220
230
  TAG_WORKSPACE_PATH => env['GITHUB_WORKSPACE'],
221
231
  Datadog::Ext::Git::TAG_COMMIT_AUTHOR_NAME => env['BUILD_REQUESTEDFORID'],
@@ -225,12 +235,17 @@ module Datadog
225
235
  end
226
236
 
227
237
  def extract_gitlab(env)
238
+ commit_author_name, commit_author_email = extract_name_email(env['CI_COMMIT_AUTHOR'])
239
+
228
240
  url = env['CI_PIPELINE_URL']
229
241
  {
230
- Datadog::Ext::Git::TAG_BRANCH => env['CI_COMMIT_BRANCH'],
242
+ Datadog::Ext::Git::TAG_BRANCH => env['CI_COMMIT_REF_NAME'],
231
243
  Datadog::Ext::Git::TAG_COMMIT_SHA => env['CI_COMMIT_SHA'],
232
244
  Datadog::Ext::Git::TAG_REPOSITORY_URL => env['CI_REPOSITORY_URL'],
233
245
  Datadog::Ext::Git::TAG_TAG => env['CI_COMMIT_TAG'],
246
+ Datadog::Ext::Git::TAG_COMMIT_AUTHOR_NAME => commit_author_name,
247
+ Datadog::Ext::Git::TAG_COMMIT_AUTHOR_EMAIL => commit_author_email,
248
+ Datadog::Ext::Git::TAG_COMMIT_AUTHOR_DATE => env['CI_COMMIT_TIMESTAMP'],
234
249
  TAG_STAGE_NAME => env['CI_JOB_STAGE'],
235
250
  TAG_JOB_NAME => env['CI_JOB_NAME'],
236
251
  TAG_JOB_URL => env['CI_JOB_URL'],
@@ -254,7 +269,7 @@ module Datadog
254
269
  {
255
270
  Datadog::Ext::Git::TAG_BRANCH => branch,
256
271
  Datadog::Ext::Git::TAG_COMMIT_SHA => env['GIT_COMMIT'],
257
- Datadog::Ext::Git::TAG_REPOSITORY_URL => env['GIT_URL'],
272
+ Datadog::Ext::Git::TAG_REPOSITORY_URL => env['GIT_URL'] || env['GIT_URL_1'],
258
273
  Datadog::Ext::Git::TAG_TAG => tag,
259
274
  TAG_PIPELINE_ID => env['BUILD_TAG'],
260
275
  TAG_PIPELINE_NAME => name,
@@ -312,7 +327,7 @@ module Datadog
312
327
  {
313
328
  TAG_PROVIDER_NAME => 'bitrise',
314
329
  TAG_PIPELINE_ID => env['BITRISE_BUILD_SLUG'],
315
- TAG_PIPELINE_NAME => env['BITRISE_APP_TITLE'],
330
+ TAG_PIPELINE_NAME => env['BITRISE_TRIGGERED_WORKFLOW_ID'],
316
331
  TAG_PIPELINE_NUMBER => env['BITRISE_BUILD_NUMBER'],
317
332
  TAG_PIPELINE_URL => env['BITRISE_BUILD_URL'],
318
333
  TAG_WORKSPACE_PATH => env['BITRISE_SOURCE_DIR'],
@@ -324,6 +339,22 @@ module Datadog
324
339
  }
325
340
  end
326
341
 
342
+ def extract_user_defined_git(env)
343
+ {
344
+ Datadog::Ext::Git::TAG_REPOSITORY_URL => env[Datadog::Ext::Git::ENV_REPOSITORY_URL],
345
+ Datadog::Ext::Git::TAG_COMMIT_SHA => env[Datadog::Ext::Git::ENV_COMMIT_SHA],
346
+ Datadog::Ext::Git::TAG_BRANCH => env[Datadog::Ext::Git::ENV_BRANCH],
347
+ Datadog::Ext::Git::TAG_TAG => env[Datadog::Ext::Git::ENV_TAG],
348
+ Datadog::Ext::Git::TAG_COMMIT_MESSAGE => env[Datadog::Ext::Git::ENV_COMMIT_MESSAGE],
349
+ Datadog::Ext::Git::TAG_COMMIT_AUTHOR_NAME => env[Datadog::Ext::Git::ENV_COMMIT_AUTHOR_NAME],
350
+ Datadog::Ext::Git::TAG_COMMIT_AUTHOR_EMAIL => env[Datadog::Ext::Git::ENV_COMMIT_AUTHOR_EMAIL],
351
+ Datadog::Ext::Git::TAG_COMMIT_AUTHOR_DATE => env[Datadog::Ext::Git::ENV_COMMIT_AUTHOR_DATE],
352
+ Datadog::Ext::Git::TAG_COMMIT_COMMITTER_NAME => env[Datadog::Ext::Git::ENV_COMMIT_COMMITTER_NAME],
353
+ Datadog::Ext::Git::TAG_COMMIT_COMMITTER_EMAIL => env[Datadog::Ext::Git::ENV_COMMIT_COMMITTER_EMAIL],
354
+ Datadog::Ext::Git::TAG_COMMIT_COMMITTER_DATE => env[Datadog::Ext::Git::ENV_COMMIT_COMMITTER_DATE]
355
+ }.reject { |_, v| v.nil? || v.strip.empty? }
356
+ end
357
+
327
358
  def git_commit_users
328
359
  # Get committer and author information in one command.
329
360
  output = exec_git_command("git show -s --format='%an\t%ae\t%at\t%cn\t%ce\t%ct'")
@@ -428,7 +459,7 @@ module Datadog
428
459
 
429
460
  def branch_or_tag(branch_or_tag)
430
461
  branch = tag = nil
431
- if branch_or_tag.include?('tags/')
462
+ if branch_or_tag && branch_or_tag.include?('tags/')
432
463
  tag = branch_or_tag
433
464
  else
434
465
  branch = branch_or_tag
@@ -436,6 +467,17 @@ module Datadog
436
467
 
437
468
  [branch, tag]
438
469
  end
470
+
471
+ def extract_name_email(name_and_email)
472
+ if name_and_email.include?('<') && (match = /^([^<]*)<([^>]*)>$/.match(name_and_email))
473
+ name = match[1]
474
+ name = name.strip if name
475
+ email = match[2]
476
+ return [name, email] if name && email
477
+ end
478
+
479
+ [nil, name_and_email]
480
+ end
439
481
  end
440
482
  # rubocop:enable Metrics/ModuleLength:
441
483
  end
@@ -8,6 +8,7 @@ module Datadog
8
8
 
9
9
  TAG_ARGUMENTS = 'test.arguments'.freeze
10
10
  TAG_FRAMEWORK = 'test.framework'.freeze
11
+ TAG_FRAMEWORK_VERSION = 'test.framework_version'.freeze
11
12
  TAG_NAME = 'test.name'.freeze
12
13
  TAG_SKIP_REASON = 'test.skip_reason'.freeze # DEV: Not populated yet
13
14
  TAG_STATUS = 'test.status'.freeze
@@ -38,10 +38,14 @@ module Datadog
38
38
  span.context.origin = Ext::Test::CONTEXT_ORIGIN if span.context
39
39
  Datadog::Contrib::Analytics.set_measured(span)
40
40
  span.set_tag(Ext::Test::TAG_SPAN_KIND, Ext::AppTypes::TEST)
41
- Ext::Environment.tags(ENV).each { |k, v| span.set_tag(k, v) }
41
+
42
+ # Set environment tags
43
+ @environment_tags ||= Ext::Environment.tags(ENV)
44
+ @environment_tags.each { |k, v| span.set_tag(k, v) }
42
45
 
43
46
  # Set contextual tags
44
47
  span.set_tag(Ext::Test::TAG_FRAMEWORK, tags[:framework]) if tags[:framework]
48
+ span.set_tag(Ext::Test::TAG_FRAMEWORK_VERSION, tags[:framework_version]) if tags[:framework_version]
45
49
  span.set_tag(Ext::Test::TAG_NAME, tags[:test_name]) if tags[:test_name]
46
50
  span.set_tag(Ext::Test::TAG_SUITE, tags[:test_suite]) if tags[:test_suite]
47
51
  span.set_tag(Ext::Test::TAG_TYPE, tags[:test_type]) if tags[:test_type]