ddtrace 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -2
  3. data/ext/ddtrace_profiling_loader/extconf.rb +1 -1
  4. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +66 -6
  5. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -54
  6. data/ext/ddtrace_profiling_native_extension/collectors_stack.c +11 -13
  7. data/ext/ddtrace_profiling_native_extension/extconf.rb +1 -1
  8. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +3 -2
  9. data/ext/ddtrace_profiling_native_extension/setup_signal_handler.c +96 -0
  10. data/ext/ddtrace_profiling_native_extension/setup_signal_handler.h +7 -0
  11. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +70 -18
  12. data/ext/ddtrace_profiling_native_extension/stack_recorder.h +1 -0
  13. data/lib/datadog/appsec/assets/blocked.html +98 -3
  14. data/lib/datadog/appsec/assets/blocked.json +1 -0
  15. data/lib/datadog/appsec/assets/blocked.text +5 -0
  16. data/lib/datadog/appsec/assets/waf_rules/recommended.json +35 -46
  17. data/lib/datadog/appsec/assets/waf_rules/risky.json +1 -1
  18. data/lib/datadog/appsec/assets/waf_rules/strict.json +46 -1
  19. data/lib/datadog/appsec/assets.rb +2 -2
  20. data/lib/datadog/appsec/configuration/settings.rb +6 -0
  21. data/lib/datadog/appsec/configuration.rb +4 -0
  22. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +4 -8
  23. data/lib/datadog/appsec/contrib/rack/request.rb +17 -0
  24. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +2 -2
  25. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +2 -2
  26. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -6
  27. data/lib/datadog/appsec/contrib/sinatra/ext.rb +1 -0
  28. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +1 -1
  29. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -8
  30. data/lib/datadog/appsec/extensions.rb +10 -0
  31. data/lib/datadog/appsec/processor.rb +18 -0
  32. data/lib/datadog/appsec/response.rb +54 -0
  33. data/lib/datadog/core/runtime/ext.rb +1 -1
  34. data/lib/datadog/opentracer/distributed_headers.rb +5 -7
  35. data/lib/datadog/opentracer/rack_propagator.rb +0 -3
  36. data/lib/datadog/opentracer/text_map_propagator.rb +5 -7
  37. data/lib/datadog/profiling/collectors/cpu_and_wall_time.rb +10 -4
  38. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +4 -0
  39. data/lib/datadog/profiling/collectors/old_stack.rb +7 -0
  40. data/lib/datadog/profiling/exporter.rb +5 -0
  41. data/lib/datadog/profiling/old_recorder.rb +8 -0
  42. data/lib/datadog/profiling/profiler.rb +7 -0
  43. data/lib/datadog/profiling/scheduler.rb +4 -7
  44. data/lib/datadog/profiling/stack_recorder.rb +22 -0
  45. data/lib/datadog/profiling/tasks/setup.rb +0 -7
  46. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +4 -0
  47. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -1
  48. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +6 -12
  49. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +27 -0
  50. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +38 -0
  51. data/lib/datadog/tracing/contrib/grpc/patcher.rb +0 -2
  52. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +32 -0
  53. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +33 -0
  54. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -0
  55. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -0
  56. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -0
  57. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  58. data/lib/datadog/tracing/contrib/que/tracer.rb +2 -0
  59. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +4 -1
  60. data/lib/datadog/tracing/contrib/racecar/events/message.rb +4 -1
  61. data/lib/datadog/tracing/contrib/rack/middlewares.rb +2 -0
  62. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +2 -0
  63. data/lib/datadog/tracing/contrib/resque/resque_job.rb +2 -0
  64. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +2 -0
  65. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +5 -0
  66. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -0
  67. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +2 -0
  68. data/lib/datadog/tracing/distributed/b3.rb +66 -0
  69. data/lib/datadog/tracing/distributed/b3_single.rb +66 -0
  70. data/lib/datadog/tracing/distributed/datadog.rb +153 -0
  71. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -0
  72. data/lib/datadog/tracing/distributed/fetcher.rb +30 -0
  73. data/lib/datadog/tracing/distributed/headers/ext.rb +18 -16
  74. data/lib/datadog/tracing/distributed/helpers.rb +7 -6
  75. data/lib/datadog/tracing/distributed/propagation.rb +127 -0
  76. data/lib/datadog/tracing/propagation/http.rb +3 -106
  77. data/lib/datadog/tracing/trace_segment.rb +1 -1
  78. data/lib/ddtrace/transport/trace_formatter.rb +2 -5
  79. data/lib/ddtrace/version.rb +2 -2
  80. metadata +19 -14
  81. data/lib/datadog/tracing/distributed/headers/b3.rb +0 -55
  82. data/lib/datadog/tracing/distributed/headers/b3_single.rb +0 -67
  83. data/lib/datadog/tracing/distributed/headers/datadog.rb +0 -144
  84. data/lib/datadog/tracing/distributed/headers/parser.rb +0 -37
  85. data/lib/datadog/tracing/distributed/metadata/b3.rb +0 -55
  86. data/lib/datadog/tracing/distributed/metadata/b3_single.rb +0 -66
  87. data/lib/datadog/tracing/distributed/metadata/datadog.rb +0 -73
  88. data/lib/datadog/tracing/distributed/metadata/parser.rb +0 -34
  89. data/lib/datadog/tracing/propagation/grpc.rb +0 -98
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a13232b0a3e34d8c05f4e9cd14083d3da7899e4cd4a8a52a95495193dce81b4
4
- data.tar.gz: '0975d24547f318e657422c35ef54f8f68cff797436594d30514d163b55170220'
3
+ metadata.gz: 38409b6f228216767057d98b4acb4cc0effeecd34e6b98e202dde946aa4aec99
4
+ data.tar.gz: 6a5b909f6a73516c965c1125b7c70fe212972bf89ac829e0cabc356d82a353a0
5
5
  SHA512:
