datadog 2.0.0.beta2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -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 +23 -1
  26. data/lib/datadog/opentelemetry/sdk/trace/span.rb +3 -1
  27. data/lib/datadog/profiling/component.rb +26 -2
  28. data/lib/datadog/profiling/crashtracker.rb +91 -0
  29. data/lib/datadog/profiling/exporter.rb +6 -3
  30. data/lib/datadog/profiling/http_transport.rb +7 -11
  31. data/lib/datadog/profiling/profiler.rb +9 -2
  32. data/lib/datadog/profiling/stack_recorder.rb +6 -2
  33. data/lib/datadog/profiling.rb +1 -0
  34. data/lib/datadog/tracing/component.rb +5 -1
  35. data/lib/datadog/tracing/configuration/dynamic.rb +39 -1
  36. data/lib/datadog/tracing/configuration/settings.rb +1 -0
  37. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -0
  38. data/lib/datadog/tracing/contrib/active_record/integration.rb +10 -0
  39. data/lib/datadog/tracing/contrib/configuration/resolver.rb +43 -0
  40. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +1 -1
  41. data/lib/datadog/tracing/correlation.rb +10 -6
  42. data/lib/datadog/tracing/remote.rb +5 -1
  43. data/lib/datadog/tracing/sampling/ext.rb +5 -1
  44. data/lib/datadog/tracing/sampling/matcher.rb +60 -31
  45. data/lib/datadog/tracing/sampling/rule.rb +12 -5
  46. data/lib/datadog/tracing/sampling/rule_sampler.rb +17 -1
  47. data/lib/datadog/tracing/sampling/span/matcher.rb +13 -41
  48. data/lib/datadog/tracing/span_link.rb +12 -6
  49. data/lib/datadog/tracing/span_operation.rb +6 -4
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c98feb8dee5da784f25da37c6989ed32929eeac8189590c0aa6763303ac5076d
4
- data.tar.gz: 9324b595c888213affe2a342c4be4dcb49643328cdf3ab0aab9616c41a9b5dd6
3
+ metadata.gz: 5008d4cb7f37cf4c4e1ed7956198b1fba9837c52f379b5d892898506ce4b2640
4
+ data.tar.gz: 684c2b438ef7230e7f30ebd69b43041b2eea81413bbe2d4aedfe8140af01e955
5
5
  SHA512:
