datadog 2.0.0.beta2 → 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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -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 +8 -20
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +18 -10
  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 +38 -3
  9. data/ext/datadog_profiling_native_extension/heap_recorder.h +5 -0
  10. data/ext/datadog_profiling_native_extension/http_transport.c +0 -93
  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 +156 -55
  18. data/lib/datadog/appsec/contrib/devise/tracking.rb +8 -0
  19. data/lib/datadog/core/configuration/settings.rb +10 -79
  20. data/lib/datadog/core/remote/client.rb +1 -5
  21. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  22. data/lib/datadog/core/remote/dispatcher.rb +3 -3
  23. data/lib/datadog/core/telemetry/emitter.rb +1 -1
  24. data/lib/datadog/core/telemetry/http/response.rb +4 -0
  25. data/lib/datadog/opentelemetry/sdk/span_processor.rb +18 -1
  26. data/lib/datadog/profiling/component.rb +26 -2
  27. data/lib/datadog/profiling/crashtracker.rb +91 -0
  28. data/lib/datadog/profiling/exporter.rb +6 -3
  29. data/lib/datadog/profiling/http_transport.rb +7 -11
  30. data/lib/datadog/profiling/profiler.rb +9 -2
  31. data/lib/datadog/profiling/stack_recorder.rb +6 -2
  32. data/lib/datadog/profiling.rb +1 -0
  33. data/lib/datadog/tracing/component.rb +5 -1
  34. data/lib/datadog/tracing/configuration/dynamic.rb +39 -1
  35. data/lib/datadog/tracing/configuration/settings.rb +1 -0
  36. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -0
  37. data/lib/datadog/tracing/contrib/active_record/integration.rb +10 -0
  38. data/lib/datadog/tracing/contrib/configuration/resolver.rb +43 -0
  39. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +1 -1
  40. data/lib/datadog/tracing/remote.rb +5 -1
  41. data/lib/datadog/tracing/sampling/ext.rb +5 -1
  42. data/lib/datadog/tracing/sampling/matcher.rb +60 -31
  43. data/lib/datadog/tracing/sampling/rule.rb +12 -5
  44. data/lib/datadog/tracing/sampling/rule_sampler.rb +17 -1
  45. data/lib/datadog/tracing/sampling/span/matcher.rb +13 -41
  46. data/lib/datadog/tracing/span_link.rb +12 -6
  47. data/lib/datadog/tracing/span_operation.rb +6 -4
  48. data/lib/datadog/version.rb +1 -1
  49. metadata +7 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c98feb8dee5da784f25da37c6989ed32929eeac8189590c0aa6763303ac5076d
4
- data.tar.gz: 9324b595c888213affe2a342c4be4dcb49643328cdf3ab0aab9616c41a9b5dd6
3
+ metadata.gz: 5c8f4fef81d3a238299c70c27f6ece67dc31e76e4b6ad2180a2d1126e4c9545a
4
+ data.tar.gz: 17bc14574f265b2fdba65d4030e37e513f6af33af6ce8be77e1bbb535d4f6ae4
5
5
  SHA512:
