datadog 2.36.0 → 2.38.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 +74 -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 +29 -18
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
- data/ext/datadog_profiling_native_extension/collectors_stack.c +19 -24
- data/ext/datadog_profiling_native_extension/collectors_stack.h +0 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +348 -213
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +11 -3
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
- data/ext/datadog_profiling_native_extension/time_helpers.h +12 -7
- 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/request.rb +5 -0
- 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 +7 -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/agentless_settings_resolver.rb +2 -2
- 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 +4 -7
- data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/knuth_sampler.rb +3 -1
- data/lib/datadog/core/metrics/client.rb +1 -1
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
- data/lib/datadog/core/transport/http/builder.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/time.rb +5 -1
- data/lib/datadog/di/base.rb +3 -1
- data/lib/datadog/di/boot.rb +3 -0
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +112 -38
- data/lib/datadog/di/configuration.rb +6 -0
- data/lib/datadog/di/context.rb +9 -3
- data/lib/datadog/di/el/compiler.rb +1 -1
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +412 -165
- data/lib/datadog/di/probe.rb +61 -2
- data/lib/datadog/di/probe_builder.rb +83 -1
- 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 +52 -8
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +176 -15
- data/lib/datadog/di/serializer.rb +34 -15
- 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 +6 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +9 -1
- data/lib/datadog/profiling/component.rb +17 -0
- 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/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +12 -0
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +2 -3
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -12
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -2
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
- 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/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +1 -0
- metadata +25 -8
|
@@ -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"
|
|
@@ -42,11 +43,8 @@
|
|
|
42
43
|
// When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
|
|
43
44
|
// context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
|
|
44
45
|
//
|
|
45
|
-
// While `cpu_time_at_gc_start_ns` is set,
|
|
46
|
-
//
|
|
47
|
-
// Wall-time accounting is not affected by this (e.g. we still record 60 seconds every 60 seconds).
|
|
48
|
-
//
|
|
49
|
-
// (Regular samples can still account for the cpu-time between the previous sample and the start of GC.)
|
|
46
|
+
// While `cpu_time_at_gc_start_ns` is set, we don't expect the thread to be sampled: the VM is doing GC
|
|
47
|
+
// on the thread holding the GVL so no other samples can/will be triggered until GC finishes.
|
|
50
48
|
//
|
|
51
49
|
// When `thread_context_collector_on_gc_finish` gets called, the cpu-time and wall-time spent during GC gets recorded
|
|
52
50
|
// into the global gc_tracking structure, and further samples are not affected. (The `cpu_time_at_previous_sample_ns`
|
|
@@ -75,8 +73,6 @@
|
|
|
75
73
|
|
|
76
74
|
#define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
|
|
77
75
|
#define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
|
|
78
|
-
#define IS_WALL_TIME true
|
|
79
|
-
#define IS_CPU_TIME false
|
|
80
76
|
#define MISSING_TRACER_CONTEXT_KEY 0
|
|
81
77
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
|
82
78
|
#define GVL_SUSPENDED ((uint64_t)1)
|
|
@@ -108,6 +104,20 @@ static ID server_id; // id of :server in Ruby
|
|
|
108
104
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
109
105
|
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
110
106
|
|
|
107
|
+
// This is mutable and gets set last-writer-wins style by
|
|
108
|
+
// `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
|
|
109
|
+
// profiling is starting or restating.
|
|
110
|
+
//
|
|
111
|
+
// Note: We must be careful to not change this value while the profiler is still running,
|
|
112
|
+
// otherwise new threads can get the new value and cause profiling to stop with an
|
|
113
|
+
// exception due to the mismatched size.
|
|
114
|
+
//
|
|
115
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
116
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
117
|
+
static uint16_t latest_max_frames = 400;
|
|
118
|
+
|
|
119
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
120
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
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,21 @@ 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 via `thread_context_collector_reset_all_per_thread_contexts` when the profiler starts.
|
|
191
|
+
//
|
|
192
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
193
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
194
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
195
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
196
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
197
|
+
// * etc
|
|
198
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
199
|
+
// however this only matters for a very short period when a thread starts.
|
|
175
200
|
struct per_thread_context {
|
|
176
201
|
sampling_buffer sampling_buffer;
|
|
177
202
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
@@ -179,8 +204,8 @@ struct per_thread_context {
|
|
|
179
204
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
180
205
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
181
206
|
thread_cpu_time_id thread_cpu_time_id;
|
|
182
|
-
long cpu_time_at_previous_sample_ns;
|
|
183
|
-
long wall_time_at_previous_sample_ns;
|
|
207
|
+
long cpu_time_at_previous_sample_ns;
|
|
208
|
+
long wall_time_at_previous_sample_ns;
|
|
184
209
|
|
|
185
210
|
// There are 3 possible states for the GVL (per thread), and 3 transitions for which we receive GVL events:
|
|
186
211
|
// Thread holds the GVL
|
|
@@ -233,6 +258,14 @@ struct per_thread_context {
|
|
|
233
258
|
// but this is deemed worth it for this optimization. In any case we don't know exactly
|
|
234
259
|
// at what time a thread was doing CPU work (unless it's on CPU 100% of the time).
|
|
235
260
|
bool was_skipped_at_last_sample;
|
|
261
|
+
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
262
|
+
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
263
|
+
// reporting period during the on_serialize flush.
|
|
264
|
+
//
|
|
265
|
+
// These threads are always in native code so their stacks aren't interesting;
|
|
266
|
+
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
267
|
+
// things using a mix of Ruby and native code, so that one isn't considered internal.
|
|
268
|
+
bool is_profiler_internal_thread;
|
|
236
269
|
|
|
237
270
|
struct {
|
|
238
271
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -270,7 +303,6 @@ static void update_metrics_and_sample(
|
|
|
270
303
|
thread_context_collector_state *state,
|
|
271
304
|
VALUE thread_being_sampled,
|
|
272
305
|
per_thread_context *thread_context,
|
|
273
|
-
sampling_buffer* sampling_buffer,
|
|
274
306
|
long current_cpu_time_ns,
|
|
275
307
|
long current_monotonic_wall_time_ns,
|
|
276
308
|
bool force_sample
|
|
@@ -279,7 +311,6 @@ static void trigger_sample_for_thread(
|
|
|
279
311
|
thread_context_collector_state *state,
|
|
280
312
|
VALUE thread_being_sampled,
|
|
281
313
|
per_thread_context *thread_context,
|
|
282
|
-
sampling_buffer* sampling_buffer,
|
|
283
314
|
sample_values values,
|
|
284
315
|
long current_monotonic_wall_time_ns,
|
|
285
316
|
ddog_CharSlice *ruby_vm_type,
|
|
@@ -288,14 +319,16 @@ static void trigger_sample_for_thread(
|
|
|
288
319
|
bool is_safe_to_allocate_objects
|
|
289
320
|
);
|
|
290
321
|
static VALUE _native_thread_list(VALUE self);
|
|
291
|
-
static
|
|
292
|
-
static
|
|
322
|
+
static void check_frozen_thread(VALUE thread);
|
|
323
|
+
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
324
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
293
325
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
294
326
|
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
295
327
|
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
296
328
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
|
297
329
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
|
298
|
-
static long
|
|
330
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns);
|
|
331
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns);
|
|
299
332
|
static long cpu_time_now_ns(per_thread_context *thread_context);
|
|
300
333
|
static long thread_id_for(VALUE thread);
|
|
301
334
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
|
@@ -326,17 +359,16 @@ static bool handle_gvl_waiting(
|
|
|
326
359
|
thread_context_collector_state *state,
|
|
327
360
|
VALUE thread_being_sampled,
|
|
328
361
|
per_thread_context *thread_context,
|
|
329
|
-
sampling_buffer* sampling_buffer,
|
|
330
362
|
long current_cpu_time_ns
|
|
331
363
|
);
|
|
364
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
365
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
332
366
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
333
|
-
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
334
367
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
335
368
|
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
336
|
-
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
337
369
|
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
370
|
#endif
|
|
371
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
340
372
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
341
373
|
thread_context_collector_state *state,
|
|
342
374
|
VALUE thread,
|
|
@@ -348,12 +380,16 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
|
348
380
|
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
349
381
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
350
382
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
351
|
-
static VALUE
|
|
352
|
-
static
|
|
383
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
384
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
385
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
386
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
387
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
353
388
|
|
|
354
389
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
355
390
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
356
391
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
392
|
+
|
|
357
393
|
// Hosts methods used for testing the native code using RSpec
|
|
358
394
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
359
395
|
|
|
@@ -383,15 +419,17 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
383
419
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
384
420
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
385
421
|
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
386
|
-
rb_define_singleton_method(testing_module, "
|
|
422
|
+
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
423
|
+
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
424
|
+
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
425
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
426
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
387
427
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
388
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
389
428
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
390
429
|
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
391
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
392
430
|
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
431
|
#endif
|
|
432
|
+
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
433
|
|
|
396
434
|
at_active_span_id = rb_intern_const("@active_span");
|
|
397
435
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -417,6 +455,8 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
417
455
|
// This will raise if Ruby already ran out of thread-local keys
|
|
418
456
|
per_thread_context_tls_init();
|
|
419
457
|
|
|
458
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
459
|
+
|
|
420
460
|
gc_profiling_init();
|
|
421
461
|
}
|
|
422
462
|
|
|
@@ -485,17 +525,24 @@ static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
|
485
525
|
free(ctx);
|
|
486
526
|
}
|
|
487
527
|
|
|
488
|
-
|
|
528
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
529
|
+
// This simulates situations where we observe a thread running before our RUBY_EVENT_THREAD_BEGIN tracepoint fires,
|
|
530
|
+
// so that we can test our `get_per_thread_context(...) => null` code paths.
|
|
531
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
532
|
+
check_frozen_thread(thread);
|
|
489
533
|
per_thread_context *ctx = get_per_thread_context(thread);
|
|
490
534
|
if (ctx != NULL) {
|
|
491
535
|
set_per_thread_context(thread, NULL);
|
|
492
|
-
|
|
493
|
-
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
494
|
-
}
|
|
536
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
495
537
|
}
|
|
496
538
|
return Qnil;
|
|
497
539
|
}
|
|
498
540
|
|
|
541
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
542
|
+
thread_context_collector_reset_all_per_thread_contexts(collector_instance);
|
|
543
|
+
return Qnil;
|
|
544
|
+
}
|
|
545
|
+
|
|
499
546
|
static VALUE _native_new(VALUE klass) {
|
|
500
547
|
thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
|
|
501
548
|
|
|
@@ -554,12 +601,14 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
554
601
|
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
555
602
|
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
556
603
|
|
|
604
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
605
|
+
|
|
557
606
|
thread_context_collector_state *state;
|
|
558
607
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
559
608
|
|
|
560
609
|
// Update this when modifying state struct
|
|
561
|
-
state->locations.len =
|
|
562
|
-
state->locations.ptr = ruby_xcalloc(
|
|
610
|
+
state->locations.len = max_frame_int;
|
|
611
|
+
state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
|
|
563
612
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
564
613
|
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
565
614
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
@@ -585,6 +634,11 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
585
634
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
586
635
|
}
|
|
587
636
|
|
|
637
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
638
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
639
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
640
|
+
}
|
|
641
|
+
|
|
588
642
|
return Qtrue;
|
|
589
643
|
}
|
|
590
644
|
|
|
@@ -643,17 +697,8 @@ static void record_sampling_overhead(thread_context_collector_state *state, per_
|
|
|
643
697
|
long wall_time_after_sampling = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
644
698
|
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
645
699
|
|
|
646
|
-
long overhead_cpu_time_ns =
|
|
647
|
-
|
|
648
|
-
cpu_time_after_sampling,
|
|
649
|
-
current_thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
650
|
-
IS_CPU_TIME);
|
|
651
|
-
|
|
652
|
-
long overhead_wall_time_ns = update_time_since_previous_sample(
|
|
653
|
-
¤t_thread_context->wall_time_at_previous_sample_ns,
|
|
654
|
-
wall_time_after_sampling,
|
|
655
|
-
INVALID_TIME,
|
|
656
|
-
IS_WALL_TIME);
|
|
700
|
+
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
701
|
+
long overhead_wall_time_ns = update_wall_time_since_previous_sample(current_thread_context, wall_time_after_sampling);
|
|
657
702
|
|
|
658
703
|
ddog_prof_Label overhead_labels[] = {
|
|
659
704
|
{.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("0"), .num = 0},
|
|
@@ -698,7 +743,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
698
743
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
699
744
|
|
|
700
745
|
VALUE current_thread = rb_thread_current();
|
|
701
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread
|
|
746
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
702
747
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
|
703
748
|
|
|
704
749
|
VALUE threads = thread_list(state);
|
|
@@ -706,7 +751,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
706
751
|
const long thread_count = RARRAY_LEN(threads);
|
|
707
752
|
for (long i = 0; i < thread_count; i++) {
|
|
708
753
|
VALUE thread = RARRAY_AREF(threads, i);
|
|
709
|
-
per_thread_context *thread_context = get_or_create_context_for(thread
|
|
754
|
+
per_thread_context *thread_context = get_or_create_context_for(thread);
|
|
710
755
|
|
|
711
756
|
// We account for cpu-time for the current thread in a different way: we use the cpu-time at sampling start,
|
|
712
757
|
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
@@ -717,44 +762,41 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
717
762
|
state,
|
|
718
763
|
thread,
|
|
719
764
|
thread_context,
|
|
720
|
-
&thread_context->sampling_buffer,
|
|
721
765
|
current_cpu_time_ns,
|
|
722
766
|
current_monotonic_wall_time_ns,
|
|
723
767
|
false);
|
|
724
768
|
}
|
|
725
769
|
|
|
726
770
|
state->stats.sample_count++;
|
|
727
|
-
|
|
771
|
+
|
|
772
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
773
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
774
|
+
// (both are under the same datadog group in the flamegraph).
|
|
775
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
776
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
777
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
778
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
779
|
+
record_sampling_overhead(state, current_thread_context);
|
|
780
|
+
}
|
|
728
781
|
}
|
|
729
782
|
|
|
730
783
|
static void update_metrics_and_sample(
|
|
731
784
|
thread_context_collector_state *state,
|
|
732
785
|
VALUE thread_being_sampled,
|
|
733
786
|
per_thread_context *thread_context,
|
|
734
|
-
sampling_buffer* sampling_buffer,
|
|
735
787
|
long current_cpu_time_ns,
|
|
736
788
|
long current_monotonic_wall_time_ns,
|
|
737
|
-
bool
|
|
789
|
+
bool force_sample
|
|
738
790
|
) {
|
|
739
791
|
bool is_gvl_waiting_state =
|
|
740
|
-
handle_gvl_waiting(state, thread_being_sampled, thread_context,
|
|
792
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
741
793
|
|
|
742
|
-
if (skip_sample(state, thread_context, is_gvl_waiting_state,
|
|
794
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
743
795
|
|
|
744
796
|
// Don't assign/update cpu during "Waiting for GVL"
|
|
745
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 :
|
|
746
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
747
|
-
current_cpu_time_ns,
|
|
748
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
749
|
-
IS_CPU_TIME
|
|
750
|
-
);
|
|
797
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
751
798
|
|
|
752
|
-
long wall_time_elapsed_ns =
|
|
753
|
-
&thread_context->wall_time_at_previous_sample_ns,
|
|
754
|
-
current_monotonic_wall_time_ns,
|
|
755
|
-
INVALID_TIME,
|
|
756
|
-
IS_WALL_TIME
|
|
757
|
-
);
|
|
799
|
+
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
758
800
|
|
|
759
801
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
760
802
|
//
|
|
@@ -775,7 +817,6 @@ static void update_metrics_and_sample(
|
|
|
775
817
|
state,
|
|
776
818
|
thread_being_sampled,
|
|
777
819
|
thread_context,
|
|
778
|
-
sampling_buffer,
|
|
779
820
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
780
821
|
current_monotonic_wall_time_ns,
|
|
781
822
|
NULL,
|
|
@@ -785,7 +826,12 @@ static void update_metrics_and_sample(
|
|
|
785
826
|
);
|
|
786
827
|
}
|
|
787
828
|
|
|
788
|
-
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool
|
|
829
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
830
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
831
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
|
|
789
835
|
// Racy read but harmless, can only cause an extra sample
|
|
790
836
|
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
791
837
|
|
|
@@ -797,7 +843,7 @@ static bool skip_sample(thread_context_collector_state *state, per_thread_contex
|
|
|
797
843
|
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
798
844
|
// TODO: we could probably also skip while "Waiting for GVL"
|
|
799
845
|
if (!is_gvl_waiting_state &&
|
|
800
|
-
!
|
|
846
|
+
!force_sample &&
|
|
801
847
|
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
802
848
|
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
803
849
|
state->stats.inactive_thread_samples_skipped++;
|
|
@@ -829,10 +875,8 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
829
875
|
|
|
830
876
|
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
831
877
|
|
|
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)!
|
|
878
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
879
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
836
880
|
if (thread_context == NULL) {
|
|
837
881
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
838
882
|
return;
|
|
@@ -862,8 +906,7 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
862
906
|
|
|
863
907
|
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
864
908
|
|
|
865
|
-
//
|
|
866
|
-
// how often this happens -- see on_gc_start.
|
|
909
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
867
910
|
if (thread_context == NULL) return false;
|
|
868
911
|
|
|
869
912
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
@@ -904,9 +947,9 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
904
947
|
state->gc_tracking.accumulated_wall_time_ns += gc_wall_time_elapsed_ns;
|
|
905
948
|
state->gc_tracking.wall_time_at_previous_gc_ns = wall_time_at_finish_ns;
|
|
906
949
|
|
|
907
|
-
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample
|
|
908
|
-
// We don't
|
|
909
|
-
//
|
|
950
|
+
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
|
|
951
|
+
// We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
|
|
952
|
+
// progress -- time always goes forward regardless of the thread making progress on what it wanted.
|
|
910
953
|
if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
|
|
911
954
|
thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
|
|
912
955
|
}
|
|
@@ -982,7 +1025,6 @@ static void trigger_sample_for_thread(
|
|
|
982
1025
|
thread_context_collector_state *state,
|
|
983
1026
|
VALUE thread_being_sampled,
|
|
984
1027
|
per_thread_context *thread_context,
|
|
985
|
-
sampling_buffer* sampling_buffer,
|
|
986
1028
|
sample_values values,
|
|
987
1029
|
long current_monotonic_wall_time_ns,
|
|
988
1030
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
@@ -1104,7 +1146,7 @@ static void trigger_sample_for_thread(
|
|
|
1104
1146
|
|
|
1105
1147
|
sample_thread(
|
|
1106
1148
|
thread_being_sampled,
|
|
1107
|
-
sampling_buffer,
|
|
1149
|
+
&thread_context->sampling_buffer,
|
|
1108
1150
|
state->locations,
|
|
1109
1151
|
state->recorder_instance,
|
|
1110
1152
|
values,
|
|
@@ -1133,17 +1175,22 @@ static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
|
1133
1175
|
return result;
|
|
1134
1176
|
}
|
|
1135
1177
|
|
|
1178
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1179
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1180
|
+
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);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// See the docs on struct per_thread_context.
|
|
1136
1185
|
// 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
|
|
1186
|
+
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1138
1187
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1139
1188
|
if (thread_context != NULL) return thread_context;
|
|
1140
1189
|
|
|
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
|
-
}
|
|
1190
|
+
check_frozen_thread(thread);
|
|
1144
1191
|
|
|
1145
1192
|
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
|
|
1193
|
+
initialize_context(thread, thread_context);
|
|
1147
1194
|
|
|
1148
1195
|
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1149
1196
|
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
@@ -1152,6 +1199,14 @@ static per_thread_context *get_or_create_context_for(VALUE thread, thread_contex
|
|
|
1152
1199
|
return thread_context;
|
|
1153
1200
|
}
|
|
1154
1201
|
|
|
1202
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1203
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1204
|
+
|
|
1205
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1206
|
+
ENFORCE_THREAD(thread);
|
|
1207
|
+
get_or_create_context_for(thread);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1155
1210
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
1156
1211
|
|
|
1157
1212
|
// The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
|
|
@@ -1173,8 +1228,11 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1173
1228
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1174
1229
|
}
|
|
1175
1230
|
|
|
1176
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context
|
|
1177
|
-
|
|
1231
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1232
|
+
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1233
|
+
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1234
|
+
// so we expect to always see here the latest correct value to be used.
|
|
1235
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1178
1236
|
|
|
1179
1237
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1180
1238
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1193,7 +1251,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1193
1251
|
} else {
|
|
1194
1252
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1195
1253
|
}
|
|
1196
|
-
} else if (thread !=
|
|
1254
|
+
} else if (thread != rb_thread_main()) {
|
|
1197
1255
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1198
1256
|
// 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
1257
|
// symbol attached to it.
|
|
@@ -1207,9 +1265,8 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1207
1265
|
|
|
1208
1266
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1209
1267
|
|
|
1210
|
-
|
|
1211
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1212
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1268
|
+
thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1269
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1213
1270
|
|
|
1214
1271
|
// These will only be used during a GC operation
|
|
1215
1272
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
@@ -1219,6 +1276,45 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1219
1276
|
thread_context->gvl_state_change_count = 0;
|
|
1220
1277
|
}
|
|
1221
1278
|
|
|
1279
|
+
// This MUST be called before profiling starts, so that a new profiler session starts from a fresh state and never
|
|
1280
|
+
// observes or includes any leftover stale state from a previous session.
|
|
1281
|
+
// Such a call MUST happen while the CpuAndWallTimeWorker is stopped (e.g. no tracepoints active, no signals
|
|
1282
|
+
// triggering samples, no gvl hooks, etc).
|
|
1283
|
+
//
|
|
1284
|
+
// It updates the global `latest_max_frames` from the given (latest) ThreadContext and (re)creates every per-thread
|
|
1285
|
+
// context's sampling buffer sized accordingly, so the buffers always match the collector that's about to start
|
|
1286
|
+
// sampling -- even if a previous session used a different max_frames.
|
|
1287
|
+
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance) {
|
|
1288
|
+
thread_context_collector_state *state;
|
|
1289
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1290
|
+
|
|
1291
|
+
// Update global max frames to be used when allocating sampling buffers
|
|
1292
|
+
if (state->locations.len == 0) {
|
|
1293
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected locations.len == 0. Is this ThreadContext not initialized?");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
latest_max_frames = state->locations.len;
|
|
1297
|
+
|
|
1298
|
+
VALUE threads = thread_list(state);
|
|
1299
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1300
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1301
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1302
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1303
|
+
if (thread_context != NULL) {
|
|
1304
|
+
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
1305
|
+
|
|
1306
|
+
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1307
|
+
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1308
|
+
initialize_context(thread, thread_context);
|
|
1309
|
+
|
|
1310
|
+
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1311
|
+
} else {
|
|
1312
|
+
// If thread didn't have a context, let's trigger its creation
|
|
1313
|
+
get_or_create_context_for(thread);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1222
1318
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1223
1319
|
thread_context_collector_state *state;
|
|
1224
1320
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -1271,6 +1367,7 @@ static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context)
|
|
|
1271
1367
|
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1272
1368
|
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1273
1369
|
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1370
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1274
1371
|
};
|
|
1275
1372
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1276
1373
|
|
|
@@ -1284,6 +1381,7 @@ static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE has
|
|
|
1284
1381
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1285
1382
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1286
1383
|
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1384
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1287
1385
|
};
|
|
1288
1386
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1289
1387
|
return hash;
|
|
@@ -1323,48 +1421,59 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
|
|
|
1323
1421
|
return result;
|
|
1324
1422
|
}
|
|
1325
1423
|
|
|
1326
|
-
|
|
1327
|
-
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) {
|
|
1424
|
+
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1328
1425
|
// If we didn't have a time for the previous sample, we use the current one
|
|
1329
1426
|
if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
|
|
1330
1427
|
|
|
1331
|
-
// We don't
|
|
1332
|
-
//
|
|
1333
|
-
|
|
1334
|
-
|
|
1428
|
+
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1429
|
+
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1430
|
+
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
1431
|
+
raise_error(
|
|
1432
|
+
rb_eRuntimeError,
|
|
1433
|
+
"BUG: Unexpected sample during GC (thread_id=%s, gc_tracking.cpu_time_at_start_ns=%ld, "
|
|
1434
|
+
"gc_tracking.wall_time_at_start_ns=%ld, monotonic_wall_time_now_ns=%ld)",
|
|
1435
|
+
thread_context->thread_id,
|
|
1436
|
+
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1437
|
+
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1438
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1335
1441
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1442
|
+
long elapsed_time_ns = current_time_ns - *time_at_previous_sample_ns; // Capture all time since previous sample
|
|
1443
|
+
*time_at_previous_sample_ns = current_time_ns;
|
|
1338
1444
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
} else {
|
|
1342
|
-
elapsed_time_ns = gc_start_time_ns - *time_at_previous_sample_ns; // Capture time between previous sample and start of GC only
|
|
1343
|
-
}
|
|
1445
|
+
return elapsed_time_ns;
|
|
1446
|
+
}
|
|
1344
1447
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1448
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1449
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1450
|
+
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1451
|
+
current_cpu_time_ns,
|
|
1452
|
+
thread_context
|
|
1453
|
+
);
|
|
1351
1454
|
|
|
1455
|
+
// We don't expect cpu-time to go backwards, so let's flag this as a bug
|
|
1352
1456
|
if (elapsed_time_ns < 0) {
|
|
1353
|
-
|
|
1354
|
-
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1355
|
-
// was a bug.
|
|
1356
|
-
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1357
|
-
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1358
|
-
elapsed_time_ns = 0;
|
|
1359
|
-
} else {
|
|
1360
|
-
// We don't expect non-wall time to go backwards, so let's flag this as a bug
|
|
1361
|
-
raise_error(rb_eRuntimeError, "BUG: Unexpected negative elapsed_time_ns between samples");
|
|
1362
|
-
}
|
|
1457
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1363
1458
|
}
|
|
1364
1459
|
|
|
1365
1460
|
return elapsed_time_ns;
|
|
1366
1461
|
}
|
|
1367
1462
|
|
|
1463
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns) {
|
|
1464
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1465
|
+
&thread_context->wall_time_at_previous_sample_ns,
|
|
1466
|
+
current_wall_time_ns,
|
|
1467
|
+
thread_context
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1471
|
+
// was a bug.
|
|
1472
|
+
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1473
|
+
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1474
|
+
return long_max_of(elapsed_time_ns, 0);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1368
1477
|
// Safety: This function is assumed never to raise exceptions by callers
|
|
1369
1478
|
static long cpu_time_now_ns(per_thread_context *thread_context) {
|
|
1370
1479
|
thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
|
|
@@ -1521,8 +1630,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector
|
|
|
1521
1630
|
|
|
1522
1631
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
|
1523
1632
|
|
|
1524
|
-
//
|
|
1525
|
-
|
|
1633
|
+
// No need to clean-up the per-thread context because the CpuAndWallTimeWorker always cleans
|
|
1634
|
+
// it up unconditionally on every start/restart and that includes after a fork.
|
|
1526
1635
|
|
|
1527
1636
|
rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
|
|
1528
1637
|
|
|
@@ -1634,11 +1743,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
|
|
|
1634
1743
|
state,
|
|
1635
1744
|
current_thread,
|
|
1636
1745
|
thread_context,
|
|
1637
|
-
&thread_context->sampling_buffer,
|
|
1638
1746
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1639
1747
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1640
1748
|
&ruby_vm_type,
|
|
1641
|
-
|
|
1749
|
+
&class_name,
|
|
1642
1750
|
/* is_gvl_waiting_state: */ false,
|
|
1643
1751
|
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1644
1752
|
);
|
|
@@ -1649,9 +1757,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
|
|
|
1649
1757
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1650
1758
|
// It SHOULD NOT be used for other purposes.
|
|
1651
1759
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1760
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1761
|
+
if (thread_context == NULL) {
|
|
1762
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1763
|
+
}
|
|
1655
1764
|
|
|
1656
1765
|
debug_enter_unsafe_context();
|
|
1657
1766
|
|
|
@@ -1972,49 +2081,115 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
1972
2081
|
state->stats = (struct stats) {};
|
|
1973
2082
|
}
|
|
1974
2083
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2084
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2085
|
+
ctx->is_profiler_internal_thread = true;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2089
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2090
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2094
|
+
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2095
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2096
|
+
return Qnil;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2100
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
|
|
2101
|
+
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2102
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2103
|
+
// threads are already dead and absent from thread_list.
|
|
2104
|
+
void thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2105
|
+
thread_context_collector_state *state;
|
|
2106
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2107
|
+
|
|
2108
|
+
VALUE current_thread = rb_thread_current();
|
|
2109
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2110
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2111
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
1978
2112
|
}
|
|
1979
2113
|
|
|
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);
|
|
2114
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2115
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1987
2116
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2117
|
+
update_metrics_and_sample(
|
|
2118
|
+
state,
|
|
2119
|
+
current_thread,
|
|
2120
|
+
thread_context,
|
|
2121
|
+
current_cpu_time_ns,
|
|
2122
|
+
current_monotonic_wall_time_ns,
|
|
2123
|
+
true);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2127
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2128
|
+
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2129
|
+
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2130
|
+
thread_context_collector_state *state;
|
|
2131
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2132
|
+
|
|
2133
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2134
|
+
VALUE threads = thread_list(state);
|
|
2135
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2136
|
+
|
|
2137
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2138
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
2139
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2140
|
+
|
|
2141
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2142
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2143
|
+
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2144
|
+
update_metrics_and_sample(
|
|
2145
|
+
state,
|
|
2146
|
+
thread,
|
|
2147
|
+
thread_context,
|
|
2148
|
+
current_cpu_time_ns,
|
|
2149
|
+
current_monotonic_wall_time_ns,
|
|
2150
|
+
true);
|
|
2008
2151
|
}
|
|
2009
2152
|
}
|
|
2153
|
+
}
|
|
2010
2154
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2155
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2156
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2157
|
+
}
|
|
2014
2158
|
|
|
2015
|
-
|
|
2016
|
-
|
|
2159
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2160
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
2161
|
+
if (current_monotonic_wall_time_ns <= 0) return;
|
|
2162
|
+
|
|
2163
|
+
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2167
|
+
ENFORCE_THREAD(thread);
|
|
2168
|
+
|
|
2169
|
+
debug_enter_unsafe_context();
|
|
2170
|
+
|
|
2171
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2172
|
+
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2173
|
+
|
|
2174
|
+
debug_leave_unsafe_context();
|
|
2175
|
+
|
|
2176
|
+
return Qnil;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2180
|
+
ENFORCE_THREAD(thread);
|
|
2181
|
+
|
|
2182
|
+
debug_enter_unsafe_context();
|
|
2183
|
+
|
|
2184
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2185
|
+
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2017
2186
|
|
|
2187
|
+
debug_leave_unsafe_context();
|
|
2188
|
+
|
|
2189
|
+
return Qnil;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
2018
2193
|
// This function runs on the passed thread and has the GVL because it gets called just after the Ruby thread acquired the GVL
|
|
2019
2194
|
__attribute__((warn_unused_result))
|
|
2020
2195
|
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context) {
|
|
@@ -2103,7 +2278,7 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2103
2278
|
thread_context_collector_state *state;
|
|
2104
2279
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2105
2280
|
|
|
2106
|
-
per_thread_context *thread_context = get_or_create_context_for(current_thread
|
|
2281
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2107
2282
|
|
|
2108
2283
|
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2109
2284
|
|
|
@@ -2126,7 +2301,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2126
2301
|
state,
|
|
2127
2302
|
current_thread,
|
|
2128
2303
|
thread_context,
|
|
2129
|
-
&thread_context->sampling_buffer,
|
|
2130
2304
|
cpu_time_for_thread,
|
|
2131
2305
|
current_monotonic_wall_time_ns,
|
|
2132
2306
|
false);
|
|
@@ -2141,7 +2315,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2141
2315
|
thread_context_collector_state *state,
|
|
2142
2316
|
VALUE thread_being_sampled,
|
|
2143
2317
|
per_thread_context *thread_context,
|
|
2144
|
-
sampling_buffer* sampling_buffer,
|
|
2145
2318
|
long current_cpu_time_ns
|
|
2146
2319
|
) {
|
|
2147
2320
|
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
@@ -2200,26 +2373,16 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2200
2373
|
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
|
2201
2374
|
|
|
2202
2375
|
if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
|
|
2203
|
-
long cpu_time_elapsed_ns =
|
|
2204
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
2205
|
-
current_cpu_time_ns,
|
|
2206
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
2207
|
-
IS_CPU_TIME
|
|
2208
|
-
);
|
|
2376
|
+
long cpu_time_elapsed_ns = update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
2209
2377
|
|
|
2210
|
-
long duration_until_start_of_gvl_waiting_ns =
|
|
2211
|
-
|
|
2212
|
-
gvl_waiting_started_wall_time_ns,
|
|
2213
|
-
INVALID_TIME,
|
|
2214
|
-
IS_WALL_TIME
|
|
2215
|
-
);
|
|
2378
|
+
long duration_until_start_of_gvl_waiting_ns =
|
|
2379
|
+
update_wall_time_since_previous_sample(thread_context, gvl_waiting_started_wall_time_ns);
|
|
2216
2380
|
|
|
2217
2381
|
// Push extra sample
|
|
2218
2382
|
trigger_sample_for_thread(
|
|
2219
2383
|
state,
|
|
2220
2384
|
thread_being_sampled,
|
|
2221
2385
|
thread_context,
|
|
2222
|
-
sampling_buffer,
|
|
2223
2386
|
(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
2387
|
gvl_waiting_started_wall_time_ns,
|
|
2225
2388
|
NULL,
|
|
@@ -2232,19 +2395,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2232
2395
|
return true;
|
|
2233
2396
|
}
|
|
2234
2397
|
|
|
2235
|
-
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2236
|
-
ENFORCE_THREAD(thread);
|
|
2237
|
-
|
|
2238
|
-
debug_enter_unsafe_context();
|
|
2239
|
-
|
|
2240
|
-
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2241
|
-
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2242
|
-
|
|
2243
|
-
debug_leave_unsafe_context();
|
|
2244
|
-
|
|
2245
|
-
return Qnil;
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
2398
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2249
2399
|
ENFORCE_THREAD(thread);
|
|
2250
2400
|
|
|
@@ -2276,19 +2426,6 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2276
2426
|
return result;
|
|
2277
2427
|
}
|
|
2278
2428
|
|
|
2279
|
-
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2280
|
-
ENFORCE_THREAD(thread);
|
|
2281
|
-
|
|
2282
|
-
debug_enter_unsafe_context();
|
|
2283
|
-
|
|
2284
|
-
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2285
|
-
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2286
|
-
|
|
2287
|
-
debug_leave_unsafe_context();
|
|
2288
|
-
|
|
2289
|
-
return Qnil;
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
2429
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception) {
|
|
2293
2430
|
ENFORCE_THREAD(thread);
|
|
2294
2431
|
ENFORCE_BOOLEAN(allow_exception);
|
|
@@ -2306,29 +2443,27 @@ void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
|
2306
2443
|
return result;
|
|
2307
2444
|
}
|
|
2308
2445
|
|
|
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
2446
|
#else
|
|
2321
2447
|
static bool handle_gvl_waiting(
|
|
2322
2448
|
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2323
2449
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2324
2450
|
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2325
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2326
2451
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2327
2452
|
) { return false; }
|
|
2328
2453
|
|
|
2329
|
-
void thread_context_collector_on_serialize(DDTRACE_UNUSED VALUE self_instance) { }
|
|
2330
2454
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2331
2455
|
|
|
2456
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2457
|
+
ENFORCE_THREAD(thread);
|
|
2458
|
+
|
|
2459
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2460
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2461
|
+
|
|
2462
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2463
|
+
|
|
2464
|
+
return Qtrue;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2332
2467
|
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2333
2468
|
|
|
2334
2469
|
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|