datadog 2.8.0 → 2.10.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -1
- data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +66 -56
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +221 -127
- data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
- data/ext/datadog_profiling_native_extension/heap_recorder.h +2 -2
- data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
- data/ext/datadog_profiling_native_extension/profiling.c +10 -8
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
- data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -76
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -2
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
- data/ext/libdatadog_api/crashtracker.c +3 -0
- data/lib/datadog/appsec/actions_handler.rb +27 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
- data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
- data/lib/datadog/appsec/component.rb +14 -8
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/context.rb +74 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +12 -8
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +20 -30
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +67 -96
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +7 -7
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -60
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +23 -33
- data/lib/datadog/appsec/contrib/rails/patcher.rb +4 -14
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +7 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +45 -65
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +5 -28
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +6 -6
- data/lib/datadog/appsec/event.rb +6 -6
- data/lib/datadog/appsec/ext.rb +8 -1
- data/lib/datadog/appsec/metrics/collector.rb +38 -0
- data/lib/datadog/appsec/metrics/exporter.rb +35 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
- data/lib/datadog/appsec/metrics.rb +13 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +23 -32
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +6 -6
- data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
- data/lib/datadog/appsec/processor.rb +4 -3
- data/lib/datadog/appsec/response.rb +18 -80
- data/lib/datadog/appsec/security_engine/result.rb +67 -0
- data/lib/datadog/appsec/security_engine/runner.rb +88 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec.rb +17 -8
- data/lib/datadog/auto_instrument.rb +3 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
- data/lib/datadog/core/configuration/components.rb +4 -2
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +1 -3
- data/lib/datadog/core/telemetry/event.rb +87 -3
- data/lib/datadog/core/telemetry/logging.rb +2 -2
- data/lib/datadog/core/telemetry/metric.rb +22 -0
- data/lib/datadog/core/telemetry/worker.rb +33 -0
- data/lib/datadog/di/base.rb +115 -0
- data/lib/datadog/di/code_tracker.rb +7 -4
- data/lib/datadog/di/component.rb +19 -11
- data/lib/datadog/di/configuration/settings.rb +11 -1
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +26 -0
- data/lib/datadog/di/error.rb +5 -0
- data/lib/datadog/di/instrumenter.rb +39 -18
- data/lib/datadog/di/{init.rb → preload.rb} +2 -4
- data/lib/datadog/di/probe_manager.rb +4 -4
- data/lib/datadog/di/probe_notification_builder.rb +22 -2
- data/lib/datadog/di/probe_notifier_worker.rb +5 -6
- data/lib/datadog/di/redactor.rb +0 -1
- data/lib/datadog/di/remote.rb +30 -9
- data/lib/datadog/di/transport.rb +2 -4
- data/lib/datadog/di.rb +5 -108
- data/lib/datadog/kit/appsec/events.rb +3 -3
- data/lib/datadog/kit/identity.rb +4 -4
- data/lib/datadog/profiling/component.rb +55 -53
- data/lib/datadog/profiling/http_transport.rb +1 -26
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/extensions.rb +15 -3
- data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
- data/lib/datadog/tracing/span.rb +12 -4
- data/lib/datadog/tracing/span_event.rb +123 -3
- data/lib/datadog/tracing/span_operation.rb +6 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
- data/lib/datadog/version.rb +1 -1
- metadata +40 -17
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/processor/context.rb +0 -107
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/scope.rb +0 -58
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
@@ -9,6 +9,7 @@
|
|
9
9
|
#include "private_vm_api_access.h"
|
10
10
|
#include "stack_recorder.h"
|
11
11
|
#include "time_helpers.h"
|
12
|
+
#include "unsafe_api_calls_check.h"
|
12
13
|
|
13
14
|
// Used to trigger sampling of threads, based on external "events", such as:
|
14
15
|
// * periodic timer for cpu-time and wall-time
|
@@ -112,14 +113,14 @@ static uint32_t global_waiting_for_gvl_threshold_ns = MILLIS_AS_NS(10);
|
|
112
113
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
113
114
|
|
114
115
|
// Contains state for a single ThreadContext instance
|
115
|
-
struct
|
116
|
+
typedef struct {
|
116
117
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
117
118
|
// "Update this when modifying state struct"
|
118
119
|
|
119
120
|
// Required by Datadog::Profiling::Collectors::Stack as a scratch buffer during sampling
|
120
121
|
ddog_prof_Location *locations;
|
121
122
|
uint16_t max_frames;
|
122
|
-
// Hashmap <Thread Object,
|
123
|
+
// Hashmap <Thread Object, per_thread_context>
|
123
124
|
st_table *hash_map_per_thread_context;
|
124
125
|
// Datadog::Profiling::StackRecorder instance
|
125
126
|
VALUE recorder_instance;
|
@@ -162,10 +163,10 @@ struct thread_context_collector_state {
|
|
162
163
|
long wall_time_at_previous_gc_ns; // Will be INVALID_TIME unless there's accumulated time above
|
163
164
|
long wall_time_at_last_flushed_gc_event_ns; // Starts at 0 and then will always be valid
|
164
165
|
} gc_tracking;
|
165
|
-
};
|
166
|
+
} thread_context_collector_state;
|
166
167
|
|
167
168
|
// Tracks per-thread state
|
168
|
-
struct
|
169
|
+
typedef struct {
|
169
170
|
sampling_buffer *sampling_buffer;
|
170
171
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
171
172
|
ddog_CharSlice thread_id_char_slice;
|
@@ -181,21 +182,21 @@ struct per_thread_context {
|
|
181
182
|
long cpu_time_at_start_ns;
|
182
183
|
long wall_time_at_start_ns;
|
183
184
|
} gc_tracking;
|
184
|
-
};
|
185
|
+
} per_thread_context;
|
185
186
|
|
186
187
|
// Used to correlate profiles with traces
|
187
|
-
struct
|
188
|
+
typedef struct {
|
188
189
|
bool valid;
|
189
190
|
uint64_t local_root_span_id;
|
190
191
|
uint64_t span_id;
|
191
192
|
VALUE trace_endpoint;
|
192
|
-
};
|
193
|
+
} trace_identifiers;
|
193
194
|
|
194
|
-
struct
|
195
|
+
typedef struct {
|
195
196
|
VALUE span;
|
196
197
|
VALUE span_id;
|
197
198
|
VALUE trace_id;
|
198
|
-
};
|
199
|
+
} otel_span;
|
199
200
|
|
200
201
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
201
202
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
@@ -203,24 +204,24 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t _val
|
|
203
204
|
static int hash_map_per_thread_context_free_values(st_data_t _thread, st_data_t value_per_thread_context, st_data_t _argument);
|
204
205
|
static VALUE _native_new(VALUE klass);
|
205
206
|
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
206
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread);
|
207
|
+
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception);
|
207
208
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
208
209
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
209
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state);
|
210
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception);
|
210
211
|
static void update_metrics_and_sample(
|
211
|
-
|
212
|
+
thread_context_collector_state *state,
|
212
213
|
VALUE thread_being_sampled,
|
213
214
|
VALUE stack_from_thread,
|
214
|
-
|
215
|
+
per_thread_context *thread_context,
|
215
216
|
sampling_buffer* sampling_buffer,
|
216
217
|
long current_cpu_time_ns,
|
217
218
|
long current_monotonic_wall_time_ns
|
218
219
|
);
|
219
220
|
static void trigger_sample_for_thread(
|
220
|
-
|
221
|
+
thread_context_collector_state *state,
|
221
222
|
VALUE thread,
|
222
223
|
VALUE stack_from_thread,
|
223
|
-
|
224
|
+
per_thread_context *thread_context,
|
224
225
|
sampling_buffer* sampling_buffer,
|
225
226
|
sample_values values,
|
226
227
|
long current_monotonic_wall_time_ns,
|
@@ -230,37 +231,37 @@ static void trigger_sample_for_thread(
|
|
230
231
|
bool is_safe_to_allocate_objects
|
231
232
|
);
|
232
233
|
static VALUE _native_thread_list(VALUE self);
|
233
|
-
static
|
234
|
-
static
|
235
|
-
static void initialize_context(VALUE thread,
|
236
|
-
static void free_context(
|
234
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state);
|
235
|
+
static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state);
|
236
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state);
|
237
|
+
static void free_context(per_thread_context* thread_context);
|
237
238
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
238
|
-
static VALUE per_thread_context_st_table_as_ruby_hash(
|
239
|
+
static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state);
|
239
240
|
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash);
|
240
|
-
static VALUE stats_as_ruby_hash(
|
241
|
-
static VALUE gc_tracking_as_ruby_hash(
|
242
|
-
static void remove_context_for_dead_threads(
|
241
|
+
static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
|
242
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
243
|
+
static void remove_context_for_dead_threads(thread_context_collector_state *state);
|
243
244
|
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
244
245
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
245
246
|
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time);
|
246
|
-
static long cpu_time_now_ns(
|
247
|
+
static long cpu_time_now_ns(per_thread_context *thread_context);
|
247
248
|
static long thread_id_for(VALUE thread);
|
248
249
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
249
250
|
static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
|
250
251
|
static void trace_identifiers_for(
|
251
|
-
|
252
|
+
thread_context_collector_state *state,
|
252
253
|
VALUE thread,
|
253
|
-
|
254
|
+
trace_identifiers *trace_identifiers_result,
|
254
255
|
bool is_safe_to_allocate_objects
|
255
256
|
);
|
256
257
|
static bool should_collect_resource(VALUE root_span);
|
257
258
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
258
|
-
static VALUE thread_list(
|
259
|
+
static VALUE thread_list(thread_context_collector_state *state);
|
259
260
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
|
260
261
|
static VALUE _native_new_empty_thread(VALUE self);
|
261
262
|
static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
|
262
263
|
static void ddtrace_otel_trace_identifiers_for(
|
263
|
-
|
264
|
+
thread_context_collector_state *state,
|
264
265
|
VALUE *active_trace,
|
265
266
|
VALUE *root_span,
|
266
267
|
VALUE *numeric_span_id,
|
@@ -270,10 +271,10 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
270
271
|
);
|
271
272
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
|
272
273
|
static bool handle_gvl_waiting(
|
273
|
-
|
274
|
+
thread_context_collector_state *state,
|
274
275
|
VALUE thread_being_sampled,
|
275
276
|
VALUE stack_from_thread,
|
276
|
-
|
277
|
+
per_thread_context *thread_context,
|
277
278
|
sampling_buffer* sampling_buffer,
|
278
279
|
long current_cpu_time_ns
|
279
280
|
);
|
@@ -283,13 +284,14 @@ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
283
284
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
284
285
|
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns);
|
285
286
|
static void otel_without_ddtrace_trace_identifiers_for(
|
286
|
-
|
287
|
+
thread_context_collector_state *state,
|
287
288
|
VALUE thread,
|
288
|
-
|
289
|
+
trace_identifiers *trace_identifiers_result,
|
289
290
|
bool is_safe_to_allocate_objects
|
290
291
|
);
|
291
|
-
static
|
292
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
|
292
293
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
294
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
293
295
|
|
294
296
|
void collectors_thread_context_init(VALUE profiling_module) {
|
295
297
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
@@ -310,11 +312,11 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
310
312
|
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
311
313
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
312
314
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
313
|
-
rb_define_singleton_method(testing_module, "_native_sample", _native_sample,
|
315
|
+
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 3);
|
314
316
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
315
317
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
316
318
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
317
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc,
|
319
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 3);
|
318
320
|
rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
|
319
321
|
rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
|
320
322
|
rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
|
@@ -355,7 +357,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
355
357
|
gc_profiling_init();
|
356
358
|
}
|
357
359
|
|
358
|
-
// This structure is used to define a Ruby object that stores a pointer to a
|
360
|
+
// This structure is used to define a Ruby object that stores a pointer to a thread_context_collector_state
|
359
361
|
// See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
|
360
362
|
static const rb_data_type_t thread_context_collector_typed_data = {
|
361
363
|
.wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
|
@@ -371,7 +373,7 @@ static const rb_data_type_t thread_context_collector_typed_data = {
|
|
371
373
|
// This function is called by the Ruby GC to give us a chance to mark any Ruby objects that we're holding on to,
|
372
374
|
// so that they don't get garbage collected
|
373
375
|
static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
374
|
-
|
376
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
375
377
|
|
376
378
|
// Update this when modifying state struct
|
377
379
|
rb_gc_mark(state->recorder_instance);
|
@@ -382,7 +384,7 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
382
384
|
}
|
383
385
|
|
384
386
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
385
|
-
|
387
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
386
388
|
|
387
389
|
// Update this when modifying state struct
|
388
390
|
|
@@ -407,13 +409,13 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, DDTRACE_UNUSED
|
|
407
409
|
|
408
410
|
// Used to clear each of the per_thread_contexts inside the hash_map_per_thread_context
|
409
411
|
static int hash_map_per_thread_context_free_values(DDTRACE_UNUSED st_data_t _thread, st_data_t value_per_thread_context, DDTRACE_UNUSED st_data_t _argument) {
|
410
|
-
|
412
|
+
per_thread_context *thread_context = (per_thread_context*) value_per_thread_context;
|
411
413
|
free_context(thread_context);
|
412
414
|
return ST_CONTINUE;
|
413
415
|
}
|
414
416
|
|
415
417
|
static VALUE _native_new(VALUE klass) {
|
416
|
-
|
418
|
+
thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
|
417
419
|
|
418
420
|
// Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
|
419
421
|
// being leaked.
|
@@ -469,8 +471,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
469
471
|
ENFORCE_BOOLEAN(timeline_enabled);
|
470
472
|
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
471
473
|
|
472
|
-
|
473
|
-
TypedData_Get_Struct(self_instance,
|
474
|
+
thread_context_collector_state *state;
|
475
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
474
476
|
|
475
477
|
// Update this when modifying state struct
|
476
478
|
state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
@@ -504,40 +506,63 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
504
506
|
|
505
507
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
506
508
|
// It SHOULD NOT be used for other purposes.
|
507
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread) {
|
509
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception) {
|
510
|
+
ENFORCE_BOOLEAN(allow_exception);
|
511
|
+
|
508
512
|
if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
|
509
513
|
|
514
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
515
|
+
|
510
516
|
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE), profiler_overhead_stack_thread);
|
517
|
+
|
518
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
519
|
+
|
511
520
|
return Qtrue;
|
512
521
|
}
|
513
522
|
|
514
523
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
515
524
|
// It SHOULD NOT be used for other purposes.
|
516
525
|
static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
526
|
+
debug_enter_unsafe_context();
|
527
|
+
|
517
528
|
thread_context_collector_on_gc_start(collector_instance);
|
529
|
+
|
530
|
+
debug_leave_unsafe_context();
|
531
|
+
|
518
532
|
return Qtrue;
|
519
533
|
}
|
520
534
|
|
521
535
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
522
536
|
// It SHOULD NOT be used for other purposes.
|
523
537
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
538
|
+
debug_enter_unsafe_context();
|
539
|
+
|
524
540
|
(void) !thread_context_collector_on_gc_finish(collector_instance);
|
541
|
+
|
542
|
+
debug_leave_unsafe_context();
|
543
|
+
|
525
544
|
return Qtrue;
|
526
545
|
}
|
527
546
|
|
528
547
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
529
548
|
// It SHOULD NOT be used for other purposes.
|
530
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state) {
|
549
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception) {
|
531
550
|
ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
|
551
|
+
ENFORCE_BOOLEAN(allow_exception);
|
532
552
|
|
533
|
-
|
534
|
-
TypedData_Get_Struct(collector_instance,
|
553
|
+
thread_context_collector_state *state;
|
554
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
535
555
|
|
536
556
|
if (reset_monotonic_to_system_state == Qtrue) {
|
537
557
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
538
558
|
}
|
539
559
|
|
560
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
561
|
+
|
540
562
|
thread_context_collector_sample_after_gc(collector_instance);
|
563
|
+
|
564
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
565
|
+
|
541
566
|
return Qtrue;
|
542
567
|
}
|
543
568
|
|
@@ -552,11 +577,11 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
552
577
|
// The `profiler_overhead_stack_thread` is used to attribute the profiler overhead to a stack borrowed from a different thread
|
553
578
|
// (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
|
554
579
|
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
|
555
|
-
|
556
|
-
TypedData_Get_Struct(self_instance,
|
580
|
+
thread_context_collector_state *state;
|
581
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
557
582
|
|
558
583
|
VALUE current_thread = rb_thread_current();
|
559
|
-
|
584
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread, state);
|
560
585
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
561
586
|
|
562
587
|
VALUE threads = thread_list(state);
|
@@ -564,7 +589,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
564
589
|
const long thread_count = RARRAY_LEN(threads);
|
565
590
|
for (long i = 0; i < thread_count; i++) {
|
566
591
|
VALUE thread = RARRAY_AREF(threads, i);
|
567
|
-
|
592
|
+
per_thread_context *thread_context = get_or_create_context_for(thread, state);
|
568
593
|
|
569
594
|
// We account for cpu-time for the current thread in a different way -- we use the cpu-time at sampling start, to avoid
|
570
595
|
// blaming the time the profiler took on whatever's running on the thread right now
|
@@ -600,10 +625,10 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
600
625
|
}
|
601
626
|
|
602
627
|
static void update_metrics_and_sample(
|
603
|
-
|
628
|
+
thread_context_collector_state *state,
|
604
629
|
VALUE thread_being_sampled,
|
605
630
|
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
606
|
-
|
631
|
+
per_thread_context *thread_context,
|
607
632
|
sampling_buffer* sampling_buffer,
|
608
633
|
long current_cpu_time_ns,
|
609
634
|
long current_monotonic_wall_time_ns
|
@@ -671,12 +696,12 @@ static void update_metrics_and_sample(
|
|
671
696
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
672
697
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
673
698
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
674
|
-
|
699
|
+
thread_context_collector_state *state;
|
675
700
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
676
701
|
// This should never fail the the above check passes
|
677
|
-
TypedData_Get_Struct(self_instance,
|
702
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
678
703
|
|
679
|
-
|
704
|
+
per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
|
680
705
|
|
681
706
|
// If there was no previously-existing context for this thread, we won't allocate one (see safety). For now we just drop
|
682
707
|
// the GC sample, under the assumption that "a thread that is so new that we never sampled it even once before it triggers
|
@@ -704,12 +729,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
704
729
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
705
730
|
__attribute__((warn_unused_result))
|
706
731
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
707
|
-
|
732
|
+
thread_context_collector_state *state;
|
708
733
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
709
734
|
// This should never fail the the above check passes
|
710
|
-
TypedData_Get_Struct(self_instance,
|
735
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
711
736
|
|
712
|
-
|
737
|
+
per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
|
713
738
|
|
714
739
|
// If there was no previously-existing context for this thread, we won't allocate one (see safety). We keep a metric for
|
715
740
|
// how often this happens -- see on_gc_start.
|
@@ -782,8 +807,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
782
807
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
783
808
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
784
809
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
785
|
-
|
786
|
-
TypedData_Get_Struct(self_instance,
|
810
|
+
thread_context_collector_state *state;
|
811
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
787
812
|
|
788
813
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
789
814
|
rb_raise(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
|
@@ -832,10 +857,10 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
832
857
|
}
|
833
858
|
|
834
859
|
static void trigger_sample_for_thread(
|
835
|
-
|
860
|
+
thread_context_collector_state *state,
|
836
861
|
VALUE thread,
|
837
862
|
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
838
|
-
|
863
|
+
per_thread_context *thread_context,
|
839
864
|
sampling_buffer* sampling_buffer,
|
840
865
|
sample_values values,
|
841
866
|
long current_monotonic_wall_time_ns,
|
@@ -883,7 +908,7 @@ static void trigger_sample_for_thread(
|
|
883
908
|
};
|
884
909
|
}
|
885
910
|
|
886
|
-
|
911
|
+
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
887
912
|
trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
|
888
913
|
|
889
914
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
@@ -982,18 +1007,24 @@ static void trigger_sample_for_thread(
|
|
982
1007
|
// It SHOULD NOT be used for other purposes.
|
983
1008
|
static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
984
1009
|
VALUE result = rb_ary_new();
|
1010
|
+
|
1011
|
+
debug_enter_unsafe_context();
|
1012
|
+
|
985
1013
|
ddtrace_thread_list(result);
|
1014
|
+
|
1015
|
+
debug_leave_unsafe_context();
|
1016
|
+
|
986
1017
|
return result;
|
987
1018
|
}
|
988
1019
|
|
989
|
-
static
|
990
|
-
|
1020
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state) {
|
1021
|
+
per_thread_context* thread_context = NULL;
|
991
1022
|
st_data_t value_context = 0;
|
992
1023
|
|
993
1024
|
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
994
|
-
thread_context = (
|
1025
|
+
thread_context = (per_thread_context*) value_context;
|
995
1026
|
} else {
|
996
|
-
thread_context = ruby_xcalloc(1, sizeof(
|
1027
|
+
thread_context = ruby_xcalloc(1, sizeof(per_thread_context));
|
997
1028
|
initialize_context(thread, thread_context, state);
|
998
1029
|
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
999
1030
|
}
|
@@ -1001,12 +1032,12 @@ static struct per_thread_context *get_or_create_context_for(VALUE thread, struct
|
|
1001
1032
|
return thread_context;
|
1002
1033
|
}
|
1003
1034
|
|
1004
|
-
static
|
1005
|
-
|
1035
|
+
static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state) {
|
1036
|
+
per_thread_context* thread_context = NULL;
|
1006
1037
|
st_data_t value_context = 0;
|
1007
1038
|
|
1008
1039
|
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
1009
|
-
thread_context = (
|
1040
|
+
thread_context = (per_thread_context*) value_context;
|
1010
1041
|
}
|
1011
1042
|
|
1012
1043
|
return thread_context;
|
@@ -1033,7 +1064,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
1033
1064
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
1034
1065
|
}
|
1035
1066
|
|
1036
|
-
static void initialize_context(VALUE thread,
|
1067
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state) {
|
1037
1068
|
thread_context->sampling_buffer = sampling_buffer_new(state->max_frames, state->locations);
|
1038
1069
|
|
1039
1070
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
@@ -1090,14 +1121,14 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
1090
1121
|
#endif
|
1091
1122
|
}
|
1092
1123
|
|
1093
|
-
static void free_context(
|
1124
|
+
static void free_context(per_thread_context* thread_context) {
|
1094
1125
|
sampling_buffer_free(thread_context->sampling_buffer);
|
1095
1126
|
ruby_xfree(thread_context);
|
1096
1127
|
}
|
1097
1128
|
|
1098
1129
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1099
|
-
|
1100
|
-
TypedData_Get_Struct(collector_instance,
|
1130
|
+
thread_context_collector_state *state;
|
1131
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1101
1132
|
|
1102
1133
|
VALUE result = rb_str_new2(" (native state)");
|
1103
1134
|
|
@@ -1125,7 +1156,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
1125
1156
|
return result;
|
1126
1157
|
}
|
1127
1158
|
|
1128
|
-
static VALUE per_thread_context_st_table_as_ruby_hash(
|
1159
|
+
static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state) {
|
1129
1160
|
VALUE result = rb_hash_new();
|
1130
1161
|
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
1131
1162
|
return result;
|
@@ -1133,7 +1164,7 @@ static VALUE per_thread_context_st_table_as_ruby_hash(struct thread_context_coll
|
|
1133
1164
|
|
1134
1165
|
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
|
1135
1166
|
VALUE thread = (VALUE) key_thread;
|
1136
|
-
|
1167
|
+
per_thread_context *thread_context = (per_thread_context*) value_context;
|
1137
1168
|
VALUE result = (VALUE) result_hash;
|
1138
1169
|
VALUE context_as_hash = rb_hash_new();
|
1139
1170
|
rb_hash_aset(result, thread, context_as_hash);
|
@@ -1158,7 +1189,7 @@ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value
|
|
1158
1189
|
return ST_CONTINUE;
|
1159
1190
|
}
|
1160
1191
|
|
1161
|
-
static VALUE stats_as_ruby_hash(
|
1192
|
+
static VALUE stats_as_ruby_hash(thread_context_collector_state *state) {
|
1162
1193
|
// Update this when modifying state struct (stats inner struct)
|
1163
1194
|
VALUE stats_as_hash = rb_hash_new();
|
1164
1195
|
VALUE arguments[] = {
|
@@ -1169,7 +1200,7 @@ static VALUE stats_as_ruby_hash(struct thread_context_collector_state *state) {
|
|
1169
1200
|
return stats_as_hash;
|
1170
1201
|
}
|
1171
1202
|
|
1172
|
-
static VALUE gc_tracking_as_ruby_hash(
|
1203
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
1173
1204
|
// Update this when modifying state struct (gc_tracking inner struct)
|
1174
1205
|
VALUE result = rb_hash_new();
|
1175
1206
|
VALUE arguments[] = {
|
@@ -1182,13 +1213,13 @@ static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *sta
|
|
1182
1213
|
return result;
|
1183
1214
|
}
|
1184
1215
|
|
1185
|
-
static void remove_context_for_dead_threads(
|
1216
|
+
static void remove_context_for_dead_threads(thread_context_collector_state *state) {
|
1186
1217
|
st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
|
1187
1218
|
}
|
1188
1219
|
|
1189
1220
|
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
|
1190
1221
|
VALUE thread = (VALUE) key_thread;
|
1191
|
-
|
1222
|
+
per_thread_context* thread_context = (per_thread_context*) value_context;
|
1192
1223
|
|
1193
1224
|
if (is_thread_alive(thread)) return ST_CONTINUE;
|
1194
1225
|
|
@@ -1201,8 +1232,8 @@ static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context,
|
|
1201
1232
|
//
|
1202
1233
|
// Returns the whole contents of the per_thread_context structs being tracked.
|
1203
1234
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1204
|
-
|
1205
|
-
TypedData_Get_Struct(collector_instance,
|
1235
|
+
thread_context_collector_state *state;
|
1236
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1206
1237
|
|
1207
1238
|
return per_thread_context_st_table_as_ruby_hash(state);
|
1208
1239
|
}
|
@@ -1247,7 +1278,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
1247
1278
|
}
|
1248
1279
|
|
1249
1280
|
// Safety: This function is assumed never to raise exceptions by callers
|
1250
|
-
static long cpu_time_now_ns(
|
1281
|
+
static long cpu_time_now_ns(per_thread_context *thread_context) {
|
1251
1282
|
thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
|
1252
1283
|
|
1253
1284
|
if (!cpu_time.valid) {
|
@@ -1285,8 +1316,8 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
1285
1316
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
1286
1317
|
// It SHOULD NOT be used for other purposes.
|
1287
1318
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1288
|
-
|
1289
|
-
TypedData_Get_Struct(collector_instance,
|
1319
|
+
thread_context_collector_state *state;
|
1320
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1290
1321
|
|
1291
1322
|
return stats_as_ruby_hash(state);
|
1292
1323
|
}
|
@@ -1294,17 +1325,17 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance)
|
|
1294
1325
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
1295
1326
|
// It SHOULD NOT be used for other purposes.
|
1296
1327
|
static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1297
|
-
|
1298
|
-
TypedData_Get_Struct(collector_instance,
|
1328
|
+
thread_context_collector_state *state;
|
1329
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1299
1330
|
|
1300
1331
|
return gc_tracking_as_ruby_hash(state);
|
1301
1332
|
}
|
1302
1333
|
|
1303
1334
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
1304
1335
|
static void trace_identifiers_for(
|
1305
|
-
|
1336
|
+
thread_context_collector_state *state,
|
1306
1337
|
VALUE thread,
|
1307
|
-
|
1338
|
+
trace_identifiers *trace_identifiers_result,
|
1308
1339
|
bool is_safe_to_allocate_objects
|
1309
1340
|
) {
|
1310
1341
|
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
@@ -1384,8 +1415,8 @@ static bool should_collect_resource(VALUE root_span) {
|
|
1384
1415
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
1385
1416
|
// trigger samples at the same time.
|
1386
1417
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
1387
|
-
|
1388
|
-
TypedData_Get_Struct(collector_instance,
|
1418
|
+
thread_context_collector_state *state;
|
1419
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1389
1420
|
|
1390
1421
|
// Release all context memory before clearing the existing context
|
1391
1422
|
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
@@ -1399,7 +1430,7 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector
|
|
1399
1430
|
return Qtrue;
|
1400
1431
|
}
|
1401
1432
|
|
1402
|
-
static VALUE thread_list(
|
1433
|
+
static VALUE thread_list(thread_context_collector_state *state) {
|
1403
1434
|
VALUE result = state->thread_list_buffer;
|
1404
1435
|
rb_ary_clear(result);
|
1405
1436
|
ddtrace_thread_list(result);
|
@@ -1407,8 +1438,8 @@ static VALUE thread_list(struct thread_context_collector_state *state) {
|
|
1407
1438
|
}
|
1408
1439
|
|
1409
1440
|
void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
|
1410
|
-
|
1411
|
-
TypedData_Get_Struct(self_instance,
|
1441
|
+
thread_context_collector_state *state;
|
1442
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1412
1443
|
|
1413
1444
|
VALUE current_thread = rb_thread_current();
|
1414
1445
|
|
@@ -1419,11 +1450,8 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1419
1450
|
|
1420
1451
|
// Since this is stack allocated, be careful about moving it
|
1421
1452
|
ddog_CharSlice class_name;
|
1422
|
-
ddog_CharSlice *optional_class_name = NULL;
|
1423
1453
|
char imemo_type[100];
|
1424
1454
|
|
1425
|
-
optional_class_name = &class_name;
|
1426
|
-
|
1427
1455
|
if (
|
1428
1456
|
type == RUBY_T_OBJECT ||
|
1429
1457
|
type == RUBY_T_CLASS ||
|
@@ -1479,9 +1507,9 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1479
1507
|
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
1480
1508
|
}
|
1481
1509
|
|
1482
|
-
track_object(state->recorder_instance, new_object, sample_weight,
|
1510
|
+
track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
1483
1511
|
|
1484
|
-
|
1512
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
1485
1513
|
|
1486
1514
|
trigger_sample_for_thread(
|
1487
1515
|
state,
|
@@ -1492,7 +1520,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1492
1520
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
1493
1521
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
1494
1522
|
&ruby_vm_type,
|
1495
|
-
|
1523
|
+
&class_name,
|
1496
1524
|
/* is_gvl_waiting_state: */ false,
|
1497
1525
|
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
1498
1526
|
);
|
@@ -1501,7 +1529,12 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1501
1529
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
1502
1530
|
// It SHOULD NOT be used for other purposes.
|
1503
1531
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
1532
|
+
debug_enter_unsafe_context();
|
1533
|
+
|
1504
1534
|
thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
|
1535
|
+
|
1536
|
+
debug_leave_unsafe_context();
|
1537
|
+
|
1505
1538
|
return Qtrue;
|
1506
1539
|
}
|
1507
1540
|
|
@@ -1549,7 +1582,7 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
|
1549
1582
|
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
1550
1583
|
}
|
1551
1584
|
|
1552
|
-
static VALUE get_otel_current_span_key(
|
1585
|
+
static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
|
1553
1586
|
if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
|
1554
1587
|
if (!is_safe_to_allocate_objects) {
|
1555
1588
|
// Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
|
@@ -1572,7 +1605,7 @@ static VALUE get_otel_current_span_key(struct thread_context_collector_state *st
|
|
1572
1605
|
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
1573
1606
|
// differently, and this codepath handles it.
|
1574
1607
|
static void ddtrace_otel_trace_identifiers_for(
|
1575
|
-
|
1608
|
+
thread_context_collector_state *state,
|
1576
1609
|
VALUE *active_trace,
|
1577
1610
|
VALUE *root_span,
|
1578
1611
|
VALUE *numeric_span_id,
|
@@ -1597,7 +1630,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
1597
1630
|
// trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
|
1598
1631
|
// list. The local root span is going to be the trace/span we find at the end of this linked list.
|
1599
1632
|
while (otel_values != Qnil) {
|
1600
|
-
VALUE otel_span =
|
1633
|
+
VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
|
1601
1634
|
if (otel_span == Qnil) break;
|
1602
1635
|
VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
|
1603
1636
|
if (next_trace == Qnil) break;
|
@@ -1616,8 +1649,8 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
1616
1649
|
}
|
1617
1650
|
|
1618
1651
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
1619
|
-
|
1620
|
-
TypedData_Get_Struct(self_instance,
|
1652
|
+
thread_context_collector_state *state;
|
1653
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1621
1654
|
|
1622
1655
|
ddog_prof_Label labels[] = {
|
1623
1656
|
// Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
|
@@ -1640,7 +1673,12 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta
|
|
1640
1673
|
}
|
1641
1674
|
|
1642
1675
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
|
1676
|
+
debug_enter_unsafe_context();
|
1677
|
+
|
1643
1678
|
thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
|
1679
|
+
|
1680
|
+
debug_leave_unsafe_context();
|
1681
|
+
|
1644
1682
|
return Qtrue;
|
1645
1683
|
}
|
1646
1684
|
|
@@ -1666,9 +1704,9 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
1666
1704
|
// root span id.
|
1667
1705
|
// This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
|
1668
1706
|
static void otel_without_ddtrace_trace_identifiers_for(
|
1669
|
-
|
1707
|
+
thread_context_collector_state *state,
|
1670
1708
|
VALUE thread,
|
1671
|
-
|
1709
|
+
trace_identifiers *trace_identifiers_result,
|
1672
1710
|
bool is_safe_to_allocate_objects
|
1673
1711
|
) {
|
1674
1712
|
VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
@@ -1682,14 +1720,14 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
1682
1720
|
int active_context_index = RARRAY_LEN(context_storage) - 1;
|
1683
1721
|
if (active_context_index < 0) return;
|
1684
1722
|
|
1685
|
-
|
1723
|
+
otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
1686
1724
|
if (active_span.span == Qnil) return;
|
1687
1725
|
|
1688
|
-
|
1726
|
+
otel_span local_root_span = active_span;
|
1689
1727
|
|
1690
1728
|
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
1691
1729
|
for (int i = active_context_index - 1; i >= 0; i--) {
|
1692
|
-
|
1730
|
+
otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
1693
1731
|
if (checking_span.span == Qnil) return;
|
1694
1732
|
|
1695
1733
|
if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
|
@@ -1709,7 +1747,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
1709
1747
|
|
1710
1748
|
VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
|
1711
1749
|
// We filter out spans that don't have `kind: :server`
|
1712
|
-
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || SYM2ID(root_span_type) != server_id) return;
|
1750
|
+
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || !RB_STATIC_SYM_P(root_span_type) || SYM2ID(root_span_type) != server_id) return;
|
1713
1751
|
|
1714
1752
|
VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
|
1715
1753
|
if (!RB_TYPE_P(trace_resource, T_STRING)) return;
|
@@ -1717,8 +1755,8 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
1717
1755
|
trace_identifiers_result->trace_endpoint = trace_resource;
|
1718
1756
|
}
|
1719
1757
|
|
1720
|
-
static
|
1721
|
-
|
1758
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
|
1759
|
+
otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
|
1722
1760
|
|
1723
1761
|
if (otel_context == Qnil) return failed;
|
1724
1762
|
|
@@ -1726,7 +1764,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
1726
1764
|
if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
|
1727
1765
|
|
1728
1766
|
// If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
|
1729
|
-
VALUE span =
|
1767
|
+
VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
|
1730
1768
|
if (span == Qnil) return failed;
|
1731
1769
|
|
1732
1770
|
// If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
|
@@ -1737,7 +1775,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
1737
1775
|
VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
|
1738
1776
|
if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
|
1739
1777
|
|
1740
|
-
return (
|
1778
|
+
return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
|
1741
1779
|
}
|
1742
1780
|
|
1743
1781
|
// Otel span ids are represented as a big-endian 8-byte string
|
@@ -1839,8 +1877,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1839
1877
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
1840
1878
|
// exists to enable testing.
|
1841
1879
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
1842
|
-
|
1843
|
-
TypedData_Get_Struct(self_instance,
|
1880
|
+
thread_context_collector_state *state;
|
1881
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1844
1882
|
|
1845
1883
|
if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
|
1846
1884
|
|
@@ -1854,7 +1892,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1854
1892
|
return Qfalse;
|
1855
1893
|
}
|
1856
1894
|
|
1857
|
-
|
1895
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
1858
1896
|
|
1859
1897
|
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
1860
1898
|
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
@@ -1880,10 +1918,10 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1880
1918
|
// need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
|
1881
1919
|
__attribute__((warn_unused_result))
|
1882
1920
|
static bool handle_gvl_waiting(
|
1883
|
-
|
1921
|
+
thread_context_collector_state *state,
|
1884
1922
|
VALUE thread_being_sampled,
|
1885
1923
|
VALUE stack_from_thread,
|
1886
|
-
|
1924
|
+
per_thread_context *thread_context,
|
1887
1925
|
sampling_buffer* sampling_buffer,
|
1888
1926
|
long current_cpu_time_ns
|
1889
1927
|
) {
|
@@ -1979,40 +2017,62 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1979
2017
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1980
2018
|
ENFORCE_THREAD(thread);
|
1981
2019
|
|
2020
|
+
debug_enter_unsafe_context();
|
2021
|
+
|
1982
2022
|
thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
|
2023
|
+
|
2024
|
+
debug_leave_unsafe_context();
|
2025
|
+
|
1983
2026
|
return Qnil;
|
1984
2027
|
}
|
1985
2028
|
|
1986
2029
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1987
2030
|
ENFORCE_THREAD(thread);
|
1988
2031
|
|
2032
|
+
debug_enter_unsafe_context();
|
2033
|
+
|
1989
2034
|
intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
|
2035
|
+
|
2036
|
+
debug_leave_unsafe_context();
|
2037
|
+
|
1990
2038
|
return LONG2NUM(gvl_waiting_at);
|
1991
2039
|
}
|
1992
2040
|
|
1993
2041
|
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1994
2042
|
ENFORCE_THREAD(thread);
|
1995
2043
|
|
1996
|
-
|
2044
|
+
debug_enter_unsafe_context();
|
2045
|
+
|
2046
|
+
VALUE result = thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
2047
|
+
|
2048
|
+
debug_leave_unsafe_context();
|
2049
|
+
|
2050
|
+
return result;
|
1997
2051
|
}
|
1998
2052
|
|
1999
2053
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
2000
2054
|
ENFORCE_THREAD(thread);
|
2001
2055
|
|
2002
|
-
|
2056
|
+
debug_enter_unsafe_context();
|
2057
|
+
|
2058
|
+
VALUE result = thread_context_collector_sample_after_gvl_running(
|
2003
2059
|
collector_instance,
|
2004
2060
|
thread,
|
2005
2061
|
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
2006
2062
|
);
|
2063
|
+
|
2064
|
+
debug_leave_unsafe_context();
|
2065
|
+
|
2066
|
+
return result;
|
2007
2067
|
}
|
2008
2068
|
|
2009
2069
|
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
|
2010
2070
|
ENFORCE_THREAD(thread);
|
2011
2071
|
|
2012
|
-
|
2013
|
-
TypedData_Get_Struct(collector_instance,
|
2072
|
+
thread_context_collector_state *state;
|
2073
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
2014
2074
|
|
2015
|
-
|
2075
|
+
per_thread_context *thread_context = get_context_for(thread, state);
|
2016
2076
|
if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
2017
2077
|
|
2018
2078
|
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
@@ -2022,11 +2082,45 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
2022
2082
|
|
2023
2083
|
#else
|
2024
2084
|
static bool handle_gvl_waiting(
|
2025
|
-
DDTRACE_UNUSED
|
2085
|
+
DDTRACE_UNUSED thread_context_collector_state *state,
|
2026
2086
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
2027
2087
|
DDTRACE_UNUSED VALUE stack_from_thread,
|
2028
|
-
DDTRACE_UNUSED
|
2088
|
+
DDTRACE_UNUSED per_thread_context *thread_context,
|
2029
2089
|
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
2030
2090
|
DDTRACE_UNUSED long current_cpu_time_ns
|
2031
2091
|
) { return false; }
|
2032
2092
|
#endif // NO_GVL_INSTRUMENTATION
|
2093
|
+
|
2094
|
+
#define MAX_SAFE_LOOKUP_SIZE 16
|
2095
|
+
|
2096
|
+
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
2097
|
+
|
2098
|
+
static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
|
2099
|
+
safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
|
2100
|
+
|
2101
|
+
if (key == state->lookup_key) {
|
2102
|
+
state->result = value;
|
2103
|
+
return ST_STOP;
|
2104
|
+
}
|
2105
|
+
|
2106
|
+
return ST_CONTINUE;
|
2107
|
+
}
|
2108
|
+
|
2109
|
+
// This method exists because we need to look up a hash during sampling, but we don't want to invoke any
|
2110
|
+
// Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
|
2111
|
+
// which we want to avoid.
|
2112
|
+
// Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
|
2113
|
+
//
|
2114
|
+
// To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
|
2115
|
+
// Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
|
2116
|
+
// depending on the internal storage order of the hash we may or not find the key, and instead we always
|
2117
|
+
// enforce the size.
|
2118
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
|
2119
|
+
if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
|
2120
|
+
|
2121
|
+
safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
|
2122
|
+
|
2123
|
+
rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
|
2124
|
+
|
2125
|
+
return state.result;
|
2126
|
+
}
|