datadog 2.36.0 → 2.37.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 +46 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +6 -3
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -98
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +2 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +11 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
- data/ext/libdatadog_api/di.c +76 -0
- data/ext/libdatadog_api/extconf.rb +6 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +6 -4
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -4
- data/lib/datadog/appsec/configuration.rb +6 -0
- data/lib/datadog/appsec/context.rb +4 -2
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +4 -17
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +2 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/ext.rb +1 -0
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +9 -3
- data/lib/datadog/appsec/remote.rb +9 -3
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/core/configuration/components.rb +82 -2
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/settings.rb +0 -3
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
- data/lib/datadog/di/base.rb +3 -1
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +110 -36
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +381 -165
- data/lib/datadog/di/probe.rb +14 -0
- data/lib/datadog/di/probe_file_loader.rb +8 -3
- data/lib/datadog/di/probe_manager.rb +67 -9
- data/lib/datadog/di/probe_notification_builder.rb +4 -1
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +175 -14
- data/lib/datadog/di/serializer.rb +12 -5
- data/lib/datadog/di/transport/input.rb +8 -4
- data/lib/datadog/di.rb +63 -0
- data/lib/datadog/kit/appsec/events/v2.rb +60 -2
- data/lib/datadog/open_feature/component.rb +29 -6
- data/lib/datadog/open_feature/configuration.rb +8 -0
- data/lib/datadog/open_feature/ext.rb +2 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
- data/lib/datadog/open_feature/native_evaluator.rb +22 -0
- data/lib/datadog/open_feature/provider.rb +59 -27
- data/lib/datadog/open_feature/transport.rb +55 -1
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/symbol_database/component.rb +164 -47
- data/lib/datadog/symbol_database/configuration.rb +12 -4
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +124 -87
- data/lib/datadog/symbol_database/file_hash.rb +3 -1
- data/lib/datadog/symbol_database/remote.rb +8 -3
- data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
- data/lib/datadog/symbol_database/uploader.rb +9 -2
- data/lib/datadog/symbol_database.rb +22 -0
- data/lib/datadog/tracing/distributed/baggage.rb +2 -1
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/remote.rb +34 -3
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +1 -0
- metadata +19 -6
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
|
+
#include <ruby/debug.h>
|
|
2
3
|
|
|
3
4
|
#include "datadog_ruby_common.h"
|
|
4
5
|
#include "collectors_thread_context.h"
|
|
@@ -108,6 +109,15 @@ static ID server_id; // id of :server in Ruby
|
|
|
108
109
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
109
110
|
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
110
111
|
|
|
112
|
+
// This is mutable and gets set last-writer-wins style whenever a new `ThreadContext` is created.
|
|
113
|
+
//
|
|
114
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
115
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
116
|
+
static uint16_t latest_max_frames = 400;
|
|
117
|
+
|
|
118
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
119
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
120
|
+
|
|
111
121
|
|
|
112
122
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
|
113
123
|
typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
|
|
@@ -160,6 +170,7 @@ typedef struct {
|
|
|
160
170
|
// How many per-thread samples were skipped because the thread has been continuously suspended
|
|
161
171
|
// (no GVL) since its previous sample, so its Ruby stack cannot have changed.
|
|
162
172
|
unsigned int inactive_thread_samples_skipped;
|
|
173
|
+
unsigned int profiler_thread_samples_skipped;
|
|
163
174
|
} stats;
|
|
164
175
|
|
|
165
176
|
struct {
|
|
@@ -171,7 +182,20 @@ typedef struct {
|
|
|
171
182
|
} gc_tracking;
|
|
172
183
|
} thread_context_collector_state;
|
|
173
184
|
|
|
174
|
-
// Tracks per-thread state
|
|
185
|
+
// Tracks per-thread state.
|
|
186
|
+
// This state is global and lives forever on the Ruby Thread (until the Thread is GC'd).
|
|
187
|
+
// The state is created early on for all threads on the main Ractor
|
|
188
|
+
// (enabling a TracePoint only enables it for the current Ractor).
|
|
189
|
+
// The state is either created when the Thread starts running (via the RUBY_EVENT_THREAD_BEGIN TracePoint),
|
|
190
|
+
// or the first time we create a ThreadContext by iterating Thread.list.
|
|
191
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
192
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
193
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
194
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
195
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
196
|
+
// * etc
|
|
197
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
198
|
+
// however this only matters for a very short period when a thread starts.
|
|
175
199
|
struct per_thread_context {
|
|
176
200
|
sampling_buffer sampling_buffer;
|
|
177
201
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
@@ -179,8 +203,8 @@ struct per_thread_context {
|
|
|
179
203
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
180
204
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
181
205
|
thread_cpu_time_id thread_cpu_time_id;
|
|
182
|
-
long cpu_time_at_previous_sample_ns;
|
|
183
|
-
long wall_time_at_previous_sample_ns;
|
|
206
|
+
long cpu_time_at_previous_sample_ns;
|
|
207
|
+
long wall_time_at_previous_sample_ns;
|
|
184
208
|
|
|
185
209
|
// There are 3 possible states for the GVL (per thread), and 3 transitions for which we receive GVL events:
|
|
186
210
|
// Thread holds the GVL
|
|
@@ -233,6 +257,14 @@ struct per_thread_context {
|
|
|
233
257
|
// but this is deemed worth it for this optimization. In any case we don't know exactly
|
|
234
258
|
// at what time a thread was doing CPU work (unless it's on CPU 100% of the time).
|
|
235
259
|
bool was_skipped_at_last_sample;
|
|
260
|
+
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
261
|
+
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
262
|
+
// reporting period during the on_serialize flush.
|
|
263
|
+
//
|
|
264
|
+
// These threads are always in native code so their stacks aren't interesting;
|
|
265
|
+
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
266
|
+
// things using a mix of Ruby and native code, so that one isn't considered internal.
|
|
267
|
+
bool is_profiler_internal_thread;
|
|
236
268
|
|
|
237
269
|
struct {
|
|
238
270
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -270,7 +302,6 @@ static void update_metrics_and_sample(
|
|
|
270
302
|
thread_context_collector_state *state,
|
|
271
303
|
VALUE thread_being_sampled,
|
|
272
304
|
per_thread_context *thread_context,
|
|
273
|
-
sampling_buffer* sampling_buffer,
|
|
274
305
|
long current_cpu_time_ns,
|
|
275
306
|
long current_monotonic_wall_time_ns,
|
|
276
307
|
bool force_sample
|
|
@@ -279,7 +310,6 @@ static void trigger_sample_for_thread(
|
|
|
279
310
|
thread_context_collector_state *state,
|
|
280
311
|
VALUE thread_being_sampled,
|
|
281
312
|
per_thread_context *thread_context,
|
|
282
|
-
sampling_buffer* sampling_buffer,
|
|
283
313
|
sample_values values,
|
|
284
314
|
long current_monotonic_wall_time_ns,
|
|
285
315
|
ddog_CharSlice *ruby_vm_type,
|
|
@@ -288,8 +318,9 @@ static void trigger_sample_for_thread(
|
|
|
288
318
|
bool is_safe_to_allocate_objects
|
|
289
319
|
);
|
|
290
320
|
static VALUE _native_thread_list(VALUE self);
|
|
291
|
-
static
|
|
292
|
-
static
|
|
321
|
+
static void check_frozen_thread(VALUE thread);
|
|
322
|
+
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
323
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
293
324
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
294
325
|
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
295
326
|
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
@@ -326,7 +357,6 @@ static bool handle_gvl_waiting(
|
|
|
326
357
|
thread_context_collector_state *state,
|
|
327
358
|
VALUE thread_being_sampled,
|
|
328
359
|
per_thread_context *thread_context,
|
|
329
|
-
sampling_buffer* sampling_buffer,
|
|
330
360
|
long current_cpu_time_ns
|
|
331
361
|
);
|
|
332
362
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
@@ -335,8 +365,8 @@ static bool handle_gvl_waiting(
|
|
|
335
365
|
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
336
366
|
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
337
367
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception);
|
|
338
|
-
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
339
368
|
#endif
|
|
369
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
340
370
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
341
371
|
thread_context_collector_state *state,
|
|
342
372
|
VALUE thread,
|
|
@@ -349,11 +379,15 @@ static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE k
|
|
|
349
379
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
350
380
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
351
381
|
static VALUE _native_clear_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
352
|
-
static
|
|
382
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
383
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
384
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
385
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
353
386
|
|
|
354
387
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
355
388
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
356
389
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
390
|
+
|
|
357
391
|
// Hosts methods used for testing the native code using RSpec
|
|
358
392
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
359
393
|
|
|
@@ -384,14 +418,16 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
384
418
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
385
419
|
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
386
420
|
rb_define_singleton_method(testing_module, "_native_clear_per_thread_context_for", _native_clear_per_thread_context_for, 1);
|
|
421
|
+
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
422
|
+
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
387
423
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
388
424
|
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
389
425
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
390
426
|
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
391
427
|
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
392
428
|
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 3);
|
|
393
|
-
rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 2);
|
|
394
429
|
#endif
|
|
430
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 2);
|
|
395
431
|
|
|
396
432
|
at_active_span_id = rb_intern_const("@active_span");
|
|
397
433
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -417,6 +453,8 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
417
453
|
// This will raise if Ruby already ran out of thread-local keys
|
|
418
454
|
per_thread_context_tls_init();
|
|
419
455
|
|
|
456
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
457
|
+
|
|
420
458
|
gc_profiling_init();
|
|
421
459
|
}
|
|
422
460
|
|
|
@@ -485,13 +523,20 @@ static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
|
485
523
|
free(ctx);
|
|
486
524
|
}
|
|
487
525
|
|
|
488
|
-
static VALUE _native_clear_per_thread_context_for(
|
|
526
|
+
static VALUE _native_clear_per_thread_context_for(VALUE self, VALUE thread) {
|
|
527
|
+
_native_remove_per_thread_context_for(self, thread);
|
|
528
|
+
get_or_create_context_for(thread);
|
|
529
|
+
return Qnil;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
533
|
+
// This simulates a thread that starts running before the RUBY_EVENT_THREAD_BEGIN tracepoint fires.
|
|
534
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
535
|
+
check_frozen_thread(thread);
|
|
489
536
|
per_thread_context *ctx = get_per_thread_context(thread);
|
|
490
537
|
if (ctx != NULL) {
|
|
491
538
|
set_per_thread_context(thread, NULL);
|
|
492
|
-
|
|
493
|
-
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
494
|
-
}
|
|
539
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
495
540
|
}
|
|
496
541
|
return Qnil;
|
|
497
542
|
}
|
|
@@ -554,12 +599,15 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
554
599
|
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
555
600
|
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
556
601
|
|
|
602
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
603
|
+
latest_max_frames = max_frame_int;
|
|
604
|
+
|
|
557
605
|
thread_context_collector_state *state;
|
|
558
606
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
559
607
|
|
|
560
608
|
// Update this when modifying state struct
|
|
561
|
-
state->locations.len =
|
|
562
|
-
state->locations.ptr = ruby_xcalloc(
|
|
609
|
+
state->locations.len = max_frame_int;
|
|
610
|
+
state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
|
|
563
611
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
564
612
|
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
565
613
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
@@ -585,6 +633,19 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
585
633
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
586
634
|
}
|
|
587
635
|
|
|
636
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
637
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
638
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
639
|
+
|
|
640
|
+
VALUE thread_list = rb_ary_new();
|
|
641
|
+
ddtrace_thread_list(thread_list);
|
|
642
|
+
long thread_count = RARRAY_LEN(thread_list);
|
|
643
|
+
for (long i = 0; i < thread_count; i++) {
|
|
644
|
+
get_or_create_context_for(RARRAY_AREF(thread_list, i));
|
|
645
|
+
}
|
|
646
|
+
RB_GC_GUARD(thread_list);
|
|
647
|
+
}
|
|
648
|
+
|
|
588
649
|
return Qtrue;
|
|
589
650
|
}
|
|
590
651
|
|
|
@@ -698,7 +759,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
698
759
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
699
760
|
|
|
700
761
|
VALUE current_thread = rb_thread_current();
|
|
701
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread
|
|
762
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
702
763
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
|
703
764
|
|
|
704
765
|
VALUE threads = thread_list(state);
|
|
@@ -706,7 +767,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
706
767
|
const long thread_count = RARRAY_LEN(threads);
|
|
707
768
|
for (long i = 0; i < thread_count; i++) {
|
|
708
769
|
VALUE thread = RARRAY_AREF(threads, i);
|
|
709
|
-
per_thread_context *thread_context = get_or_create_context_for(thread
|
|
770
|
+
per_thread_context *thread_context = get_or_create_context_for(thread);
|
|
710
771
|
|
|
711
772
|
// We account for cpu-time for the current thread in a different way: we use the cpu-time at sampling start,
|
|
712
773
|
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
@@ -717,29 +778,36 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
717
778
|
state,
|
|
718
779
|
thread,
|
|
719
780
|
thread_context,
|
|
720
|
-
&thread_context->sampling_buffer,
|
|
721
781
|
current_cpu_time_ns,
|
|
722
782
|
current_monotonic_wall_time_ns,
|
|
723
783
|
false);
|
|
724
784
|
}
|
|
725
785
|
|
|
726
786
|
state->stats.sample_count++;
|
|
727
|
-
|
|
787
|
+
|
|
788
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
789
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
790
|
+
// (both are under the same datadog group in the flamegraph).
|
|
791
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
792
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
793
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
794
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
795
|
+
record_sampling_overhead(state, current_thread_context);
|
|
796
|
+
}
|
|
728
797
|
}
|
|
729
798
|
|
|
730
799
|
static void update_metrics_and_sample(
|
|
731
800
|
thread_context_collector_state *state,
|
|
732
801
|
VALUE thread_being_sampled,
|
|
733
802
|
per_thread_context *thread_context,
|
|
734
|
-
sampling_buffer* sampling_buffer,
|
|
735
803
|
long current_cpu_time_ns,
|
|
736
804
|
long current_monotonic_wall_time_ns,
|
|
737
|
-
bool
|
|
805
|
+
bool force_sample
|
|
738
806
|
) {
|
|
739
807
|
bool is_gvl_waiting_state =
|
|
740
|
-
handle_gvl_waiting(state, thread_being_sampled, thread_context,
|
|
808
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
741
809
|
|
|
742
|
-
if (skip_sample(state, thread_context, is_gvl_waiting_state,
|
|
810
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
743
811
|
|
|
744
812
|
// Don't assign/update cpu during "Waiting for GVL"
|
|
745
813
|
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_time_since_previous_sample(
|
|
@@ -775,7 +843,6 @@ static void update_metrics_and_sample(
|
|
|
775
843
|
state,
|
|
776
844
|
thread_being_sampled,
|
|
777
845
|
thread_context,
|
|
778
|
-
sampling_buffer,
|
|
779
846
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
780
847
|
current_monotonic_wall_time_ns,
|
|
781
848
|
NULL,
|
|
@@ -785,7 +852,12 @@ static void update_metrics_and_sample(
|
|
|
785
852
|
);
|
|
786
853
|
}
|
|
787
854
|
|
|
788
|
-
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool
|
|
855
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
856
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
857
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
|
|
789
861
|
// Racy read but harmless, can only cause an extra sample
|
|
790
862
|
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
791
863
|
|
|
@@ -797,7 +869,7 @@ static bool skip_sample(thread_context_collector_state *state, per_thread_contex
|
|
|
797
869
|
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
798
870
|
// TODO: we could probably also skip while "Waiting for GVL"
|
|
799
871
|
if (!is_gvl_waiting_state &&
|
|
800
|
-
!
|
|
872
|
+
!force_sample &&
|
|
801
873
|
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
802
874
|
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
803
875
|
state->stats.inactive_thread_samples_skipped++;
|
|
@@ -829,10 +901,8 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
829
901
|
|
|
830
902
|
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
831
903
|
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
// GC" is a rare enough case that we can just ignore it.
|
|
835
|
-
// We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
|
|
904
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
905
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
836
906
|
if (thread_context == NULL) {
|
|
837
907
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
838
908
|
return;
|
|
@@ -862,8 +932,7 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
862
932
|
|
|
863
933
|
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
864
934
|
|
|
865
|
-
//
|
|
866
|
-
// how often this happens -- see on_gc_start.
|
|
935
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
867
936
|
if (thread_context == NULL) return false;
|
|
868
937
|
|
|
869
938
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
@@ -982,7 +1051,6 @@ static void trigger_sample_for_thread(
|
|
|
982
1051
|
thread_context_collector_state *state,
|
|
983
1052
|
VALUE thread_being_sampled,
|
|
984
1053
|
per_thread_context *thread_context,
|
|
985
|
-
sampling_buffer* sampling_buffer,
|
|
986
1054
|
sample_values values,
|
|
987
1055
|
long current_monotonic_wall_time_ns,
|
|
988
1056
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
@@ -1104,7 +1172,7 @@ static void trigger_sample_for_thread(
|
|
|
1104
1172
|
|
|
1105
1173
|
sample_thread(
|
|
1106
1174
|
thread_being_sampled,
|
|
1107
|
-
sampling_buffer,
|
|
1175
|
+
&thread_context->sampling_buffer,
|
|
1108
1176
|
state->locations,
|
|
1109
1177
|
state->recorder_instance,
|
|
1110
1178
|
values,
|
|
@@ -1133,17 +1201,22 @@ static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
|
1133
1201
|
return result;
|
|
1134
1202
|
}
|
|
1135
1203
|
|
|
1204
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1205
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1206
|
+
raise_error(rb_eFrozenError, "Cannot setup profiler state for Thread %"PRIsVALUE" because it is frozen. Please avoid freezing Thread instances and/or report the issue to dd-trace-rb", thread);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// See the docs on struct per_thread_context.
|
|
1136
1211
|
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1137
|
-
static per_thread_context *get_or_create_context_for(VALUE thread
|
|
1212
|
+
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1138
1213
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1139
1214
|
if (thread_context != NULL) return thread_context;
|
|
1140
1215
|
|
|
1141
|
-
|
|
1142
|
-
raise_error(rb_eFrozenError, "Cannot setup profiler state for Thread %"PRIsVALUE" because it is frozen. Please avoid freezing Thread instances and/or report the issue to dd-trace-rb", thread);
|
|
1143
|
-
}
|
|
1216
|
+
check_frozen_thread(thread);
|
|
1144
1217
|
|
|
1145
1218
|
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1146
|
-
initialize_context(thread, thread_context
|
|
1219
|
+
initialize_context(thread, thread_context);
|
|
1147
1220
|
|
|
1148
1221
|
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1149
1222
|
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
@@ -1152,6 +1225,14 @@ static per_thread_context *get_or_create_context_for(VALUE thread, thread_contex
|
|
|
1152
1225
|
return thread_context;
|
|
1153
1226
|
}
|
|
1154
1227
|
|
|
1228
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1229
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1230
|
+
|
|
1231
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1232
|
+
ENFORCE_THREAD(thread);
|
|
1233
|
+
get_or_create_context_for(thread);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1155
1236
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
1156
1237
|
|
|
1157
1238
|
// The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
|
|
@@ -1173,8 +1254,12 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1173
1254
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1174
1255
|
}
|
|
1175
1256
|
|
|
1176
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context
|
|
1177
|
-
|
|
1257
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1258
|
+
// We always create per_thread_context's with latest_max_frames because
|
|
1259
|
+
// 1) we don't always have access to the ThreadContext object (e.g. in a TracePoint).
|
|
1260
|
+
// 2) the per_thread_context's are global and so might not match the ThreadContext#max_frames anyway.
|
|
1261
|
+
// This is fine because sample_thread() handles when they don't match and resizes as needed.
|
|
1262
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1178
1263
|
|
|
1179
1264
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1180
1265
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1193,7 +1278,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1193
1278
|
} else {
|
|
1194
1279
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1195
1280
|
}
|
|
1196
|
-
} else if (thread !=
|
|
1281
|
+
} else if (thread != rb_thread_main()) {
|
|
1197
1282
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1198
1283
|
// NOTE: In the future, I wonder if we could take the pointer to the native function, and try to see if there's a native
|
|
1199
1284
|
// symbol attached to it.
|
|
@@ -1207,9 +1292,8 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1207
1292
|
|
|
1208
1293
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1209
1294
|
|
|
1210
|
-
|
|
1211
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1212
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1295
|
+
thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1296
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1213
1297
|
|
|
1214
1298
|
// These will only be used during a GC operation
|
|
1215
1299
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
@@ -1271,6 +1355,7 @@ static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context)
|
|
|
1271
1355
|
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1272
1356
|
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1273
1357
|
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1358
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1274
1359
|
};
|
|
1275
1360
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1276
1361
|
|
|
@@ -1284,6 +1369,7 @@ static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE has
|
|
|
1284
1369
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1285
1370
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1286
1371
|
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1372
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1287
1373
|
};
|
|
1288
1374
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1289
1375
|
return hash;
|
|
@@ -1634,11 +1720,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
|
|
|
1634
1720
|
state,
|
|
1635
1721
|
current_thread,
|
|
1636
1722
|
thread_context,
|
|
1637
|
-
&thread_context->sampling_buffer,
|
|
1638
1723
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1639
1724
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1640
1725
|
&ruby_vm_type,
|
|
1641
|
-
|
|
1726
|
+
&class_name,
|
|
1642
1727
|
/* is_gvl_waiting_state: */ false,
|
|
1643
1728
|
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1644
1729
|
);
|
|
@@ -1649,9 +1734,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
|
|
|
1649
1734
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1650
1735
|
// It SHOULD NOT be used for other purposes.
|
|
1651
1736
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1737
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1738
|
+
if (thread_context == NULL) {
|
|
1739
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1740
|
+
}
|
|
1655
1741
|
|
|
1656
1742
|
debug_enter_unsafe_context();
|
|
1657
1743
|
|
|
@@ -1972,41 +2058,81 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
1972
2058
|
state->stats = (struct stats) {};
|
|
1973
2059
|
}
|
|
1974
2060
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2061
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2062
|
+
ctx->is_profiler_internal_thread = true;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2066
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2067
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2071
|
+
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2072
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2073
|
+
return Qnil;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2077
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
|
|
2078
|
+
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2079
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2080
|
+
// threads are already dead and absent from thread_list.
|
|
2081
|
+
void thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2082
|
+
thread_context_collector_state *state;
|
|
2083
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2084
|
+
|
|
2085
|
+
VALUE current_thread = rb_thread_current();
|
|
2086
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2087
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2088
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
1978
2089
|
}
|
|
1979
2090
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
// time recorded in this reporting period. Without this, a thread that sleeps across the whole
|
|
1983
|
-
// period would not be reported at all.
|
|
1984
|
-
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
1985
|
-
thread_context_collector_state *state;
|
|
1986
|
-
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2091
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2092
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1987
2093
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2094
|
+
update_metrics_and_sample(
|
|
2095
|
+
state,
|
|
2096
|
+
current_thread,
|
|
2097
|
+
thread_context,
|
|
2098
|
+
current_cpu_time_ns,
|
|
2099
|
+
current_monotonic_wall_time_ns,
|
|
2100
|
+
true);
|
|
2101
|
+
}
|
|
1991
2102
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2103
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2104
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2105
|
+
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2106
|
+
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2107
|
+
thread_context_collector_state *state;
|
|
2108
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2109
|
+
|
|
2110
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2111
|
+
VALUE threads = thread_list(state);
|
|
2112
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2113
|
+
|
|
2114
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2115
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
2116
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2117
|
+
|
|
2118
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2119
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2120
|
+
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2121
|
+
update_metrics_and_sample(
|
|
2122
|
+
state,
|
|
2123
|
+
thread,
|
|
2124
|
+
thread_context,
|
|
2125
|
+
current_cpu_time_ns,
|
|
2126
|
+
current_monotonic_wall_time_ns,
|
|
2127
|
+
true);
|
|
2008
2128
|
}
|
|
2009
2129
|
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
2133
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2134
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2135
|
+
}
|
|
2010
2136
|
|
|
2011
2137
|
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2012
2138
|
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
@@ -2103,7 +2229,7 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2103
2229
|
thread_context_collector_state *state;
|
|
2104
2230
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2105
2231
|
|
|
2106
|
-
per_thread_context *thread_context = get_or_create_context_for(current_thread
|
|
2232
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2107
2233
|
|
|
2108
2234
|
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2109
2235
|
|
|
@@ -2126,7 +2252,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2126
2252
|
state,
|
|
2127
2253
|
current_thread,
|
|
2128
2254
|
thread_context,
|
|
2129
|
-
&thread_context->sampling_buffer,
|
|
2130
2255
|
cpu_time_for_thread,
|
|
2131
2256
|
current_monotonic_wall_time_ns,
|
|
2132
2257
|
false);
|
|
@@ -2141,7 +2266,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2141
2266
|
thread_context_collector_state *state,
|
|
2142
2267
|
VALUE thread_being_sampled,
|
|
2143
2268
|
per_thread_context *thread_context,
|
|
2144
|
-
sampling_buffer* sampling_buffer,
|
|
2145
2269
|
long current_cpu_time_ns
|
|
2146
2270
|
) {
|
|
2147
2271
|
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
@@ -2219,7 +2343,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2219
2343
|
state,
|
|
2220
2344
|
thread_being_sampled,
|
|
2221
2345
|
thread_context,
|
|
2222
|
-
sampling_buffer,
|
|
2223
2346
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
|
2224
2347
|
gvl_waiting_started_wall_time_ns,
|
|
2225
2348
|
NULL,
|
|
@@ -2306,29 +2429,27 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2306
2429
|
return result;
|
|
2307
2430
|
}
|
|
2308
2431
|
|
|
2309
|
-
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2310
|
-
ENFORCE_THREAD(thread);
|
|
2311
|
-
|
|
2312
|
-
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2313
|
-
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2314
|
-
|
|
2315
|
-
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2316
|
-
|
|
2317
|
-
return Qtrue;
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
2432
|
#else
|
|
2321
2433
|
static bool handle_gvl_waiting(
|
|
2322
2434
|
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2323
2435
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2324
2436
|
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2325
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2326
2437
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2327
2438
|
) { return false; }
|
|
2328
2439
|
|
|
2329
|
-
void thread_context_collector_on_serialize(DDTRACE_UNUSED VALUE self_instance) { }
|
|
2330
2440
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2331
2441
|
|
|
2442
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2443
|
+
ENFORCE_THREAD(thread);
|
|
2444
|
+
|
|
2445
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2446
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2447
|
+
|
|
2448
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2449
|
+
|
|
2450
|
+
return Qtrue;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2332
2453
|
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2333
2454
|
|
|
2334
2455
|
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|