ddtrace 1.21.0 → 1.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +40 -32
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -12
- data/ext/datadog_profiling_native_extension/heap_recorder.c +81 -4
- data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +5 -5
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +161 -62
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -13
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/auto_instrument.rb +3 -0
- data/lib/datadog/core/configuration/components.rb +2 -1
- data/lib/datadog/core/configuration/option.rb +7 -5
- data/lib/datadog/core/configuration/settings.rb +38 -17
- data/lib/datadog/core/configuration.rb +20 -4
- data/lib/datadog/core/environment/platform.rb +7 -1
- data/lib/datadog/core/remote/client/capabilities.rb +1 -1
- data/lib/datadog/core/remote/transport/http/config.rb +1 -1
- data/lib/datadog/core/telemetry/client.rb +18 -10
- data/lib/datadog/core/telemetry/emitter.rb +9 -13
- data/lib/datadog/core/telemetry/event.rb +247 -57
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/heartbeat.rb +1 -3
- data/lib/datadog/core/telemetry/http/ext.rb +4 -1
- data/lib/datadog/core/telemetry/http/response.rb +4 -0
- data/lib/datadog/core/telemetry/http/transport.rb +9 -4
- data/lib/datadog/core/telemetry/request.rb +59 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +10 -4
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +25 -0
- data/lib/datadog/profiling/component.rb +23 -15
- data/lib/datadog/profiling/exporter.rb +6 -3
- data/lib/datadog/profiling/load_native_extension.rb +14 -1
- data/lib/datadog/profiling/stack_recorder.rb +6 -2
- data/lib/datadog/profiling.rb +11 -0
- data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +11 -4
- data/lib/datadog/tracing/contrib/configurable.rb +1 -1
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +0 -5
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +4 -28
- data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -16
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +3 -6
- data/lib/datadog/tracing/metadata/ext.rb +0 -2
- data/lib/datadog/tracing/sampling/matcher.rb +23 -3
- data/lib/datadog/tracing/sampling/rule.rb +7 -2
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/ddtrace/version.rb +1 -1
- metadata +6 -16
- data/lib/datadog/core/telemetry/collector.rb +0 -250
- data/lib/datadog/core/telemetry/v1/app_event.rb +0 -59
- data/lib/datadog/core/telemetry/v1/application.rb +0 -92
- data/lib/datadog/core/telemetry/v1/configuration.rb +0 -25
- data/lib/datadog/core/telemetry/v1/dependency.rb +0 -43
- data/lib/datadog/core/telemetry/v1/host.rb +0 -59
- data/lib/datadog/core/telemetry/v1/install_signature.rb +0 -38
- data/lib/datadog/core/telemetry/v1/integration.rb +0 -64
- data/lib/datadog/core/telemetry/v1/product.rb +0 -36
- data/lib/datadog/core/telemetry/v1/telemetry_request.rb +0 -106
- data/lib/datadog/core/telemetry/v2/app_client_configuration_change.rb +0 -41
- data/lib/datadog/core/telemetry/v2/request.rb +0 -29
@@ -169,38 +169,62 @@ static const uint8_t all_value_types_positions[] =
|
|
169
169
|
|
170
170
|
#define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(ddog_prof_ValueType))
|
171
171
|
|
172
|
+
// Struct for storing stats related to a profile in a particular slot.
|
173
|
+
// These stats will share the same lifetime as the data in that profile slot.
|
174
|
+
typedef struct slot_stats {
|
175
|
+
// How many individual samples were recorded into this slot (un-weighted)
|
176
|
+
uint64_t recorded_samples;
|
177
|
+
} stats_slot;
|
178
|
+
|
179
|
+
typedef struct profile_slot {
|
180
|
+
ddog_prof_Profile profile;
|
181
|
+
stats_slot stats;
|
182
|
+
} profile_slot;
|
183
|
+
|
172
184
|
// Contains native state for each instance
|
173
185
|
struct stack_recorder_state {
|
174
186
|
// Heap recorder instance
|
175
187
|
heap_recorder *heap_recorder;
|
176
188
|
|
177
|
-
pthread_mutex_t
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
ddog_prof_Profile slot_two_profile;
|
189
|
+
pthread_mutex_t mutex_slot_one;
|
190
|
+
profile_slot profile_slot_one;
|
191
|
+
pthread_mutex_t mutex_slot_two;
|
192
|
+
profile_slot profile_slot_two;
|
182
193
|
|
183
194
|
short active_slot; // MUST NEVER BE ACCESSED FROM record_sample; this is NOT for the sampler thread to use.
|
184
195
|
|
185
196
|
uint8_t position_for[ALL_VALUE_TYPES_COUNT];
|
186
197
|
uint8_t enabled_values_count;
|
198
|
+
|
199
|
+
// Struct for storing stats related to behaviour of a stack recorder instance during its entire lifetime.
|
200
|
+
struct lifetime_stats {
|
201
|
+
// How many profiles have we serialized successfully so far
|
202
|
+
uint64_t serialization_successes;
|
203
|
+
// How many profiles have we serialized unsuccessfully so far
|
204
|
+
uint64_t serialization_failures;
|
205
|
+
// Stats on profile serialization time
|
206
|
+
long serialization_time_ns_min;
|
207
|
+
long serialization_time_ns_max;
|
208
|
+
uint64_t serialization_time_ns_total;
|
209
|
+
} stats_lifetime;
|
187
210
|
};
|
188
211
|
|
189
|
-
// Used to
|
190
|
-
struct
|
212
|
+
// Used to group mutex and the corresponding profile slot for easy unlocking after work is done.
|
213
|
+
typedef struct locked_profile_slot {
|
191
214
|
pthread_mutex_t *mutex;
|
192
|
-
|
193
|
-
};
|
215
|
+
profile_slot *data;
|
216
|
+
} locked_profile_slot;
|
194
217
|
|
195
218
|
struct call_serialize_without_gvl_arguments {
|
196
219
|
// Set by caller
|
197
220
|
struct stack_recorder_state *state;
|
198
221
|
ddog_Timespec finish_timestamp;
|
199
|
-
size_t gc_count_before_serialize;
|
200
222
|
|
201
223
|
// Set by callee
|
202
|
-
|
224
|
+
profile_slot *slot;
|
203
225
|
ddog_prof_Profile_SerializeResult result;
|
226
|
+
long heap_profile_build_time_ns;
|
227
|
+
long serialize_no_gvl_time_ns;
|
204
228
|
|
205
229
|
// Set by both
|
206
230
|
bool serialize_ran;
|
@@ -223,9 +247,9 @@ static VALUE _native_initialize(
|
|
223
247
|
static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
|
224
248
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time);
|
225
249
|
static void *call_serialize_without_gvl(void *call_args);
|
226
|
-
static
|
227
|
-
static void sampler_unlock_active_profile(
|
228
|
-
static
|
250
|
+
static locked_profile_slot sampler_lock_active_profile(struct stack_recorder_state *state);
|
251
|
+
static void sampler_unlock_active_profile(locked_profile_slot active_slot);
|
252
|
+
static profile_slot* serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state);
|
229
253
|
static VALUE _native_active_slot(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
230
254
|
static VALUE _native_is_slot_one_mutex_locked(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
231
255
|
static VALUE _native_is_slot_two_mutex_locked(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
@@ -234,7 +258,7 @@ static ddog_Timespec system_epoch_now_timespec(void);
|
|
234
258
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_instance);
|
235
259
|
static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec start_time);
|
236
260
|
static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint);
|
237
|
-
static void
|
261
|
+
static void reset_profile_slot(profile_slot *slot, ddog_Timespec *start_time /* Can be null */);
|
238
262
|
static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class);
|
239
263
|
static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locations);
|
240
264
|
static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
@@ -242,6 +266,8 @@ static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self
|
|
242
266
|
static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
243
267
|
static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
244
268
|
static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
269
|
+
static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance);
|
270
|
+
static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns);
|
245
271
|
|
246
272
|
|
247
273
|
void stack_recorder_init(VALUE profiling_module) {
|
@@ -262,6 +288,7 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
262
288
|
rb_define_singleton_method(stack_recorder_class, "_native_initialize", _native_initialize, 7);
|
263
289
|
rb_define_singleton_method(stack_recorder_class, "_native_serialize", _native_serialize, 1);
|
264
290
|
rb_define_singleton_method(stack_recorder_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
291
|
+
rb_define_singleton_method(stack_recorder_class, "_native_stats", _native_stats, 1);
|
265
292
|
rb_define_singleton_method(testing_module, "_native_active_slot", _native_active_slot, 1);
|
266
293
|
rb_define_singleton_method(testing_module, "_native_slot_one_mutex_locked?", _native_is_slot_one_mutex_locked, 1);
|
267
294
|
rb_define_singleton_method(testing_module, "_native_slot_two_mutex_locked?", _native_is_slot_two_mutex_locked, 1);
|
@@ -306,6 +333,9 @@ static VALUE _native_new(VALUE klass) {
|
|
306
333
|
initialize_slot_concurrency_control(state);
|
307
334
|
for (uint8_t i = 0; i < ALL_VALUE_TYPES_COUNT; i++) { state->position_for[i] = all_value_types_positions[i]; }
|
308
335
|
state->enabled_values_count = ALL_VALUE_TYPES_COUNT;
|
336
|
+
state->stats_lifetime = (struct lifetime_stats) {
|
337
|
+
.serialization_time_ns_min = INT64_MAX,
|
338
|
+
};
|
309
339
|
|
310
340
|
// Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
|
311
341
|
// before using them so it's ok for us to go ahead and create the StackRecorder object.
|
@@ -326,11 +356,11 @@ static VALUE _native_new(VALUE klass) {
|
|
326
356
|
}
|
327
357
|
|
328
358
|
static void initialize_slot_concurrency_control(struct stack_recorder_state *state) {
|
329
|
-
state->
|
330
|
-
state->
|
359
|
+
state->mutex_slot_one = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
|
360
|
+
state->mutex_slot_two = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
|
331
361
|
|
332
362
|
// A newly-created StackRecorder starts with slot one being active for samples, so let's lock slot two
|
333
|
-
ENFORCE_SUCCESS_GVL(pthread_mutex_lock(&state->
|
363
|
+
ENFORCE_SUCCESS_GVL(pthread_mutex_lock(&state->mutex_slot_two));
|
334
364
|
|
335
365
|
state->active_slot = 1;
|
336
366
|
}
|
@@ -353,18 +383,22 @@ static void initialize_profiles(struct stack_recorder_state *state, ddog_prof_Sl
|
|
353
383
|
rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err));
|
354
384
|
}
|
355
385
|
|
356
|
-
state->
|
357
|
-
|
386
|
+
state->profile_slot_one = (profile_slot) {
|
387
|
+
.profile = slot_one_profile_result.ok,
|
388
|
+
};
|
389
|
+
state->profile_slot_two = (profile_slot) {
|
390
|
+
.profile = slot_two_profile_result.ok,
|
391
|
+
};
|
358
392
|
}
|
359
393
|
|
360
394
|
static void stack_recorder_typed_data_free(void *state_ptr) {
|
361
395
|
struct stack_recorder_state *state = (struct stack_recorder_state *) state_ptr;
|
362
396
|
|
363
|
-
pthread_mutex_destroy(&state->
|
364
|
-
ddog_prof_Profile_drop(&state->
|
397
|
+
pthread_mutex_destroy(&state->mutex_slot_one);
|
398
|
+
ddog_prof_Profile_drop(&state->profile_slot_one.profile);
|
365
399
|
|
366
|
-
pthread_mutex_destroy(&state->
|
367
|
-
ddog_prof_Profile_drop(&state->
|
400
|
+
pthread_mutex_destroy(&state->mutex_slot_two);
|
401
|
+
ddog_prof_Profile_drop(&state->profile_slot_two.profile);
|
368
402
|
|
369
403
|
heap_recorder_free(state->heap_recorder);
|
370
404
|
|
@@ -463,8 +497,8 @@ static VALUE _native_initialize(
|
|
463
497
|
state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++;
|
464
498
|
}
|
465
499
|
|
466
|
-
ddog_prof_Profile_drop(&state->
|
467
|
-
ddog_prof_Profile_drop(&state->
|
500
|
+
ddog_prof_Profile_drop(&state->profile_slot_one.profile);
|
501
|
+
ddog_prof_Profile_drop(&state->profile_slot_two.profile);
|
468
502
|
|
469
503
|
ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count};
|
470
504
|
initialize_profiles(state, sample_types);
|
@@ -480,16 +514,17 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
480
514
|
// Need to do this while still holding on to the Global VM Lock; see comments on method for why
|
481
515
|
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
482
516
|
|
517
|
+
long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
483
518
|
// Prepare the iteration on heap recorder we'll be doing outside the GVL. The preparation needs to
|
484
519
|
// happen while holding on to the GVL.
|
485
520
|
heap_recorder_prepare_iteration(state->heap_recorder);
|
521
|
+
long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - heap_iteration_prep_start_time_ns;
|
486
522
|
|
487
523
|
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
|
488
524
|
// is pending
|
489
525
|
struct call_serialize_without_gvl_arguments args = {
|
490
526
|
.state = state,
|
491
527
|
.finish_timestamp = finish_timestamp,
|
492
|
-
.gc_count_before_serialize = rb_gc_count(),
|
493
528
|
.serialize_ran = false
|
494
529
|
};
|
495
530
|
|
@@ -510,12 +545,27 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
510
545
|
// Cleanup after heap recorder iteration. This needs to happen while holding on to the GVL.
|
511
546
|
heap_recorder_finish_iteration(state->heap_recorder);
|
512
547
|
|
548
|
+
// NOTE: We are focusing on the serialization time outside of the GVL in this stat here. This doesn't
|
549
|
+
// really cover the full serialization process but it gives a more useful number since it bypasses
|
550
|
+
// the noise of acquiring GVLs and dealing with interruptions which is highly specific to runtime
|
551
|
+
// conditions and over which we really have no control about.
|
552
|
+
long serialization_time_ns = args.serialize_no_gvl_time_ns;
|
553
|
+
if (serialization_time_ns >= 0) {
|
554
|
+
// Only update stats if our serialization time is valid.
|
555
|
+
state->stats_lifetime.serialization_time_ns_max = long_max_of(state->stats_lifetime.serialization_time_ns_max, serialization_time_ns);
|
556
|
+
state->stats_lifetime.serialization_time_ns_min = long_min_of(state->stats_lifetime.serialization_time_ns_min, serialization_time_ns);
|
557
|
+
state->stats_lifetime.serialization_time_ns_total += serialization_time_ns;
|
558
|
+
}
|
559
|
+
|
513
560
|
ddog_prof_Profile_SerializeResult serialized_profile = args.result;
|
514
561
|
|
515
562
|
if (serialized_profile.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
|
563
|
+
state->stats_lifetime.serialization_failures++;
|
516
564
|
return rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&serialized_profile.err));
|
517
565
|
}
|
518
566
|
|
567
|
+
state->stats_lifetime.serialization_successes++;
|
568
|
+
|
519
569
|
VALUE encoded_pprof = ruby_string_from_vec_u8(serialized_profile.ok.buffer);
|
520
570
|
|
521
571
|
ddog_Timespec ddprof_start = serialized_profile.ok.start;
|
@@ -525,8 +575,9 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
525
575
|
|
526
576
|
VALUE start = ruby_time_from(ddprof_start);
|
527
577
|
VALUE finish = ruby_time_from(ddprof_finish);
|
578
|
+
VALUE profile_stats = build_profile_stats(args.slot, serialization_time_ns, heap_iteration_prep_time_ns, args.heap_profile_build_time_ns);
|
528
579
|
|
529
|
-
return rb_ary_new_from_args(2, ok_symbol, rb_ary_new_from_args(
|
580
|
+
return rb_ary_new_from_args(2, ok_symbol, rb_ary_new_from_args(4, start, finish, encoded_pprof, profile_stats));
|
530
581
|
}
|
531
582
|
|
532
583
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time) {
|
@@ -539,7 +590,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
539
590
|
struct stack_recorder_state *state;
|
540
591
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
541
592
|
|
542
|
-
|
593
|
+
locked_profile_slot active_slot = sampler_lock_active_profile(state);
|
543
594
|
|
544
595
|
// Note: We initialize this array to have ALL_VALUE_TYPES_COUNT but only tell libdatadog to use the first
|
545
596
|
// state->enabled_values_count values. This simplifies handling disabled value types -- we still put them on the
|
@@ -563,7 +614,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
563
614
|
}
|
564
615
|
|
565
616
|
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
566
|
-
active_slot.profile,
|
617
|
+
&active_slot.data->profile,
|
567
618
|
(ddog_prof_Sample) {
|
568
619
|
.locations = locations,
|
569
620
|
.values = (ddog_Slice_I64) {.ptr = metric_values, .len = state->enabled_values_count},
|
@@ -572,6 +623,8 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
572
623
|
labels.end_timestamp_ns
|
573
624
|
);
|
574
625
|
|
626
|
+
active_slot.data->stats.recorded_samples++;
|
627
|
+
|
575
628
|
sampler_unlock_active_profile(active_slot);
|
576
629
|
|
577
630
|
if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
@@ -592,9 +645,9 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
592
645
|
struct stack_recorder_state *state;
|
593
646
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
594
647
|
|
595
|
-
|
648
|
+
locked_profile_slot active_slot = sampler_lock_active_profile(state);
|
596
649
|
|
597
|
-
ddog_prof_Profile_Result result = ddog_prof_Profile_set_endpoint(active_slot.profile, local_root_span_id, endpoint);
|
650
|
+
ddog_prof_Profile_Result result = ddog_prof_Profile_set_endpoint(&active_slot.data->profile, local_root_span_id, endpoint);
|
598
651
|
|
599
652
|
sampler_unlock_active_profile(active_slot);
|
600
653
|
|
@@ -609,12 +662,10 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
609
662
|
// during iteration of heap recorder live objects.
|
610
663
|
typedef struct heap_recorder_iteration_context {
|
611
664
|
struct stack_recorder_state *state;
|
612
|
-
|
665
|
+
profile_slot *slot;
|
613
666
|
|
614
667
|
bool error;
|
615
668
|
char error_msg[MAX_LEN_HEAP_ITERATION_ERROR_MSG];
|
616
|
-
|
617
|
-
size_t profile_gen;
|
618
669
|
} heap_recorder_iteration_context;
|
619
670
|
|
620
671
|
static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteration_data iteration_data, void *extra_arg) {
|
@@ -643,11 +694,11 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
|
|
643
694
|
}
|
644
695
|
labels[label_offset++] = (ddog_prof_Label) {
|
645
696
|
.key = DDOG_CHARSLICE_C("gc gen age"),
|
646
|
-
.num =
|
697
|
+
.num = object_data->gen_age,
|
647
698
|
};
|
648
699
|
|
649
700
|
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
650
|
-
context->profile,
|
701
|
+
&context->slot->profile,
|
651
702
|
(ddog_prof_Sample) {
|
652
703
|
.locations = iteration_data.locations,
|
653
704
|
.values = (ddog_Slice_I64) {.ptr = metric_values, .len = context->state->enabled_values_count},
|
@@ -659,6 +710,8 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
|
|
659
710
|
0
|
660
711
|
);
|
661
712
|
|
713
|
+
context->slot->stats.recorded_samples++;
|
714
|
+
|
662
715
|
if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
663
716
|
read_ddogerr_string_and_drop(&result.err, context->error_msg, MAX_LEN_HEAP_ITERATION_ERROR_MSG);
|
664
717
|
context->error = true;
|
@@ -670,13 +723,12 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
|
|
670
723
|
return true;
|
671
724
|
}
|
672
725
|
|
673
|
-
static void build_heap_profile_without_gvl(struct stack_recorder_state *state,
|
726
|
+
static void build_heap_profile_without_gvl(struct stack_recorder_state *state, profile_slot *slot) {
|
674
727
|
heap_recorder_iteration_context iteration_context = {
|
675
728
|
.state = state,
|
676
|
-
.
|
729
|
+
.slot = slot,
|
677
730
|
.error = false,
|
678
731
|
.error_msg = {0},
|
679
|
-
.profile_gen = gc_count_before_serialize,
|
680
732
|
};
|
681
733
|
bool iterated = heap_recorder_for_each_live_object(state->heap_recorder, add_heap_sample_to_active_profile_without_gvl, (void*) &iteration_context);
|
682
734
|
// We wait until we're out of the iteration to grab the gvl and raise. This is important because during
|
@@ -694,15 +746,21 @@ static void build_heap_profile_without_gvl(struct stack_recorder_state *state, d
|
|
694
746
|
static void *call_serialize_without_gvl(void *call_args) {
|
695
747
|
struct call_serialize_without_gvl_arguments *args = (struct call_serialize_without_gvl_arguments *) call_args;
|
696
748
|
|
697
|
-
|
749
|
+
long serialize_no_gvl_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
750
|
+
|
751
|
+
profile_slot *slot_now_inactive = serializer_flip_active_and_inactive_slots(args->state);
|
752
|
+
|
753
|
+
args->slot = slot_now_inactive;
|
698
754
|
|
699
755
|
// Now that we have the inactive profile with all but heap samples, lets fill it with heap data
|
700
756
|
// without needing to race with the active sampler
|
701
|
-
build_heap_profile_without_gvl(args->state, args->
|
757
|
+
build_heap_profile_without_gvl(args->state, args->slot);
|
758
|
+
args->heap_profile_build_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns;
|
702
759
|
|
703
760
|
// Note: The profile gets reset by the serialize call
|
704
|
-
args->result = ddog_prof_Profile_serialize(args->profile, &args->finish_timestamp, NULL /* duration_nanos is optional */, NULL /* start_time is optional */);
|
761
|
+
args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->finish_timestamp, NULL /* duration_nanos is optional */, NULL /* start_time is optional */);
|
705
762
|
args->serialize_ran = true;
|
763
|
+
args->serialize_no_gvl_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns;
|
706
764
|
|
707
765
|
return NULL; // Unused
|
708
766
|
}
|
@@ -712,42 +770,42 @@ VALUE enforce_recorder_instance(VALUE object) {
|
|
712
770
|
return object;
|
713
771
|
}
|
714
772
|
|
715
|
-
static
|
773
|
+
static locked_profile_slot sampler_lock_active_profile(struct stack_recorder_state *state) {
|
716
774
|
int error;
|
717
775
|
|
718
776
|
for (int attempts = 0; attempts < 2; attempts++) {
|
719
|
-
error = pthread_mutex_trylock(&state->
|
777
|
+
error = pthread_mutex_trylock(&state->mutex_slot_one);
|
720
778
|
if (error && error != EBUSY) ENFORCE_SUCCESS_GVL(error);
|
721
779
|
|
722
780
|
// Slot one is active
|
723
|
-
if (!error) return (
|
781
|
+
if (!error) return (locked_profile_slot) {.mutex = &state->mutex_slot_one, .data = &state->profile_slot_one};
|
724
782
|
|
725
783
|
// If we got here, slot one was not active, let's try slot two
|
726
784
|
|
727
|
-
error = pthread_mutex_trylock(&state->
|
785
|
+
error = pthread_mutex_trylock(&state->mutex_slot_two);
|
728
786
|
if (error && error != EBUSY) ENFORCE_SUCCESS_GVL(error);
|
729
787
|
|
730
788
|
// Slot two is active
|
731
|
-
if (!error) return (
|
789
|
+
if (!error) return (locked_profile_slot) {.mutex = &state->mutex_slot_two, .data = &state->profile_slot_two};
|
732
790
|
}
|
733
791
|
|
734
792
|
// We already tried both multiple times, and we did not succeed. This is not expected to happen. Let's stop sampling.
|
735
793
|
rb_raise(rb_eRuntimeError, "Failed to grab either mutex in sampler_lock_active_profile");
|
736
794
|
}
|
737
795
|
|
738
|
-
static void sampler_unlock_active_profile(
|
796
|
+
static void sampler_unlock_active_profile(locked_profile_slot active_slot) {
|
739
797
|
ENFORCE_SUCCESS_GVL(pthread_mutex_unlock(active_slot.mutex));
|
740
798
|
}
|
741
799
|
|
742
|
-
static
|
800
|
+
static profile_slot* serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state) {
|
743
801
|
int previously_active_slot = state->active_slot;
|
744
802
|
|
745
803
|
if (previously_active_slot != 1 && previously_active_slot != 2) {
|
746
804
|
grab_gvl_and_raise(rb_eRuntimeError, "Unexpected active_slot state %d in serializer_flip_active_and_inactive_slots", previously_active_slot);
|
747
805
|
}
|
748
806
|
|
749
|
-
pthread_mutex_t *previously_active = (previously_active_slot == 1) ? &state->
|
750
|
-
pthread_mutex_t *previously_inactive = (previously_active_slot == 1) ? &state->
|
807
|
+
pthread_mutex_t *previously_active = (previously_active_slot == 1) ? &state->mutex_slot_one : &state->mutex_slot_two;
|
808
|
+
pthread_mutex_t *previously_inactive = (previously_active_slot == 1) ? &state->mutex_slot_two : &state->mutex_slot_one;
|
751
809
|
|
752
810
|
// Release the lock, thus making this slot active
|
753
811
|
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_unlock(previously_inactive));
|
@@ -758,8 +816,8 @@ static ddog_prof_Profile *serializer_flip_active_and_inactive_slots(struct stack
|
|
758
816
|
// Update active_slot
|
759
817
|
state->active_slot = (previously_active_slot == 1) ? 2 : 1;
|
760
818
|
|
761
|
-
// Return
|
762
|
-
return (previously_active_slot == 1) ? &state->
|
819
|
+
// Return pointer to previously active slot (now inactive)
|
820
|
+
return (previously_active_slot == 1) ? &state->profile_slot_one : &state->profile_slot_two;
|
763
821
|
}
|
764
822
|
|
765
823
|
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
@@ -783,7 +841,7 @@ static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot) {
|
|
783
841
|
struct stack_recorder_state *state;
|
784
842
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
785
843
|
|
786
|
-
pthread_mutex_t *slot_mutex = (slot == 1) ? &state->
|
844
|
+
pthread_mutex_t *slot_mutex = (slot == 1) ? &state->mutex_slot_one : &state->mutex_slot_two;
|
787
845
|
|
788
846
|
// Like Heisenberg's uncertainty principle, we can't observe without affecting...
|
789
847
|
int error = pthread_mutex_trylock(slot_mutex);
|
@@ -818,8 +876,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
818
876
|
// resulting state is inconsistent, we make sure to reset it back to the initial state.
|
819
877
|
initialize_slot_concurrency_control(state);
|
820
878
|
|
821
|
-
|
822
|
-
|
879
|
+
reset_profile_slot(&state->profile_slot_one, /* start_time: */ NULL);
|
880
|
+
reset_profile_slot(&state->profile_slot_two, /* start_time: */ NULL);
|
823
881
|
|
824
882
|
heap_recorder_after_fork(state->heap_recorder);
|
825
883
|
|
@@ -830,8 +888,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
830
888
|
// not be interrupted part-way through by a VM fork.
|
831
889
|
static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec start_time) {
|
832
890
|
// Before making this profile active, we reset it so that it uses the correct start_time for its start
|
833
|
-
|
834
|
-
|
891
|
+
profile_slot *next_profile_slot = (state->active_slot == 1) ? &state->profile_slot_two : &state->profile_slot_one;
|
892
|
+
reset_profile_slot(next_profile_slot, &start_time);
|
835
893
|
}
|
836
894
|
|
837
895
|
static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint) {
|
@@ -877,11 +935,12 @@ static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locatio
|
|
877
935
|
return Qnil;
|
878
936
|
}
|
879
937
|
|
880
|
-
static void
|
881
|
-
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(profile, start_time);
|
938
|
+
static void reset_profile_slot(profile_slot *slot, ddog_Timespec *start_time /* Can be null */) {
|
939
|
+
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(&slot->profile, start_time);
|
882
940
|
if (reset_result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
883
941
|
rb_raise(rb_eRuntimeError, "Failed to reset profile: %"PRIsVALUE, get_error_details_and_drop(&reset_result.err));
|
884
942
|
}
|
943
|
+
slot->stats = (stats_slot) {};
|
885
944
|
}
|
886
945
|
|
887
946
|
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
@@ -918,10 +977,13 @@ static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recor
|
|
918
977
|
#pragma GCC diagnostic push
|
919
978
|
// rb_gc_force_recycle was deprecated in latest versions of Ruby and is a noop.
|
920
979
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
980
|
+
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
921
981
|
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
922
982
|
// It SHOULD NOT be used for other purposes.
|
923
983
|
static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj) {
|
924
|
-
|
984
|
+
#ifdef HAVE_WORKING_RB_GC_FORCE_RECYCLE
|
985
|
+
rb_gc_force_recycle(obj);
|
986
|
+
#endif
|
925
987
|
return Qnil;
|
926
988
|
}
|
927
989
|
#pragma GCC diagnostic pop
|
@@ -939,3 +1001,40 @@ static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj) {
|
|
939
1001
|
return Qfalse;
|
940
1002
|
#endif
|
941
1003
|
}
|
1004
|
+
|
1005
|
+
static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE recorder_instance) {
|
1006
|
+
struct stack_recorder_state *state;
|
1007
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
1008
|
+
|
1009
|
+
uint64_t total_serializations = state->stats_lifetime.serialization_successes + state->stats_lifetime.serialization_failures;
|
1010
|
+
|
1011
|
+
VALUE heap_recorder_snapshot = state->heap_recorder ?
|
1012
|
+
heap_recorder_state_snapshot(state->heap_recorder) : Qnil;
|
1013
|
+
|
1014
|
+
VALUE stats_as_hash = rb_hash_new();
|
1015
|
+
VALUE arguments[] = {
|
1016
|
+
ID2SYM(rb_intern("serialization_successes")), /* => */ ULL2NUM(state->stats_lifetime.serialization_successes),
|
1017
|
+
ID2SYM(rb_intern("serialization_failures")), /* => */ ULL2NUM(state->stats_lifetime.serialization_failures),
|
1018
|
+
|
1019
|
+
ID2SYM(rb_intern("serialization_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats_lifetime.serialization_time_ns_min, != INT64_MAX, LONG2NUM),
|
1020
|
+
ID2SYM(rb_intern("serialization_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats_lifetime.serialization_time_ns_max, > 0, LONG2NUM),
|
1021
|
+
ID2SYM(rb_intern("serialization_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats_lifetime.serialization_time_ns_total, > 0, LONG2NUM),
|
1022
|
+
ID2SYM(rb_intern("serialization_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats_lifetime.serialization_time_ns_total, total_serializations),
|
1023
|
+
|
1024
|
+
ID2SYM(rb_intern("heap_recorder_snapshot")), /* => */ heap_recorder_snapshot,
|
1025
|
+
};
|
1026
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
1027
|
+
return stats_as_hash;
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns) {
|
1031
|
+
VALUE stats_as_hash = rb_hash_new();
|
1032
|
+
VALUE arguments[] = {
|
1033
|
+
ID2SYM(rb_intern("recorded_samples")), /* => */ ULL2NUM(slot->stats.recorded_samples),
|
1034
|
+
ID2SYM(rb_intern("serialization_time_ns")), /* => */ LONG2NUM(serialization_time_ns),
|
1035
|
+
ID2SYM(rb_intern("heap_iteration_prep_time_ns")), /* => */ LONG2NUM(heap_iteration_prep_time_ns),
|
1036
|
+
ID2SYM(rb_intern("heap_profile_build_time_ns")), /* => */ LONG2NUM(heap_profile_build_time_ns),
|
1037
|
+
};
|
1038
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
1039
|
+
return stats_as_hash;
|
1040
|
+
}
|
@@ -13,6 +13,18 @@ module Datadog
|
|
13
13
|
module AppSec
|
14
14
|
module Contrib
|
15
15
|
module Rack
|
16
|
+
# Create an array of lowercased headers
|
17
|
+
WAF_VENDOR_HEADERS_TAGS = %w[
|
18
|
+
X-Amzn-Trace-Id
|
19
|
+
Cloudfront-Viewer-Ja3-Fingerprint
|
20
|
+
Cf-Ray
|
21
|
+
X-Cloud-Trace-Context
|
22
|
+
X-Appgw-Trace-id
|
23
|
+
X-SigSci-RequestID
|
24
|
+
X-SigSci-Tags
|
25
|
+
Akamai-User-Risk
|
26
|
+
].map(&:downcase).freeze
|
27
|
+
|
16
28
|
# Topmost Rack middleware for AppSec
|
17
29
|
# This should be inserted just below Datadog::Tracing::Contrib::Rack::TraceMiddleware
|
18
30
|
class RequestMiddleware
|
@@ -20,6 +32,7 @@ module Datadog
|
|
20
32
|
@app = app
|
21
33
|
|
22
34
|
@oneshot_tags_sent = false
|
35
|
+
@rack_headers = {}
|
23
36
|
end
|
24
37
|
|
25
38
|
# rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/MethodLength
|
@@ -54,7 +67,8 @@ module Datadog
|
|
54
67
|
|
55
68
|
gateway_request = Gateway::Request.new(env)
|
56
69
|
|
57
|
-
add_appsec_tags(processor, scope
|
70
|
+
add_appsec_tags(processor, scope)
|
71
|
+
add_request_tags(scope, env)
|
58
72
|
|
59
73
|
request_return, request_response = catch(::Datadog::AppSec::Ext::INTERRUPT) do
|
60
74
|
Instrumentation.gateway.push('rack.request', gateway_request) do
|
@@ -129,7 +143,7 @@ module Datadog
|
|
129
143
|
Datadog::Tracing.active_span
|
130
144
|
end
|
131
145
|
|
132
|
-
def add_appsec_tags(processor, scope
|
146
|
+
def add_appsec_tags(processor, scope)
|
133
147
|
span = scope.service_entry_span
|
134
148
|
trace = scope.trace
|
135
149
|
|
@@ -139,17 +153,6 @@ module Datadog
|
|
139
153
|
span.set_tag('_dd.runtime_family', 'ruby')
|
140
154
|
span.set_tag('_dd.appsec.waf.version', Datadog::AppSec::WAF::VERSION::BASE_STRING)
|
141
155
|
|
142
|
-
if span && span.get_tag(Tracing::Metadata::Ext::HTTP::TAG_CLIENT_IP).nil?
|
143
|
-
request_header_collection = Datadog::Tracing::Contrib::Rack::Header::RequestHeaderCollection.new(env)
|
144
|
-
|
145
|
-
# always collect client ip, as this is part of AppSec provided functionality
|
146
|
-
Datadog::Tracing::ClientIp.set_client_ip_tag!(
|
147
|
-
span,
|
148
|
-
headers: request_header_collection,
|
149
|
-
remote_ip: env['REMOTE_ADDR']
|
150
|
-
)
|
151
|
-
end
|
152
|
-
|
153
156
|
if processor.diagnostics
|
154
157
|
diagnostics = processor.diagnostics
|
155
158
|
|
@@ -175,6 +178,29 @@ module Datadog
|
|
175
178
|
end
|
176
179
|
end
|
177
180
|
|
181
|
+
def add_request_tags(scope, env)
|
182
|
+
span = scope.service_entry_span
|
183
|
+
|
184
|
+
return unless span
|
185
|
+
|
186
|
+
# Always add WAF vendors headers
|
187
|
+
WAF_VENDOR_HEADERS_TAGS.each do |lowercase_header|
|
188
|
+
rack_header = to_rack_header(lowercase_header)
|
189
|
+
span.set_tag("http.request.headers.#{lowercase_header}", env[rack_header]) if env[rack_header]
|
190
|
+
end
|
191
|
+
|
192
|
+
if span && span.get_tag(Tracing::Metadata::Ext::HTTP::TAG_CLIENT_IP).nil?
|
193
|
+
request_header_collection = Datadog::Tracing::Contrib::Rack::Header::RequestHeaderCollection.new(env)
|
194
|
+
|
195
|
+
# always collect client ip, as this is part of AppSec provided functionality
|
196
|
+
Datadog::Tracing::ClientIp.set_client_ip_tag!(
|
197
|
+
span,
|
198
|
+
headers: request_header_collection,
|
199
|
+
remote_ip: env['REMOTE_ADDR']
|
200
|
+
)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
178
204
|
def add_waf_runtime_tags(scope)
|
179
205
|
span = scope.service_entry_span
|
180
206
|
context = scope.processor_context
|
@@ -187,6 +213,10 @@ module Datadog
|
|
187
213
|
span.set_tag('_dd.appsec.waf.duration', context.time_ns / 1000.0)
|
188
214
|
span.set_tag('_dd.appsec.waf.duration_ext', context.time_ext_ns / 1000.0)
|
189
215
|
end
|
216
|
+
|
217
|
+
def to_rack_header(header)
|
218
|
+
@rack_headers[header] ||= Datadog::Tracing::Contrib::Rack::Header.to_rack_header(header)
|
219
|
+
end
|
190
220
|
end
|
191
221
|
end
|
192
222
|
end
|
data/lib/datadog/appsec/event.rb
CHANGED
@@ -138,7 +138,7 @@ module Datadog
|
|
138
138
|
private
|
139
139
|
|
140
140
|
def compressed_and_base64_encoded(value)
|
141
|
-
Base64.
|
141
|
+
Base64.strict_encode64(gzip(value))
|
142
142
|
rescue TypeError => e
|
143
143
|
Datadog.logger.debug do
|
144
144
|
"Failed to compress and encode value when populating AppSec::Event. Error: #{e.message}"
|
@@ -62,7 +62,8 @@ module Datadog
|
|
62
62
|
|
63
63
|
Telemetry::Client.new(
|
64
64
|
enabled: enabled,
|
65
|
-
heartbeat_interval_seconds: settings.telemetry.heartbeat_interval_seconds
|
65
|
+
heartbeat_interval_seconds: settings.telemetry.heartbeat_interval_seconds,
|
66
|
+
dependency_collection: settings.telemetry.dependency_collection
|
66
67
|
)
|
67
68
|
end
|
68
69
|
end
|
@@ -8,7 +8,13 @@ module Datadog
|
|
8
8
|
# Represents an instance of an integration configuration option
|
9
9
|
# @public_api
|
10
10
|
class Option
|
11
|
-
|
11
|
+
# @!attribute [r] definition
|
12
|
+
# The definition object that matches this option.
|
13
|
+
# @return [Configuration::OptionDefinition]
|
14
|
+
# @!attribute [r] precedence_set
|
15
|
+
# When this option was last set, what was the value precedence used?
|
16
|
+
# @return [Precedence::Value]
|
17
|
+
attr_reader :definition, :precedence_set
|
12
18
|
|
13
19
|
# Option setting precedence.
|
14
20
|
module Precedence
|
@@ -303,10 +309,6 @@ module Datadog
|
|
303
309
|
['true', '1'].include?(ENV.fetch('DD_EXPERIMENTAL_SKIP_CONFIGURATION_VALIDATION', '').strip)
|
304
310
|
end
|
305
311
|
|
306
|
-
# Used for testing
|
307
|
-
attr_reader :precedence_set
|
308
|
-
private :precedence_set
|
309
|
-
|
310
312
|
# Anchor object that represents a value that is not set.
|
311
313
|
# This is necessary because `nil` is a valid value to be set.
|
312
314
|
UNSET = Object.new
|