ddtrace 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -2
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +3 -0
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +67 -52
- data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.c +22 -14
- data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.h +4 -0
- data/ext/ddtrace_profiling_native_extension/collectors_gc_profiling_helper.c +156 -0
- data/ext/ddtrace_profiling_native_extension/collectors_gc_profiling_helper.h +5 -0
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +43 -102
- data/ext/ddtrace_profiling_native_extension/collectors_stack.h +10 -3
- data/ext/ddtrace_profiling_native_extension/collectors_thread_context.c +167 -125
- data/ext/ddtrace_profiling_native_extension/collectors_thread_context.h +2 -1
- data/ext/ddtrace_profiling_native_extension/extconf.rb +44 -10
- data/ext/ddtrace_profiling_native_extension/heap_recorder.c +970 -0
- data/ext/ddtrace_profiling_native_extension/heap_recorder.h +155 -0
- data/ext/ddtrace_profiling_native_extension/helpers.h +2 -0
- data/ext/ddtrace_profiling_native_extension/http_transport.c +5 -2
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.c +20 -0
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +11 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +83 -18
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +6 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +2 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +147 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +28 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +330 -13
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +3 -0
- data/lib/datadog/appsec/component.rb +4 -1
- data/lib/datadog/appsec/configuration/settings.rb +4 -0
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +2 -0
- data/lib/datadog/appsec/processor/rule_loader.rb +60 -0
- data/lib/datadog/appsec/remote.rb +12 -9
- data/lib/datadog/core/configuration/settings.rb +139 -22
- data/lib/datadog/core/configuration.rb +4 -0
- data/lib/datadog/core/remote/worker.rb +1 -0
- data/lib/datadog/core/telemetry/collector.rb +10 -0
- data/lib/datadog/core/telemetry/event.rb +2 -1
- data/lib/datadog/core/telemetry/ext.rb +3 -0
- data/lib/datadog/core/telemetry/v1/app_event.rb +8 -1
- data/lib/datadog/core/telemetry/v1/install_signature.rb +38 -0
- data/lib/datadog/core/workers/async.rb +1 -0
- data/lib/datadog/kit/enable_core_dumps.rb +5 -6
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +7 -11
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
- data/lib/datadog/profiling/component.rb +210 -18
- data/lib/datadog/profiling/scheduler.rb +4 -6
- data/lib/datadog/profiling/stack_recorder.rb +13 -2
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +4 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +24 -0
- data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +0 -2
- data/lib/datadog/tracing/workers.rb +1 -0
- data/lib/ddtrace/version.rb +1 -1
- metadata +11 -6
@@ -7,6 +7,7 @@
|
|
7
7
|
#include "libdatadog_helpers.h"
|
8
8
|
#include "ruby_helpers.h"
|
9
9
|
#include "time_helpers.h"
|
10
|
+
#include "heap_recorder.h"
|
10
11
|
|
11
12
|
// Used to wrap a ddog_prof_Profile in a Ruby object and expose Ruby-level serialization APIs
|
12
13
|
// This file implements the native bits of the Datadog::Profiling::StackRecorder class
|
@@ -129,8 +130,6 @@
|
|
129
130
|
static VALUE ok_symbol = Qnil; // :ok in Ruby
|
130
131
|
static VALUE error_symbol = Qnil; // :error in Ruby
|
131
132
|
|
132
|
-
static VALUE stack_recorder_class = Qnil;
|
133
|
-
|
134
133
|
// Note: Please DO NOT use `VALUE_STRING` anywhere else, instead use `DDOG_CHARSLICE_C`.
|
135
134
|
// `VALUE_STRING` is only needed because older versions of gcc (4.9.2, used in our Ruby 2.2 CI test images)
|
136
135
|
// tripped when compiling `enabled_value_types` using `-std=gnu99` due to the extra cast that is included in
|
@@ -152,18 +151,29 @@ static VALUE stack_recorder_class = Qnil;
|
|
152
151
|
#define WALL_TIME_VALUE_ID 2
|
153
152
|
#define ALLOC_SAMPLES_VALUE {.type_ = VALUE_STRING("alloc-samples"), .unit = VALUE_STRING("count")}
|
154
153
|
#define ALLOC_SAMPLES_VALUE_ID 3
|
154
|
+
#define HEAP_SAMPLES_VALUE {.type_ = VALUE_STRING("heap-live-samples"), .unit = VALUE_STRING("count")}
|
155
|
+
#define HEAP_SAMPLES_VALUE_ID 4
|
156
|
+
#define HEAP_SIZE_VALUE {.type_ = VALUE_STRING("heap-live-size"), .unit = VALUE_STRING("bytes")}
|
157
|
+
#define HEAP_SIZE_VALUE_ID 5
|
158
|
+
#define TIMELINE_VALUE {.type_ = VALUE_STRING("timeline"), .unit = VALUE_STRING("nanoseconds")}
|
159
|
+
#define TIMELINE_VALUE_ID 6
|
155
160
|
|
156
|
-
static const ddog_prof_ValueType all_value_types[] =
|
161
|
+
static const ddog_prof_ValueType all_value_types[] =
|
162
|
+
{CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE, ALLOC_SAMPLES_VALUE, HEAP_SAMPLES_VALUE, HEAP_SIZE_VALUE, TIMELINE_VALUE};
|
157
163
|
|
158
164
|
// This array MUST be kept in sync with all_value_types above and is intended to act as a "hashmap" between VALUE_ID and the position it
|
159
165
|
// occupies on the all_value_types array.
|
160
166
|
// E.g. all_value_types_positions[CPU_TIME_VALUE_ID] => 0, means that CPU_TIME_VALUE was declared at position 0 of all_value_types.
|
161
|
-
static const uint8_t all_value_types_positions[] =
|
167
|
+
static const uint8_t all_value_types_positions[] =
|
168
|
+
{CPU_TIME_VALUE_ID, CPU_SAMPLES_VALUE_ID, WALL_TIME_VALUE_ID, ALLOC_SAMPLES_VALUE_ID, HEAP_SAMPLES_VALUE_ID, HEAP_SIZE_VALUE_ID, TIMELINE_VALUE_ID};
|
162
169
|
|
163
170
|
#define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(ddog_prof_ValueType))
|
164
171
|
|
165
172
|
// Contains native state for each instance
|
166
173
|
struct stack_recorder_state {
|
174
|
+
// Heap recorder instance
|
175
|
+
heap_recorder *heap_recorder;
|
176
|
+
|
167
177
|
pthread_mutex_t slot_one_mutex;
|
168
178
|
ddog_prof_Profile slot_one_profile;
|
169
179
|
|
@@ -186,6 +196,7 @@ struct call_serialize_without_gvl_arguments {
|
|
186
196
|
// Set by caller
|
187
197
|
struct stack_recorder_state *state;
|
188
198
|
ddog_Timespec finish_timestamp;
|
199
|
+
size_t gc_count_before_serialize;
|
189
200
|
|
190
201
|
// Set by callee
|
191
202
|
ddog_prof_Profile *profile;
|
@@ -199,11 +210,20 @@ static VALUE _native_new(VALUE klass);
|
|
199
210
|
static void initialize_slot_concurrency_control(struct stack_recorder_state *state);
|
200
211
|
static void initialize_profiles(struct stack_recorder_state *state, ddog_prof_Slice_ValueType sample_types);
|
201
212
|
static void stack_recorder_typed_data_free(void *data);
|
202
|
-
static VALUE _native_initialize(
|
213
|
+
static VALUE _native_initialize(
|
214
|
+
DDTRACE_UNUSED VALUE _self,
|
215
|
+
VALUE recorder_instance,
|
216
|
+
VALUE cpu_time_enabled,
|
217
|
+
VALUE alloc_samples_enabled,
|
218
|
+
VALUE heap_samples_enabled,
|
219
|
+
VALUE heap_size_enabled,
|
220
|
+
VALUE heap_sample_every,
|
221
|
+
VALUE timeline_enabled
|
222
|
+
);
|
203
223
|
static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
|
204
224
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time);
|
205
225
|
static void *call_serialize_without_gvl(void *call_args);
|
206
|
-
static struct active_slot_pair sampler_lock_active_profile();
|
226
|
+
static struct active_slot_pair sampler_lock_active_profile(struct stack_recorder_state *state);
|
207
227
|
static void sampler_unlock_active_profile(struct active_slot_pair active_slot);
|
208
228
|
static ddog_prof_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state);
|
209
229
|
static VALUE _native_active_slot(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
@@ -215,9 +235,17 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
215
235
|
static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec start_time);
|
216
236
|
static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint);
|
217
237
|
static void reset_profile(ddog_prof_Profile *profile, ddog_Timespec *start_time /* Can be null */);
|
238
|
+
static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class);
|
239
|
+
static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locations);
|
240
|
+
static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
241
|
+
static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
242
|
+
static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
243
|
+
static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
244
|
+
static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
245
|
+
|
218
246
|
|
219
247
|
void stack_recorder_init(VALUE profiling_module) {
|
220
|
-
stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
|
248
|
+
VALUE stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
|
221
249
|
// Hosts methods used for testing the native code using RSpec
|
222
250
|
VALUE testing_module = rb_define_module_under(stack_recorder_class, "Testing");
|
223
251
|
|
@@ -231,13 +259,25 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
231
259
|
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
232
260
|
rb_define_alloc_func(stack_recorder_class, _native_new);
|
233
261
|
|
234
|
-
rb_define_singleton_method(stack_recorder_class, "_native_initialize", _native_initialize,
|
262
|
+
rb_define_singleton_method(stack_recorder_class, "_native_initialize", _native_initialize, 7);
|
235
263
|
rb_define_singleton_method(stack_recorder_class, "_native_serialize", _native_serialize, 1);
|
236
264
|
rb_define_singleton_method(stack_recorder_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
237
265
|
rb_define_singleton_method(testing_module, "_native_active_slot", _native_active_slot, 1);
|
238
266
|
rb_define_singleton_method(testing_module, "_native_slot_one_mutex_locked?", _native_is_slot_one_mutex_locked, 1);
|
239
267
|
rb_define_singleton_method(testing_module, "_native_slot_two_mutex_locked?", _native_is_slot_two_mutex_locked, 1);
|
240
268
|
rb_define_singleton_method(testing_module, "_native_record_endpoint", _native_record_endpoint, 3);
|
269
|
+
rb_define_singleton_method(testing_module, "_native_track_object", _native_track_object, 4);
|
270
|
+
rb_define_singleton_method(testing_module, "_native_check_heap_hashes", _native_check_heap_hashes, 1);
|
271
|
+
rb_define_singleton_method(testing_module, "_native_start_fake_slow_heap_serialization",
|
272
|
+
_native_start_fake_slow_heap_serialization, 1);
|
273
|
+
rb_define_singleton_method(testing_module, "_native_end_fake_slow_heap_serialization",
|
274
|
+
_native_end_fake_slow_heap_serialization, 1);
|
275
|
+
rb_define_singleton_method(testing_module, "_native_debug_heap_recorder",
|
276
|
+
_native_debug_heap_recorder, 1);
|
277
|
+
rb_define_singleton_method(testing_module, "_native_gc_force_recycle",
|
278
|
+
_native_gc_force_recycle, 1);
|
279
|
+
rb_define_singleton_method(testing_module, "_native_has_seen_id_flag",
|
280
|
+
_native_has_seen_id_flag, 1);
|
241
281
|
|
242
282
|
ok_symbol = ID2SYM(rb_intern_const("ok"));
|
243
283
|
error_symbol = ID2SYM(rb_intern_const("error"));
|
@@ -272,6 +312,12 @@ static VALUE _native_new(VALUE klass) {
|
|
272
312
|
|
273
313
|
VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
|
274
314
|
|
315
|
+
// NOTE: We initialize this because we want a new recorder to be operational even without initialization and our
|
316
|
+
// default is everything enabled. However, if during recording initialization it turns out we don't want
|
317
|
+
// heap samples, we will free and reset heap_recorder to NULL, effectively disabling all behaviour specific
|
318
|
+
// to heap profiling (all calls to heap_recorder_* with a NULL heap recorder are noops).
|
319
|
+
state->heap_recorder = heap_recorder_new();
|
320
|
+
|
275
321
|
// Note: Don't raise exceptions after this point, since it'll lead to libdatadog memory leaking!
|
276
322
|
|
277
323
|
initialize_profiles(state, sample_types);
|
@@ -320,27 +366,51 @@ static void stack_recorder_typed_data_free(void *state_ptr) {
|
|
320
366
|
pthread_mutex_destroy(&state->slot_two_mutex);
|
321
367
|
ddog_prof_Profile_drop(&state->slot_two_profile);
|
322
368
|
|
369
|
+
heap_recorder_free(state->heap_recorder);
|
370
|
+
|
323
371
|
ruby_xfree(state);
|
324
372
|
}
|
325
373
|
|
326
|
-
static VALUE _native_initialize(
|
374
|
+
static VALUE _native_initialize(
|
375
|
+
DDTRACE_UNUSED VALUE _self,
|
376
|
+
VALUE recorder_instance,
|
377
|
+
VALUE cpu_time_enabled,
|
378
|
+
VALUE alloc_samples_enabled,
|
379
|
+
VALUE heap_samples_enabled,
|
380
|
+
VALUE heap_size_enabled,
|
381
|
+
VALUE heap_sample_every,
|
382
|
+
VALUE timeline_enabled
|
383
|
+
) {
|
327
384
|
ENFORCE_BOOLEAN(cpu_time_enabled);
|
328
385
|
ENFORCE_BOOLEAN(alloc_samples_enabled);
|
386
|
+
ENFORCE_BOOLEAN(heap_samples_enabled);
|
387
|
+
ENFORCE_BOOLEAN(heap_size_enabled);
|
388
|
+
ENFORCE_TYPE(heap_sample_every, T_FIXNUM);
|
389
|
+
ENFORCE_BOOLEAN(timeline_enabled);
|
329
390
|
|
330
391
|
struct stack_recorder_state *state;
|
331
392
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
332
393
|
|
333
|
-
|
394
|
+
heap_recorder_set_sample_rate(state->heap_recorder, NUM2INT(heap_sample_every));
|
395
|
+
|
396
|
+
uint8_t requested_values_count = ALL_VALUE_TYPES_COUNT -
|
397
|
+
(cpu_time_enabled == Qtrue ? 0 : 1) -
|
398
|
+
(alloc_samples_enabled == Qtrue? 0 : 1) -
|
399
|
+
(heap_samples_enabled == Qtrue ? 0 : 1) -
|
400
|
+
(heap_size_enabled == Qtrue ? 0 : 1) -
|
401
|
+
(timeline_enabled == Qtrue ? 0 : 1);
|
402
|
+
|
403
|
+
if (requested_values_count == ALL_VALUE_TYPES_COUNT) return Qtrue; // Nothing to do, this is the default
|
334
404
|
|
335
405
|
// When some sample types are disabled, we need to reconfigure libdatadog to record less types,
|
336
406
|
// as well as reconfigure the position_for array to push the disabled types to the end so they don't get recorded.
|
337
407
|
// See record_sample for details on the use of position_for.
|
338
408
|
|
339
|
-
state->enabled_values_count =
|
409
|
+
state->enabled_values_count = requested_values_count;
|
340
410
|
|
341
411
|
ddog_prof_ValueType enabled_value_types[ALL_VALUE_TYPES_COUNT];
|
342
412
|
uint8_t next_enabled_pos = 0;
|
343
|
-
uint8_t next_disabled_pos =
|
413
|
+
uint8_t next_disabled_pos = requested_values_count;
|
344
414
|
|
345
415
|
// CPU_SAMPLES_VALUE is always enabled
|
346
416
|
enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) CPU_SAMPLES_VALUE;
|
@@ -364,6 +434,35 @@ static VALUE _native_initialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_insta
|
|
364
434
|
state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_disabled_pos++;
|
365
435
|
}
|
366
436
|
|
437
|
+
if (heap_samples_enabled == Qtrue) {
|
438
|
+
enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) HEAP_SAMPLES_VALUE;
|
439
|
+
state->position_for[HEAP_SAMPLES_VALUE_ID] = next_enabled_pos++;
|
440
|
+
} else {
|
441
|
+
state->position_for[HEAP_SAMPLES_VALUE_ID] = next_disabled_pos++;
|
442
|
+
}
|
443
|
+
|
444
|
+
if (heap_size_enabled == Qtrue) {
|
445
|
+
enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) HEAP_SIZE_VALUE;
|
446
|
+
state->position_for[HEAP_SIZE_VALUE_ID] = next_enabled_pos++;
|
447
|
+
} else {
|
448
|
+
state->position_for[HEAP_SIZE_VALUE_ID] = next_disabled_pos++;
|
449
|
+
}
|
450
|
+
heap_recorder_set_size_enabled(state->heap_recorder, heap_size_enabled);
|
451
|
+
|
452
|
+
if (heap_samples_enabled == Qfalse && heap_size_enabled == Qfalse) {
|
453
|
+
// Turns out heap sampling is disabled but we initialized everything in _native_new
|
454
|
+
// assuming all samples were enabled. We need to deinitialize the heap recorder.
|
455
|
+
heap_recorder_free(state->heap_recorder);
|
456
|
+
state->heap_recorder = NULL;
|
457
|
+
}
|
458
|
+
|
459
|
+
if (timeline_enabled == Qtrue) {
|
460
|
+
enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) TIMELINE_VALUE;
|
461
|
+
state->position_for[TIMELINE_VALUE_ID] = next_enabled_pos++;
|
462
|
+
} else {
|
463
|
+
state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++;
|
464
|
+
}
|
465
|
+
|
367
466
|
ddog_prof_Profile_drop(&state->slot_one_profile);
|
368
467
|
ddog_prof_Profile_drop(&state->slot_two_profile);
|
369
468
|
|
@@ -381,9 +480,18 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
381
480
|
// Need to do this while still holding on to the Global VM Lock; see comments on method for why
|
382
481
|
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
383
482
|
|
483
|
+
// Prepare the iteration on heap recorder we'll be doing outside the GVL. The preparation needs to
|
484
|
+
// happen while holding on to the GVL.
|
485
|
+
heap_recorder_prepare_iteration(state->heap_recorder);
|
486
|
+
|
384
487
|
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
|
385
488
|
// is pending
|
386
|
-
struct call_serialize_without_gvl_arguments args = {
|
489
|
+
struct call_serialize_without_gvl_arguments args = {
|
490
|
+
.state = state,
|
491
|
+
.finish_timestamp = finish_timestamp,
|
492
|
+
.gc_count_before_serialize = rb_gc_count(),
|
493
|
+
.serialize_ran = false
|
494
|
+
};
|
387
495
|
|
388
496
|
while (!args.serialize_ran) {
|
389
497
|
// Give the Ruby VM an opportunity to process any pending interruptions (including raising exceptions).
|
@@ -399,6 +507,9 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
399
507
|
rb_thread_call_without_gvl2(call_serialize_without_gvl, &args, NULL /* No interruption function needed in this case */, NULL /* Not needed */);
|
400
508
|
}
|
401
509
|
|
510
|
+
// Cleanup after heap recorder iteration. This needs to happen while holding on to the GVL.
|
511
|
+
heap_recorder_finish_iteration(state->heap_recorder);
|
512
|
+
|
402
513
|
ddog_prof_Profile_SerializeResult serialized_profile = args.result;
|
403
514
|
|
404
515
|
if (serialized_profile.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
|
@@ -441,6 +552,15 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
441
552
|
metric_values[position_for[CPU_SAMPLES_VALUE_ID]] = values.cpu_or_wall_samples;
|
442
553
|
metric_values[position_for[WALL_TIME_VALUE_ID]] = values.wall_time_ns;
|
443
554
|
metric_values[position_for[ALLOC_SAMPLES_VALUE_ID]] = values.alloc_samples;
|
555
|
+
metric_values[position_for[TIMELINE_VALUE_ID]] = values.timeline_wall_time_ns;
|
556
|
+
|
557
|
+
if (values.alloc_samples != 0) {
|
558
|
+
// If we got an allocation sample end the heap allocation recording to commit the heap sample.
|
559
|
+
// FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
|
560
|
+
// very late in the allocation-sampling path (which is shared with the cpu sampling path). This can
|
561
|
+
// be fixed with some refactoring but for now this leads to a less impactful change.
|
562
|
+
end_heap_allocation_recording(state->heap_recorder, locations);
|
563
|
+
}
|
444
564
|
|
445
565
|
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
446
566
|
active_slot.profile,
|
@@ -459,6 +579,15 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
459
579
|
}
|
460
580
|
}
|
461
581
|
|
582
|
+
void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class) {
|
583
|
+
struct stack_recorder_state *state;
|
584
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
585
|
+
// FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
|
586
|
+
// very late in the allocation-sampling path (which is shared with the cpu sampling path). This can
|
587
|
+
// be fixed with some refactoring but for now this leads to a less impactful change.
|
588
|
+
start_heap_allocation_recording(state->heap_recorder, new_object, sample_weight, alloc_class);
|
589
|
+
}
|
590
|
+
|
462
591
|
void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint) {
|
463
592
|
struct stack_recorder_state *state;
|
464
593
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
@@ -474,10 +603,103 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
474
603
|
}
|
475
604
|
}
|
476
605
|
|
606
|
+
#define MAX_LEN_HEAP_ITERATION_ERROR_MSG 256
|
607
|
+
|
608
|
+
// Heap recorder iteration context allows us access to stack recorder state and profile being serialized
|
609
|
+
// during iteration of heap recorder live objects.
|
610
|
+
typedef struct heap_recorder_iteration_context {
|
611
|
+
struct stack_recorder_state *state;
|
612
|
+
ddog_prof_Profile *profile;
|
613
|
+
|
614
|
+
bool error;
|
615
|
+
char error_msg[MAX_LEN_HEAP_ITERATION_ERROR_MSG];
|
616
|
+
|
617
|
+
size_t profile_gen;
|
618
|
+
} heap_recorder_iteration_context;
|
619
|
+
|
620
|
+
static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteration_data iteration_data, void *extra_arg) {
|
621
|
+
heap_recorder_iteration_context *context = (heap_recorder_iteration_context*) extra_arg;
|
622
|
+
|
623
|
+
live_object_data *object_data = &iteration_data.object_data;
|
624
|
+
|
625
|
+
int64_t metric_values[ALL_VALUE_TYPES_COUNT] = {0};
|
626
|
+
uint8_t *position_for = context->state->position_for;
|
627
|
+
|
628
|
+
metric_values[position_for[HEAP_SAMPLES_VALUE_ID]] = object_data->weight;
|
629
|
+
metric_values[position_for[HEAP_SIZE_VALUE_ID]] = object_data->size * object_data->weight;
|
630
|
+
|
631
|
+
ddog_prof_Label labels[2];
|
632
|
+
size_t label_offset = 0;
|
633
|
+
|
634
|
+
if (object_data->class != NULL) {
|
635
|
+
labels[label_offset++] = (ddog_prof_Label) {
|
636
|
+
.key = DDOG_CHARSLICE_C("allocation class"),
|
637
|
+
.str = (ddog_CharSlice) {
|
638
|
+
.ptr = object_data->class,
|
639
|
+
.len = strlen(object_data->class),
|
640
|
+
},
|
641
|
+
.num = 0, // This shouldn't be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
|
642
|
+
};
|
643
|
+
}
|
644
|
+
labels[label_offset++] = (ddog_prof_Label) {
|
645
|
+
.key = DDOG_CHARSLICE_C("gc gen age"),
|
646
|
+
.num = context->profile_gen - object_data->alloc_gen,
|
647
|
+
};
|
648
|
+
|
649
|
+
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
650
|
+
context->profile,
|
651
|
+
(ddog_prof_Sample) {
|
652
|
+
.locations = iteration_data.locations,
|
653
|
+
.values = (ddog_Slice_I64) {.ptr = metric_values, .len = context->state->enabled_values_count},
|
654
|
+
.labels = (ddog_prof_Slice_Label) {
|
655
|
+
.ptr = labels,
|
656
|
+
.len = label_offset,
|
657
|
+
}
|
658
|
+
},
|
659
|
+
0
|
660
|
+
);
|
661
|
+
|
662
|
+
if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
663
|
+
read_ddogerr_string_and_drop(&result.err, context->error_msg, MAX_LEN_HEAP_ITERATION_ERROR_MSG);
|
664
|
+
context->error = true;
|
665
|
+
// By returning false we cancel the iteration
|
666
|
+
return false;
|
667
|
+
}
|
668
|
+
|
669
|
+
// Keep on iterating to next item!
|
670
|
+
return true;
|
671
|
+
}
|
672
|
+
|
673
|
+
static void build_heap_profile_without_gvl(struct stack_recorder_state *state, ddog_prof_Profile *profile, size_t gc_count_before_serialize) {
|
674
|
+
heap_recorder_iteration_context iteration_context = {
|
675
|
+
.state = state,
|
676
|
+
.profile = profile,
|
677
|
+
.error = false,
|
678
|
+
.error_msg = {0},
|
679
|
+
.profile_gen = gc_count_before_serialize,
|
680
|
+
};
|
681
|
+
bool iterated = heap_recorder_for_each_live_object(state->heap_recorder, add_heap_sample_to_active_profile_without_gvl, (void*) &iteration_context);
|
682
|
+
// We wait until we're out of the iteration to grab the gvl and raise. This is important because during
|
683
|
+
// iteration we may potentially acquire locks in the heap recorder and we could reach a deadlock if the
|
684
|
+
// same locks are acquired by the heap recorder while holding the gvl (since we'd be operating on the
|
685
|
+
// same locks but acquiring them in different order).
|
686
|
+
if (!iterated) {
|
687
|
+
grab_gvl_and_raise(rb_eRuntimeError, "Failure during heap profile building: iteration cancelled");
|
688
|
+
}
|
689
|
+
else if (iteration_context.error) {
|
690
|
+
grab_gvl_and_raise(rb_eRuntimeError, "Failure during heap profile building: %s", iteration_context.error_msg);
|
691
|
+
}
|
692
|
+
}
|
693
|
+
|
477
694
|
static void *call_serialize_without_gvl(void *call_args) {
|
478
695
|
struct call_serialize_without_gvl_arguments *args = (struct call_serialize_without_gvl_arguments *) call_args;
|
479
696
|
|
480
697
|
args->profile = serializer_flip_active_and_inactive_slots(args->state);
|
698
|
+
|
699
|
+
// Now that we have the inactive profile with all but heap samples, lets fill it with heap data
|
700
|
+
// without needing to race with the active sampler
|
701
|
+
build_heap_profile_without_gvl(args->state, args->profile, args->gc_count_before_serialize);
|
702
|
+
|
481
703
|
// Note: The profile gets reset by the serialize call
|
482
704
|
args->result = ddog_prof_Profile_serialize(args->profile, &args->finish_timestamp, NULL /* duration_nanos is optional */, NULL /* start_time is optional */);
|
483
705
|
args->serialize_ran = true;
|
@@ -599,6 +821,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
599
821
|
reset_profile(&state->slot_one_profile, /* start_time: */ NULL);
|
600
822
|
reset_profile(&state->slot_two_profile, /* start_time: */ NULL);
|
601
823
|
|
824
|
+
heap_recorder_after_fork(state->heap_recorder);
|
825
|
+
|
602
826
|
return Qtrue;
|
603
827
|
}
|
604
828
|
|
@@ -616,9 +840,102 @@ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_
|
|
616
840
|
return Qtrue;
|
617
841
|
}
|
618
842
|
|
843
|
+
static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class) {
|
844
|
+
ENFORCE_TYPE(weight, T_FIXNUM);
|
845
|
+
ddog_CharSlice alloc_class_slice = char_slice_from_ruby_string(alloc_class);
|
846
|
+
track_object(recorder_instance, new_obj, NUM2UINT(weight), &alloc_class_slice);
|
847
|
+
return Qtrue;
|
848
|
+
}
|
849
|
+
|
850
|
+
static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locations) {
|
851
|
+
ENFORCE_TYPE(locations, T_ARRAY);
|
852
|
+
size_t locations_len = rb_array_len(locations);
|
853
|
+
ddog_prof_Location locations_arr[locations_len];
|
854
|
+
for (size_t i = 0; i < locations_len; i++) {
|
855
|
+
VALUE location = rb_ary_entry(locations, i);
|
856
|
+
ENFORCE_TYPE(location, T_ARRAY);
|
857
|
+
VALUE name = rb_ary_entry(location, 0);
|
858
|
+
VALUE filename = rb_ary_entry(location, 1);
|
859
|
+
VALUE line = rb_ary_entry(location, 2);
|
860
|
+
ENFORCE_TYPE(name, T_STRING);
|
861
|
+
ENFORCE_TYPE(filename, T_STRING);
|
862
|
+
ENFORCE_TYPE(line, T_FIXNUM);
|
863
|
+
locations_arr[i] = (ddog_prof_Location) {
|
864
|
+
.line = line,
|
865
|
+
.function = (ddog_prof_Function) {
|
866
|
+
.name = char_slice_from_ruby_string(name),
|
867
|
+
.filename = char_slice_from_ruby_string(filename),
|
868
|
+
}
|
869
|
+
};
|
870
|
+
}
|
871
|
+
ddog_prof_Slice_Location ddog_locations = {
|
872
|
+
.len = locations_len,
|
873
|
+
.ptr = locations_arr,
|
874
|
+
};
|
875
|
+
heap_recorder_testonly_assert_hash_matches(ddog_locations);
|
876
|
+
|
877
|
+
return Qnil;
|
878
|
+
}
|
879
|
+
|
619
880
|
static void reset_profile(ddog_prof_Profile *profile, ddog_Timespec *start_time /* Can be null */) {
|
620
881
|
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(profile, start_time);
|
621
882
|
if (reset_result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
622
883
|
rb_raise(rb_eRuntimeError, "Failed to reset profile: %"PRIsVALUE, get_error_details_and_drop(&reset_result.err));
|
623
884
|
}
|
624
885
|
}
|
886
|
+
|
887
|
+
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
888
|
+
// It SHOULD NOT be used for other purposes.
|
889
|
+
static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
890
|
+
struct stack_recorder_state *state;
|
891
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
892
|
+
|
893
|
+
heap_recorder_prepare_iteration(state->heap_recorder);
|
894
|
+
|
895
|
+
return Qnil;
|
896
|
+
}
|
897
|
+
|
898
|
+
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
899
|
+
// It SHOULD NOT be used for other purposes.
|
900
|
+
static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
901
|
+
struct stack_recorder_state *state;
|
902
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
903
|
+
|
904
|
+
heap_recorder_finish_iteration(state->heap_recorder);
|
905
|
+
|
906
|
+
return Qnil;
|
907
|
+
}
|
908
|
+
|
909
|
+
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
910
|
+
// It SHOULD NOT be used for other purposes.
|
911
|
+
static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
912
|
+
struct stack_recorder_state *state;
|
913
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
914
|
+
|
915
|
+
return heap_recorder_testonly_debug(state->heap_recorder);
|
916
|
+
}
|
917
|
+
|
918
|
+
#pragma GCC diagnostic push
|
919
|
+
// rb_gc_force_recycle was deprecated in latest versions of Ruby and is a noop.
|
920
|
+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
921
|
+
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
922
|
+
// It SHOULD NOT be used for other purposes.
|
923
|
+
static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj) {
|
924
|
+
rb_gc_force_recycle(obj);
|
925
|
+
return Qnil;
|
926
|
+
}
|
927
|
+
#pragma GCC diagnostic pop
|
928
|
+
|
929
|
+
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
930
|
+
// It SHOULD NOT be used for other purposes.
|
931
|
+
static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj) {
|
932
|
+
#ifndef NO_SEEN_OBJ_ID_FLAG
|
933
|
+
if (RB_FL_TEST(obj, RUBY_FL_SEEN_OBJ_ID)) {
|
934
|
+
return Qtrue;
|
935
|
+
} else {
|
936
|
+
return Qfalse;
|
937
|
+
}
|
938
|
+
#else
|
939
|
+
return Qfalse;
|
940
|
+
#endif
|
941
|
+
}
|
@@ -1,12 +1,14 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
3
|
#include <datadog/profiling.h>
|
4
|
+
#include <ruby.h>
|
4
5
|
|
5
6
|
typedef struct {
|
6
7
|
int64_t cpu_time_ns;
|
7
8
|
int64_t wall_time_ns;
|
8
9
|
uint32_t cpu_or_wall_samples;
|
9
10
|
uint32_t alloc_samples;
|
11
|
+
int64_t timeline_wall_time_ns;
|
10
12
|
} sample_values;
|
11
13
|
|
12
14
|
typedef struct sample_labels {
|
@@ -21,4 +23,5 @@ typedef struct sample_labels {
|
|
21
23
|
|
22
24
|
void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations, sample_values values, sample_labels labels);
|
23
25
|
void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
|
26
|
+
void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class);
|
24
27
|
VALUE enforce_recorder_instance(VALUE object);
|
@@ -38,12 +38,15 @@ module Datadog
|
|
38
38
|
|
39
39
|
data = AppSec::Processor::RuleLoader.load_data(
|
40
40
|
ip_denylist: settings.appsec.ip_denylist,
|
41
|
-
user_id_denylist: settings.appsec.user_id_denylist
|
41
|
+
user_id_denylist: settings.appsec.user_id_denylist,
|
42
42
|
)
|
43
43
|
|
44
|
+
exclusions = AppSec::Processor::RuleLoader.load_exclusions(ip_passlist: settings.appsec.ip_passlist)
|
45
|
+
|
44
46
|
ruleset = AppSec::Processor::RuleMerger.merge(
|
45
47
|
rules: [rules],
|
46
48
|
data: data,
|
49
|
+
exclusions: exclusions,
|
47
50
|
)
|
48
51
|
|
49
52
|
processor = Processor.new(ruleset: ruleset)
|
@@ -47,6 +47,13 @@ module Datadog
|
|
47
47
|
data
|
48
48
|
end
|
49
49
|
|
50
|
+
def load_exclusions(ip_passlist: [])
|
51
|
+
exclusions = []
|
52
|
+
exclusions << [passlist_exclusions(ip_passlist)] if ip_passlist.any?
|
53
|
+
|
54
|
+
exclusions
|
55
|
+
end
|
56
|
+
|
50
57
|
private
|
51
58
|
|
52
59
|
def denylist_data(id, denylist)
|
@@ -56,6 +63,59 @@ module Datadog
|
|
56
63
|
'data' => denylist.map { |v| { 'value' => v.to_s, 'expiration' => 2**63 } }
|
57
64
|
}
|
58
65
|
end
|
66
|
+
|
67
|
+
def passlist_exclusions(ip_passlist) # rubocop:disable Metrics/MethodLength
|
68
|
+
case ip_passlist
|
69
|
+
when Array
|
70
|
+
pass = ip_passlist
|
71
|
+
monitor = []
|
72
|
+
when Hash
|
73
|
+
pass = ip_passlist[:pass]
|
74
|
+
monitor = ip_passlist[:monitor]
|
75
|
+
else
|
76
|
+
pass = []
|
77
|
+
monitor = []
|
78
|
+
end
|
79
|
+
|
80
|
+
exclusions = []
|
81
|
+
|
82
|
+
exclusions << {
|
83
|
+
'conditions' => [
|
84
|
+
{
|
85
|
+
'operator' => 'ip_match',
|
86
|
+
'parameters' => {
|
87
|
+
'inputs' => [
|
88
|
+
{
|
89
|
+
'address' => 'http.client_ip'
|
90
|
+
}
|
91
|
+
],
|
92
|
+
'list' => pass
|
93
|
+
}
|
94
|
+
}
|
95
|
+
],
|
96
|
+
'id' => SecureRandom.uuid,
|
97
|
+
}
|
98
|
+
|
99
|
+
exclusions << {
|
100
|
+
'conditions' => [
|
101
|
+
{
|
102
|
+
'operator' => 'ip_match',
|
103
|
+
'parameters' => {
|
104
|
+
'inputs' => [
|
105
|
+
{
|
106
|
+
'address' => 'http.client_ip'
|
107
|
+
}
|
108
|
+
],
|
109
|
+
'list' => monitor
|
110
|
+
}
|
111
|
+
}
|
112
|
+
],
|
113
|
+
'id' => SecureRandom.uuid,
|
114
|
+
'on_match' => 'monitor'
|
115
|
+
}
|
116
|
+
|
117
|
+
exclusions
|
118
|
+
end
|
59
119
|
end
|
60
120
|
end
|
61
121
|
end
|
@@ -12,15 +12,17 @@ module Datadog
|
|
12
12
|
class NoRulesError < StandardError; end
|
13
13
|
|
14
14
|
class << self
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
CAP_ASM_RESERVED_1 = 1 << 0 # RESERVED
|
16
|
+
CAP_ASM_ACTIVATION = 1 << 1 # Remote activation via ASM_FEATURES product
|
17
|
+
CAP_ASM_IP_BLOCKING = 1 << 2 # accept IP blocking data from ASM_DATA product
|
18
|
+
CAP_ASM_DD_RULES = 1 << 3 # read ASM rules from ASM_DD product
|
19
|
+
CAP_ASM_EXCLUSIONS = 1 << 4 # exclusion filters (passlist) via ASM product
|
20
|
+
CAP_ASM_REQUEST_BLOCKING = 1 << 5 # can block on request info
|
21
|
+
CAP_ASM_RESPONSE_BLOCKING = 1 << 6 # can block on response info
|
22
|
+
CAP_ASM_USER_BLOCKING = 1 << 7 # accept user blocking data from ASM_DATA product
|
23
|
+
CAP_ASM_CUSTOM_RULES = 1 << 8 # accept custom rules
|
24
|
+
CAP_ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 # supports custom http code or redirect sa blocking response
|
25
|
+
CAP_ASM_TRUSTED_IPS = 1 << 10 # supports trusted ip
|
24
26
|
|
25
27
|
# TODO: we need to dynamically add CAP_ASM_ACTIVATION once we support it
|
26
28
|
ASM_CAPABILITIES = [
|
@@ -32,6 +34,7 @@ module Datadog
|
|
32
34
|
CAP_ASM_DD_RULES,
|
33
35
|
CAP_ASM_CUSTOM_RULES,
|
34
36
|
CAP_ASM_CUSTOM_BLOCKING_RESPONSE,
|
37
|
+
CAP_ASM_TRUSTED_IPS,
|
35
38
|
].freeze
|
36
39
|
|
37
40
|
ASM_PRODUCTS = [
|