6
- metadata.gz: f4ee11c65fe76ee70b5c85d67de21a5feadc6938976a0869ae230a6d3a28c38d5ed2c8516633d2046f7fb311ed19d62de9a46317e70a1ade2d4147c139693a44
7
- data.tar.gz: 05b8bb6ec82ddb7cd7bce5e461c5e48416df745a23cfcc724d7b2cc0de86e047a4b0e8b7d26dbf9f0b62a12f94dc0492937da3676adb97e458fb41a501a005f3
6
+ metadata.gz: 1582a1f36cdf990613dc46e6febadb29f45275a363d32f4213237b5c8dc5be145218226639a590cb16e2fe7881a91b05dfb714c018e85fbb99f407376f68d71c
7
+ data.tar.gz: e08d4d1645d47bd4fb8e4c6d03f088571bca24df622bc8e9da0d5ca696b33602fe28dc855e34da9f1783c62e4797916e1a0736f5d8fa29850d71d57a5508ec4e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.0.0] - 2024-06-06
6
+
7
+ ### Added
8
+
9
+ * Tracing: Remap http status code tag for otel span for trace metrics ([#3664][])
10
+
11
+ ## [2.0.0.rc1] - 2024-05-24
12
+
13
+ ### Added
14
+
15
+ * Core: Add libdatadog crash tracker ([#3384][])
16
+ * OpenTelemetry: Add support for Span Links ([#3572][])
17
+ * Profiling: Enable endpoint profiling for Sidekiq and other background job processors ([#3610][])
18
+ * Tracing: Add dynamically configurable sampling rules ([#3598][])
19
+ * Tracing: Add sampling rule glob pattern matching ([#3616][])
20
+
21
+ ### Changed
22
+
23
+ * Appsec: Fix undefined method error when Tracing disabled ([#3645][])
24
+ * Profiling: Upgrade to libdatadog 9 ([#3627][])
25
+ * Tracing: Cache ActiveRecord configuration resolver ([#3630][])
26
+
27
+ ### Fixed
28
+
29
+ * Core: Fix error during telemetry debug logging attempt ([#3617][])
30
+ * OpenTelemetry: Fix attribute merge with Datadog tags ([#3651][])
31
+ * Tracing: Fix environment logger repeated entries ([#3624][])
32
+
33
+ ### Removed
34
+
35
+ * Profiling: Remove profiler support for Ruby 2.3 and 2.4 ([#3621][])
36
+ * Profiling: Remove deprecated profiler settings ([#3597][])
37
+
38
+ ## [1.23.0] - 2024-05-09
39
+
40
+ ### Added
41
+
42
+ * Profiling: Enable endpoint profiling for Sidekiq and similar background job processors ([#3619][])
43
+
44
+ ### Fixed
45
+
46
+ * Fix no such file or directory issue when using single step instrumentation ([#3623][])
47
+ * Fix error during telemetry debug logging attempt ([#3618][])
48
+
5
49
  ## [2.0.0.beta2] - 2024-04-18
6
50
 
7
51
  ### Added
@@ -2850,7 +2894,10 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2850
2894
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2851
2895
 
2852
2896
 
2853
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...master
2897
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0...master
2898
+ [2.0.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.rc1...v2.0.0
2899
+ [2.0.0.rc1]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.beta2...v2.0.0.rc1
2900
+ [1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
2854
2901
  [2.0.0.beta2]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.beta1...v2.0.0.beta2
2855
2902
  [1.22.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v1.22.0
2856
2903
  [2.0.0.beta1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v2.0.0.beta1
@@ -4145,6 +4192,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4145
4192
  [#3370]: https://github.com/DataDog/dd-trace-rb/issues/3370
4146
4193
  [#3373]: https://github.com/DataDog/dd-trace-rb/issues/3373
4147
4194
  [#3374]: https://github.com/DataDog/dd-trace-rb/issues/3374
4195
+ [#3384]: https://github.com/DataDog/dd-trace-rb/issues/3384
4148
4196
  [#3386]: https://github.com/DataDog/dd-trace-rb/issues/3386
4149
4197
  [#3388]: https://github.com/DataDog/dd-trace-rb/issues/3388
4150
4198
  [#3392]: https://github.com/DataDog/dd-trace-rb/issues/3392
@@ -4202,10 +4250,26 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4202
4250
  [#3551]: https://github.com/DataDog/dd-trace-rb/issues/3551
4203
4251
  [#3558]: https://github.com/DataDog/dd-trace-rb/issues/3558
4204
4252
  [#3565]: https://github.com/DataDog/dd-trace-rb/issues/3565
4253
+ [#3572]: https://github.com/DataDog/dd-trace-rb/issues/3572
4205
4254
  [#3573]: https://github.com/DataDog/dd-trace-rb/issues/3573
4206
4255
  [#3582]: https://github.com/DataDog/dd-trace-rb/issues/3582
4207
4256
  [#3585]: https://github.com/DataDog/dd-trace-rb/issues/3585
4208
4257
  [#3587]: https://github.com/DataDog/dd-trace-rb/issues/3587
4258
+ [#3597]: https://github.com/DataDog/dd-trace-rb/issues/3597
4259
+ [#3598]: https://github.com/DataDog/dd-trace-rb/issues/3598
4260
+ [#3610]: https://github.com/DataDog/dd-trace-rb/issues/3610
4261
+ [#3616]: https://github.com/DataDog/dd-trace-rb/issues/3616
4262
+ [#3617]: https://github.com/DataDog/dd-trace-rb/issues/3617
4263
+ [#3618]: https://github.com/DataDog/dd-trace-rb/issues/3618
4264
+ [#3619]: https://github.com/DataDog/dd-trace-rb/issues/3619
4265
+ [#3621]: https://github.com/DataDog/dd-trace-rb/issues/3621
4266
+ [#3623]: https://github.com/DataDog/dd-trace-rb/issues/3623
4267
+ [#3624]: https://github.com/DataDog/dd-trace-rb/issues/3624
4268
+ [#3627]: https://github.com/DataDog/dd-trace-rb/issues/3627
4269
+ [#3630]: https://github.com/DataDog/dd-trace-rb/issues/3630
4270
+ [#3645]: https://github.com/DataDog/dd-trace-rb/issues/3645
4271
+ [#3651]: https://github.com/DataDog/dd-trace-rb/issues/3651
4272
+ [#3664]: https://github.com/DataDog/dd-trace-rb/issues/3664
4209
4273
  [@AdrianLC]: https://github.com/AdrianLC
4210
4274
  [@Azure7111]: https://github.com/Azure7111
4211
4275
  [@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(