6
- metadata.gz: da8917c06511999a28152037efa177fceba67d0329534bc8a462d17ffb151bfad0f360318262be095fc73ce42990051e6856b2cc3663317091b552d76730116c
7
- data.tar.gz: 44a8062936ae2f20fdca4d524d6390825b87092d58ebb934d2e4b715f442b595be020752b3a2c0fd432d4dc3901ad611725adf4d70139c095d78e7b2b127fdd1
6
+ metadata.gz: 73d2114938c7a420a6846042c3172f4b08b5a9fb5b68efc6aa155441fa80d6221201ff1a09afbcbd8ccb12655590622326510cf17995443cd1e5cadca0fedbb3
7
+ data.tar.gz: 6c8005ac09cb122bbcf32ee569799f61ada81f8e7a735b2b754e0b0f2969a77980b95a1d8871440efe8e617a3c53c77b4f286dceb98be00dd446684ed5bec43b
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.7.0] - 2022-11-29
6
+
7
+ ### Added
8
+ * Integrations: Support que 2 ([#2382][]) ([@danhodge][])
9
+ * Tracing: Unified tagging `span.kind` as `server` and `client` ([#2365][])
10
+ * Tracing: Adds `span.kind` tag for `kafka`, `sidekiq`, `racecar`, `que`, `shoryuken`, `sneakers`, and `resque` ([#2420][], [#2419][], [#2413][], [#2394][])
11
+ * Tracing: Adds `span.kind` with values `producer` and `consumer` for `delayed_job` ([#2393][])
12
+ * Tracing: Adds `span.kind` as `client` for `redis` ([#2392][])
13
+ * Appsec: Pass HTTP client IP to WAF ([#2316][])
14
+ * Unified tagging `process_id` ([#2276][])
15
+
16
+ ### Changed
17
+ * Allow `debase-ruby_core_source` 0.10.18 to be used ([#2435][])
18
+ * Update AppSec ruleset to v1.4.2 ([#2390][])
19
+ * Refactored clearing of profile data after Ruby app forks ([#2362][], [#2367][])
20
+ * Tracing: Move distributed propagation to Contrib ([#2352][])
21
+
22
+ ### Fixed
23
+ * Fix ddtrace installation issue when users have CI=true ([#2378][])
24
+
5
25
  ## [1.6.1] - 2022-11-16
6
26
 
7
27
  ### Changed
@@ -2198,7 +2218,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2198
2218
 
2199
2219
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2200
2220
 
2201
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.6.1...master
2221
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.7.0...master
2222
+ [1.7.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.6.1...v1.7.0
2202
2223
  [1.6.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.6.0...v1.6.1
2203
2224
  [1.6.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.5.2...v1.6.0
2204
2225
  [1.5.2]: https://github.com/DataDog/dd-trace-rb/compare/v1.5.1...v1.5.2
@@ -3102,6 +3123,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3102
3123
  [#2260]: https://github.com/DataDog/dd-trace-rb/issues/2260
3103
3124
  [#2265]: https://github.com/DataDog/dd-trace-rb/issues/2265
3104
3125
  [#2267]: https://github.com/DataDog/dd-trace-rb/issues/2267
3126
+ [#2276]: https://github.com/DataDog/dd-trace-rb/issues/2276
3105
3127
  [#2279]: https://github.com/DataDog/dd-trace-rb/issues/2279
3106
3128
  [#2283]: https://github.com/DataDog/dd-trace-rb/issues/2283
3107
3129
  [#2289]: https://github.com/DataDog/dd-trace-rb/issues/2289
@@ -3114,6 +3136,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3114
3136
  [#2310]: https://github.com/DataDog/dd-trace-rb/issues/2310
3115
3137
  [#2311]: https://github.com/DataDog/dd-trace-rb/issues/2311
3116
3138
  [#2313]: https://github.com/DataDog/dd-trace-rb/issues/2313
3139
+ [#2316]: https://github.com/DataDog/dd-trace-rb/issues/2316
3117
3140
  [#2317]: https://github.com/DataDog/dd-trace-rb/issues/2317
3118
3141
  [#2318]: https://github.com/DataDog/dd-trace-rb/issues/2318
3119
3142
  [#2319]: https://github.com/DataDog/dd-trace-rb/issues/2319
@@ -3124,7 +3147,21 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3124
3147
  [#2331]: https://github.com/DataDog/dd-trace-rb/issues/2331
3125
3148
  [#2335]: https://github.com/DataDog/dd-trace-rb/issues/2335
3126
3149
  [#2339]: https://github.com/DataDog/dd-trace-rb/issues/2339
3150
+ [#2352]: https://github.com/DataDog/dd-trace-rb/issues/2352
3151
+ [#2362]: https://github.com/DataDog/dd-trace-rb/issues/2362
3127
3152
  [#2363]: https://github.com/DataDog/dd-trace-rb/issues/2363
3153
+ [#2365]: https://github.com/DataDog/dd-trace-rb/issues/2365
3154
+ [#2367]: https://github.com/DataDog/dd-trace-rb/issues/2367
3155
+ [#2378]: https://github.com/DataDog/dd-trace-rb/issues/2378
3156
+ [#2382]: https://github.com/DataDog/dd-trace-rb/issues/2382
3157
+ [#2390]: https://github.com/DataDog/dd-trace-rb/issues/2390
3158
+ [#2392]: https://github.com/DataDog/dd-trace-rb/issues/2392
3159
+ [#2393]: https://github.com/DataDog/dd-trace-rb/issues/2393
3160
+ [#2394]: https://github.com/DataDog/dd-trace-rb/issues/2394
3161
+ [#2413]: https://github.com/DataDog/dd-trace-rb/issues/2413
3162
+ [#2419]: https://github.com/DataDog/dd-trace-rb/issues/2419
3163
+ [#2420]: https://github.com/DataDog/dd-trace-rb/issues/2420
3164
+ [#2435]: https://github.com/DataDog/dd-trace-rb/issues/2435
3128
3165
  [@AdrianLC]: https://github.com/AdrianLC
3129
3166
  [@Azure7111]: https://github.com/Azure7111
3130
3167
  [@BabyGroot]: https://github.com/BabyGroot
@@ -3170,6 +3207,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3170
3207
  [@components]: https://github.com/components
3171
3208
  [@cswatt]: https://github.com/cswatt
3172
3209
  [@cwoodcox]: https://github.com/cwoodcox
3210
+ [@danhodge]: https://github.com/danhodge
3173
3211
  [@dasch]: https://github.com/dasch
3174
3212
  [@dim]: https://github.com/dim
3175
3213
  [@dirk]: https://github.com/dirk
@@ -3268,4 +3306,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3268
3306
  [@y-yagi]: https://github.com/y-yagi
3269
3307
  [@yujideveloper]: https://github.com/yujideveloper
3270
3308
  [@yukimurasawa]: https://github.com/yukimurasawa
3271
- [@zachmccormick]: https://github.com/zachmccormick
3309
+ [@zachmccormick]: https://github.com/zachmccormick
@@ -26,7 +26,7 @@ end
26
26
 
27
27
  # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
28
28
  # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
29
- add_compiler_flag '-Werror' if ENV['CI'] == 'true'
29
+ add_compiler_flag '-Werror' if ENV['DDTRACE_CI'] == 'true'
30
30
 
31
31
  # Older gcc releases may not default to C99 and we need to ask for this. This is also used:
32
32
  # * by upstream Ruby -- search for gnu99 in the codebase
@@ -66,10 +66,12 @@
66
66
  #define IS_NOT_WALL_TIME false
67
67
  #define MISSING_TRACER_CONTEXT_KEY 0
68
68
 
69
- static ID at_active_trace_id; // id of :@active_trace in Ruby
70
- static ID at_root_span_id; // id of :@root_span in Ruby
71
69
  static ID at_active_span_id; // id of :@active_span in Ruby
70
+ static ID at_active_trace_id; // id of :@active_trace in Ruby
72
71
  static ID at_id_id; // id of :@id in Ruby
72
+ static ID at_resource_id; // id of :@resource in Ruby
73
+ static ID at_root_span_id; // id of :@root_span in Ruby
74
+ static ID at_type_id; // id of :@type in Ruby
73
75
 
74
76
  // Contains state for a single CpuAndWallTime instance
75
77
  struct cpu_and_wall_time_collector_state {
@@ -91,7 +93,7 @@ struct cpu_and_wall_time_collector_state {
91
93
  // is not (just) a stat.
92
94
  unsigned int sample_count;
93
95
 
94
- struct {
96
+ struct stats {
95
97
  // Track how many garbage collection samples we've taken.
96
98
  unsigned int gc_samples;
97
99
  // See cpu_and_wall_time_collector_on_gc_start for details
@@ -129,6 +131,7 @@ struct trace_identifiers {
129
131
  ddog_CharSlice span_id;
130
132
  char local_root_span_id_buffer[MAXIMUM_LENGTH_64_BIT_IDENTIFIER];
131
133
  char span_id_buffer[MAXIMUM_LENGTH_64_BIT_IDENTIFIER];
134
+ VALUE trace_endpoint;
132
135
  };
133
136
 
134
137
  static void cpu_and_wall_time_collector_typed_data_mark(void *state_ptr);
@@ -165,6 +168,8 @@ static long wall_time_now_ns(bool raise_on_failure);
165
168
  static long thread_id_for(VALUE thread);
166
169
  static VALUE _native_stats(VALUE self, VALUE collector_instance);
167
170
  static void trace_identifiers_for(struct cpu_and_wall_time_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result);
171
+ static bool is_type_web(VALUE root_span_type);
172
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
168
173
 
169
174
  void collectors_cpu_and_wall_time_init(VALUE profiling_module) {
170
175
  VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
@@ -184,6 +189,7 @@ void collectors_cpu_and_wall_time_init(VALUE profiling_module) {
184
189
 
185
190
  rb_define_singleton_method(collectors_cpu_and_wall_time_class, "_native_initialize", _native_initialize, 4);
186
191
  rb_define_singleton_method(collectors_cpu_and_wall_time_class, "_native_inspect", _native_inspect, 1);
192
+ rb_define_singleton_method(collectors_cpu_and_wall_time_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
187
193
  rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 1);
188
194
  rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
189
195
  rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
@@ -192,10 +198,12 @@ void collectors_cpu_and_wall_time_init(VALUE profiling_module) {
192
198
  rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
193
199
  rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
194
200
 
195
- at_active_trace_id = rb_intern_const("@active_trace");
196
- at_root_span_id = rb_intern_const("@root_span");
197
201
  at_active_span_id = rb_intern_const("@active_span");
202
+ at_active_trace_id = rb_intern_const("@active_trace");
198
203
  at_id_id = rb_intern_const("@id");
204
+ at_resource_id = rb_intern_const("@resource");
205
+ at_root_span_id = rb_intern_const("@root_span");
206
+ at_type_id = rb_intern_const("@type");
199
207
  }
200
208
 
201
209
  // This structure is used to define a Ruby object that stores a pointer to a struct cpu_and_wall_time_collector_state
@@ -570,12 +578,29 @@ static void trigger_sample_for_thread(
570
578
  };
571
579
  }
572
580
 
573
- struct trace_identifiers trace_identifiers_result = {.valid = false};
581
+ struct trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
574
582
  trace_identifiers_for(state, thread, &trace_identifiers_result);
575
583
 
576
584
  if (trace_identifiers_result.valid) {
577
585
  labels[label_pos++] = (ddog_Label) {.key = DDOG_CHARSLICE_C("local root span id"), .str = trace_identifiers_result.local_root_span_id};
578
586
  labels[label_pos++] = (ddog_Label) {.key = DDOG_CHARSLICE_C("span id"), .str = trace_identifiers_result.span_id};
587
+
588
+ if (trace_identifiers_result.trace_endpoint != Qnil) {
589
+ // The endpoint gets recorded in a different way because it is mutable in the tracer and can change during a
590
+ // trace.
591
+ //
592
+ // Instead of each sample for the same local_root_span_id getting a potentially-different endpoint,
593
+ // `record_endpoint` (via libdatadog) keeps a list of local_root_span_id values and their most-recently-seen
594
+ // endpoint values, and at serialization time the most-recently-seen endpoint is applied to all relevant samples.
595
+ //
596
+ // This is why the endpoint is not directly added in this function to the labels array, although it will later
597
+ // show up in the array in the output pprof.
598
+ record_endpoint(
599
+ state->recorder_instance,
600
+ trace_identifiers_result.local_root_span_id,
601
+ char_slice_from_ruby_string(trace_identifiers_result.trace_endpoint)
602
+ );
603
+ }
579
604
  }
580
605
 
581
606
  // The number of times `label_pos++` shows up in this function needs to match `max_label_count`. To avoid "oops I
@@ -862,4 +887,39 @@ static void trace_identifiers_for(struct cpu_and_wall_time_collector_state *stat
862
887
  };
863
888
 
864
889
  trace_identifiers_result->valid = true;
890
+
891
+ VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
892
+ if (root_span_type == Qnil || !is_type_web(root_span_type)) return;
893
+
894
+ VALUE trace_resource = rb_ivar_get(active_trace, at_resource_id /* @resource */);
895
+ if (RB_TYPE_P(trace_resource, T_STRING)) {
896
+ trace_identifiers_result->trace_endpoint = trace_resource;
897
+ } else if (trace_resource == Qnil) {
898
+ // Fall back to resource from span, if any
899
+ trace_identifiers_result->trace_endpoint = rb_ivar_get(root_span, at_resource_id /* @resource */);
900
+ }
901
+ }
902
+
903
+ static bool is_type_web(VALUE root_span_type) {
904
+ ENFORCE_TYPE(root_span_type, T_STRING);
905
+
906
+ return RSTRING_LEN(root_span_type) == strlen("web") &&
907
+ (memcmp("web", StringValuePtr(root_span_type), strlen("web")) == 0);
908
+ }
909
+
910
+ // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
911
+ //
912
+ // Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
913
+ // trigger samples at the same time.
914
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
915
+ struct cpu_and_wall_time_collector_state *state;
916
+ TypedData_Get_Struct(collector_instance, struct cpu_and_wall_time_collector_state, &cpu_and_wall_time_collector_typed_data, state);
917
+
918
+ st_clear(state->hash_map_per_thread_context);
919
+
920
+ state->stats = (struct stats) {}; // Resets all stats back to zero
921
+
922
+ rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
923
+
924
+ return Qtrue;
865
925
  }
@@ -8,6 +8,7 @@
8
8
  #include "ruby_helpers.h"
9
9
  #include "collectors_cpu_and_wall_time.h"
10
10
  #include "private_vm_api_access.h"
11
+ #include "setup_signal_handler.h"
11
12
 
12
13
  // Used to trigger the periodic execution of Collectors::CpuAndWallTime, which implements all of the sampling logic
13
14
  // itself; this class only implements the "doing it periodically" part.
@@ -29,7 +30,7 @@
29
30
  // internals, we may be able to figure out a way of overcoming it. But it's definitely going to be hard so for now
30
31
  // we're considering it as a given.
31
32
  //
32
- // ### Flow for triggering samples
33
+ // ### Flow for triggering CPU/Wall-time samples
33
34
  //
34
35
  // The flow for triggering samples is as follows:
35
36
  //
@@ -56,6 +57,16 @@
56
57
  // 4. The Ruby VM calls our `sample_from_postponed_job` from a thread holding the global VM lock. A sample is recorded by
57
58
  // calling `cpu_and_wall_time_collector_sample`.
58
59
  //
60
+ // ### TracePoints and Forking
61
+ //
62
+ // When the Ruby VM forks, the CPU/Wall-time profiling stops naturally because it's triggered by a background thread
63
+ // that doesn't get automatically restarted by the VM on the child process. (The profiler does trigger its restart at
64
+ // some point -- see `Profiling::Tasks::Setup` for details).
65
+ //
66
+ // But this doesn't apply to any `TracePoint`s this class may use, which will continue to be active. Thus, we need to
67
+ // always remember consider this case of -- the worker thread may not be alive but the `TracePoint`s can continue to
68
+ // trigger samples.
69
+ //
59
70
  // ---
60
71
 
61
72
  // Contains state for a single CpuAndWallTimeWorker instance
@@ -86,9 +97,6 @@ static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
86
97
  static VALUE _native_sampling_loop(VALUE self, VALUE instance);
87
98
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance);
88
99
  static VALUE stop(VALUE self_instance, VALUE optional_exception);
89
- static void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *));
90
- static void remove_sigprof_signal_handler(void);
91
- static void block_sigprof_signal_handler_from_running_in_current_thread(void);
92
100
  static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
93
101
  static void *run_sampling_trigger_loop(void *state_ptr);
94
102
  static void interrupt_sampling_trigger_loop(void *state_ptr);
@@ -107,6 +115,7 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused);
107
115
  static void safely_call(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg, VALUE instance);
108
116
  static VALUE _native_simulate_handle_sampling_signal(DDTRACE_UNUSED VALUE self);
109
117
  static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE self);
118
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance);
110
119
 
111
120
  // Global state -- be very careful when accessing or modifying it
112
121
 
@@ -139,6 +148,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
139
148
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, 3);
140
149
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
141
150
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 1);
151
+ rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
142
152
  rb_define_singleton_method(testing_module, "_native_current_sigprof_signal_handler", _native_current_sigprof_signal_handler, 0);
143
153
  rb_define_singleton_method(testing_module, "_native_is_running?", _native_is_running, 1);
144
154
  rb_define_singleton_method(testing_module, "_native_install_testing_signal_handler", _native_install_testing_signal_handler, 0);
@@ -235,7 +245,7 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
235
245
 
236
246
  block_sigprof_signal_handler_from_running_in_current_thread(); // We want to interrupt the thread with the global VM lock, never this one
237
247
 
238
- install_sigprof_signal_handler(handle_sampling_signal);
248
+ install_sigprof_signal_handler(handle_sampling_signal, "handle_sampling_signal");
239
249
  if (state->gc_profiling_enabled) rb_tracepoint_enable(state->gc_tracepoint);
240
250
 
241
251
  // Release GVL, get to the actual work!
@@ -245,7 +255,18 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
245
255
  // The sample trigger loop finished (either cleanly or with an error); let's clean up
246
256
 
247
257
  rb_tracepoint_disable(state->gc_tracepoint);
248
- remove_sigprof_signal_handler();
258
+
259
+ // Why replace and not use remove the signal handler? We do this because when a process receives a SIGPROF without
260
+ // having an explicit signal handler set up, the process will instantly terminate with a confusing
261
+ // "Profiling timer expired" message left behind. (This message doesn't come from us -- it's the default message for
262
+ // an unhandled SIGPROF. Pretty confusing UNIX/POSIX behavior...)
263
+ //
264
+ // Unfortunately, because signal delivery is asynchronous, there's no way to guarantee that there are no pending
265
+ // profiler-sent signals by the time we get here and want to clean up.
266
+ // @ivoanjo: I suspect this will never happen, but the cost of getting it wrong is really high (VM terminates) so this
267
+ // is a just-in-case situation.
268
+ replace_sigprof_signal_handler_with_empty_handler(handle_sampling_signal);
269
+
249
270
  active_sampler_instance = Qnil;
250
271
  active_sampler_owner_thread = Qnil;
251
272
 
@@ -274,53 +295,6 @@ static VALUE stop(VALUE self_instance, VALUE optional_exception) {
274
295
  return Qtrue;
275
296
  }
276
297
 
277
- static void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *)) {
278
- struct sigaction existing_signal_handler_config = {.sa_sigaction = NULL};
279
- struct sigaction signal_handler_config = {
280
- .sa_flags = SA_RESTART | SA_SIGINFO,
281
- .sa_sigaction = signal_handler_function
282
- };
283
- sigemptyset(&signal_handler_config.sa_mask);
284
-
285
- if (sigaction(SIGPROF, &signal_handler_config, &existing_signal_handler_config) != 0) {
286
- rb_sys_fail("Could not start CpuAndWallTimeWorker: Could not install signal handler");
287
- }
288
-
289
- // In some corner cases (e.g. after a fork), our signal handler may still be around, and that's ok
290
- if (existing_signal_handler_config.sa_sigaction == handle_sampling_signal) return;
291
-
292
- if (existing_signal_handler_config.sa_handler != NULL || existing_signal_handler_config.sa_sigaction != NULL) {
293
- // A previous signal handler already existed. Currently we don't support this situation, so let's just back out
294
- // of the installation.
295
-
296
- if (sigaction(SIGPROF, &existing_signal_handler_config, NULL) != 0) {
297
- rb_sys_fail(
298
- "Could not start CpuAndWallTimeWorker: Could not re-install pre-existing SIGPROF signal handler. " \
299
- "This may break the component had installed it."
300
- );
301
- }
302
-
303
- rb_raise(rb_eRuntimeError, "Could not start CpuAndWallTimeWorker: There's a pre-existing SIGPROF signal handler");
304
- }
305
- }
306
-
307
- static void remove_sigprof_signal_handler(void) {
308
- struct sigaction signal_handler_config = {
309
- .sa_handler = SIG_DFL, // Reset back to default
310
- .sa_flags = SA_RESTART // TODO: Unclear if this is actually needed/does anything at all
311
- };
312
- sigemptyset(&signal_handler_config.sa_mask);
313
-
314
- if (sigaction(SIGPROF, &signal_handler_config, NULL) != 0) rb_sys_fail("Failure while removing the signal handler");
315
- }
316
-
317
- static void block_sigprof_signal_handler_from_running_in_current_thread(void) {
318
- sigset_t signals_to_block;
319
- sigemptyset(&signals_to_block);
320
- sigaddset(&signals_to_block, SIGPROF);
321
- pthread_sigmask(SIG_BLOCK, &signals_to_block, NULL);
322
- }
323
-
324
298
  // NOTE: Remember that this will run in the thread and within the scope of user code, including user C code.
325
299
  // We need to be careful not to change any state that may be observed OR to restore it if we do. For instance, if anything
326
300
  // we do here can set `errno`, then we must be careful to restore the old `errno` after the fact.
@@ -403,6 +377,8 @@ static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self) {
403
377
 
404
378
  if (existing_signal_handler_config.sa_sigaction == handle_sampling_signal) {
405
379
  return ID2SYM(rb_intern("profiling"));
380
+ } else if (existing_signal_handler_config.sa_sigaction == empty_signal_handler) {
381
+ return ID2SYM(rb_intern("empty"));
406
382
  } else if (existing_signal_handler_config.sa_sigaction != NULL) {
407
383
  return ID2SYM(rb_intern("other"));
408
384
  } else {
@@ -437,7 +413,7 @@ static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
437
413
  // This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
438
414
  // It SHOULD NOT be used for other purposes.
439
415
  static VALUE _native_install_testing_signal_handler(DDTRACE_UNUSED VALUE self) {
440
- install_sigprof_signal_handler(testing_signal_handler);
416
+ install_sigprof_signal_handler(testing_signal_handler, "testing_signal_handler");
441
417
  return Qtrue;
442
418
  }
443
419
 
@@ -567,3 +543,24 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
567
543
  sample_from_postponed_job(NULL);
568
544
  return Qtrue;
569
545
  }
546
+
547
+ // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
548
+ //
549
+ // Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related tracepoints may still
550
+ // be active, so we make sure to disable them before calling into anything else, so that there are no components
551
+ // attempting to trigger samples at the same time as the reset is done.
552
+ //
553
+ // In the future, if we add more other components with tracepoints, we will need to coordinate stopping all such
554
+ // tracepoints before doing the other cleaning steps.
555
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance) {
556
+ struct cpu_and_wall_time_worker_state *state;
557
+ TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
558
+
559
+ // Disable all tracepoints, so that there are no more attempts to mutate the profile
560
+ rb_tracepoint_disable(state->gc_tracepoint);
561
+
562
+ // Remove all state from the `Collectors::CpuAndWallTime` and connected downstream components
563
+ rb_funcall(state->cpu_and_wall_time_collector_instance, rb_intern("reset_after_fork"), 0);
564
+
565
+ return Qtrue;
566
+ }
@@ -144,11 +144,10 @@ void sample_thread(
144
144
 
145
145
  // Samples thread into recorder, including as a top frame in the stack a frame named "Garbage Collection"
146
146
  if (type == SAMPLE_IN_GC) {
147
+ ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
148
+ ddog_CharSlice function_filename = DDOG_CHARSLICE_C("Garbage Collection");
147
149
  buffer->lines[0] = (ddog_Line) {
148
- .function = (ddog_Function) {
149
- .name = DDOG_CHARSLICE_C(""),
150
- .filename = DDOG_CHARSLICE_C("Garbage Collection")
151
- },
150
+ .function = (ddog_Function) {.name = function_name, .filename = function_filename},
152
151
  .line = 0
153
152
  };
154
153
  // To avoid changing sample_thread_internal, we just prepare a new buffer struct that uses the same underlying storage as the
@@ -300,11 +299,10 @@ static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer*
300
299
 
301
300
  // Important note: `frames_omitted_message` MUST have a lifetime that is at least as long as the call to
302
301
  // `record_sample`. So be careful where it gets allocated. (We do have tests for this, at least!)
302
+ ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
303
+ ddog_CharSlice function_filename = {.ptr = frames_omitted_message, .len = strlen(frames_omitted_message)};
303
304
  buffer->lines[buffer->max_frames - 1] = (ddog_Line) {
304
- .function = (ddog_Function) {
305
- .name = DDOG_CHARSLICE_C(""),
306
- .filename = ((ddog_CharSlice) {.ptr = frames_omitted_message, .len = strlen(frames_omitted_message)})
307
- },
305
+ .function = (ddog_Function) {.name = function_name, .filename = function_filename},
308
306
  .line = 0,
309
307
  };
310
308
  }
@@ -337,11 +335,10 @@ static void record_placeholder_stack_in_native_code(
337
335
  sampling_buffer *record_buffer,
338
336
  int extra_frames_in_record_buffer
339
337
  ) {
338
+ ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
339
+ ddog_CharSlice function_filename = DDOG_CHARSLICE_C("In native code");
340
340
  buffer->lines[0] = (ddog_Line) {
341
- .function = (ddog_Function) {
342
- .name = DDOG_CHARSLICE_C(""),
343
- .filename = DDOG_CHARSLICE_C("In native code")
344
- },
341
+ .function = (ddog_Function) {.name = function_name, .filename = function_filename},
345
342
  .line = 0
346
343
  };
347
344
 
@@ -373,7 +370,8 @@ sampling_buffer *sampling_buffer_new(unsigned int max_frames) {
373
370
  // Currently we have a 1-to-1 correspondence between lines and locations, so we just initialize the locations once
374
371
  // here and then only mutate the contents of the lines.
375
372
  for (unsigned int i = 0; i < max_frames; i++) {
376
- buffer->locations[i] = (ddog_Location) {.lines = (ddog_Slice_line) {.ptr = &buffer->lines[i], .len = 1}};
373
+ ddog_Slice_line lines = (ddog_Slice_line) {.ptr = &buffer->lines[i], .len = 1};
374
+ buffer->locations[i] = (ddog_Location) {.lines = lines};
377
375
  }
378
376
 
379
377
  return buffer;
@@ -79,7 +79,7 @@ end
79
79
 
80
80
  # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
81
81
  # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
82
- add_compiler_flag '-Werror' if ENV['CI'] == 'true'
82
+ add_compiler_flag '-Werror' if ENV['DDTRACE_CI'] == 'true'
83
83
 
84
84
  # Older gcc releases may not default to C99 and we need to ask for this. This is also used:
85
85
  # * by upstream Ruby -- search for gnu99 in the codebase
@@ -14,10 +14,11 @@
14
14
  #else
15
15
  // On older Rubies, use a copy of the VM internal headers shipped in the debase-ruby_core_source gem
16
16
 
17
- // We can't do anything about warnings in VM headers, so we just use this technique to surpress them.
17
+ // We can't do anything about warnings in VM headers, so we just use this technique to suppress them.
18
18
  // See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
19
19
  #pragma GCC diagnostic push
20
20
  #pragma GCC diagnostic ignored "-Wunused-parameter"
21
+ #pragma GCC diagnostic ignored "-Wattributes"
21
22
  #include <vm_core.h>
22
23
  #pragma GCC diagnostic pop
23
24
  #include <iseq.h>
@@ -35,7 +36,7 @@
35
36
  // if the argument passed in is not actually a `Thread` instance.
36
37
  static inline rb_thread_t *thread_struct_from_object(VALUE thread) {
37
38
  static const rb_data_type_t *thread_data_type = NULL;
38
- if (thread_data_type == NULL) thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
39
+ if (UNLIKELY(thread_data_type == NULL)) thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
39
40
 
40
41
  return (rb_thread_t *) rb_check_typeddata(thread, thread_data_type);
41
42
  }
@@ -0,0 +1,96 @@
1
+ #include <ruby.h>
2
+ #include <signal.h>
3
+ #include <errno.h>
4
+ #include <stdbool.h>
5
+
6
+ #include "helpers.h"
7
+ #include "setup_signal_handler.h"
8
+
9
+ static void install_sigprof_signal_handler_internal(
10
+ void (*signal_handler_function)(int, siginfo_t *, void *),
11
+ const char *handler_pretty_name,
12
+ void (*signal_handler_to_replace)(int, siginfo_t *, void *)
13
+ );
14
+
15
+ void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) { }
16
+
17
+ void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name) {
18
+ install_sigprof_signal_handler_internal(signal_handler_function, handler_pretty_name, NULL);
19
+ }
20
+
21
+ void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *)) {
22
+ install_sigprof_signal_handler_internal(empty_signal_handler, "empty_signal_handler", expected_existing_handler);
23
+ }
24
+
25
+ static void install_sigprof_signal_handler_internal(
26
+ void (*signal_handler_function)(int, siginfo_t *, void *),
27
+ const char *handler_pretty_name,
28
+ void (*signal_handler_to_replace)(int, siginfo_t *, void *)
29
+ ) {
30
+ struct sigaction existing_signal_handler_config = {.sa_sigaction = NULL};
31
+ struct sigaction signal_handler_config = {
32
+ .sa_flags = SA_RESTART | SA_SIGINFO,
33
+ .sa_sigaction = signal_handler_function
34
+ };
35
+ sigemptyset(&signal_handler_config.sa_mask);
36
+
37
+ if (sigaction(SIGPROF, &signal_handler_config, &existing_signal_handler_config) != 0) {
38
+ rb_exc_raise(rb_syserr_new_str(errno, rb_sprintf("Could not install profiling signal handler (%s)", handler_pretty_name)));
39
+ }
40
+
41
+ // Because signal handler functions are global, let's check if we're not stepping on someone else's toes.
42
+
43
+ // If the existing signal handler was our empty one, that's ok as well
44
+ if (existing_signal_handler_config.sa_sigaction == empty_signal_handler ||
45
+ // In some corner cases (e.g. after a fork), our signal handler may still be around, and that's ok
46
+ existing_signal_handler_config.sa_sigaction == signal_handler_function ||
47
+ // Are we replacing a known handler with another one?
48
+ (signal_handler_to_replace != NULL && existing_signal_handler_config.sa_sigaction == signal_handler_to_replace)
49
+ ) { return; }
50
+
51
+ if (existing_signal_handler_config.sa_handler != NULL || existing_signal_handler_config.sa_sigaction != NULL) {
52
+ // An unexpected/unknown signal handler already existed. Currently we don't support this situation, so let's just back out
53
+ // of the installation.
54
+
55
+ if (sigaction(SIGPROF, &existing_signal_handler_config, NULL) != 0) {
56
+ rb_exc_raise(
57
+ rb_syserr_new_str(
58
+ errno,
59
+ rb_sprintf(
60
+ "Failed to install profiling signal handler (%s): " \
61
+ "While installing a SIGPROF signal handler, the profiler detected that another software/library/gem had " \
62
+ "previously installed a different SIGPROF signal handler. " \
63
+ "The profiler tried to restore the previous SIGPROF signal handler, but this failed. " \
64
+ "The other software/library/gem may have been left in a broken state. ",
65
+ handler_pretty_name
66
+ )
67
+ )
68
+ );
69
+ }
70
+
71
+ rb_raise(
72
+ rb_eRuntimeError,
73
+ "Could not install profiling signal handler (%s): There's a pre-existing SIGPROF signal handler",
74
+ handler_pretty_name
75
+ );
76
+ }
77
+ }
78
+
79
+ // Note: Be careful when using this; you probably want to use `replace_sigprof_signal_handler_with_empty_handler` instead.
80
+ // (See comments on `collectors_cpu_and_wall_time_worker.c` for details)
81
+ void remove_sigprof_signal_handler(void) {
82
+ struct sigaction signal_handler_config = {
83
+ .sa_handler = SIG_DFL, // Reset back to default
84
+ .sa_flags = SA_RESTART // TODO: Unclear if this is actually needed/does anything at all
85
+ };
86
+ sigemptyset(&signal_handler_config.sa_mask);
87
+
88
+ if (sigaction(SIGPROF, &signal_handler_config, NULL) != 0) rb_sys_fail("Failure while removing the signal handler");
89
+ }
90
+
91
+ void block_sigprof_signal_handler_from_running_in_current_thread(void) {
92
+ sigset_t signals_to_block;
93
+ sigemptyset(&signals_to_block);
94
+ sigaddset(&signals_to_block, SIGPROF);
95
+ pthread_sigmask(SIG_BLOCK, &signals_to_block, NULL);
96
+ }
@@ -0,0 +1,7 @@
1
+ #pragma once
2
+
3
+ void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
4
+ void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
5
+ void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *));
6
+ void remove_sigprof_signal_handler(void);
7
+ void block_sigprof_signal_handler_from_running_in_current_thread(void);