ddtrace 0.52.0 → 0.54.2

Sign up to get free protection for your applications and to get access to all the features.
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]