ddtrace 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -16
- data/CHANGELOG.md +31 -2
- data/LICENSE-3rdparty.csv +3 -2
- data/README.md +2 -2
- data/ddtrace.gemspec +12 -3
- data/docs/GettingStarted.md +19 -2
- data/docs/ProfilingDevelopment.md +8 -8
- data/docs/UpgradeGuide.md +3 -3
- data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +118 -0
- data/ext/ddtrace_profiling_loader/extconf.rb +53 -0
- data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +31 -5
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +0 -8
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +278 -0
- data/ext/ddtrace_profiling_native_extension/extconf.rb +70 -100
- data/ext/ddtrace_profiling_native_extension/libddprof_helpers.h +13 -0
- data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +186 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +579 -7
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +30 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +7 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +139 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +28 -0
- data/lib/datadog/appsec/autoload.rb +2 -2
- data/lib/datadog/appsec/configuration/settings.rb +19 -0
- data/lib/datadog/appsec/configuration.rb +8 -0
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +76 -33
- data/lib/datadog/appsec/contrib/rack/integration.rb +1 -0
- data/lib/datadog/appsec/contrib/rack/patcher.rb +0 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +64 -0
- data/lib/datadog/appsec/contrib/rack/request.rb +6 -0
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +41 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +60 -5
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +81 -0
- data/lib/datadog/appsec/contrib/rails/patcher.rb +34 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +68 -0
- data/lib/datadog/appsec/contrib/rails/request.rb +33 -0
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +124 -0
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +69 -2
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +63 -0
- data/lib/datadog/appsec/event.rb +33 -18
- data/lib/datadog/appsec/extensions.rb +0 -3
- data/lib/datadog/appsec/processor.rb +45 -2
- data/lib/datadog/appsec/rate_limiter.rb +5 -0
- data/lib/datadog/appsec/reactive/operation.rb +0 -1
- data/lib/datadog/ci/ext/environment.rb +21 -7
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -1
- data/lib/datadog/core/configuration/components.rb +22 -4
- data/lib/datadog/core/configuration/settings.rb +3 -3
- data/lib/datadog/core/configuration.rb +7 -5
- data/lib/datadog/core/environment/cgroup.rb +3 -1
- data/lib/datadog/core/environment/container.rb +2 -1
- data/lib/datadog/core/environment/variable_helpers.rb +26 -2
- data/lib/datadog/core/logging/ext.rb +11 -0
- data/lib/datadog/core/metrics/client.rb +15 -5
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/workers/async.rb +3 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +0 -3
- data/lib/datadog/core.rb +6 -0
- data/lib/datadog/kit/enable_core_dumps.rb +50 -0
- data/lib/datadog/kit/identity.rb +63 -0
- data/lib/datadog/kit.rb +11 -0
- data/lib/datadog/opentracer/tracer.rb +0 -2
- data/lib/datadog/profiling/collectors/old_stack.rb +298 -0
- data/lib/datadog/profiling/collectors/stack.rb +6 -287
- data/lib/datadog/profiling/encoding/profile.rb +0 -1
- data/lib/datadog/profiling/ext.rb +1 -1
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/load_native_extension.rb +22 -0
- data/lib/datadog/profiling/recorder.rb +1 -1
- data/lib/datadog/profiling/scheduler.rb +1 -1
- data/lib/datadog/profiling/stack_recorder.rb +33 -0
- data/lib/datadog/profiling/tag_builder.rb +48 -0
- data/lib/datadog/profiling/tasks/exec.rb +2 -2
- data/lib/datadog/profiling/tasks/setup.rb +6 -4
- data/lib/datadog/profiling.rb +29 -27
- data/lib/datadog/tracing/buffer.rb +9 -3
- data/lib/datadog/tracing/contrib/action_view/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/vendor/connection_specification.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +4 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +10 -3
- data/lib/datadog/tracing/contrib/dalli/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +9 -3
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +38 -2
- data/lib/datadog/tracing/contrib/ethon/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/extensions.rb +0 -2
- data/lib/datadog/tracing/contrib/faraday/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/grape/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/grpc/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/qless/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/que/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/racecar/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +3 -16
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/rake/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/redis/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/resque/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/rest_client/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +20 -1
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +11 -0
- data/lib/datadog/tracing/contrib/sinatra/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/sneakers/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +0 -1
- data/lib/datadog/tracing/event.rb +2 -1
- data/lib/datadog/tracing/sampling/priority_sampler.rb +4 -5
- data/lib/datadog/tracing/sampling/rule.rb +12 -6
- data/lib/datadog/tracing/sampling/rule_sampler.rb +3 -5
- data/lib/datadog/tracing/span_operation.rb +2 -3
- data/lib/datadog/tracing/trace_operation.rb +0 -1
- data/lib/ddtrace/transport/http/client.rb +2 -1
- data/lib/ddtrace/transport/http/response.rb +34 -4
- data/lib/ddtrace/transport/io/client.rb +3 -1
- data/lib/ddtrace/version.rb +1 -1
- data/lib/ddtrace.rb +1 -0
- metadata +43 -6
@@ -0,0 +1,278 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/debug.h>
|
3
|
+
#include "extconf.h"
|
4
|
+
#include "libddprof_helpers.h"
|
5
|
+
#include "private_vm_api_access.h"
|
6
|
+
#include "stack_recorder.h"
|
7
|
+
|
8
|
+
// Gathers stack traces from running threads, storing them in a StackRecorder instance
|
9
|
+
// This file implements the native bits of the Datadog::Profiling::Collectors::Stack class
|
10
|
+
|
11
|
+
#define MAX_FRAMES_LIMIT 10000
|
12
|
+
#define MAX_FRAMES_LIMIT_AS_STRING "10000"
|
13
|
+
|
14
|
+
static VALUE missing_string = Qnil;
|
15
|
+
|
16
|
+
// Used as scratch space during sampling
|
17
|
+
typedef struct sampling_buffer {
|
18
|
+
unsigned int max_frames;
|
19
|
+
VALUE *stack_buffer;
|
20
|
+
int *lines_buffer;
|
21
|
+
bool *is_ruby_frame;
|
22
|
+
ddprof_ffi_Location *locations;
|
23
|
+
ddprof_ffi_Line *lines;
|
24
|
+
} sampling_buffer;
|
25
|
+
|
26
|
+
static VALUE _native_sample(VALUE self, VALUE thread, VALUE recorder_instance, VALUE metric_values_hash, VALUE labels_array, VALUE max_frames);
|
27
|
+
void sample(VALUE thread, sampling_buffer* buffer, VALUE recorder_instance, ddprof_ffi_Slice_i64 metric_values, ddprof_ffi_Slice_label labels);
|
28
|
+
void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer* buffer, char *frames_omitted_message, int frames_omitted_message_size);
|
29
|
+
void record_placeholder_stack_in_native_code(VALUE recorder_instance, ddprof_ffi_Slice_i64 metric_values, ddprof_ffi_Slice_label labels);
|
30
|
+
sampling_buffer *sampling_buffer_new(unsigned int max_frames);
|
31
|
+
void sampling_buffer_free(sampling_buffer *buffer);
|
32
|
+
|
33
|
+
void collectors_stack_init(VALUE profiling_module) {
|
34
|
+
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
35
|
+
VALUE collectors_stack_class = rb_define_class_under(collectors_module, "Stack", rb_cObject);
|
36
|
+
|
37
|
+
rb_define_singleton_method(collectors_stack_class, "_native_sample", _native_sample, 5);
|
38
|
+
|
39
|
+
missing_string = rb_str_new2("");
|
40
|
+
rb_global_variable(&missing_string);
|
41
|
+
}
|
42
|
+
|
43
|
+
// This method exists only to enable testing Collectors::Stack behavior using RSpec.
|
44
|
+
// It SHOULD NOT be used for other purposes.
|
45
|
+
static VALUE _native_sample(VALUE self, VALUE thread, VALUE recorder_instance, VALUE metric_values_hash, VALUE labels_array, VALUE max_frames) {
|
46
|
+
Check_Type(metric_values_hash, T_HASH);
|
47
|
+
Check_Type(labels_array, T_ARRAY);
|
48
|
+
|
49
|
+
if (RHASH_SIZE(metric_values_hash) != ENABLED_VALUE_TYPES_COUNT) {
|
50
|
+
rb_raise(
|
51
|
+
rb_eArgError,
|
52
|
+
"Mismatched values for metrics; expected %lu values and got %lu instead",
|
53
|
+
ENABLED_VALUE_TYPES_COUNT,
|
54
|
+
RHASH_SIZE(metric_values_hash)
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
58
|
+
int64_t metric_values[ENABLED_VALUE_TYPES_COUNT];
|
59
|
+
for (unsigned int i = 0; i < ENABLED_VALUE_TYPES_COUNT; i++) {
|
60
|
+
VALUE metric_value = rb_hash_fetch(metric_values_hash, rb_str_new_cstr(enabled_value_types[i].type_.ptr));
|
61
|
+
metric_values[i] = NUM2LONG(metric_value);
|
62
|
+
}
|
63
|
+
|
64
|
+
long labels_count = RARRAY_LEN(labels_array);
|
65
|
+
ddprof_ffi_Label labels[labels_count];
|
66
|
+
|
67
|
+
for (int i = 0; i < labels_count; i++) {
|
68
|
+
VALUE key_str_pair = rb_ary_entry(labels_array, i);
|
69
|
+
|
70
|
+
labels[i] = (ddprof_ffi_Label) {
|
71
|
+
.key = char_slice_from_ruby_string(rb_ary_entry(key_str_pair, 0)),
|
72
|
+
.str = char_slice_from_ruby_string(rb_ary_entry(key_str_pair, 1))
|
73
|
+
};
|
74
|
+
}
|
75
|
+
|
76
|
+
int max_frames_requested = NUM2INT(max_frames);
|
77
|
+
if (max_frames_requested < 0) rb_raise(rb_eArgError, "Invalid max_frames: value must not be negative");
|
78
|
+
|
79
|
+
sampling_buffer *buffer = sampling_buffer_new(max_frames_requested);
|
80
|
+
|
81
|
+
sample(
|
82
|
+
thread,
|
83
|
+
buffer,
|
84
|
+
recorder_instance,
|
85
|
+
(ddprof_ffi_Slice_i64) {.ptr = metric_values, .len = ENABLED_VALUE_TYPES_COUNT},
|
86
|
+
(ddprof_ffi_Slice_label) {.ptr = labels, .len = labels_count}
|
87
|
+
);
|
88
|
+
|
89
|
+
sampling_buffer_free(buffer);
|
90
|
+
|
91
|
+
return Qtrue;
|
92
|
+
}
|
93
|
+
|
94
|
+
void sample(VALUE thread, sampling_buffer* buffer, VALUE recorder_instance, ddprof_ffi_Slice_i64 metric_values, ddprof_ffi_Slice_label labels) {
|
95
|
+
int captured_frames = ddtrace_rb_profile_frames(
|
96
|
+
thread,
|
97
|
+
0 /* stack starting depth */,
|
98
|
+
buffer->max_frames,
|
99
|
+
buffer->stack_buffer,
|
100
|
+
buffer->lines_buffer,
|
101
|
+
buffer->is_ruby_frame
|
102
|
+
);
|
103
|
+
|
104
|
+
// Idea: Should we release the global vm lock (GVL) after we get the data from `rb_profile_frames`? That way other Ruby threads
|
105
|
+
// could continue making progress while the sample was ingested into the profile.
|
106
|
+
//
|
107
|
+
// Other things to take into consideration if we go in that direction:
|
108
|
+
// * Is it safe to call `rb_profile_frame_...` methods on things from the `stack_buffer` without the GVL acquired?
|
109
|
+
// * We need to make `VALUE` references in the `stack_buffer` visible to the Ruby GC
|
110
|
+
// * Should we move this into a different thread entirely?
|
111
|
+
// * If we don't move it into a different thread, does releasing the GVL on a Ruby thread mean that we're introducing
|
112
|
+
// a new thread switch point where there previously was none?
|
113
|
+
|
114
|
+
// Ruby does not give us path and line number for methods implemented using native code.
|
115
|
+
// The convention in Kernel#caller_locations is to instead use the path and line number of the first Ruby frame
|
116
|
+
// on the stack that is below (e.g. directly or indirectly has called) the native method.
|
117
|
+
// Thus, we keep that frame here to able to replicate that behavior.
|
118
|
+
// (This is why we also iterate the sampling buffers backwards below -- so that it's easier to keep the last_ruby_frame)
|
119
|
+
VALUE last_ruby_frame = Qnil;
|
120
|
+
int last_ruby_line = 0;
|
121
|
+
|
122
|
+
if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
|
123
|
+
record_placeholder_stack_in_native_code(recorder_instance, metric_values, labels);
|
124
|
+
return;
|
125
|
+
}
|
126
|
+
|
127
|
+
for (int i = captured_frames - 1; i >= 0; i--) {
|
128
|
+
VALUE name, filename;
|
129
|
+
int line;
|
130
|
+
|
131
|
+
if (buffer->is_ruby_frame[i]) {
|
132
|
+
last_ruby_frame = buffer->stack_buffer[i];
|
133
|
+
last_ruby_line = buffer->lines_buffer[i];
|
134
|
+
|
135
|
+
name = rb_profile_frame_base_label(buffer->stack_buffer[i]);
|
136
|
+
filename = rb_profile_frame_path(buffer->stack_buffer[i]);
|
137
|
+
line = buffer->lines_buffer[i];
|
138
|
+
} else {
|
139
|
+
// **IMPORTANT**: Be very careful when calling any `rb_profile_frame_...` API with a non-Ruby frame, as legacy
|
140
|
+
// Rubies may assume that what's in a buffer will lead to a Ruby frame.
|
141
|
+
//
|
142
|
+
// In particular for Ruby 2.2 and below the buffer contains a Ruby string (see the notes on our custom
|
143
|
+
// rb_profile_frames for Ruby 2.2 and below) and CALLING **ANY** OF THOSE APIs ON IT WILL CAUSE INSTANT VM CRASHES
|
144
|
+
|
145
|
+
#ifndef USE_LEGACY_RB_PROFILE_FRAMES // Modern Rubies
|
146
|
+
name = ddtrace_rb_profile_frame_method_name(buffer->stack_buffer[i]);
|
147
|
+
#else // Ruby < 2.3
|
148
|
+
name = buffer->stack_buffer[i];
|
149
|
+
#endif
|
150
|
+
|
151
|
+
filename = NIL_P(last_ruby_frame) ? Qnil : rb_profile_frame_path(last_ruby_frame);
|
152
|
+
line = last_ruby_line;
|
153
|
+
}
|
154
|
+
|
155
|
+
name = NIL_P(name) ? missing_string : name;
|
156
|
+
filename = NIL_P(filename) ? missing_string : filename;
|
157
|
+
|
158
|
+
buffer->lines[i] = (ddprof_ffi_Line) {
|
159
|
+
.function = (ddprof_ffi_Function) {
|
160
|
+
.name = char_slice_from_ruby_string(name),
|
161
|
+
.filename = char_slice_from_ruby_string(filename)
|
162
|
+
},
|
163
|
+
.line = line,
|
164
|
+
};
|
165
|
+
|
166
|
+
buffer->locations[i] = (ddprof_ffi_Location) {.lines = (ddprof_ffi_Slice_line) {.ptr = &buffer->lines[i], .len = 1}};
|
167
|
+
}
|
168
|
+
|
169
|
+
// Used below; since we want to stack-allocate this, we must do it here rather than in maybe_add_placeholder_frames_omitted
|
170
|
+
const int frames_omitted_message_size = sizeof(MAX_FRAMES_LIMIT_AS_STRING " frames omitted");
|
171
|
+
char frames_omitted_message[frames_omitted_message_size];
|
172
|
+
|
173
|
+
// If we filled up the buffer, some frames may have been omitted. In that case, we'll add a placeholder frame
|
174
|
+
// with that info.
|
175
|
+
if (captured_frames == (long) buffer->max_frames) {
|
176
|
+
maybe_add_placeholder_frames_omitted(thread, buffer, frames_omitted_message, frames_omitted_message_size);
|
177
|
+
}
|
178
|
+
|
179
|
+
record_sample(
|
180
|
+
recorder_instance,
|
181
|
+
(ddprof_ffi_Sample) {
|
182
|
+
.locations = (ddprof_ffi_Slice_location) {.ptr = buffer->locations, .len = captured_frames},
|
183
|
+
.values = metric_values,
|
184
|
+
.labels = labels,
|
185
|
+
}
|
186
|
+
);
|
187
|
+
}
|
188
|
+
|
189
|
+
void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer* buffer, char *frames_omitted_message, int frames_omitted_message_size) {
|
190
|
+
ptrdiff_t frames_omitted = stack_depth_for(thread) - buffer->max_frames;
|
191
|
+
|
192
|
+
if (frames_omitted == 0) return; // Perfect fit!
|
193
|
+
|
194
|
+
// The placeholder frame takes over a space, so if 10 frames were left out and we consume one other space for the
|
195
|
+
// placeholder, then 11 frames are omitted in total
|
196
|
+
frames_omitted++;
|
197
|
+
|
198
|
+
snprintf(frames_omitted_message, frames_omitted_message_size, "%td frames omitted", frames_omitted);
|
199
|
+
|
200
|
+
// Important note: `frames_omitted_message` MUST have a lifetime that is at least as long as the call to
|
201
|
+
// `record_sample`. So be careful where it gets allocated. (We do have tests for this, at least!)
|
202
|
+
buffer->lines[buffer->max_frames - 1] = (ddprof_ffi_Line) {
|
203
|
+
.function = (ddprof_ffi_Function) {
|
204
|
+
.name = DDPROF_FFI_CHARSLICE_C(""),
|
205
|
+
.filename = ((ddprof_ffi_CharSlice) {.ptr = frames_omitted_message, .len = strlen(frames_omitted_message)})
|
206
|
+
},
|
207
|
+
.line = 0,
|
208
|
+
};
|
209
|
+
}
|
210
|
+
|
211
|
+
// Our custom rb_profile_frames returning PLACEHOLDER_STACK_IN_NATIVE_CODE is equivalent to when the
|
212
|
+
// Ruby `Thread#backtrace` API returns an empty array: we know that a thread is alive but we don't know what it's doing:
|
213
|
+
//
|
214
|
+
// 1. It can be starting up
|
215
|
+
// ```
|
216
|
+
// > Thread.new { sleep }.backtrace
|
217
|
+
// => [] # <-- note the thread hasn't actually started running sleep yet, we got there first
|
218
|
+
// ```
|
219
|
+
// 2. It can be running native code
|
220
|
+
// ```
|
221
|
+
// > t = Process.detach(fork { sleep })
|
222
|
+
// => #<Process::Waiter:0x00007ffe7285f7a0 run>
|
223
|
+
// > t.backtrace
|
224
|
+
// => [] # <-- this can happen even minutes later, e.g. it's not a race as in 1.
|
225
|
+
// ```
|
226
|
+
// This effect has been observed in threads created by the Iodine web server and the ffi gem,
|
227
|
+
// see for instance https://github.com/ffi/ffi/pull/883 and https://github.com/DataDog/dd-trace-rb/pull/1719 .
|
228
|
+
//
|
229
|
+
// To give customers visibility into these threads, rather than reporting an empty stack, we replace the empty stack
|
230
|
+
// with one containing a placeholder frame, so that these threads are properly represented in the UX.
|
231
|
+
void record_placeholder_stack_in_native_code(VALUE recorder_instance, ddprof_ffi_Slice_i64 metric_values, ddprof_ffi_Slice_label labels) {
|
232
|
+
ddprof_ffi_Line placeholder_stack_in_native_code_line = {
|
233
|
+
.function = (ddprof_ffi_Function) {
|
234
|
+
.name = DDPROF_FFI_CHARSLICE_C(""),
|
235
|
+
.filename = DDPROF_FFI_CHARSLICE_C("In native code")
|
236
|
+
},
|
237
|
+
.line = 0
|
238
|
+
};
|
239
|
+
ddprof_ffi_Location placeholder_stack_in_native_code_location =
|
240
|
+
{.lines = (ddprof_ffi_Slice_line) {.ptr = &placeholder_stack_in_native_code_line, .len = 1}};
|
241
|
+
|
242
|
+
record_sample(
|
243
|
+
recorder_instance,
|
244
|
+
(ddprof_ffi_Sample) {
|
245
|
+
.locations = (ddprof_ffi_Slice_location) {.ptr = &placeholder_stack_in_native_code_location, .len = 1},
|
246
|
+
.values = metric_values,
|
247
|
+
.labels = labels,
|
248
|
+
}
|
249
|
+
);
|
250
|
+
}
|
251
|
+
|
252
|
+
sampling_buffer *sampling_buffer_new(unsigned int max_frames) {
|
253
|
+
if (max_frames < 5) rb_raise(rb_eArgError, "Invalid max_frames: value must be >= 5");
|
254
|
+
if (max_frames > MAX_FRAMES_LIMIT) rb_raise(rb_eArgError, "Invalid max_frames: value must be <= " MAX_FRAMES_LIMIT_AS_STRING);
|
255
|
+
|
256
|
+
// Note: never returns NULL; if out of memory, it calls the Ruby out-of-memory handlers
|
257
|
+
sampling_buffer* buffer = ruby_xcalloc(1, sizeof(sampling_buffer));
|
258
|
+
|
259
|
+
buffer->max_frames = max_frames;
|
260
|
+
|
261
|
+
buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(VALUE));
|
262
|
+
buffer->lines_buffer = ruby_xcalloc(max_frames, sizeof(int));
|
263
|
+
buffer->is_ruby_frame = ruby_xcalloc(max_frames, sizeof(bool));
|
264
|
+
buffer->locations = ruby_xcalloc(max_frames, sizeof(ddprof_ffi_Location));
|
265
|
+
buffer->lines = ruby_xcalloc(max_frames, sizeof(ddprof_ffi_Line));
|
266
|
+
|
267
|
+
return buffer;
|
268
|
+
}
|
269
|
+
|
270
|
+
void sampling_buffer_free(sampling_buffer *buffer) {
|
271
|
+
ruby_xfree(buffer->stack_buffer);
|
272
|
+
ruby_xfree(buffer->lines_buffer);
|
273
|
+
ruby_xfree(buffer->is_ruby_frame);
|
274
|
+
ruby_xfree(buffer->locations);
|
275
|
+
ruby_xfree(buffer->lines);
|
276
|
+
|
277
|
+
ruby_xfree(buffer);
|
278
|
+
}
|
@@ -1,92 +1,40 @@
|
|
1
1
|
# typed: ignore
|
2
2
|
|
3
3
|
# rubocop:disable Style/StderrPuts
|
4
|
+
# rubocop:disable Style/GlobalVars
|
4
5
|
|
5
|
-
|
6
|
-
CAN_USE_MJIT_HEADER = RUBY_VERSION >= '2.6'
|
6
|
+
require_relative 'native_extension_helpers'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
RUBY_ENGINE == 'jruby'
|
12
|
-
end
|
13
|
-
|
14
|
-
def on_truffleruby?
|
15
|
-
# We don't officially support TruffleRuby for dd-trace-rb at all BUT let's not break adventurous customers that
|
16
|
-
# want to give it a try.
|
17
|
-
RUBY_ENGINE == 'truffleruby'
|
18
|
-
end
|
19
|
-
|
20
|
-
def on_windows?
|
21
|
-
# Microsoft Windows is unsupported, so let's not build the extension there.
|
22
|
-
Gem.win_platform?
|
23
|
-
end
|
24
|
-
|
25
|
-
def expected_to_use_mjit_but_mjit_is_disabled?
|
26
|
-
# On some Rubies, we require the mjit header to be present. If Ruby was installed without MJIT support, we also skip
|
27
|
-
# building the extension.
|
28
|
-
mjit_disabled = CAN_USE_MJIT_HEADER && RbConfig::CONFIG['MJIT_SUPPORT'] != 'yes'
|
29
|
-
|
30
|
-
if mjit_disabled
|
31
|
-
$stderr.puts(%(
|
32
|
-
+------------------------------------------------------------------------------+
|
33
|
-
| Your Ruby has been compiled without JIT support (--disable-jit-support). |
|
34
|
-
| The profiling native extension requires a Ruby compiled with JIT support, |
|
35
|
-
| even if the JIT is not in use by the application itself. |
|
36
|
-
| |
|
37
|
-
| WARNING: Without the profiling native extension, some profiling features |
|
38
|
-
| will not be available. |
|
39
|
-
+------------------------------------------------------------------------------+
|
40
|
-
|
41
|
-
))
|
42
|
-
end
|
43
|
-
|
44
|
-
mjit_disabled
|
45
|
-
end
|
46
|
-
|
47
|
-
def disabled_via_env?
|
48
|
-
# Experimental toggle to disable building the extension.
|
49
|
-
# Disabling the extension will lead to the profiler not working in future releases.
|
50
|
-
# If you needed to use this, please tell us why on <https://github.com/DataDog/dd-trace-rb/issues/new>.
|
51
|
-
ENV['DD_PROFILING_NO_EXTENSION'].to_s.downcase == 'true'
|
52
|
-
end
|
53
|
-
|
54
|
-
def skip_building_extension?
|
55
|
-
disabled_via_env? || on_jruby? || on_truffleruby? || on_windows? || expected_to_use_mjit_but_mjit_is_disabled?
|
56
|
-
end
|
8
|
+
SKIPPED_REASON_FILE = "#{__dir__}/skipped_reason.txt".freeze
|
9
|
+
# Not a problem if the file doesn't exist or we can't delete it
|
10
|
+
File.delete(SKIPPED_REASON_FILE) rescue nil
|
57
11
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
12
|
+
def skip_building_extension!(reason)
|
13
|
+
$stderr.puts(Datadog::Profiling::NativeExtensionHelpers::Supported.failure_banner_for(**reason))
|
14
|
+
File.write(
|
15
|
+
SKIPPED_REASON_FILE,
|
16
|
+
Datadog::Profiling::NativeExtensionHelpers::Supported.render_skipped_reason_file(**reason),
|
17
|
+
)
|
63
18
|
|
64
|
-
def skip_building_extension!
|
65
19
|
File.write('Makefile', 'all install clean: # dummy makefile that does nothing')
|
66
20
|
exit
|
67
21
|
end
|
68
22
|
|
69
|
-
|
70
|
-
|
71
|
-
+------------------------------------------------------------------------------+
|
72
|
-
| Skipping build of profiling native extension and replacing it with a no-op |
|
73
|
-
| Makefile |
|
74
|
-
+------------------------------------------------------------------------------+
|
75
|
-
|
76
|
-
))
|
77
|
-
skip_building_extension!
|
23
|
+
unless Datadog::Profiling::NativeExtensionHelpers::Supported.supported?
|
24
|
+
skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported.unsupported_reason)
|
78
25
|
end
|
79
26
|
|
80
27
|
$stderr.puts(%(
|
81
28
|
+------------------------------------------------------------------------------+
|
82
|
-
| ** Preparing to build the ddtrace native extension... **
|
29
|
+
| ** Preparing to build the ddtrace profiling native extension... ** |
|
83
30
|
| |
|
84
31
|
| If you run into any failures during this step, you can set the |
|
85
32
|
| `DD_PROFILING_NO_EXTENSION` environment variable to `true` e.g. |
|
86
33
|
| `$ DD_PROFILING_NO_EXTENSION=true bundle install` to skip this step. |
|
87
34
|
| |
|
88
|
-
|
|
89
|
-
|
|
35
|
+
| If you disable this extension, the Datadog Continuous Profiler will |
|
36
|
+
| not be available, but all other ddtrace features will work fine! |
|
37
|
+
| |
|
90
38
|
| If you needed to use this, please tell us why on |
|
91
39
|
| <https://github.com/DataDog/dd-trace-rb/issues/new> so we can fix it :\) |
|
92
40
|
| |
|
@@ -99,6 +47,22 @@ $stderr.puts(%(
|
|
99
47
|
# that may fail on an environment not properly setup for building Ruby extensions.
|
100
48
|
require 'mkmf'
|
101
49
|
|
50
|
+
# mkmf on modern Rubies actually has an append_cflags that does something similar
|
51
|
+
# (see https://github.com/ruby/ruby/pull/5760), but as usual we need a bit more boilerplate to deal with legacy Rubies
|
52
|
+
def add_compiler_flag(flag)
|
53
|
+
if try_cflags(flag)
|
54
|
+
$CFLAGS << ' ' << flag
|
55
|
+
else
|
56
|
+
$stderr.puts("WARNING: '#{flag}' not accepted by compiler, skipping it")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Older gcc releases may not default to C99 and we need to ask for this. This is also used:
|
61
|
+
# * by upstream Ruby -- search for gnu99 in the codebase
|
62
|
+
# * by msgpack, another ddtrace dependency
|
63
|
+
# (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
|
64
|
+
add_compiler_flag '-std=gnu99'
|
65
|
+
|
102
66
|
# Gets really noisy when we include the MJIT header, let's omit it
|
103
67
|
add_compiler_flag '-Wno-unused-function'
|
104
68
|
|
@@ -128,13 +92,41 @@ if RUBY_PLATFORM.include?('linux')
|
|
128
92
|
$defs << '-DHAVE_PTHREAD_GETCPUCLOCKID'
|
129
93
|
end
|
130
94
|
|
95
|
+
# On older Rubies, we need to use a backported version of this function. See private_vm_api_access.h for details.
|
96
|
+
$defs << '-DUSE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME' if RUBY_VERSION < '3'
|
97
|
+
|
98
|
+
# On older Rubies, we need to use rb_thread_t instead of rb_execution_context_t
|
99
|
+
$defs << '-DUSE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT' if RUBY_VERSION < '2.5'
|
100
|
+
|
101
|
+
# On older Rubies...
|
102
|
+
if RUBY_VERSION < '2.4'
|
103
|
+
# ...we need to use RUBY_VM_NORMAL_ISEQ_P instead of VM_FRAME_RUBYFRAME_P
|
104
|
+
$defs << '-DUSE_ISEQ_P_INSTEAD_OF_RUBYFRAME_P'
|
105
|
+
# ...we use a legacy copy of rb_vm_frame_method_entry
|
106
|
+
$defs << '-DUSE_LEGACY_RB_VM_FRAME_METHOD_ENTRY'
|
107
|
+
end
|
108
|
+
|
109
|
+
# For REALLY OLD Rubies...
|
110
|
+
if RUBY_VERSION < '2.3'
|
111
|
+
# ...there was no rb_time_timespec_new function
|
112
|
+
$defs << '-DNO_RB_TIME_TIMESPEC_NEW'
|
113
|
+
# ...the VM changed enough that we need an alternative legacy rb_profile_frames
|
114
|
+
$defs << '-DUSE_LEGACY_RB_PROFILE_FRAMES'
|
115
|
+
end
|
116
|
+
|
117
|
+
# If we got here, libddprof is available and loaded
|
118
|
+
ENV['PKG_CONFIG_PATH'] = "#{ENV['PKG_CONFIG_PATH']}:#{Libddprof.pkgconfig_folder}"
|
119
|
+
unless pkg_config('ddprof_ffi_with_rpath')
|
120
|
+
skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::FAILED_TO_CONFIGURE_LIBDDPROF)
|
121
|
+
end
|
122
|
+
|
131
123
|
# Tag the native extension library with the Ruby version and Ruby platform.
|
132
124
|
# This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
|
133
125
|
# the wrong library is never loaded.
|
134
126
|
# When requiring, we need to use the exact same string, including the version and the platform.
|
135
127
|
EXTENSION_NAME = "ddtrace_profiling_native_extension.#{RUBY_VERSION}_#{RUBY_PLATFORM}".freeze
|
136
128
|
|
137
|
-
if CAN_USE_MJIT_HEADER
|
129
|
+
if Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER
|
138
130
|
mjit_header_file_name = "rb_mjit_min_header-#{RUBY_VERSION}.h"
|
139
131
|
|
140
132
|
# Validate that the mjit header can actually be compiled on this system. We learned via
|
@@ -146,42 +138,15 @@ if CAN_USE_MJIT_HEADER
|
|
146
138
|
original_common_headers = MakeMakefile::COMMON_HEADERS
|
147
139
|
MakeMakefile::COMMON_HEADERS = ''.freeze
|
148
140
|
unless have_macro('RUBY_MJIT_H', mjit_header_file_name)
|
149
|
-
|
150
|
-
+------------------------------------------------------------------------------+
|
151
|
-
| WARNING: Unable to compile a needed component for ddtrace native extension. |
|
152
|
-
| Your C compiler or Ruby VM just-in-time compiler seems to be broken. |
|
153
|
-
| |
|
154
|
-
| You will be NOT be able to use ddtrace profiling features, |
|
155
|
-
| but all other features will work fine! |
|
156
|
-
| |
|
157
|
-
| For help solving this issue, please contact Datadog support at |
|
158
|
-
| <https://docs.datadoghq.com/help/>. |
|
159
|
-
+------------------------------------------------------------------------------+
|
160
|
-
|
161
|
-
))
|
162
|
-
skip_building_extension!
|
141
|
+
skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILATION_BROKEN)
|
163
142
|
end
|
164
143
|
MakeMakefile::COMMON_HEADERS = original_common_headers
|
165
144
|
|
166
|
-
$defs <<
|
145
|
+
$defs << "-DRUBY_MJIT_HEADER='\"#{mjit_header_file_name}\"'"
|
167
146
|
|
168
147
|
# NOTE: This needs to come after all changes to $defs
|
169
148
|
create_header
|
170
149
|
|
171
|
-
# The MJIT header is always (afaik?) suffixed with the exact Ruby VM version,
|
172
|
-
# including patch (e.g. 2.7.2). Thus, we add to the header file a definition
|
173
|
-
# containing the exact file, so that it can be used in a #include in the C code.
|
174
|
-
header_contents =
|
175
|
-
File.read($extconf_h)
|
176
|
-
.sub('#endif',
|
177
|
-
<<-EXTCONF_H.strip
|
178
|
-
#define RUBY_MJIT_HEADER "#{mjit_header_file_name}"
|
179
|
-
|
180
|
-
#endif
|
181
|
-
EXTCONF_H
|
182
|
-
)
|
183
|
-
File.open($extconf_h, 'w') { |file| file.puts(header_contents) }
|
184
|
-
|
185
150
|
create_makefile EXTENSION_NAME
|
186
151
|
else
|
187
152
|
# On older Rubies, we use the debase-ruby_core_source gem to get access to private VM headers.
|
@@ -201,6 +166,11 @@ else
|
|
201
166
|
dir_config('ruby') # allow user to pass in non-standard core include directory
|
202
167
|
|
203
168
|
Debase::RubyCoreSource
|
204
|
-
.create_makefile_with_core(
|
169
|
+
.create_makefile_with_core(
|
170
|
+
proc { have_header('vm_core.h') && have_header('iseq.h') && thread_native_for_ruby_2_1.call },
|
171
|
+
EXTENSION_NAME,
|
172
|
+
)
|
205
173
|
end
|
174
|
+
|
175
|
+
# rubocop:enable Style/GlobalVars
|
206
176
|
# rubocop:enable Style/StderrPuts
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include <ddprof/ffi.h>
|
4
|
+
|
5
|
+
inline static ddprof_ffi_CharSlice char_slice_from_ruby_string(VALUE string) {
|
6
|
+
Check_Type(string, T_STRING);
|
7
|
+
ddprof_ffi_CharSlice char_slice = {.ptr = StringValuePtr(string), .len = RSTRING_LEN(string)};
|
8
|
+
return char_slice;
|
9
|
+
}
|
10
|
+
|
11
|
+
inline static VALUE ruby_string_from_vec_u8(ddprof_ffi_Vec_u8 string) {
|
12
|
+
return rb_str_new((char *) string.ptr, string.len);
|
13
|
+
}
|