datadog 2.0.0.beta1 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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}"