6
- metadata.gz: f4ee11c65fe76ee70b5c85d67de21a5feadc6938976a0869ae230a6d3a28c38d5ed2c8516633d2046f7fb311ed19d62de9a46317e70a1ade2d4147c139693a44
7
- data.tar.gz: 05b8bb6ec82ddb7cd7bce5e461c5e48416df745a23cfcc724d7b2cc0de86e047a4b0e8b7d26dbf9f0b62a12f94dc0492937da3676adb97e458fb41a501a005f3
6
+ metadata.gz: fff160e4a8e316ec82381e5ae6ef5687a434aa0798debcc689c37e127d688e3d0c0f1009befb6871096c77032caeed348df777ba9baab4c8f7d6f4582d02c8bd
7
+ data.tar.gz: 6fb4d250c8838a101a489cc10ee0672c23dd4a9793ad850b17bc2c375881c0e3e4cf2c7c3bf5645a6ce8bce1e59c06cbb82609dffe489101fb3ba158d655f034
data/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.0.0.rc1] - 2024-05-24
6
+
7
+ ### Added
8
+
9
+ * Core: Add libdatadog crash tracker ([#3384][])
10
+ * OpenTelemetry: Add support for Span Links ([#3572][])
11
+ * Profiling: Enable endpoint profiling for Sidekiq and other background job processors ([#3610][])
12
+ * Tracing: Add dynamically configurable sampling rules ([#3598][])
13
+ * Tracing: Add sampling rule glob pattern matching ([#3616][])
14
+
15
+ ### Changed
16
+
17
+ * Appsec: Fix undefined method error when Tracing disabled ([#3645][])
18
+ * Profiling: Upgrade to libdatadog 9 ([#3627][])
19
+ * Tracing: Cache ActiveRecord configuration resolver ([#3630][])
20
+
21
+ ### Fixed
22
+
23
+ * Core: Fix error during telemetry debug logging attempt ([#3617][])
24
+ * OpenTelemetry: Fix attribute merge with Datadog tags ([#3651][])
25
+ * Tracing: Fix environment logger repeated entries ([#3624][])
26
+
27
+ ### Removed
28
+
29
+ * Profiling: Remove profiler support for Ruby 2.3 and 2.4 ([#3621][])
30
+ * Profiling: Remove deprecated profiler settings ([#3597][])
31
+
32
+ ## [1.23.0] - 2024-05-09
33
+
34
+ ### Added
35
+
36
+ * Profiling: Enable endpoint profiling for Sidekiq and similar background job processors ([#3619][])
37
+
38
+ ### Fixed
39
+
40
+ * Fix no such file or directory issue when using single step instrumentation ([#3623][])
41
+ * Fix error during telemetry debug logging attempt ([#3618][])
42
+
5
43
  ## [2.0.0.beta2] - 2024-04-18
6
44
 
7
45
  ### Added
@@ -2850,7 +2888,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2850
2888
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2851
2889
 
2852
2890
 
2853
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...master
2891
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.rc1...master
2892
+ [2.0.0.rc1]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.beta2...v2.0.0.rc1
2893
+ [1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
2854
2894
  [2.0.0.beta2]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.beta1...v2.0.0.beta2
2855
2895
  [1.22.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v1.22.0
2856
2896
  [2.0.0.beta1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v2.0.0.beta1
@@ -4145,6 +4185,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4145
4185
  [#3370]: https://github.com/DataDog/dd-trace-rb/issues/3370
4146
4186
  [#3373]: https://github.com/DataDog/dd-trace-rb/issues/3373
4147
4187
  [#3374]: https://github.com/DataDog/dd-trace-rb/issues/3374
4188
+ [#3384]: https://github.com/DataDog/dd-trace-rb/issues/3384
4148
4189
  [#3386]: https://github.com/DataDog/dd-trace-rb/issues/3386
4149
4190
  [#3388]: https://github.com/DataDog/dd-trace-rb/issues/3388
4150
4191
  [#3392]: https://github.com/DataDog/dd-trace-rb/issues/3392
@@ -4202,10 +4243,25 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4202
4243
  [#3551]: https://github.com/DataDog/dd-trace-rb/issues/3551
4203
4244
  [#3558]: https://github.com/DataDog/dd-trace-rb/issues/3558
4204
4245
  [#3565]: https://github.com/DataDog/dd-trace-rb/issues/3565
4246
+ [#3572]: https://github.com/DataDog/dd-trace-rb/issues/3572
4205
4247
  [#3573]: https://github.com/DataDog/dd-trace-rb/issues/3573
4206
4248
  [#3582]: https://github.com/DataDog/dd-trace-rb/issues/3582
4207
4249
  [#3585]: https://github.com/DataDog/dd-trace-rb/issues/3585
4208
4250
  [#3587]: https://github.com/DataDog/dd-trace-rb/issues/3587
4251
+ [#3597]: https://github.com/DataDog/dd-trace-rb/issues/3597
4252
+ [#3598]: https://github.com/DataDog/dd-trace-rb/issues/3598
4253
+ [#3610]: https://github.com/DataDog/dd-trace-rb/issues/3610
4254
+ [#3616]: https://github.com/DataDog/dd-trace-rb/issues/3616
4255
+ [#3617]: https://github.com/DataDog/dd-trace-rb/issues/3617
4256
+ [#3618]: https://github.com/DataDog/dd-trace-rb/issues/3618
4257
+ [#3619]: https://github.com/DataDog/dd-trace-rb/issues/3619
4258
+ [#3621]: https://github.com/DataDog/dd-trace-rb/issues/3621
4259
+ [#3623]: https://github.com/DataDog/dd-trace-rb/issues/3623
4260
+ [#3624]: https://github.com/DataDog/dd-trace-rb/issues/3624
4261
+ [#3627]: https://github.com/DataDog/dd-trace-rb/issues/3627
4262
+ [#3630]: https://github.com/DataDog/dd-trace-rb/issues/3630
4263
+ [#3645]: https://github.com/DataDog/dd-trace-rb/issues/3645
4264
+ [#3651]: https://github.com/DataDog/dd-trace-rb/issues/3651
4209
4265
  [@AdrianLC]: https://github.com/AdrianLC
4210
4266
  [@Azure7111]: https://github.com/Azure7111
4211
4267
  [@BabyGroot]: https://github.com/BabyGroot
@@ -28,7 +28,7 @@ documentation.**
28
28
  The profiling native extension is (and must always be) designed to **not cause failures** during gem installation, even
29
29
  if some features, Ruby versions, or operating systems are not supported.
30
30
 
31
- E.g. the extension must not break installation on Ruby 2.1 (or the oldest Ruby version we support at the time) on 64-bit ARM macOS,
31
+ E.g. the extension must not break installation on Ruby 2.5 (or the oldest Ruby version we support at the time) on 64-bit ARM macOS,
32
32
  even if at run time it will effectively do nothing for such a setup.
33
33
 
34
34
  We have a CI setup to help validate this, but this is really important to keep in mind when adding to or changing the
@@ -929,18 +929,6 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
929
929
  struct cpu_and_wall_time_worker_state *state;
930
930
  TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
931
931
 
932
- VALUE pretty_cpu_sampling_time_ns_min = state->stats.cpu_sampling_time_ns_min == UINT64_MAX ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_min);
933
- VALUE pretty_cpu_sampling_time_ns_max = state->stats.cpu_sampling_time_ns_max == 0 ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_max);
934
- VALUE pretty_cpu_sampling_time_ns_total = state->stats.cpu_sampling_time_ns_total == 0 ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_total);
935
- VALUE pretty_cpu_sampling_time_ns_avg =
936
- state->stats.cpu_sampled == 0 ? Qnil : DBL2NUM(((double) state->stats.cpu_sampling_time_ns_total) / state->stats.cpu_sampled);
937
-
938
- VALUE pretty_allocation_sampling_time_ns_min = state->stats.allocation_sampling_time_ns_min == UINT64_MAX ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_min);
939
- VALUE pretty_allocation_sampling_time_ns_max = state->stats.allocation_sampling_time_ns_max == 0 ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_max);
940
- VALUE pretty_allocation_sampling_time_ns_total = state->stats.allocation_sampling_time_ns_total == 0 ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_total);
941
- VALUE pretty_allocation_sampling_time_ns_avg =
942
- state->stats.allocation_sampled == 0 ? Qnil : DBL2NUM(((double) state->stats.allocation_sampling_time_ns_total) / state->stats.allocation_sampled);
943
-
944
932
  unsigned long total_cpu_samples_attempted = state->stats.cpu_sampled + state->stats.cpu_skipped;
945
933
  VALUE effective_cpu_sample_rate =
946
934
  total_cpu_samples_attempted == 0 ? Qnil : DBL2NUM(((double) state->stats.cpu_sampled) / total_cpu_samples_attempted);
@@ -968,19 +956,19 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
968
956
  ID2SYM(rb_intern("cpu_sampled")), /* => */ UINT2NUM(state->stats.cpu_sampled),
969
957
  ID2SYM(rb_intern("cpu_skipped")), /* => */ UINT2NUM(state->stats.cpu_skipped),
970
958
  ID2SYM(rb_intern("cpu_effective_sample_rate")), /* => */ effective_cpu_sample_rate,
971
- ID2SYM(rb_intern("cpu_sampling_time_ns_min")), /* => */ pretty_cpu_sampling_time_ns_min,
972
- ID2SYM(rb_intern("cpu_sampling_time_ns_max")), /* => */ pretty_cpu_sampling_time_ns_max,
973
- ID2SYM(rb_intern("cpu_sampling_time_ns_total")), /* => */ pretty_cpu_sampling_time_ns_total,
974
- ID2SYM(rb_intern("cpu_sampling_time_ns_avg")), /* => */ pretty_cpu_sampling_time_ns_avg,
959
+ ID2SYM(rb_intern("cpu_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
960
+ ID2SYM(rb_intern("cpu_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_max, > 0, ULL2NUM),
961
+ ID2SYM(rb_intern("cpu_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_total, > 0, ULL2NUM),
962
+ ID2SYM(rb_intern("cpu_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.cpu_sampling_time_ns_total, state->stats.cpu_sampled),
975
963
 
976
964
  // Allocation stats
977
965
  ID2SYM(rb_intern("allocation_sampled")), /* => */ state->allocation_profiling_enabled ? ULONG2NUM(state->stats.allocation_sampled) : Qnil,
978
966
  ID2SYM(rb_intern("allocation_skipped")), /* => */ state->allocation_profiling_enabled ? ULONG2NUM(state->stats.allocation_skipped) : Qnil,
979
967
  ID2SYM(rb_intern("allocation_effective_sample_rate")), /* => */ effective_allocation_sample_rate,
980
- ID2SYM(rb_intern("allocation_sampling_time_ns_min")), /* => */ pretty_allocation_sampling_time_ns_min,
981
- ID2SYM(rb_intern("allocation_sampling_time_ns_max")), /* => */ pretty_allocation_sampling_time_ns_max,
982
- ID2SYM(rb_intern("allocation_sampling_time_ns_total")), /* => */ pretty_allocation_sampling_time_ns_total,
983
- ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ pretty_allocation_sampling_time_ns_avg,
968
+ ID2SYM(rb_intern("allocation_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
969
+ ID2SYM(rb_intern("allocation_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_max, > 0, ULL2NUM),
970
+ ID2SYM(rb_intern("allocation_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_total, > 0, ULL2NUM),
971
+ ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.allocation_sampling_time_ns_total, state->stats.allocation_sampled),
984
972
  ID2SYM(rb_intern("allocation_sampler_snapshot")), /* => */ allocation_sampler_snapshot,
985
973
  ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
986
974
  };
@@ -217,7 +217,7 @@ static long thread_id_for(VALUE thread);
217
217
  static VALUE _native_stats(VALUE self, VALUE collector_instance);
218
218
  static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
219
219
  static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result);
220
- static bool should_collect_resource(VALUE root_span_type);
220
+ static bool should_collect_resource(VALUE root_span);
221
221
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
222
222
  static VALUE thread_list(struct thread_context_collector_state *state);
223
223
  static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
@@ -1146,10 +1146,7 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
1146
1146
 
1147
1147
  trace_identifiers_result->valid = true;
1148
1148
 
1149
- if (!state->endpoint_collection_enabled) return;
1150
-
1151
- VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
1152
- if (root_span_type == Qnil || !should_collect_resource(root_span_type)) return;
1149
+ if (!state->endpoint_collection_enabled || !should_collect_resource(root_span)) return;
1153
1150
 
1154
1151
  VALUE trace_resource = rb_ivar_get(active_trace, at_resource_id /* @resource */);
1155
1152
  if (RB_TYPE_P(trace_resource, T_STRING)) {
@@ -1160,21 +1157,32 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
1160
1157
  }
1161
1158
  }
1162
1159
 
1163
- // We only collect the resource for spans of types:
1160
+ // We opt-in to collecting the resource for spans of types:
1164
1161
  // * 'web', for web requests
1165
- // * proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
1162
+ // * 'proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
1163
+ // * 'worker', used for sidekiq and similar background job processors
1166
1164
  //
1167
- // NOTE: Currently we're only interested in HTTP service endpoints. Over time, this list may be expanded.
1165
+ // Over time, this list may be expanded.
1168
1166
  // Resources MUST NOT include personal identifiable information (PII); this should not be the case with
1169
1167
  // ddtrace integrations, but worth mentioning just in case :)
1170
- static bool should_collect_resource(VALUE root_span_type) {
1168
+ static bool should_collect_resource(VALUE root_span) {
1169
+ VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
1170
+ if (root_span_type == Qnil) return false;
1171
1171
  ENFORCE_TYPE(root_span_type, T_STRING);
1172
1172
 
1173
1173
  int root_span_type_length = RSTRING_LEN(root_span_type);
1174
1174
  const char *root_span_type_value = StringValuePtr(root_span_type);
1175
1175
 
1176
- return (root_span_type_length == strlen("web") && (memcmp("web", root_span_type_value, strlen("web")) == 0)) ||
1176
+ bool is_web_request =
1177
+ (root_span_type_length == strlen("web") && (memcmp("web", root_span_type_value, strlen("web")) == 0)) ||
1177
1178
  (root_span_type_length == strlen("proxy") && (memcmp("proxy", root_span_type_value, strlen("proxy")) == 0));
1179
+
1180
+ if (is_web_request) return true;
1181
+
1182
+ bool is_worker_request =
1183
+ (root_span_type_length == strlen("worker") && (memcmp("worker", root_span_type_value, strlen("worker")) == 0));
1184
+
1185
+ return is_worker_request;
1178
1186
  }
1179
1187
 
1180
1188
  // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
@@ -0,0 +1,108 @@
1
+ #include <ruby.h>
2
+ #include <datadog/common.h>
3
+ #include <libdatadog_helpers.h>
4
+
5
+ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
6
+ static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);
7
+
8
+ // Used to report Ruby VM crashes.
9
+ // Once initialized, segfaults will be reported automatically using libdatadog.
10
+
11
+ void crashtracker_init(VALUE profiling_module) {
12
+ VALUE crashtracker_class = rb_define_class_under(profiling_module, "Crashtracker", rb_cObject);
13
+
14
+ rb_define_singleton_method(crashtracker_class, "_native_start_or_update_on_fork", _native_start_or_update_on_fork, -1);
15
+ rb_define_singleton_method(crashtracker_class, "_native_stop", _native_stop, 0);
16
+ }
17
+
18
+ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
19
+ VALUE options;
20
+ rb_scan_args(argc, argv, "0:", &options);
21
+
22
+ VALUE exporter_configuration = rb_hash_fetch(options, ID2SYM(rb_intern("exporter_configuration")));
23
+ VALUE path_to_crashtracking_receiver_binary = rb_hash_fetch(options, ID2SYM(rb_intern("path_to_crashtracking_receiver_binary")));
24
+ VALUE ld_library_path = rb_hash_fetch(options, ID2SYM(rb_intern("ld_library_path")));
25
+ VALUE tags_as_array = rb_hash_fetch(options, ID2SYM(rb_intern("tags_as_array")));
26
+ VALUE action = rb_hash_fetch(options, ID2SYM(rb_intern("action")));
27
+ VALUE upload_timeout_seconds = rb_hash_fetch(options, ID2SYM(rb_intern("upload_timeout_seconds")));
28
+
29
+ VALUE start_action = ID2SYM(rb_intern("start"));
30
+ VALUE update_on_fork_action = ID2SYM(rb_intern("update_on_fork"));
31
+
32
+ ENFORCE_TYPE(exporter_configuration, T_ARRAY);
33
+ ENFORCE_TYPE(tags_as_array, T_ARRAY);
34
+ ENFORCE_TYPE(path_to_crashtracking_receiver_binary, T_STRING);
35
+ ENFORCE_TYPE(ld_library_path, T_STRING);
36
+ ENFORCE_TYPE(action, T_SYMBOL);
37
+ ENFORCE_TYPE(upload_timeout_seconds, T_FIXNUM);
38
+
39
+ if (action != start_action && action != update_on_fork_action) rb_raise(rb_eArgError, "Unexpected action: %+"PRIsVALUE, action);
40
+
41
+ VALUE version = ddtrace_version();
42
+ ddog_prof_Endpoint endpoint = endpoint_from(exporter_configuration);
43
+
44
+ // Tags are heap-allocated, so after here we can't raise exceptions otherwise we'll leak this memory
45
+ // Start of exception-free zone to prevent leaks {{
46
+ ddog_Vec_Tag tags = convert_tags(tags_as_array);
47
+
48
+ ddog_prof_CrashtrackerConfiguration config = {
49
+ .additional_files = {},
50
+ // The Ruby VM already uses an alt stack to detect stack overflows so the crash handler must not overwrite it.
51
+ //
52
+ // @ivoanjo: Specifically, with `create_alt_stack = true` I saw a segfault, such as Ruby 2.6's bug with
53
+ // "Process.detach(fork { exit! }).instance_variable_get(:@foo)" being turned into a
54
+ // "-e:1:in `instance_variable_get': stack level too deep (SystemStackError)" by Ruby.
55
+ //
56
+ // The Ruby crash handler also seems to get confused when this option is enabled and
57
+ // "Process.kill('SEGV', Process.pid)" gets run.
58
+ .create_alt_stack = false,
59
+ .endpoint = endpoint,
60
+ .resolve_frames = DDOG_PROF_STACKTRACE_COLLECTION_ENABLED,
61
+ .timeout_secs = FIX2INT(upload_timeout_seconds),
62
+ };
63
+
64
+ ddog_prof_CrashtrackerMetadata metadata = {
65
+ .profiling_library_name = DDOG_CHARSLICE_C("dd-trace-rb"),
66
+ .profiling_library_version = char_slice_from_ruby_string(version),
67
+ .family = DDOG_CHARSLICE_C("ruby"),
68
+ .tags = &tags,
69
+ };
70
+
71
+ ddog_prof_EnvVar ld_library_path_env = {
72
+ .key = DDOG_CHARSLICE_C("LD_LIBRARY_PATH"),
73
+ .val = char_slice_from_ruby_string(ld_library_path),
74
+ };
75
+
76
+ ddog_prof_CrashtrackerReceiverConfig receiver_config = {
77
+ .args = {},
78
+ .env = {.ptr = &ld_library_path_env, .len = 1},
79
+ .path_to_receiver_binary = char_slice_from_ruby_string(path_to_crashtracking_receiver_binary),
80
+ .optional_stderr_filename = {},
81
+ .optional_stdout_filename = {},
82
+ };
83
+
84
+ ddog_prof_CrashtrackerResult result =
85
+ action == start_action ?
86
+ ddog_prof_Crashtracker_init(config, receiver_config, metadata) :
87
+ ddog_prof_Crashtracker_update_on_fork(config, receiver_config, metadata);
88
+
89
+ // Clean up before potentially raising any exceptions
90
+ ddog_Vec_Tag_drop(tags);
91
+ // }} End of exception-free zone to prevent leaks
92
+
93
+ if (result.tag == DDOG_PROF_CRASHTRACKER_RESULT_ERR) {
94
+ rb_raise(rb_eRuntimeError, "Failed to start/update the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
95
+ }
96
+
97
+ return Qtrue;
98
+ }
99
+
100
+ static VALUE _native_stop(DDTRACE_UNUSED VALUE _self) {
101
+ ddog_prof_CrashtrackerResult result = ddog_prof_Crashtracker_shutdown();
102
+
103
+ if (result.tag == DDOG_PROF_CRASHTRACKER_RESULT_ERR) {
104
+ rb_raise(rb_eRuntimeError, "Failed to stop the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
105
+ }
106
+
107
+ return Qtrue;
108
+ }
@@ -126,7 +126,7 @@ if RUBY_PLATFORM.include?('linux')
126
126
  # have_library 'pthread'
127
127
  # have_func 'pthread_getcpuclockid'
128
128
  # ```
129
- # but a) it broke the build on Windows, b) on older Ruby versions (2.2 and below) and c) It's slower to build
129
+ # but it's slower to build
130
130
  # so instead we just assume that we have the function we need on Linux, and nowhere else
131
131
  $defs << '-DHAVE_PTHREAD_GETCPUCLOCKID'
132
132
  end
@@ -163,6 +163,11 @@ $defs << '-DNO_THREAD_TID' if RUBY_VERSION < '3.1'
163
163
  # On older Rubies, there was no jit_return member on the rb_control_frame_t struct
164
164
  $defs << '-DNO_JIT_RETURN' if RUBY_VERSION < '3.1'
165
165
 
166
+ # On older Rubies, rb_gc_force_recycle allowed to free objects in a way that
167
+ # would be invisible to free tracepoints, finalizers and without cleaning
168
+ # obj_to_id_tbl mappings.
169
+ $defs << '-DHAVE_WORKING_RB_GC_FORCE_RECYCLE' if RUBY_VERSION < '3.1'
170
+
166
171
  # On older Rubies, we need to use a backported version of this function. See private_vm_api_access.h for details.
167
172
  $defs << '-DUSE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME' if RUBY_VERSION < '3'
168
173
 
@@ -175,34 +180,15 @@ $defs << '-DNO_IMEMO_NAME' if RUBY_VERSION < '3'
175
180
  # On older Rubies, objects would not move
176
181
  $defs << '-DNO_T_MOVED' if RUBY_VERSION < '2.7'
177
182
 
183
+ # On older Rubies, there was no RUBY_SEEN_OBJ_ID flag
184
+ $defs << '-DNO_SEEN_OBJ_ID_FLAG' if RUBY_VERSION < '2.7'
185
+
178
186
  # On older Rubies, rb_global_vm_lock_struct did not include the owner field
179
187
  $defs << '-DNO_GVL_OWNER' if RUBY_VERSION < '2.6'
180
188
 
181
189
  # On older Rubies, there was no thread->invoke_arg
182
190
  $defs << '-DNO_THREAD_INVOKE_ARG' if RUBY_VERSION < '2.6'
183
191
 
184
- # On older Rubies, we need to use rb_thread_t instead of rb_execution_context_t
185
- $defs << '-DUSE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT' if RUBY_VERSION < '2.5'
186
-
187
- # On older Rubies, extensions can't use GET_VM()
188
- $defs << '-DNO_GET_VM' if RUBY_VERSION < '2.5'
189
-
190
- # On older Rubies...
191
- if RUBY_VERSION < '2.4'
192
- # ...we need to use RUBY_VM_NORMAL_ISEQ_P instead of VM_FRAME_RUBYFRAME_P
193
- $defs << '-DUSE_ISEQ_P_INSTEAD_OF_RUBYFRAME_P'
194
- # ...we use a legacy copy of rb_vm_frame_method_entry
195
- $defs << '-DUSE_LEGACY_RB_VM_FRAME_METHOD_ENTRY'
196
- end
197
-
198
- # On older Rubies, rb_gc_force_recycle allowed to free objects in a way that
199
- # would be invisible to free tracepoints, finalizers and without cleaning
200
- # obj_to_id_tbl mappings.
201
- $defs << '-DHAVE_WORKING_RB_GC_FORCE_RECYCLE' if RUBY_VERSION < '3.1'
202
-
203
- # On older Rubies, there was no RUBY_SEEN_OBJ_ID flag
204
- $defs << '-DNO_SEEN_OBJ_ID_FLAG' if RUBY_VERSION < '2.7'
205
-
206
192
  # If we got here, libdatadog is available and loaded
207
193
  ENV['PKG_CONFIG_PATH'] = "#{ENV['PKG_CONFIG_PATH']}:#{Libdatadog.pkgconfig_folder}"
208
194
  Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV['PKG_CONFIG_PATH'].inspect}\n")
@@ -158,6 +158,13 @@ struct heap_recorder {
158
158
 
159
159
  // Sampling state
160
160
  uint num_recordings_skipped;
161
+
162
+ struct stats_last_update {
163
+ size_t objects_alive;
164
+ size_t objects_dead;
165
+ size_t objects_skipped;
166
+ size_t objects_frozen;
167
+ } stats_last_update;
161
168
  };
162
169
  static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
163
170
  static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
@@ -372,6 +379,9 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
372
379
  rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
373
380
  }
374
381
 
382
+ // Reset last update stats, we'll be building them from scratch during the st_foreach call below
383
+ heap_recorder->stats_last_update = (struct stats_last_update) {};
384
+
375
385
  st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
376
386
 
377
387
  heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
@@ -427,6 +437,22 @@ bool heap_recorder_for_each_live_object(
427
437
  return true;
428
438
  }
429
439
 
440
+ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
441
+ VALUE arguments[] = {
442
+ ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
443
+ ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
444
+
445
+ // Stats as of last update
446
+ ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
447
+ ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
448
+ ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
449
+ ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
450
+ };
451
+ VALUE hash = rb_hash_new();
452
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
453
+ return hash;
454
+ }
455
+
430
456
  void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations) {
431
457
  heap_stack *stack = heap_stack_new(locations);
432
458
  heap_record_key stack_based_key = (heap_record_key) {
@@ -497,12 +523,14 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
497
523
  // no point checking for liveness or updating its size, so exit early.
498
524
  // NOTE: This means that there should be an equivalent check during actual
499
525
  // iteration otherwise we'd iterate/expose stale object data.
526
+ recorder->stats_last_update.objects_skipped++;
500
527
  return ST_CONTINUE;
501
528
  }
502
529
 
503
530
  if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
504
531
  // Id no longer associated with a valid ref. Need to delete this object record!
505
532
  on_committed_object_record_cleanup(recorder, record);
533
+ recorder->stats_last_update.objects_dead++;
506
534
  return ST_DELETE;
507
535
  }
508
536
 
@@ -537,6 +565,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
537
565
  RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
538
566
 
539
567
  on_committed_object_record_cleanup(recorder, record);
568
+ recorder->stats_last_update.objects_dead++;
540
569
  return ST_DELETE;
541
570
  }
542
571
 
@@ -550,6 +579,11 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
550
579
  record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
551
580
  }
552
581
 
582
+ recorder->stats_last_update.objects_alive++;
583
+ if (record->object_data.is_frozen) {
584
+ recorder->stats_last_update.objects_frozen++;
585
+ }
586
+
553
587
  return ST_CONTINUE;
554
588
  }
555
589
 
@@ -767,9 +801,10 @@ void object_record_free(object_record *record) {
767
801
 
768
802
  VALUE object_record_inspect(object_record *record) {
769
803
  heap_frame top_frame = record->heap_record->stack->frames[0];
770
- VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu ",
771
- record->obj_id, record->object_data.weight, record->object_data.size, top_frame.filename,
772
- (int) top_frame.line, record->object_data.alloc_gen, record->object_data.gen_age);
804
+ live_object_data object_data = record->object_data;
805
+ VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu frozen=%d ",
806
+ record->obj_id, object_data.weight, object_data.size, top_frame.filename,
807
+ (int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
773
808
 
774
809
  const char *class = record->object_data.class;
775
810
  if (class != NULL) {
@@ -150,6 +150,11 @@ bool heap_recorder_for_each_live_object(
150
150
  bool (*for_each_callback)(heap_recorder_iteration_data data, void* extra_arg),
151
151
  void *for_each_callback_extra_arg);
152
152
 
153
+ // Return a Ruby hash containing a snapshot of this recorder's interesting state at calling time.
154
+ // WARN: This allocates in the Ruby VM and therefore should not be called without the
155
+ // VM lock or during GC.
156
+ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder);
157
+
153
158
  // v--- TEST-ONLY APIs ---v
154
159
 
155
160
  // Assert internal hashing logic is valid for the provided locations and its
@@ -11,11 +11,6 @@
11
11
  static VALUE ok_symbol = Qnil; // :ok in Ruby
12
12
  static VALUE error_symbol = Qnil; // :error in Ruby
13
13
 
14
- static ID agentless_id; // id of :agentless in Ruby
15
- static ID agent_id; // id of :agent in Ruby
16
-
17
- static ID log_failure_to_process_tag_id; // id of :log_failure_to_process_tag in Ruby
18
-
19
14
  static VALUE library_version_string = Qnil;
20
15
 
21
16
  struct call_exporter_without_gvl_arguments {
@@ -30,9 +25,6 @@ inline static ddog_ByteSlice byte_slice_from_ruby_string(VALUE string);
30
25
  static VALUE _native_validate_exporter(VALUE self, VALUE exporter_configuration);
31
26
  static ddog_prof_Exporter_NewResult create_exporter(VALUE exporter_configuration, VALUE tags_as_array);
32
27
  static VALUE handle_exporter_failure(ddog_prof_Exporter_NewResult exporter_result);
33
- static ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);
34
- static ddog_Vec_Tag convert_tags(VALUE tags_as_array);
35
- static void safely_log_failure_to_process_tag(ddog_Vec_Tag tags, VALUE err_details);
36
28
  static VALUE _native_do_export(
37
29
  VALUE self,
38
30
  VALUE exporter_configuration,
@@ -60,9 +52,6 @@ void http_transport_init(VALUE profiling_module) {
60
52
 
61
53
  ok_symbol = ID2SYM(rb_intern_const("ok"));
62
54
  error_symbol = ID2SYM(rb_intern_const("error"));
63
- agentless_id = rb_intern_const("agentless");
64
- agent_id = rb_intern_const("agent");
65
- log_failure_to_process_tag_id = rb_intern_const("log_failure_to_process_tag");
66
55
 
67
56
  library_version_string = ddtrace_version();
68
57
  rb_global_variable(&library_version_string);
@@ -116,88 +105,6 @@ static VALUE handle_exporter_failure(ddog_prof_Exporter_NewResult exporter_resul
116
105
  rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&exporter_result.err));
117
106
  }
118
107
 
119
- static ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
120
- ENFORCE_TYPE(exporter_configuration, T_ARRAY);
121
-
122
- ID working_mode = SYM2ID(rb_ary_entry(exporter_configuration, 0)); // SYM2ID verifies its input so we can do this safely
123
-
124
- if (working_mode != agentless_id && working_mode != agent_id) {
125
- rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
126
- }
127
-
128
- if (working_mode == agentless_id) {
129
- VALUE site = rb_ary_entry(exporter_configuration, 1);
130
- VALUE api_key = rb_ary_entry(exporter_configuration, 2);
131
- ENFORCE_TYPE(site, T_STRING);
132
- ENFORCE_TYPE(api_key, T_STRING);
133
-
134
- return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
135
- } else { // agent_id
136
- VALUE base_url = rb_ary_entry(exporter_configuration, 1);
137
- ENFORCE_TYPE(base_url, T_STRING);
138
-
139
- return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
140
- }
141
- }
142
-
143
- __attribute__((warn_unused_result))
144
- static ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
145
- ENFORCE_TYPE(tags_as_array, T_ARRAY);
146
-
147
- long tags_count = RARRAY_LEN(tags_as_array);
148
- ddog_Vec_Tag tags = ddog_Vec_Tag_new();
149
-
150
- for (long i = 0; i < tags_count; i++) {
151
- VALUE name_value_pair = rb_ary_entry(tags_as_array, i);
152
-
153
- if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
154
- ddog_Vec_Tag_drop(tags);
155
- ENFORCE_TYPE(name_value_pair, T_ARRAY);
156
- }
157
-
158
- // Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
159
- VALUE tag_name = rb_ary_entry(name_value_pair, 0);
160
- VALUE tag_value = rb_ary_entry(name_value_pair, 1);
161
-
162
- if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
163
- ddog_Vec_Tag_drop(tags);
164
- ENFORCE_TYPE(tag_name, T_STRING);
165
- ENFORCE_TYPE(tag_value, T_STRING);
166
- }
167
-
168
- ddog_Vec_Tag_PushResult push_result =
169
- ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));
170
-
171
- if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
172
- // libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
173
- // We warn users about such tags, and then just ignore them.
174
- safely_log_failure_to_process_tag(tags, get_error_details_and_drop(&push_result.err));
175
- }
176
- }
177
-
178
- return tags;
179
- }
180
-
181
- static VALUE log_failure_to_process_tag(VALUE err_details) {
182
- VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
183
- VALUE profiling_module = rb_const_get(datadog_module, rb_intern("Profiling"));
184
- VALUE http_transport_class = rb_const_get(profiling_module, rb_intern("HttpTransport"));
185
-
186
- return rb_funcall(http_transport_class, log_failure_to_process_tag_id, 1, err_details);
187
- }
188
-
189
- // Since we are calling into Ruby code, it may raise an exception. This method ensure that dynamically-allocated tags
190
- // get cleaned before propagating the exception.
191
- static void safely_log_failure_to_process_tag(ddog_Vec_Tag tags, VALUE err_details) {
192
- int exception_state;
193
- rb_protect(log_failure_to_process_tag, err_details, &exception_state);
194
-
195
- if (exception_state) { // An exception was raised
196
- ddog_Vec_Tag_drop(tags); // clean up
197
- rb_jump_tag(exception_state); // "Re-raise" exception
198
- }
199
- }
200
-
201
108
  // Note: This function handles a bunch of libdatadog dynamically-allocated objects, so it MUST not use any Ruby APIs
202
109
  // which can raise exceptions, otherwise the objects will be leaked.
203
110
  static VALUE perform_export(