ddtrace 0.52.0 → 0.54.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +174 -11
- data/ddtrace.gemspec +6 -3
- data/docs/DevelopmentGuide.md +1 -6
- data/docs/GettingStarted.md +109 -18
- data/docs/ProfilingDevelopment.md +2 -2
- data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +86 -0
- data/ext/ddtrace_profiling_native_extension/clock_id.h +4 -0
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +52 -0
- data/ext/ddtrace_profiling_native_extension/clock_id_noop.c +14 -0
- data/ext/ddtrace_profiling_native_extension/extconf.rb +177 -8
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +35 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +6 -1
- data/lib/datadog/ci/contrib/cucumber/formatter.rb +1 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +1 -0
- data/lib/datadog/ci/contrib/rspec/integration.rb +2 -2
- data/lib/datadog/ci/ext/environment.rb +64 -22
- data/lib/datadog/ci/ext/test.rb +1 -0
- data/lib/datadog/ci/test.rb +5 -1
- data/lib/datadog/contrib.rb +2 -0
- data/lib/datadog/core/environment/vm_cache.rb +46 -0
- data/lib/ddtrace/buffer.rb +28 -16
- data/lib/ddtrace/configuration/agent_settings_resolver.rb +131 -53
- data/lib/ddtrace/configuration/components.rb +1 -1
- data/lib/ddtrace/configuration/settings.rb +13 -3
- data/lib/ddtrace/context.rb +10 -2
- data/lib/ddtrace/contrib/action_cable/instrumentation.rb +46 -0
- data/lib/ddtrace/contrib/action_cable/patcher.rb +1 -0
- data/lib/ddtrace/contrib/action_mailer/configuration/settings.rb +32 -0
- data/lib/ddtrace/contrib/action_mailer/event.rb +50 -0
- data/lib/ddtrace/contrib/action_mailer/events/deliver.rb +54 -0
- data/lib/ddtrace/contrib/action_mailer/events/process.rb +41 -0
- data/lib/ddtrace/contrib/action_mailer/events.rb +31 -0
- data/lib/ddtrace/contrib/action_mailer/ext.rb +32 -0
- data/lib/ddtrace/contrib/action_mailer/integration.rb +45 -0
- data/lib/ddtrace/contrib/action_mailer/patcher.rb +27 -0
- data/lib/ddtrace/contrib/active_job/configuration/settings.rb +33 -0
- data/lib/ddtrace/contrib/active_job/event.rb +54 -0
- data/lib/ddtrace/contrib/active_job/events/discard.rb +46 -0
- data/lib/ddtrace/contrib/active_job/events/enqueue.rb +45 -0
- data/lib/ddtrace/contrib/active_job/events/enqueue_at.rb +45 -0
- data/lib/ddtrace/contrib/active_job/events/enqueue_retry.rb +47 -0
- data/lib/ddtrace/contrib/active_job/events/perform.rb +45 -0
- data/lib/ddtrace/contrib/active_job/events/retry_stopped.rb +46 -0
- data/lib/ddtrace/contrib/active_job/events.rb +39 -0
- data/lib/ddtrace/contrib/active_job/ext.rb +32 -0
- data/lib/ddtrace/contrib/active_job/integration.rb +46 -0
- data/lib/ddtrace/contrib/active_job/log_injection.rb +21 -0
- data/lib/ddtrace/contrib/active_job/patcher.rb +33 -0
- data/lib/ddtrace/contrib/auto_instrument.rb +0 -1
- data/lib/ddtrace/contrib/delayed_job/plugin.rb +2 -2
- data/lib/ddtrace/contrib/mongodb/instrumentation.rb +1 -1
- data/lib/ddtrace/contrib/mongodb/integration.rb +5 -0
- data/lib/ddtrace/contrib/rails/auto_instrument_railtie.rb +0 -1
- data/lib/ddtrace/contrib/rails/configuration/settings.rb +7 -0
- data/lib/ddtrace/contrib/rails/framework.rb +24 -1
- data/lib/ddtrace/contrib/rails/patcher.rb +19 -10
- data/lib/ddtrace/contrib/redis/instrumentation.rb +90 -0
- data/lib/ddtrace/contrib/redis/patcher.rb +2 -84
- data/lib/ddtrace/contrib/registerable.rb +0 -1
- data/lib/ddtrace/contrib/resque/integration.rb +1 -5
- data/lib/ddtrace/contrib/sidekiq/ext.rb +3 -0
- data/lib/ddtrace/contrib/sidekiq/integration.rb +10 -0
- data/lib/ddtrace/contrib/sidekiq/patcher.rb +26 -0
- data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/heartbeat.rb +30 -0
- data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/job_fetch.rb +30 -0
- data/lib/ddtrace/contrib/sidekiq/server_internal_tracer/scheduled_push.rb +29 -0
- data/lib/ddtrace/contrib/sinatra/env.rb +2 -1
- data/lib/ddtrace/contrib/sinatra/tracer.rb +15 -2
- data/lib/ddtrace/ext/git.rb +12 -0
- data/lib/ddtrace/ext/priority.rb +6 -4
- data/lib/ddtrace/ext/profiling.rb +8 -11
- data/lib/ddtrace/ext/runtime.rb +3 -0
- data/lib/ddtrace/ext/transport.rb +11 -0
- data/lib/ddtrace/metrics.rb +2 -2
- data/lib/ddtrace/profiling/collectors/stack.rb +112 -72
- data/lib/ddtrace/profiling/encoding/profile.rb +10 -2
- data/lib/ddtrace/profiling/events/stack.rb +13 -13
- data/lib/ddtrace/profiling/native_extension.rb +23 -1
- data/lib/ddtrace/profiling/pprof/builder.rb +8 -2
- data/lib/ddtrace/profiling/pprof/converter.rb +22 -9
- data/lib/ddtrace/profiling/pprof/stack_sample.rb +32 -9
- data/lib/ddtrace/profiling/pprof/template.rb +2 -2
- data/lib/ddtrace/profiling/scheduler.rb +20 -4
- data/lib/ddtrace/profiling/tasks/setup.rb +21 -13
- data/lib/ddtrace/profiling/trace_identifiers/ddtrace.rb +10 -9
- data/lib/ddtrace/profiling/trace_identifiers/helper.rb +5 -5
- data/lib/ddtrace/profiling/transport/http/api/endpoint.rb +8 -15
- data/lib/ddtrace/profiling/transport/http.rb +8 -17
- data/lib/ddtrace/profiling.rb +0 -2
- data/lib/ddtrace/runtime/metrics.rb +14 -0
- data/lib/ddtrace/sampler.rb +18 -8
- data/lib/ddtrace/sampling/rule_sampler.rb +13 -1
- data/lib/ddtrace/span.rb +7 -19
- data/lib/ddtrace/tracer.rb +1 -1
- data/lib/ddtrace/transport/http/adapters/net.rb +13 -3
- data/lib/ddtrace/transport/http/adapters/test.rb +4 -2
- data/lib/ddtrace/transport/http/adapters/unix_socket.rb +23 -12
- data/lib/ddtrace/transport/http/builder.rb +13 -6
- data/lib/ddtrace/transport/http.rb +5 -11
- data/lib/ddtrace/utils/time.rb +11 -6
- data/lib/ddtrace/version.rb +2 -2
- data/lib/ddtrace/workers/{loop.rb → interval_loop.rb} +0 -16
- data/lib/ddtrace/workers/polling.rb +1 -1
- metadata +40 -10
- data/lib/ddtrace/profiling/ext/cpu.rb +0 -67
- 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
|
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
|
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,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:
|
2
|
-
|
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
|
-
|
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
|
-
|
50
|
+
ENV['DD_PROFILING_NO_EXTENSION'].to_s.downcase == 'true'
|
51
|
+
end
|
11
52
|
|
12
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
-
|
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
|
@@ -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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
tags[Datadog::Ext::Git::
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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 => "
|
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 => "
|
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 =>
|
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['
|
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['
|
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
|
data/lib/datadog/ci/ext/test.rb
CHANGED
@@ -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
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -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
|
-
|
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]
|