datadog 2.0.0.beta1 → 2.0.0.rc1

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +181 -1
  3. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +1 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +40 -32
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -12
  6. data/ext/datadog_profiling_native_extension/crashtracker.c +108 -0
  7. data/ext/datadog_profiling_native_extension/extconf.rb +9 -23
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +81 -4
  9. data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
  10. data/ext/datadog_profiling_native_extension/http_transport.c +1 -94
  11. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +86 -0
  12. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +4 -0
  13. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +2 -12
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +25 -86
  15. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -5
  17. data/ext/datadog_profiling_native_extension/stack_recorder.c +161 -62
  18. data/lib/datadog/appsec/contrib/devise/tracking.rb +8 -0
  19. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -13
  20. data/lib/datadog/appsec/event.rb +2 -2
  21. data/lib/datadog/core/configuration/components.rb +2 -1
  22. data/lib/datadog/core/configuration/option.rb +7 -5
  23. data/lib/datadog/core/configuration/settings.rb +34 -79
  24. data/lib/datadog/core/configuration.rb +20 -4
  25. data/lib/datadog/core/environment/platform.rb +7 -1
  26. data/lib/datadog/core/remote/client/capabilities.rb +2 -1
  27. data/lib/datadog/core/remote/client.rb +1 -5
  28. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  29. data/lib/datadog/core/remote/dispatcher.rb +3 -3
  30. data/lib/datadog/core/remote/transport/http/config.rb +5 -5
  31. data/lib/datadog/core/telemetry/client.rb +18 -10
  32. data/lib/datadog/core/telemetry/emitter.rb +9 -13
  33. data/lib/datadog/core/telemetry/event.rb +247 -57
  34. data/lib/datadog/core/telemetry/ext.rb +1 -0
  35. data/lib/datadog/core/telemetry/heartbeat.rb +1 -3
  36. data/lib/datadog/core/telemetry/http/ext.rb +4 -1
  37. data/lib/datadog/core/telemetry/http/response.rb +4 -0
  38. data/lib/datadog/core/telemetry/http/transport.rb +9 -4
  39. data/lib/datadog/core/telemetry/request.rb +59 -0
  40. data/lib/datadog/core/utils/base64.rb +22 -0
  41. data/lib/datadog/opentelemetry/sdk/span_processor.rb +19 -2
  42. data/lib/datadog/opentelemetry/sdk/trace/span.rb +3 -17
  43. data/lib/datadog/profiling/collectors/code_provenance.rb +10 -4
  44. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +25 -0
  45. data/lib/datadog/profiling/component.rb +49 -17
  46. data/lib/datadog/profiling/crashtracker.rb +91 -0
  47. data/lib/datadog/profiling/exporter.rb +6 -3
  48. data/lib/datadog/profiling/http_transport.rb +7 -11
  49. data/lib/datadog/profiling/load_native_extension.rb +14 -1
  50. data/lib/datadog/profiling/profiler.rb +9 -2
  51. data/lib/datadog/profiling/stack_recorder.rb +6 -2
  52. data/lib/datadog/profiling.rb +12 -0
  53. data/lib/datadog/tracing/component.rb +5 -1
  54. data/lib/datadog/tracing/configuration/dynamic.rb +39 -1
  55. data/lib/datadog/tracing/configuration/settings.rb +1 -0
  56. data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
  57. data/lib/datadog/tracing/contrib/action_view/integration.rb +1 -1
  58. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -0
  59. data/lib/datadog/tracing/contrib/active_record/integration.rb +11 -1
  60. data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
  61. data/lib/datadog/tracing/contrib/configuration/resolver.rb +43 -0
  62. data/lib/datadog/tracing/contrib/grape/endpoint.rb +43 -5
  63. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +1 -1
  64. data/lib/datadog/tracing/correlation.rb +3 -4
  65. data/lib/datadog/tracing/remote.rb +5 -1
  66. data/lib/datadog/tracing/sampling/ext.rb +5 -1
  67. data/lib/datadog/tracing/sampling/matcher.rb +75 -26
  68. data/lib/datadog/tracing/sampling/rule.rb +27 -4
  69. data/lib/datadog/tracing/sampling/rule_sampler.rb +19 -1
  70. data/lib/datadog/tracing/sampling/span/matcher.rb +13 -41
  71. data/lib/datadog/tracing/span.rb +7 -2
  72. data/lib/datadog/tracing/span_link.rb +92 -0
  73. data/lib/datadog/tracing/span_operation.rb +6 -4
  74. data/lib/datadog/tracing/trace_operation.rb +12 -0
  75. data/lib/datadog/tracing/tracer.rb +4 -3
  76. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -1
  77. data/lib/datadog/tracing/utils.rb +16 -0
  78. data/lib/datadog/version.rb +1 -1
  79. metadata +10 -31
  80. data/lib/datadog/core/telemetry/collector.rb +0 -248
  81. data/lib/datadog/core/telemetry/v1/app_event.rb +0 -59
  82. data/lib/datadog/core/telemetry/v1/application.rb +0 -94
  83. data/lib/datadog/core/telemetry/v1/configuration.rb +0 -27
  84. data/lib/datadog/core/telemetry/v1/dependency.rb +0 -45
  85. data/lib/datadog/core/telemetry/v1/host.rb +0 -59
  86. data/lib/datadog/core/telemetry/v1/install_signature.rb +0 -38
  87. data/lib/datadog/core/telemetry/v1/integration.rb +0 -66
  88. data/lib/datadog/core/telemetry/v1/product.rb +0 -36
  89. data/lib/datadog/core/telemetry/v1/telemetry_request.rb +0 -108
  90. data/lib/datadog/core/telemetry/v2/app_client_configuration_change.rb +0 -41
  91. 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 slot_one_mutex;
