datadog 2.2.0 → 2.4.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 +87 -2
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +14 -26
- data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +257 -69
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +53 -28
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
- data/ext/datadog_profiling_native_extension/collectors_stack.c +136 -81
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +661 -48
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +83 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +53 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +91 -69
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +54 -12
- data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/helpers.h +6 -17
- data/ext/datadog_profiling_native_extension/http_transport.c +41 -9
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +116 -139
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +20 -11
- data/ext/datadog_profiling_native_extension/profiling.c +1 -3
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
- data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
- data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
- data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +37 -22
- data/ext/libdatadog_api/datadog_ruby_common.c +83 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +53 -0
- data/ext/libdatadog_api/extconf.rb +108 -0
- data/ext/libdatadog_api/macos_development.md +26 -0
- data/ext/libdatadog_extconf_helpers.rb +130 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
- data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
- data/lib/datadog/appsec/component.rb +29 -8
- data/lib/datadog/appsec/configuration/settings.rb +2 -2
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +35 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +109 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +71 -0
- data/lib/datadog/appsec/contrib/graphql/integration.rb +54 -0
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +3 -6
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/appsec/processor/actions.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
- data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
- data/lib/datadog/appsec/processor.rb +36 -37
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec/response.rb +15 -1
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/components.rb +18 -15
- data/lib/datadog/core/configuration/settings.rb +135 -9
- data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
- data/lib/datadog/core/crashtracking/component.rb +111 -0
- data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
- data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/metrics/client.rb +7 -0
- data/lib/datadog/core/rate_limiter.rb +183 -0
- data/lib/datadog/core/remote/client/capabilities.rb +4 -3
- data/lib/datadog/core/remote/component.rb +4 -2
- data/lib/datadog/core/remote/negotiation.rb +4 -4
- data/lib/datadog/core/remote/tie.rb +2 -0
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/telemetry/component.rb +51 -2
- data/lib/datadog/core/telemetry/emitter.rb +9 -11
- data/lib/datadog/core/telemetry/event.rb +37 -1
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
- data/lib/datadog/core/telemetry/http/ext.rb +3 -0
- data/lib/datadog/core/telemetry/http/transport.rb +38 -9
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +71 -0
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/di/code_tracker.rb +168 -0
- data/lib/datadog/di/configuration/settings.rb +163 -0
- data/lib/datadog/di/configuration.rb +11 -0
- data/lib/datadog/di/error.rb +31 -0
- data/lib/datadog/di/extensions.rb +16 -0
- data/lib/datadog/di/probe.rb +133 -0
- data/lib/datadog/di/probe_builder.rb +41 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +193 -0
- data/lib/datadog/di.rb +14 -0
- data/lib/datadog/kit/appsec/events.rb +2 -4
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +28 -26
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
- data/lib/datadog/profiling/collectors/info.rb +15 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +30 -2
- data/lib/datadog/profiling/component.rb +89 -95
- data/lib/datadog/profiling/exporter.rb +3 -3
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/profiling/ext.rb +21 -21
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/http_transport.rb +14 -7
- data/lib/datadog/profiling/load_native_extension.rb +5 -5
- data/lib/datadog/profiling/preload.rb +1 -1
- data/lib/datadog/profiling/profiler.rb +5 -8
- data/lib/datadog/profiling/scheduler.rb +33 -25
- data/lib/datadog/profiling/stack_recorder.rb +3 -0
- data/lib/datadog/profiling/tag_builder.rb +2 -2
- data/lib/datadog/profiling/tasks/exec.rb +5 -5
- data/lib/datadog/profiling/tasks/setup.rb +16 -35
- data/lib/datadog/profiling.rb +4 -5
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +4 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/ext.rb +14 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
- data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +14 -10
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +10 -4
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +15 -0
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
- data/lib/datadog/tracing/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/errors.rb +9 -1
- data/lib/datadog/tracing/metadata/ext.rb +6 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
- data/lib/datadog/tracing/remote.rb +5 -2
- data/lib/datadog/tracing/sampling/matcher.rb +6 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/rule.rb +2 -0
- data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
- data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
- data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
- data/lib/datadog/tracing/span.rb +9 -2
- data/lib/datadog/tracing/span_event.rb +41 -0
- data/lib/datadog/tracing/span_operation.rb +6 -2
- data/lib/datadog/tracing/trace_operation.rb +26 -2
- data/lib/datadog/tracing/tracer.rb +14 -12
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/io/client.rb +1 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
- data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
- data/lib/datadog/tracing/workers.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +46 -11
- data/lib/datadog/profiling/crashtracker.rb +0 -91
- data/lib/datadog/profiling/ext/forking.rb +0 -98
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -20,7 +20,9 @@
|
|
20
20
|
#define ERR_CLOCK_FAIL "failed to get clock time"
|
21
21
|
|
22
22
|
// Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
|
23
|
-
|
23
|
+
// See https://docs.google.com/document/d/1lWLB714wlLBBq6T4xZyAc4a5wtWhSmr4-hgiPKeErlA/edit#heading=h.ugp0zxcj5iqh
|
24
|
+
// (Datadog-only link) for research backing the choice of this value.
|
25
|
+
unsigned int MAX_ALLOC_WEIGHT = 10000;
|
24
26
|
|
25
27
|
// Used to trigger the execution of Collectors::ThreadState, which implements all of the sampling logic
|
26
28
|
// itself; this class only implements the "when to do it" part.
|
@@ -86,6 +88,7 @@ unsigned int MAX_ALLOC_WEIGHT = 65535;
|
|
86
88
|
// `collectors_cpu_and_wall_time_worker_init` below and always get reused after that.
|
87
89
|
static rb_postponed_job_handle_t sample_from_postponed_job_handle;
|
88
90
|
static rb_postponed_job_handle_t after_gc_from_postponed_job_handle;
|
91
|
+
static rb_postponed_job_handle_t after_gvl_running_from_postponed_job_handle;
|
89
92
|
#endif
|
90
93
|
|
91
94
|
// Contains state for a single CpuAndWallTimeWorker instance
|
@@ -96,6 +99,8 @@ struct cpu_and_wall_time_worker_state {
|
|
96
99
|
bool no_signals_workaround_enabled;
|
97
100
|
bool dynamic_sampling_rate_enabled;
|
98
101
|
bool allocation_profiling_enabled;
|
102
|
+
bool allocation_counting_enabled;
|
103
|
+
bool gvl_profiling_enabled;
|
99
104
|
bool skip_idle_samples_for_testing;
|
100
105
|
VALUE self_instance;
|
101
106
|
VALUE thread_context_collector_instance;
|
@@ -104,7 +109,6 @@ struct cpu_and_wall_time_worker_state {
|
|
104
109
|
dynamic_sampling_rate_state cpu_dynamic_sampling_rate;
|
105
110
|
discrete_dynamic_sampler allocation_sampler;
|
106
111
|
VALUE gc_tracepoint; // Used to get gc start/finish information
|
107
|
-
VALUE object_allocation_tracepoint; // Used to get allocation counts and allocation profiling
|
108
112
|
|
109
113
|
// These are mutable and used to signal things between the worker thread and other threads
|
110
114
|
|
@@ -117,10 +121,15 @@ struct cpu_and_wall_time_worker_state {
|
|
117
121
|
|
118
122
|
// Others
|
119
123
|
|
120
|
-
// Used to detect/avoid nested sampling, e.g. when
|
124
|
+
// Used to detect/avoid nested sampling, e.g. when on_newobj_event gets triggered by a memory allocation
|
121
125
|
// that happens during another sample.
|
122
126
|
bool during_sample;
|
123
127
|
|
128
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
129
|
+
// Only set when sampling is active (gets created at start and cleaned on stop)
|
130
|
+
rb_internal_thread_event_hook_t *gvl_profiling_hook;
|
131
|
+
#endif
|
132
|
+
|
124
133
|
struct stats {
|
125
134
|
// # Generic stats
|
126
135
|
// How many times we tried to trigger a sample
|
@@ -167,22 +176,15 @@ struct cpu_and_wall_time_worker_state {
|
|
167
176
|
uint64_t allocation_sampling_time_ns_total;
|
168
177
|
// How many times we saw allocations being done inside a sample
|
169
178
|
unsigned int allocations_during_sample;
|
179
|
+
|
180
|
+
// # GVL profiling stats
|
181
|
+
// How many times we triggered the after_gvl_running sampling
|
182
|
+
unsigned int after_gvl_running;
|
170
183
|
} stats;
|
171
184
|
};
|
172
185
|
|
173
186
|
static VALUE _native_new(VALUE klass);
|
174
|
-
static VALUE _native_initialize(
|
175
|
-
DDTRACE_UNUSED VALUE _self,
|
176
|
-
VALUE self_instance,
|
177
|
-
VALUE thread_context_collector_instance,
|
178
|
-
VALUE gc_profiling_enabled,
|
179
|
-
VALUE idle_sampling_helper_instance,
|
180
|
-
VALUE no_signals_workaround_enabled,
|
181
|
-
VALUE dynamic_sampling_rate_enabled,
|
182
|
-
VALUE dynamic_sampling_rate_overhead_target_percentage,
|
183
|
-
VALUE allocation_profiling_enabled,
|
184
|
-
VALUE skip_idle_samples_for_testing
|
185
|
-
);
|
187
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
186
188
|
static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
|
187
189
|
static VALUE _native_sampling_loop(VALUE self, VALUE instance);
|
188
190
|
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
|
@@ -216,7 +218,7 @@ static void grab_gvl_and_sample(void);
|
|
216
218
|
static void reset_stats_not_thread_safe(struct cpu_and_wall_time_worker_state *state);
|
217
219
|
static void sleep_for(uint64_t time_ns);
|
218
220
|
static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self);
|
219
|
-
static void on_newobj_event(VALUE
|
221
|
+
static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2);
|
220
222
|
static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state);
|
221
223
|
static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
|
222
224
|
static VALUE rescued_sample_allocation(VALUE tracepoint_data);
|
@@ -224,6 +226,25 @@ static void delayed_error(struct cpu_and_wall_time_worker_state *state, const ch
|
|
224
226
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
|
225
227
|
static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
|
226
228
|
static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
|
229
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
230
|
+
static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
|
231
|
+
static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
|
232
|
+
#endif
|
233
|
+
static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
|
234
|
+
|
235
|
+
// We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
|
236
|
+
// with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
|
237
|
+
//
|
238
|
+
// But in practice, because we pass the `RUBY_EVENT_HOOK_FLAG_RAW_ARG` flag to `rb_add_event_hook2`, it casts the
|
239
|
+
// expected signature into a `rb_event_hook_raw_arg_func_t`:
|
240
|
+
// > typedef void (*rb_event_hook_raw_arg_func_t)(VALUE data, const rb_trace_arg_t *arg); (from vm_trace.c)
|
241
|
+
// which does match `on_newobj_event`.
|
242
|
+
//
|
243
|
+
// So TL;DR we're just doing this here to avoid the warning and explain why the apparent mismatch in function signatures.
|
244
|
+
#pragma GCC diagnostic push
|
245
|
+
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
246
|
+
static const rb_event_hook_func_t on_newobj_event_as_hook = (rb_event_hook_func_t) on_newobj_event;
|
247
|
+
#pragma GCC diagnostic pop
|
227
248
|
|
228
249
|
// Note on sampler global state safety:
|
229
250
|
//
|
@@ -255,8 +276,13 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
255
276
|
int unused_flags = 0;
|
256
277
|
sample_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, sample_from_postponed_job, NULL);
|
257
278
|
after_gc_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gc_from_postponed_job, NULL);
|
279
|
+
after_gvl_running_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gvl_running_from_postponed_job, NULL);
|
258
280
|
|
259
|
-
if (
|
281
|
+
if (
|
282
|
+
sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
|
283
|
+
after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
|
284
|
+
after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID
|
285
|
+
) {
|
260
286
|
rb_raise(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
|
261
287
|
}
|
262
288
|
#else
|
@@ -278,7 +304,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
278
304
|
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
279
305
|
rb_define_alloc_func(collectors_cpu_and_wall_time_worker_class, _native_new);
|
280
306
|
|
281
|
-
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize,
|
307
|
+
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, -1);
|
282
308
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
|
283
309
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 2);
|
284
310
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
@@ -300,6 +326,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
300
326
|
rb_define_singleton_method(testing_module, "_native_is_sigprof_blocked_in_current_thread", _native_is_sigprof_blocked_in_current_thread, 0);
|
301
327
|
rb_define_singleton_method(testing_module, "_native_with_blocked_sigprof", _native_with_blocked_sigprof, 0);
|
302
328
|
rb_define_singleton_method(testing_module, "_native_delayed_error", _native_delayed_error, 2);
|
329
|
+
rb_define_singleton_method(testing_module, "_native_gvl_profiling_hook_active", _native_gvl_profiling_hook_active, 1);
|
303
330
|
}
|
304
331
|
|
305
332
|
// This structure is used to define a Ruby object that stores a pointer to a struct cpu_and_wall_time_worker_state
|
@@ -316,6 +343,8 @@ static const rb_data_type_t cpu_and_wall_time_worker_typed_data = {
|
|
316
343
|
};
|
317
344
|
|
318
345
|
static VALUE _native_new(VALUE klass) {
|
346
|
+
long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
347
|
+
|
319
348
|
struct cpu_and_wall_time_worker_state *state = ruby_xcalloc(1, sizeof(struct cpu_and_wall_time_worker_state));
|
320
349
|
|
321
350
|
// Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
|
@@ -325,13 +354,14 @@ static VALUE _native_new(VALUE klass) {
|
|
325
354
|
state->no_signals_workaround_enabled = false;
|
326
355
|
state->dynamic_sampling_rate_enabled = true;
|
327
356
|
state->allocation_profiling_enabled = false;
|
357
|
+
state->allocation_counting_enabled = false;
|
358
|
+
state->gvl_profiling_enabled = false;
|
328
359
|
state->skip_idle_samples_for_testing = false;
|
329
360
|
state->thread_context_collector_instance = Qnil;
|
330
361
|
state->idle_sampling_helper_instance = Qnil;
|
331
362
|
state->owner_thread = Qnil;
|
332
363
|
dynamic_sampling_rate_init(&state->cpu_dynamic_sampling_rate);
|
333
364
|
state->gc_tracepoint = Qnil;
|
334
|
-
state->object_allocation_tracepoint = Qnil;
|
335
365
|
|
336
366
|
atomic_init(&state->should_run, false);
|
337
367
|
state->failure_exception = Qnil;
|
@@ -339,36 +369,44 @@ static VALUE _native_new(VALUE klass) {
|
|
339
369
|
|
340
370
|
state->during_sample = false;
|
341
371
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
if (now == 0) {
|
346
|
-
ruby_xfree(state);
|
347
|
-
rb_raise(rb_eRuntimeError, ERR_CLOCK_FAIL);
|
348
|
-
}
|
372
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
373
|
+
state->gvl_profiling_hook = NULL;
|
374
|
+
#endif
|
349
375
|
|
376
|
+
reset_stats_not_thread_safe(state);
|
350
377
|
discrete_dynamic_sampler_init(&state->allocation_sampler, "allocation", now);
|
351
378
|
|
379
|
+
// Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
|
380
|
+
// to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
|
381
|
+
// since the instance representing the state does not yet exist, such objects will not get marked.
|
382
|
+
|
352
383
|
return state->self_instance = TypedData_Wrap_Struct(klass, &cpu_and_wall_time_worker_typed_data, state);
|
353
384
|
}
|
354
385
|
|
355
|
-
static VALUE _native_initialize(
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
VALUE
|
361
|
-
VALUE
|
362
|
-
VALUE
|
363
|
-
VALUE
|
364
|
-
VALUE
|
365
|
-
VALUE
|
366
|
-
)
|
386
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
387
|
+
VALUE options;
|
388
|
+
rb_scan_args(argc, argv, "0:", &options);
|
389
|
+
if (options == Qnil) options = rb_hash_new();
|
390
|
+
|
391
|
+
VALUE self_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
|
392
|
+
VALUE thread_context_collector_instance = rb_hash_fetch(options, ID2SYM(rb_intern("thread_context_collector")));
|
393
|
+
VALUE gc_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gc_profiling_enabled")));
|
394
|
+
VALUE idle_sampling_helper_instance = rb_hash_fetch(options, ID2SYM(rb_intern("idle_sampling_helper")));
|
395
|
+
VALUE no_signals_workaround_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("no_signals_workaround_enabled")));
|
396
|
+
VALUE dynamic_sampling_rate_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("dynamic_sampling_rate_enabled")));
|
397
|
+
VALUE dynamic_sampling_rate_overhead_target_percentage = rb_hash_fetch(options, ID2SYM(rb_intern("dynamic_sampling_rate_overhead_target_percentage")));
|
398
|
+
VALUE allocation_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_profiling_enabled")));
|
399
|
+
VALUE allocation_counting_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_counting_enabled")));
|
400
|
+
VALUE gvl_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gvl_profiling_enabled")));
|
401
|
+
VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
|
402
|
+
|
367
403
|
ENFORCE_BOOLEAN(gc_profiling_enabled);
|
368
404
|
ENFORCE_BOOLEAN(no_signals_workaround_enabled);
|
369
405
|
ENFORCE_BOOLEAN(dynamic_sampling_rate_enabled);
|
370
406
|
ENFORCE_TYPE(dynamic_sampling_rate_overhead_target_percentage, T_FLOAT);
|
371
407
|
ENFORCE_BOOLEAN(allocation_profiling_enabled);
|
408
|
+
ENFORCE_BOOLEAN(allocation_counting_enabled);
|
409
|
+
ENFORCE_BOOLEAN(gvl_profiling_enabled);
|
372
410
|
ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
|
373
411
|
|
374
412
|
struct cpu_and_wall_time_worker_state *state;
|
@@ -378,6 +416,8 @@ static VALUE _native_initialize(
|
|
378
416
|
state->no_signals_workaround_enabled = (no_signals_workaround_enabled == Qtrue);
|
379
417
|
state->dynamic_sampling_rate_enabled = (dynamic_sampling_rate_enabled == Qtrue);
|
380
418
|
state->allocation_profiling_enabled = (allocation_profiling_enabled == Qtrue);
|
419
|
+
state->allocation_counting_enabled = (allocation_counting_enabled == Qtrue);
|
420
|
+
state->gvl_profiling_enabled = (gvl_profiling_enabled == Qtrue);
|
381
421
|
state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
|
382
422
|
|
383
423
|
double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
|
@@ -394,7 +434,6 @@ static VALUE _native_initialize(
|
|
394
434
|
state->thread_context_collector_instance = enforce_thread_context_collector_instance(thread_context_collector_instance);
|
395
435
|
state->idle_sampling_helper_instance = idle_sampling_helper_instance;
|
396
436
|
state->gc_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_GC_ENTER | RUBY_INTERNAL_EVENT_GC_EXIT, on_gc_event, NULL /* unused */);
|
397
|
-
state->object_allocation_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, NULL /* unused */);
|
398
437
|
|
399
438
|
return Qtrue;
|
400
439
|
}
|
@@ -409,7 +448,6 @@ static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr) {
|
|
409
448
|
rb_gc_mark(state->failure_exception);
|
410
449
|
rb_gc_mark(state->stop_thread);
|
411
450
|
rb_gc_mark(state->gc_tracepoint);
|
412
|
-
rb_gc_mark(state->object_allocation_tracepoint);
|
413
451
|
}
|
414
452
|
|
415
453
|
// Called in a background thread created in CpuAndWallTimeWorker#start
|
@@ -755,7 +793,35 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
755
793
|
// because they may raise exceptions.
|
756
794
|
install_sigprof_signal_handler(handle_sampling_signal, "handle_sampling_signal");
|
757
795
|
if (state->gc_profiling_enabled) rb_tracepoint_enable(state->gc_tracepoint);
|
758
|
-
if (state->allocation_profiling_enabled)
|
796
|
+
if (state->allocation_profiling_enabled) {
|
797
|
+
rb_add_event_hook2(
|
798
|
+
on_newobj_event_as_hook,
|
799
|
+
RUBY_INTERNAL_EVENT_NEWOBJ,
|
800
|
+
state->self_instance,
|
801
|
+
RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG)
|
802
|
+
;
|
803
|
+
}
|
804
|
+
|
805
|
+
if (state->gvl_profiling_enabled) {
|
806
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
807
|
+
#ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
|
808
|
+
gvl_profiling_state_thread_tracking_workaround();
|
809
|
+
#endif
|
810
|
+
|
811
|
+
state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
|
812
|
+
on_gvl_event,
|
813
|
+
(
|
814
|
+
// For now we're only asking for these events, even though there's more
|
815
|
+
// (e.g. check docs or gvl-tracing gem)
|
816
|
+
RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
|
817
|
+
RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
|
818
|
+
),
|
819
|
+
NULL
|
820
|
+
);
|
821
|
+
#else
|
822
|
+
rb_raise(rb_eArgError, "GVL profiling is not supported in this Ruby version");
|
823
|
+
#endif
|
824
|
+
}
|
759
825
|
|
760
826
|
// Flag the profiler as running before we release the GVL, in case anyone's waiting to know about it
|
761
827
|
rb_funcall(instance, rb_intern("signal_running"), 0);
|
@@ -868,7 +934,6 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
868
934
|
|
869
935
|
state->during_sample = true;
|
870
936
|
|
871
|
-
// Trigger sampling using the Collectors::ThreadState; rescue against any exceptions that happen during sampling
|
872
937
|
safely_call(thread_context_collector_sample_after_gc, state->thread_context_collector_instance, state->self_instance);
|
873
938
|
|
874
939
|
state->during_sample = false;
|
@@ -975,6 +1040,9 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
975
1040
|
ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.allocation_sampling_time_ns_total, state->stats.allocation_sampled),
|
976
1041
|
ID2SYM(rb_intern("allocation_sampler_snapshot")), /* => */ allocation_sampler_snapshot,
|
977
1042
|
ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
|
1043
|
+
|
1044
|
+
// GVL profiling stats
|
1045
|
+
ID2SYM(rb_intern("after_gvl_running")), /* => */ UINT2NUM(state->stats.after_gvl_running),
|
978
1046
|
};
|
979
1047
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
980
1048
|
return stats_as_hash;
|
@@ -1036,46 +1104,87 @@ static void sleep_for(uint64_t time_ns) {
|
|
1036
1104
|
}
|
1037
1105
|
|
1038
1106
|
static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
|
1039
|
-
|
1107
|
+
struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
|
1108
|
+
|
1109
|
+
bool are_allocations_being_tracked = state != NULL && state->allocation_profiling_enabled && state->allocation_counting_enabled;
|
1040
1110
|
|
1041
1111
|
return are_allocations_being_tracked ? ULL2NUM(allocation_count) : Qnil;
|
1042
1112
|
}
|
1043
1113
|
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
}
|
1114
|
+
#define HANDLE_CLOCK_FAILURE(call) ({ \
|
1115
|
+
long _result = (call); \
|
1116
|
+
if (_result == 0) { \
|
1117
|
+
delayed_error(state, ERR_CLOCK_FAIL); \
|
1118
|
+
return; \
|
1119
|
+
} \
|
1120
|
+
_result; \
|
1121
|
+
})
|
1053
1122
|
|
1123
|
+
// Implements memory-related profiling events. This function is called by Ruby via the `rb_add_event_hook2`
|
1124
|
+
// when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
|
1125
|
+
//
|
1126
|
+
// When allocation sampling is enabled, this function gets called for almost all* objects allocated by the Ruby VM.
|
1127
|
+
// (*In some weird cases the VM may skip this tracepoint.)
|
1128
|
+
//
|
1129
|
+
// At a high level, there's two paths through this function:
|
1130
|
+
// 1. should_sample == false -> return
|
1131
|
+
// 2. should_sample == true -> sample
|
1132
|
+
//
|
1133
|
+
// On big applications, path 1. is the hottest, since we don't sample every object. So it's quite important for it to
|
1134
|
+
// be as fast as possible.
|
1135
|
+
//
|
1136
|
+
// NOTE: You may be wondering why we don't use any of the arguments to this function. It turns out it's possible to just
|
1137
|
+
// call `rb_tracearg_from_tracepoint(anything)` anywhere during this function or its callees to get the data, so that's
|
1138
|
+
// why it's not being passed as an argument.
|
1139
|
+
static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2) {
|
1054
1140
|
struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
1055
1141
|
|
1056
1142
|
// This should not happen in a normal situation because the tracepoint is always enabled after the instance is set
|
1057
1143
|
// and disabled before it is cleared, but just in case...
|
1058
1144
|
if (state == NULL) return;
|
1059
1145
|
|
1060
|
-
|
1146
|
+
if (RB_UNLIKELY(state->allocation_counting_enabled)) {
|
1147
|
+
// Update thread-local allocation count
|
1148
|
+
if (RB_UNLIKELY(allocation_count == UINT64_MAX)) {
|
1149
|
+
allocation_count = 0;
|
1150
|
+
} else {
|
1151
|
+
allocation_count++;
|
1152
|
+
}
|
1153
|
+
}
|
1154
|
+
|
1155
|
+
// In rare cases, we may actually be allocating an object as part of profiler sampling. We don't want to recursively
|
1061
1156
|
// sample, so we just return early
|
1062
1157
|
if (state->during_sample) {
|
1063
1158
|
state->stats.allocations_during_sample++;
|
1064
1159
|
return;
|
1065
1160
|
}
|
1066
1161
|
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1162
|
+
// Hot path: Dynamic sampling rate is usually enabled and the sampling decision is usually false
|
1163
|
+
if (RB_LIKELY(state->dynamic_sampling_rate_enabled && !discrete_dynamic_sampler_should_sample(&state->allocation_sampler))) {
|
1164
|
+
state->stats.allocation_skipped++;
|
1165
|
+
|
1166
|
+
coarse_instant now = monotonic_coarse_wall_time_now_ns();
|
1167
|
+
HANDLE_CLOCK_FAILURE(now.timestamp_ns);
|
1168
|
+
|
1169
|
+
bool needs_readjust = discrete_dynamic_sampler_skipped_sample(&state->allocation_sampler, now);
|
1170
|
+
if (RB_UNLIKELY(needs_readjust)) {
|
1171
|
+
// We rarely readjust, so this is a cold path
|
1172
|
+
// Also, while above we used the cheaper monotonic_coarse, for this call we want the regular monotonic call,
|
1173
|
+
// which is why we end up getting time "again".
|
1174
|
+
discrete_dynamic_sampler_readjust(
|
1175
|
+
&state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
|
1176
|
+
);
|
1076
1177
|
}
|
1178
|
+
|
1179
|
+
return;
|
1077
1180
|
}
|
1078
1181
|
|
1182
|
+
// From here on, we've decided to go ahead with the sample, which is way less common than skipping it
|
1183
|
+
|
1184
|
+
discrete_dynamic_sampler_before_sample(
|
1185
|
+
&state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
|
1186
|
+
);
|
1187
|
+
|
1079
1188
|
// @ivoanjo: Strictly speaking, this is not needed because Ruby should not call the same tracepoint while a previous
|
1080
1189
|
// invocation is still pending, (e.g. it wouldn't call `on_newobj_event` while it's already running), but I decided
|
1081
1190
|
// to keep this here for consistency -- every call to the thread context (other than the special gc calls which are
|
@@ -1083,7 +1192,7 @@ static void on_newobj_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused)
|
|
1083
1192
|
state->during_sample = true;
|
1084
1193
|
|
1085
1194
|
// Rescue against any exceptions that happen during sampling
|
1086
|
-
safely_call(rescued_sample_allocation,
|
1195
|
+
safely_call(rescued_sample_allocation, Qnil, state->self_instance);
|
1087
1196
|
|
1088
1197
|
if (state->dynamic_sampling_rate_enabled) {
|
1089
1198
|
long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
@@ -1108,9 +1217,15 @@ static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state) {
|
|
1108
1217
|
if (state->gc_tracepoint != Qnil) {
|
1109
1218
|
rb_tracepoint_disable(state->gc_tracepoint);
|
1110
1219
|
}
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1220
|
+
|
1221
|
+
rb_remove_event_hook_with_data(on_newobj_event_as_hook, state->self_instance);
|
1222
|
+
|
1223
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
1224
|
+
if (state->gvl_profiling_hook) {
|
1225
|
+
rb_internal_thread_remove_event_hook(state->gvl_profiling_hook);
|
1226
|
+
state->gvl_profiling_hook = NULL;
|
1227
|
+
}
|
1228
|
+
#endif
|
1114
1229
|
}
|
1115
1230
|
|
1116
1231
|
static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
|
@@ -1126,13 +1241,14 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
|
|
1126
1241
|
}
|
1127
1242
|
}
|
1128
1243
|
|
1129
|
-
static VALUE rescued_sample_allocation(VALUE
|
1244
|
+
static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
|
1130
1245
|
struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
1131
1246
|
|
1132
1247
|
// This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
|
1133
1248
|
if (state == NULL) return Qnil;
|
1134
1249
|
|
1135
|
-
|
1250
|
+
// If we're getting called from inside a tracepoint/event hook, Ruby exposes the data using this function.
|
1251
|
+
rb_trace_arg_t *data = rb_tracearg_from_tracepoint(Qnil);
|
1136
1252
|
VALUE new_object = rb_tracearg_object(data);
|
1137
1253
|
|
1138
1254
|
unsigned long allocations_since_last_sample = state->dynamic_sampling_rate_enabled ?
|
@@ -1140,9 +1256,16 @@ static VALUE rescued_sample_allocation(VALUE tracepoint_data) {
|
|
1140
1256
|
discrete_dynamic_sampler_events_since_last_sample(&state->allocation_sampler) :
|
1141
1257
|
// if we aren't, then we're sampling every event
|
1142
1258
|
1;
|
1143
|
-
|
1259
|
+
|
1260
|
+
// To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
|
1261
|
+
// assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
|
1144
1262
|
unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
|
1145
1263
|
thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
|
1264
|
+
// ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
|
1265
|
+
if (weight < allocations_since_last_sample) {
|
1266
|
+
uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
|
1267
|
+
thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
|
1268
|
+
}
|
1146
1269
|
|
1147
1270
|
// Return a dummy VALUE because we're called from rb_rescue2 which requires it
|
1148
1271
|
return Qnil;
|
@@ -1177,3 +1300,68 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
1177
1300
|
unblock_sigprof_signal_handler_from_running_in_current_thread();
|
1178
1301
|
return Qtrue;
|
1179
1302
|
}
|
1303
|
+
|
1304
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
1305
|
+
static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
|
1306
|
+
// Be very careful about touching the `state` here or doing anything at all:
|
1307
|
+
// This function gets called without the GVL, and potentially from background Ractors!
|
1308
|
+
//
|
1309
|
+
// In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
|
1310
|
+
// are not directly tied to the `target_thread` object and the like)
|
1311
|
+
gvl_profiling_thread target_thread = thread_from_event(event_data);
|
1312
|
+
|
1313
|
+
if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
|
1314
|
+
thread_context_collector_on_gvl_waiting(target_thread);
|
1315
|
+
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
|
1316
|
+
// Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
|
1317
|
+
// (And... I think target_thread will be == rb_thread_current()?)
|
1318
|
+
// But we're not sure if we're on the main Ractor yet. The thread context collector actually can actually help here:
|
1319
|
+
// it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
|
1320
|
+
// Ractor. Thus, if we really really wanted to access the state, we could do it after making sure we're on the correct Ractor.
|
1321
|
+
|
1322
|
+
#ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
|
1323
|
+
target_thread = gvl_profiling_state_maybe_initialize();
|
1324
|
+
#endif
|
1325
|
+
|
1326
|
+
bool should_sample = thread_context_collector_on_gvl_running(target_thread);
|
1327
|
+
|
1328
|
+
if (should_sample) {
|
1329
|
+
// should_sample is only true if a thread belongs to the main Ractor, so we're good to go
|
1330
|
+
#ifndef NO_POSTPONED_TRIGGER
|
1331
|
+
rb_postponed_job_trigger(after_gvl_running_from_postponed_job_handle);
|
1332
|
+
#else
|
1333
|
+
rb_postponed_job_register_one(0, after_gvl_running_from_postponed_job, NULL);
|
1334
|
+
#endif
|
1335
|
+
}
|
1336
|
+
} else {
|
1337
|
+
// This is a very delicate time and it's hard for us to raise an exception so let's at least complain to stderr
|
1338
|
+
fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
|
1339
|
+
}
|
1340
|
+
}
|
1341
|
+
|
1342
|
+
static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
1343
|
+
struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
1344
|
+
|
1345
|
+
// This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
|
1346
|
+
if (state == NULL) return;
|
1347
|
+
|
1348
|
+
state->during_sample = true;
|
1349
|
+
|
1350
|
+
safely_call(thread_context_collector_sample_after_gvl_running, state->thread_context_collector_instance, state->self_instance);
|
1351
|
+
|
1352
|
+
state->stats.after_gvl_running++;
|
1353
|
+
|
1354
|
+
state->during_sample = false;
|
1355
|
+
}
|
1356
|
+
|
1357
|
+
static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
1358
|
+
struct cpu_and_wall_time_worker_state *state;
|
1359
|
+
TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
1360
|
+
|
1361
|
+
return state->gvl_profiling_hook != NULL ? Qtrue : Qfalse;
|
1362
|
+
}
|
1363
|
+
#else
|
1364
|
+
static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, DDTRACE_UNUSED VALUE instance) {
|
1365
|
+
return Qfalse;
|
1366
|
+
}
|
1367
|
+
#endif
|