178
- ddog_prof_Profile slot_one_profile;
179
-
180
- pthread_mutex_t slot_two_mutex;
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 return a pair of values from sampler_lock_active_profile()
190
- struct active_slot_pair {
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
- ddog_prof_Profile *profile;
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
- ddog_prof_Profile *profile;
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 struct active_slot_pair sampler_lock_active_profile(struct stack_recorder_state *state);
227
- static void sampler_unlock_active_profile(struct active_slot_pair active_slot);
228
- static ddog_prof_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state);
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 reset_profile(ddog_prof_Profile *profile, ddog_Timespec *start_time /* Can be null */);
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->slot_one_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
330
- state->slot_two_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
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->slot_two_mutex));
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->slot_one_profile = slot_one_profile_result.ok;
357
- state->slot_two_profile = slot_two_profile_result.ok;
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->slot_one_mutex);
364
- ddog_prof_Profile_drop(&state->slot_one_profile);
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->slot_two_mutex);
367
- ddog_prof_Profile_drop(&state->slot_two_profile);
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->slot_one_profile);
467
- ddog_prof_Profile_drop(&state->slot_two_profile);
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(3, start, finish, encoded_pprof));
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
- struct active_slot_pair active_slot = sampler_lock_active_profile(state);
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
- struct active_slot_pair active_slot = sampler_lock_active_profile(state);
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
- ddog_prof_Profile *profile;
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 = context->profile_gen - object_data->alloc_gen,
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, ddog_prof_Profile *profile, size_t gc_count_before_serialize) {
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
- .profile = profile,
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
- args->profile = serializer_flip_active_and_inactive_slots(args->state);
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->profile, args->gc_count_before_serialize);
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 struct active_slot_pair sampler_lock_active_profile(struct stack_recorder_state *state) {
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->slot_one_mutex);
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 (struct active_slot_pair) {.mutex = &state->slot_one_mutex, .profile = &state->slot_one_profile};
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->slot_two_mutex);
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 (struct active_slot_pair) {.mutex = &state->slot_two_mutex, .profile = &state->slot_two_profile};
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(struct active_slot_pair active_slot) {
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 ddog_prof_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state) {
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->slot_one_mutex : &state->slot_two_mutex;
750
- pthread_mutex_t *previously_inactive = (previously_active_slot == 1) ? &state->slot_two_mutex : &state->slot_one_mutex;
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 profile for previously active slot (now inactive)
762
- return (previously_active_slot == 1) ? &state->slot_one_profile : &state->slot_two_profile;
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->slot_one_mutex : &state->slot_two_mutex;
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
- reset_profile(&state->slot_one_profile, /* start_time: */ NULL);
822
- reset_profile(&state->slot_two_profile, /* start_time: */ NULL);
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
- ddog_prof_Profile *next_profile = (state->active_slot == 1) ? &state->slot_two_profile : &state->slot_one_profile;
834
- reset_profile(next_profile, &start_time);
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 reset_profile(ddog_prof_Profile *profile, ddog_Timespec *start_time /* Can be null */) {
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
- rb_gc_force_recycle(obj);
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,12 +13,16 @@ module Datadog
13
13
  SIGNUP_EVENT = 'users.signup'
14
14
 
15
15
  def self.track_login_success(trace, span, user_id:, **others)
16
+ return if trace.nil? || span.nil?
17
+
16
18
  track(LOGIN_SUCCESS_EVENT, trace, span, **others)
17
19
 
18
20
  Kit::Identity.set_user(trace, span, id: user_id.to_s, **others) if user_id
19
21
  end
20
22
 
21
23
  def self.track_login_failure(trace, span, user_id:, user_exists:, **others)
24
+ return if trace.nil? || span.nil?
25
+
22
26
  track(LOGIN_FAILURE_EVENT, trace, span, **others)
23
27
 
24
28
  span.set_tag('appsec.events.users.login.failure.usr.id', user_id) if user_id
@@ -26,11 +30,15 @@ module Datadog
26
30
  end
27
31
 
28
32
  def self.track_signup(trace, span, user_id:, **others)
33
+ return if trace.nil? || span.nil?
34
+
29
35
  track(SIGNUP_EVENT, trace, span, **others)
30
36
  Kit::Identity.set_user(trace, id: user_id.to_s, **others) if user_id
31
37
  end
32
38
 
33
39
  def self.track(event, trace, span, **others)
40
+ return if trace.nil? || span.nil?
41
+
34
42
  span.set_tag("appsec.events.#{event}.track", 'true')
35
43
  span.set_tag("_dd.appsec.events.#{event}.auto.mode", Datadog.configuration.appsec.track_user_events.mode)
36
44
 
@@ -15,6 +15,18 @@ module Datadog
15
15
  module AppSec
16
16
  module Contrib
17
17
  module Rack
18
+ # Create an array of lowercased headers
19
+ WAF_VENDOR_HEADERS_TAGS = %w[
20
+ X-Amzn-Trace-Id
21
+ Cloudfront-Viewer-Ja3-Fingerprint
22
+ Cf-Ray
23
+ X-Cloud-Trace-Context
24
+ X-Appgw-Trace-id
25
+ X-SigSci-RequestID
26
+ X-SigSci-Tags
27
+ Akamai-User-Risk
28
+ ].map(&:downcase).freeze
29
+
18
30
  # Topmost Rack middleware for AppSec
19
31
  # This should be inserted just below Datadog::Tracing::Contrib::Rack::TraceMiddleware
20
32
  class RequestMiddleware
@@ -22,6 +34,7 @@ module Datadog
22
34
  @app = app
23
35
 
24
36
  @oneshot_tags_sent = false
37
+ @rack_headers = {}
25
38
  end
26
39
 
27
40
  # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/MethodLength
@@ -56,7 +69,8 @@ module Datadog
56
69
 
57
70
  gateway_request = Gateway::Request.new(env)
58
71
 
59
- add_appsec_tags(processor, scope, env)
72
+ add_appsec_tags(processor, scope)
73
+ add_request_tags(scope, env)
60
74
 
61
75
  request_return, request_response = catch(::Datadog::AppSec::Ext::INTERRUPT) do
62
76
  Instrumentation.gateway.push('rack.request', gateway_request) do
@@ -131,7 +145,7 @@ module Datadog
131
145
  Datadog::Tracing.active_span
132
146
  end
133
147
 
134
- def add_appsec_tags(processor, scope, env)
148
+ def add_appsec_tags(processor, scope)
135
149
  span = scope.service_entry_span
136
150
  trace = scope.trace
137
151
 
@@ -141,17 +155,6 @@ module Datadog
141
155
  span.set_tag('_dd.runtime_family', 'ruby')
142
156
  span.set_tag('_dd.appsec.waf.version', Datadog::AppSec::WAF::VERSION::BASE_STRING)
143
157
 
144
- if span && span.get_tag(Tracing::Metadata::Ext::HTTP::TAG_CLIENT_IP).nil?
145
- request_header_collection = Datadog::Tracing::Contrib::Rack::Header::RequestHeaderCollection.new(env)
146
-
147
- # always collect client ip, as this is part of AppSec provided functionality
148
- Datadog::Tracing::ClientIp.set_client_ip_tag!(
149
- span,
150
- headers: request_header_collection,
151
- remote_ip: env['REMOTE_ADDR']
152
- )
153
- end
154
-
155
158
  if processor.diagnostics
156
159
  diagnostics = processor.diagnostics
157
160
 
@@ -177,6 +180,29 @@ module Datadog
177
180
  end
178
181
  end
179
182
 
183
+ def add_request_tags(scope, env)
184
+ span = scope.service_entry_span
185
+
186
+ return unless span
187
+
188
+ # Always add WAF vendors headers
189
+ WAF_VENDOR_HEADERS_TAGS.each do |lowercase_header|
190
+ rack_header = to_rack_header(lowercase_header)
191
+ span.set_tag("http.request.headers.#{lowercase_header}", env[rack_header]) if env[rack_header]
192
+ end
193
+
194
+ if span && span.get_tag(Tracing::Metadata::Ext::HTTP::TAG_CLIENT_IP).nil?
195
+ request_header_collection = Datadog::Tracing::Contrib::Rack::Header::RequestHeaderCollection.new(env)
196
+
197
+ # always collect client ip, as this is part of AppSec provided functionality
198
+ Datadog::Tracing::ClientIp.set_client_ip_tag!(
199
+ span,
200
+ headers: request_header_collection,
201
+ remote_ip: env['REMOTE_ADDR']
202
+ )
203
+ end
204
+ end
205
+
180
206
  def add_waf_runtime_tags(scope)
181
207
  span = scope.service_entry_span
182
208
  context = scope.processor_context
@@ -189,6 +215,10 @@ module Datadog
189
215
  span.set_tag('_dd.appsec.waf.duration', context.time_ns / 1000.0)
190
216
  span.set_tag('_dd.appsec.waf.duration_ext', context.time_ext_ns / 1000.0)
191
217
  end
218
+
219
+ def to_rack_header(header)
220
+ @rack_headers[header] ||= Datadog::Tracing::Contrib::Rack::Header.to_rack_header(header)
221
+ end
192
222
  end
193
223
  end
194
224
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'json'
4
4
  require 'zlib'
5
- require 'base64'
6
5
 
7
6
  require_relative 'rate_limiter'
7
+ require_relative '../core/utils/base64'
8
8
 
9
9
  module Datadog
10
10
  module AppSec
@@ -140,7 +140,7 @@ module Datadog
140
140
  private
141
141
 
142
142
  def compressed_and_base64_encoded(value)
143
- Base64.encode64(gzip(value))
143
+ Datadog::Core::Utils::Base64.strict_encode64(gzip(value))
144
144
  rescue TypeError => e
145
145
  Datadog.logger.debug do
146
146
  "Failed to compress and encode value when populating AppSec::Event. Error: #{e.message}"