datadog 2.3.0 → 2.5.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 +64 -2
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +10 -22
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
- 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 +194 -34
- data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
- data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
- data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
- data/ext/libdatadog_api/crashtracker.c +20 -18
- data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- 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 +10 -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 +0 -14
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
- data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
- data/lib/datadog/appsec/event.rb +25 -1
- data/lib/datadog/appsec/ext.rb +4 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
- data/lib/datadog/appsec/processor/context.rb +109 -0
- 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 +42 -107
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec/scope.rb +1 -4
- data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
- data/lib/datadog/appsec/utils.rb +2 -0
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration/settings.rb +96 -5
- data/lib/datadog/core/configuration.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +9 -6
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/environment/yjit.rb +5 -0
- 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/remote/transport/http.rb +5 -0
- data/lib/datadog/core/remote/worker.rb +1 -1
- data/lib/datadog/core/runtime/ext.rb +1 -0
- data/lib/datadog/core/runtime/metrics.rb +5 -1
- data/lib/datadog/core/semaphore.rb +35 -0
- data/lib/datadog/core/telemetry/component.rb +2 -0
- data/lib/datadog/core/telemetry/event.rb +12 -7
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +50 -14
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/core/workers/async.rb +1 -1
- data/lib/datadog/di/code_tracker.rb +166 -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/instrumenter.rb +301 -0
- data/lib/datadog/di/probe.rb +162 -0
- data/lib/datadog/di/probe_builder.rb +47 -0
- data/lib/datadog/di/probe_notification_builder.rb +207 -0
- data/lib/datadog/di/probe_notifier_worker.rb +244 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +215 -0
- data/lib/datadog/di/transport.rb +67 -0
- data/lib/datadog/di/utils.rb +39 -0
- data/lib/datadog/di.rb +57 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
- data/lib/datadog/profiling/collectors/info.rb +12 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
- data/lib/datadog/profiling/component.rb +21 -4
- data/lib/datadog/profiling/http_transport.rb +6 -1
- data/lib/datadog/profiling/scheduler.rb +2 -0
- data/lib/datadog/profiling/stack_recorder.rb +40 -9
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/component.rb +13 -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 +3 -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/ethon/easy_patch.rb +4 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
- 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 +13 -9
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
- data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -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/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/ext.rb +2 -0
- 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 +15 -9
- data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
- data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
- data/lib/datadog/tracing/trace_operation.rb +26 -2
- data/lib/datadog/tracing/tracer.rb +29 -22
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/http.rb +4 -0
- data/lib/datadog/tracing/transport/io/client.rb +1 -0
- data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
- data/lib/datadog/tracing/workers.rb +2 -2
- data/lib/datadog/tracing/writer.rb +26 -28
- data/lib/datadog/version.rb +1 -1
- metadata +40 -15
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -76,6 +76,11 @@
|
|
76
76
|
#define MISSING_TRACER_CONTEXT_KEY 0
|
77
77
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
78
78
|
|
79
|
+
// This is used as a placeholder to mark threads that are allowed to be profiled (enabled)
|
80
|
+
// (e.g. to avoid trying to gvl profile threads that are not from the main Ractor)
|
81
|
+
// and for which there's no data yet
|
82
|
+
#define GVL_WAITING_ENABLED_EMPTY RUBY_FIXNUM_MAX
|
83
|
+
|
79
84
|
static ID at_active_span_id; // id of :@active_span in Ruby
|
80
85
|
static ID at_active_trace_id; // id of :@active_trace in Ruby
|
81
86
|
static ID at_id_id; // id of :@id in Ruby
|
@@ -86,6 +91,26 @@ static ID at_otel_values_id; // id of :@otel_values in Ruby
|
|
86
91
|
static ID at_parent_span_id_id; // id of :@parent_span_id in Ruby
|
87
92
|
static ID at_datadog_trace_id; // id of :@datadog_trace in Ruby
|
88
93
|
|
94
|
+
// Used to support reading trace identifiers from the opentelemetry Ruby library when the ddtrace gem tracing
|
95
|
+
// integration is NOT in use.
|
96
|
+
static ID at_span_id_id; // id of :@span_id in Ruby
|
97
|
+
static ID at_trace_id_id; // id of :@trace_id in Ruby
|
98
|
+
static ID at_entries_id; // id of :@entries in Ruby
|
99
|
+
static ID at_context_id; // id of :@context in Ruby
|
100
|
+
static ID at_kind_id; // id of :@kind in Ruby
|
101
|
+
static ID at_name_id; // id of :@name in Ruby
|
102
|
+
static ID server_id; // id of :server in Ruby
|
103
|
+
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
104
|
+
|
105
|
+
// This is used by `thread_context_collector_on_gvl_running`. Because when that method gets called we're not sure if
|
106
|
+
// it's safe to access the state of the thread context collector, we store this setting as a global value. This does
|
107
|
+
// mean this setting is shared among all thread context collectors, and thus it's "last writer wins".
|
108
|
+
// In production this should not be a problem: there should only be one profiler, which is the last one created,
|
109
|
+
// and that'll be the one that last wrote this setting.
|
110
|
+
static uint32_t global_waiting_for_gvl_threshold_ns = MILLIS_AS_NS(10);
|
111
|
+
|
112
|
+
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
113
|
+
|
89
114
|
// Contains state for a single ThreadContext instance
|
90
115
|
struct thread_context_collector_state {
|
91
116
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
@@ -112,13 +137,15 @@ struct thread_context_collector_state {
|
|
112
137
|
bool endpoint_collection_enabled;
|
113
138
|
// Used to omit timestamps / timeline events from collected data
|
114
139
|
bool timeline_enabled;
|
115
|
-
// Used to
|
116
|
-
|
140
|
+
// Used to control context collection
|
141
|
+
otel_context_enabled otel_context_enabled;
|
117
142
|
// Used when calling monotonic_to_system_epoch_ns
|
118
143
|
monotonic_to_system_epoch_state time_converter_state;
|
119
144
|
// Used to identify the main thread, to give it a fallback name
|
120
145
|
VALUE main_thread;
|
121
146
|
// Used when extracting trace identifiers from otel spans. Lazily initialized.
|
147
|
+
// Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
|
148
|
+
// successful and Qnil if not.
|
122
149
|
VALUE otel_current_span_key;
|
123
150
|
|
124
151
|
struct stats {
|
@@ -164,26 +191,23 @@ struct trace_identifiers {
|
|
164
191
|
VALUE trace_endpoint;
|
165
192
|
};
|
166
193
|
|
194
|
+
struct otel_span {
|
195
|
+
VALUE span;
|
196
|
+
VALUE span_id;
|
197
|
+
VALUE trace_id;
|
198
|
+
};
|
199
|
+
|
167
200
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
168
201
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
169
202
|
static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t _value, st_data_t _argument);
|
170
203
|
static int hash_map_per_thread_context_free_values(st_data_t _thread, st_data_t value_per_thread_context, st_data_t _argument);
|
171
204
|
static VALUE _native_new(VALUE klass);
|
172
|
-
static VALUE _native_initialize(
|
173
|
-
VALUE self,
|
174
|
-
VALUE collector_instance,
|
175
|
-
VALUE recorder_instance,
|
176
|
-
VALUE max_frames,
|
177
|
-
VALUE tracer_context_key,
|
178
|
-
VALUE endpoint_collection_enabled,
|
179
|
-
VALUE timeline_enabled,
|
180
|
-
VALUE allocation_type_enabled
|
181
|
-
);
|
205
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
182
206
|
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread);
|
183
207
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
184
208
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
185
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
186
|
-
void update_metrics_and_sample(
|
209
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state);
|
210
|
+
static void update_metrics_and_sample(
|
187
211
|
struct thread_context_collector_state *state,
|
188
212
|
VALUE thread_being_sampled,
|
189
213
|
VALUE stack_from_thread,
|
@@ -201,7 +225,8 @@ static void trigger_sample_for_thread(
|
|
201
225
|
sample_values values,
|
202
226
|
long current_monotonic_wall_time_ns,
|
203
227
|
ddog_CharSlice *ruby_vm_type,
|
204
|
-
ddog_CharSlice *class_name
|
228
|
+
ddog_CharSlice *class_name,
|
229
|
+
bool is_gvl_waiting_state
|
205
230
|
);
|
206
231
|
static VALUE _native_thread_list(VALUE self);
|
207
232
|
static struct per_thread_context *get_or_create_context_for(VALUE thread, struct thread_context_collector_state *state);
|
@@ -237,6 +262,26 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
237
262
|
VALUE otel_values
|
238
263
|
);
|
239
264
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
|
265
|
+
static bool handle_gvl_waiting(
|
266
|
+
struct thread_context_collector_state *state,
|
267
|
+
VALUE thread_being_sampled,
|
268
|
+
VALUE stack_from_thread,
|
269
|
+
struct per_thread_context *thread_context,
|
270
|
+
sampling_buffer* sampling_buffer,
|
271
|
+
long current_cpu_time_ns
|
272
|
+
);
|
273
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
274
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
275
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread);
|
276
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
277
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns);
|
278
|
+
static void otel_without_ddtrace_trace_identifiers_for(
|
279
|
+
struct thread_context_collector_state *state,
|
280
|
+
VALUE thread,
|
281
|
+
struct trace_identifiers *trace_identifiers_result
|
282
|
+
);
|
283
|
+
static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
|
284
|
+
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
240
285
|
|
241
286
|
void collectors_thread_context_init(VALUE profiling_module) {
|
242
287
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
@@ -254,20 +299,27 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
254
299
|
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
255
300
|
rb_define_alloc_func(collectors_thread_context_class, _native_new);
|
256
301
|
|
257
|
-
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize,
|
302
|
+
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
258
303
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
259
304
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
260
305
|
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 2);
|
261
306
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
262
307
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
263
308
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
264
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc,
|
309
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 2);
|
265
310
|
rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
|
266
311
|
rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
|
267
312
|
rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
|
268
313
|
rb_define_singleton_method(testing_module, "_native_gc_tracking", _native_gc_tracking, 1);
|
269
314
|
rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
|
270
315
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
316
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
317
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
318
|
+
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
319
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 1);
|
320
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
|
321
|
+
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, 3);
|
322
|
+
#endif
|
271
323
|
|
272
324
|
at_active_span_id = rb_intern_const("@active_span");
|
273
325
|
at_active_trace_id = rb_intern_const("@active_trace");
|
@@ -278,6 +330,19 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
278
330
|
at_otel_values_id = rb_intern_const("@otel_values");
|
279
331
|
at_parent_span_id_id = rb_intern_const("@parent_span_id");
|
280
332
|
at_datadog_trace_id = rb_intern_const("@datadog_trace");
|
333
|
+
at_span_id_id = rb_intern_const("@span_id");
|
334
|
+
at_trace_id_id = rb_intern_const("@trace_id");
|
335
|
+
at_entries_id = rb_intern_const("@entries");
|
336
|
+
at_context_id = rb_intern_const("@context");
|
337
|
+
at_kind_id = rb_intern_const("@kind");
|
338
|
+
at_name_id = rb_intern_const("@name");
|
339
|
+
server_id = rb_intern_const("server");
|
340
|
+
otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
|
341
|
+
|
342
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
343
|
+
// This will raise if Ruby already ran out of thread-local keys
|
344
|
+
gvl_profiling_init();
|
345
|
+
#endif
|
281
346
|
|
282
347
|
gc_profiling_init();
|
283
348
|
}
|
@@ -357,11 +422,11 @@ static VALUE _native_new(VALUE klass) {
|
|
357
422
|
state->thread_list_buffer = thread_list_buffer;
|
358
423
|
state->endpoint_collection_enabled = true;
|
359
424
|
state->timeline_enabled = true;
|
360
|
-
state->
|
425
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
361
426
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
362
427
|
VALUE main_thread = rb_thread_main();
|
363
428
|
state->main_thread = main_thread;
|
364
|
-
state->otel_current_span_key =
|
429
|
+
state->otel_current_span_key = Qtrue;
|
365
430
|
state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
|
366
431
|
state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = 0;
|
367
432
|
|
@@ -377,22 +442,27 @@ static VALUE _native_new(VALUE klass) {
|
|
377
442
|
return instance;
|
378
443
|
}
|
379
444
|
|
380
|
-
static VALUE _native_initialize(
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
VALUE
|
386
|
-
VALUE
|
387
|
-
VALUE
|
388
|
-
VALUE
|
389
|
-
)
|
445
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
446
|
+
VALUE options;
|
447
|
+
rb_scan_args(argc, argv, "0:", &options);
|
448
|
+
if (options == Qnil) options = rb_hash_new();
|
449
|
+
|
450
|
+
VALUE self_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
|
451
|
+
VALUE recorder_instance = rb_hash_fetch(options, ID2SYM(rb_intern("recorder")));
|
452
|
+
VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
|
453
|
+
VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
|
454
|
+
VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
|
455
|
+
VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
|
456
|
+
VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
|
457
|
+
VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
|
458
|
+
|
459
|
+
ENFORCE_TYPE(max_frames, T_FIXNUM);
|
390
460
|
ENFORCE_BOOLEAN(endpoint_collection_enabled);
|
391
461
|
ENFORCE_BOOLEAN(timeline_enabled);
|
392
|
-
|
462
|
+
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
393
463
|
|
394
464
|
struct thread_context_collector_state *state;
|
395
|
-
TypedData_Get_Struct(
|
465
|
+
TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
396
466
|
|
397
467
|
// Update this when modifying state struct
|
398
468
|
state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
@@ -401,7 +471,17 @@ static VALUE _native_initialize(
|
|
401
471
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
402
472
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
403
473
|
state->timeline_enabled = (timeline_enabled == Qtrue);
|
404
|
-
|
474
|
+
if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
|
475
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
476
|
+
} else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
|
477
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_ONLY;
|
478
|
+
} else if (otel_context_enabled == ID2SYM(rb_intern("both"))) {
|
479
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_BOTH;
|
480
|
+
} else {
|
481
|
+
rb_raise(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
|
482
|
+
}
|
483
|
+
|
484
|
+
global_waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
405
485
|
|
406
486
|
if (RTEST(tracer_context_key)) {
|
407
487
|
ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
|
@@ -433,13 +513,22 @@ static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_inst
|
|
433
513
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
434
514
|
// It SHOULD NOT be used for other purposes.
|
435
515
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
436
|
-
thread_context_collector_on_gc_finish(collector_instance);
|
516
|
+
(void) !thread_context_collector_on_gc_finish(collector_instance);
|
437
517
|
return Qtrue;
|
438
518
|
}
|
439
519
|
|
440
520
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
441
521
|
// It SHOULD NOT be used for other purposes.
|
442
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
522
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state) {
|
523
|
+
ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
|
524
|
+
|
525
|
+
struct thread_context_collector_state *state;
|
526
|
+
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
527
|
+
|
528
|
+
if (reset_monotonic_to_system_state == Qtrue) {
|
529
|
+
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
530
|
+
}
|
531
|
+
|
443
532
|
thread_context_collector_sample_after_gc(collector_instance);
|
444
533
|
return Qtrue;
|
445
534
|
}
|
@@ -502,7 +591,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
502
591
|
);
|
503
592
|
}
|
504
593
|
|
505
|
-
void update_metrics_and_sample(
|
594
|
+
static void update_metrics_and_sample(
|
506
595
|
struct thread_context_collector_state *state,
|
507
596
|
VALUE thread_being_sampled,
|
508
597
|
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
@@ -511,12 +600,17 @@ void update_metrics_and_sample(
|
|
511
600
|
long current_cpu_time_ns,
|
512
601
|
long current_monotonic_wall_time_ns
|
513
602
|
) {
|
514
|
-
|
603
|
+
bool is_gvl_waiting_state =
|
604
|
+
handle_gvl_waiting(state, thread_being_sampled, stack_from_thread, thread_context, sampling_buffer, current_cpu_time_ns);
|
605
|
+
|
606
|
+
// Don't assign/update cpu during "Waiting for GVL"
|
607
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_time_since_previous_sample(
|
515
608
|
&thread_context->cpu_time_at_previous_sample_ns,
|
516
609
|
current_cpu_time_ns,
|
517
610
|
thread_context->gc_tracking.cpu_time_at_start_ns,
|
518
611
|
IS_NOT_WALL_TIME
|
519
612
|
);
|
613
|
+
|
520
614
|
long wall_time_elapsed_ns = update_time_since_previous_sample(
|
521
615
|
&thread_context->wall_time_at_previous_sample_ns,
|
522
616
|
current_monotonic_wall_time_ns,
|
@@ -528,6 +622,21 @@ void update_metrics_and_sample(
|
|
528
622
|
IS_WALL_TIME
|
529
623
|
);
|
530
624
|
|
625
|
+
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
626
|
+
//
|
627
|
+
// As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
|
628
|
+
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
629
|
+
// to be < the time at which we started Waiting for GVL.
|
630
|
+
//
|
631
|
+
// All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
|
632
|
+
// what the extra sample is), it's possible that there's no more wall-time to be assigned.
|
633
|
+
// Thus, in this case, we don't want to produce a sample representing Waiting for GVL with a wall-time of 0, and
|
634
|
+
// thus we skip creating such a sample.
|
635
|
+
if (is_gvl_waiting_state && wall_time_elapsed_ns == 0) return;
|
636
|
+
// ...you may also wonder: is there any other situation where it makes sense to produce a sample with
|
637
|
+
// wall_time_elapsed_ns == 0? I believe that yes, because the sample still includes a timestamp and a stack, but we
|
638
|
+
// may revisit/change our minds on this in the future.
|
639
|
+
|
531
640
|
trigger_sample_for_thread(
|
532
641
|
state,
|
533
642
|
thread_being_sampled,
|
@@ -537,7 +646,8 @@ void update_metrics_and_sample(
|
|
537
646
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
538
647
|
current_monotonic_wall_time_ns,
|
539
648
|
NULL,
|
540
|
-
NULL
|
649
|
+
NULL,
|
650
|
+
is_gvl_waiting_state
|
541
651
|
);
|
542
652
|
}
|
543
653
|
|
@@ -583,6 +693,7 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
583
693
|
//
|
584
694
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
585
695
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
696
|
+
__attribute__((warn_unused_result))
|
586
697
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
587
698
|
struct thread_context_collector_state *state;
|
588
699
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
@@ -704,6 +815,9 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
704
815
|
|
705
816
|
state->stats.gc_samples++;
|
706
817
|
|
818
|
+
// Let recorder do any cleanup/updates it requires after a GC step.
|
819
|
+
recorder_after_gc_step(state->recorder_instance);
|
820
|
+
|
707
821
|
// Return a VALUE to make it easier to call this function from Ruby APIs that expect a return value (such as rb_rescue2)
|
708
822
|
return Qnil;
|
709
823
|
}
|
@@ -718,7 +832,8 @@ static void trigger_sample_for_thread(
|
|
718
832
|
long current_monotonic_wall_time_ns,
|
719
833
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
720
834
|
ddog_CharSlice *ruby_vm_type,
|
721
|
-
ddog_CharSlice *class_name
|
835
|
+
ddog_CharSlice *class_name,
|
836
|
+
bool is_gvl_waiting_state
|
722
837
|
) {
|
723
838
|
int max_label_count =
|
724
839
|
1 + // thread id
|
@@ -759,6 +874,11 @@ static void trigger_sample_for_thread(
|
|
759
874
|
struct trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
760
875
|
trace_identifiers_for(state, thread, &trace_identifiers_result);
|
761
876
|
|
877
|
+
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
878
|
+
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
879
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result);
|
880
|
+
}
|
881
|
+
|
762
882
|
if (trace_identifiers_result.valid) {
|
763
883
|
labels[label_pos++] = (ddog_prof_Label) {.key = DDOG_CHARSLICE_C("local root span id"), .num = trace_identifiers_result.local_root_span_id};
|
764
884
|
labels[label_pos++] = (ddog_prof_Label) {.key = DDOG_CHARSLICE_C("span id"), .num = trace_identifiers_result.span_id};
|
@@ -837,7 +957,12 @@ static void trigger_sample_for_thread(
|
|
837
957
|
sampling_buffer,
|
838
958
|
state->recorder_instance,
|
839
959
|
values,
|
840
|
-
(sample_labels) {
|
960
|
+
(sample_labels) {
|
961
|
+
.labels = slice_labels,
|
962
|
+
.state_label = state_label,
|
963
|
+
.end_timestamp_ns = end_timestamp_ns,
|
964
|
+
.is_gvl_waiting_state = is_gvl_waiting_state,
|
965
|
+
}
|
841
966
|
);
|
842
967
|
}
|
843
968
|
|
@@ -887,9 +1012,9 @@ static struct per_thread_context *get_context_for(VALUE thread, struct thread_co
|
|
887
1012
|
// to either run Ruby code during sampling (not great), or otherwise use some of the VM private APIs to detect this.
|
888
1013
|
//
|
889
1014
|
static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
890
|
-
|
1015
|
+
unsigned long logging_gem_path_len = strlen(LOGGING_GEM_PATH);
|
891
1016
|
char *invoke_file = StringValueCStr(invoke_file_location);
|
892
|
-
|
1017
|
+
unsigned long invoke_file_len = strlen(invoke_file);
|
893
1018
|
|
894
1019
|
if (invoke_file_len < logging_gem_path_len) return false;
|
895
1020
|
|
@@ -937,6 +1062,20 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
937
1062
|
// These will only be used during a GC operation
|
938
1063
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
939
1064
|
thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
|
1065
|
+
|
1066
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
1067
|
+
// We use this special location to store data that can be accessed without any
|
1068
|
+
// kind of synchronization (e.g. by threads without the GVL).
|
1069
|
+
//
|
1070
|
+
// We set this marker here for two purposes:
|
1071
|
+
// * To make sure there's no stale data from a previous execution of the profiler.
|
1072
|
+
// * To mark threads that are actually being profiled
|
1073
|
+
//
|
1074
|
+
// (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
|
1075
|
+
// if this gets set concurrently with context initialization, then such a value will belong
|
1076
|
+
// to the current profiler instance, so that's OK)
|
1077
|
+
gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
1078
|
+
#endif
|
940
1079
|
}
|
941
1080
|
|
942
1081
|
static void free_context(struct per_thread_context* thread_context) {
|
@@ -960,7 +1099,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
960
1099
|
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
|
961
1100
|
rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
|
962
1101
|
rb_str_concat(result, rb_sprintf(" timeline_enabled=%"PRIsVALUE, state->timeline_enabled ? Qtrue : Qfalse));
|
963
|
-
rb_str_concat(result, rb_sprintf("
|
1102
|
+
rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
|
964
1103
|
rb_str_concat(result, rb_sprintf(
|
965
1104
|
" time_converter_state={.system_epoch_ns_reference=%ld, .delta_to_epoch_ns=%ld}",
|
966
1105
|
state->time_converter_state.system_epoch_ns_reference,
|
@@ -969,6 +1108,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
969
1108
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
970
1109
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
971
1110
|
rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
|
1111
|
+
rb_str_concat(result, rb_sprintf(" global_waiting_for_gvl_threshold_ns=%u", global_waiting_for_gvl_threshold_ns));
|
972
1112
|
|
973
1113
|
return result;
|
974
1114
|
}
|
@@ -996,6 +1136,10 @@ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value
|
|
996
1136
|
|
997
1137
|
ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
|
998
1138
|
ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
|
1139
|
+
|
1140
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
1141
|
+
ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(gvl_profiling_state_thread_object_get(thread)),
|
1142
|
+
#endif
|
999
1143
|
};
|
1000
1144
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
1001
1145
|
|
@@ -1146,6 +1290,7 @@ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_ins
|
|
1146
1290
|
|
1147
1291
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
1148
1292
|
static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result) {
|
1293
|
+
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
1149
1294
|
if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
|
1150
1295
|
|
1151
1296
|
VALUE current_context = rb_thread_local_aref(thread, state->tracer_context_key);
|
@@ -1200,7 +1345,7 @@ static bool should_collect_resource(VALUE root_span) {
|
|
1200
1345
|
if (root_span_type == Qnil) return false;
|
1201
1346
|
ENFORCE_TYPE(root_span_type, T_STRING);
|
1202
1347
|
|
1203
|
-
|
1348
|
+
long root_span_type_length = RSTRING_LEN(root_span_type);
|
1204
1349
|
const char *root_span_type_value = StringValuePtr(root_span_type);
|
1205
1350
|
|
1206
1351
|
bool is_web_request =
|
@@ -1223,6 +1368,9 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector
|
|
1223
1368
|
struct thread_context_collector_state *state;
|
1224
1369
|
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1225
1370
|
|
1371
|
+
// Release all context memory before clearing the existing context
|
1372
|
+
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
1373
|
+
|
1226
1374
|
st_clear(state->hash_map_per_thread_context);
|
1227
1375
|
|
1228
1376
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
@@ -1255,62 +1403,61 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1255
1403
|
ddog_CharSlice *optional_class_name = NULL;
|
1256
1404
|
char imemo_type[100];
|
1257
1405
|
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
)
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
|
1295
|
-
} else {
|
1296
|
-
// @ivoanjo: I'm not sure this can ever happen, but just-in-case
|
1297
|
-
class_name = ruby_value_type_to_class_name(type);
|
1298
|
-
}
|
1406
|
+
optional_class_name = &class_name;
|
1407
|
+
|
1408
|
+
if (
|
1409
|
+
type == RUBY_T_OBJECT ||
|
1410
|
+
type == RUBY_T_CLASS ||
|
1411
|
+
type == RUBY_T_MODULE ||
|
1412
|
+
type == RUBY_T_FLOAT ||
|
1413
|
+
type == RUBY_T_STRING ||
|
1414
|
+
type == RUBY_T_REGEXP ||
|
1415
|
+
type == RUBY_T_ARRAY ||
|
1416
|
+
type == RUBY_T_HASH ||
|
1417
|
+
type == RUBY_T_STRUCT ||
|
1418
|
+
type == RUBY_T_BIGNUM ||
|
1419
|
+
type == RUBY_T_FILE ||
|
1420
|
+
type == RUBY_T_DATA ||
|
1421
|
+
type == RUBY_T_MATCH ||
|
1422
|
+
type == RUBY_T_COMPLEX ||
|
1423
|
+
type == RUBY_T_RATIONAL ||
|
1424
|
+
type == RUBY_T_NIL ||
|
1425
|
+
type == RUBY_T_TRUE ||
|
1426
|
+
type == RUBY_T_FALSE ||
|
1427
|
+
type == RUBY_T_SYMBOL ||
|
1428
|
+
type == RUBY_T_FIXNUM
|
1429
|
+
) {
|
1430
|
+
VALUE klass = rb_class_of(new_object);
|
1431
|
+
|
1432
|
+
// Ruby sometimes plays a bit fast and loose with some of its internal objects, e.g.
|
1433
|
+
// `rb_str_tmp_frozen_acquire` allocates a string with no class (klass=0).
|
1434
|
+
// Thus, we need to make sure there's actually a class before getting its name.
|
1435
|
+
|
1436
|
+
if (klass != 0) {
|
1437
|
+
const char *name = rb_class2name(klass);
|
1438
|
+
size_t name_length = name != NULL ? strlen(name) : 0;
|
1439
|
+
|
1440
|
+
if (name_length > 0) {
|
1441
|
+
class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
|
1299
1442
|
} else {
|
1300
|
-
//
|
1443
|
+
// @ivoanjo: I'm not sure this can ever happen, but just-in-case
|
1301
1444
|
class_name = ruby_value_type_to_class_name(type);
|
1302
1445
|
}
|
1303
|
-
} else if (type == RUBY_T_IMEMO) {
|
1304
|
-
const char *imemo_string = imemo_kind(new_object);
|
1305
|
-
if (imemo_string != NULL) {
|
1306
|
-
snprintf(imemo_type, 100, "(VM Internal, T_IMEMO, %s)", imemo_string);
|
1307
|
-
class_name = (ddog_CharSlice) {.ptr = imemo_type, .len = strlen(imemo_type)};
|
1308
|
-
} else { // Ruby < 3
|
1309
|
-
class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
|
1310
|
-
}
|
1311
1446
|
} else {
|
1312
|
-
|
1447
|
+
// Fallback for objects with no class. Objects with no class are a way for the Ruby VM to mark them
|
1448
|
+
// as internal objects; see rb_objspace_internal_object_p for details.
|
1449
|
+
class_name = ruby_value_type_to_class_name(type);
|
1450
|
+
}
|
1451
|
+
} else if (type == RUBY_T_IMEMO) {
|
1452
|
+
const char *imemo_string = imemo_kind(new_object);
|
1453
|
+
if (imemo_string != NULL) {
|
1454
|
+
snprintf(imemo_type, 100, "(VM Internal, T_IMEMO, %s)", imemo_string);
|
1455
|
+
class_name = (ddog_CharSlice) {.ptr = imemo_type, .len = strlen(imemo_type)};
|
1456
|
+
} else { // Ruby < 3
|
1457
|
+
class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
|
1313
1458
|
}
|
1459
|
+
} else {
|
1460
|
+
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
1314
1461
|
}
|
1315
1462
|
|
1316
1463
|
track_object(state->recorder_instance, new_object, sample_weight, optional_class_name);
|
@@ -1326,7 +1473,8 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1326
1473
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
1327
1474
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
1328
1475
|
&ruby_vm_type,
|
1329
|
-
optional_class_name
|
1476
|
+
optional_class_name,
|
1477
|
+
false
|
1330
1478
|
);
|
1331
1479
|
}
|
1332
1480
|
|
@@ -1372,25 +1520,29 @@ static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type) {
|
|
1372
1520
|
}
|
1373
1521
|
}
|
1374
1522
|
|
1523
|
+
// Used to access OpenTelemetry::Trace.const_get(:CURRENT_SPAN_KEY). Will raise exceptions if it fails.
|
1524
|
+
static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
1525
|
+
VALUE opentelemetry_module = rb_const_get(rb_cObject, rb_intern("OpenTelemetry"));
|
1526
|
+
ENFORCE_TYPE(opentelemetry_module, T_MODULE);
|
1527
|
+
VALUE trace_module = rb_const_get(opentelemetry_module, rb_intern("Trace"));
|
1528
|
+
ENFORCE_TYPE(trace_module, T_MODULE);
|
1529
|
+
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
1530
|
+
}
|
1531
|
+
|
1375
1532
|
static VALUE get_otel_current_span_key(struct thread_context_collector_state *state) {
|
1376
|
-
if (state->otel_current_span_key ==
|
1377
|
-
|
1378
|
-
|
1379
|
-
VALUE
|
1380
|
-
VALUE context_module = rb_const_get(api_module, rb_intern_const("Context"));
|
1381
|
-
VALUE current_span_key = rb_const_get(context_module, rb_intern_const("CURRENT_SPAN_KEY"));
|
1382
|
-
|
1383
|
-
if (current_span_key == Qnil) {
|
1384
|
-
rb_raise(rb_eRuntimeError, "Unexpected: Missing Datadog::OpenTelemetry::API::Context::CURRENT_SPAN_KEY");
|
1385
|
-
}
|
1533
|
+
if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
|
1534
|
+
// If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
|
1535
|
+
// gets refactored, we should not fall on our face)
|
1536
|
+
VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
|
1386
1537
|
|
1387
|
-
|
1538
|
+
// Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
|
1539
|
+
state->otel_current_span_key = span_key;
|
1388
1540
|
}
|
1389
1541
|
|
1390
1542
|
return state->otel_current_span_key;
|
1391
1543
|
}
|
1392
1544
|
|
1393
|
-
// This method gets used when ddtrace is being used indirectly via the
|
1545
|
+
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
1394
1546
|
// differently, and this codepath handles it.
|
1395
1547
|
static void ddtrace_otel_trace_identifiers_for(
|
1396
1548
|
struct thread_context_collector_state *state,
|
@@ -1410,6 +1562,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
1410
1562
|
if (resolved_numeric_span_id == Qnil) return;
|
1411
1563
|
|
1412
1564
|
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
1565
|
+
if (otel_current_span_key == Qnil) return;
|
1413
1566
|
VALUE current_trace = *active_trace;
|
1414
1567
|
|
1415
1568
|
// ddtrace uses a different structure when spans are created from otel, where each otel span will have a unique ddtrace
|
@@ -1462,3 +1615,388 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
1462
1615
|
thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
|
1463
1616
|
return Qtrue;
|
1464
1617
|
}
|
1618
|
+
|
1619
|
+
// This method differs from trace_identifiers_for/ddtrace_otel_trace_identifiers_for to support the situation where
|
1620
|
+
// the opentelemetry ruby library is being used for tracing AND the ddtrace tracing bits are not involved at all.
|
1621
|
+
//
|
1622
|
+
// Thus, in this case, we're directly reading from the opentelemetry stuff, which is different to how ddtrace tracing
|
1623
|
+
// does it.
|
1624
|
+
//
|
1625
|
+
// This is somewhat brittle: we're coupling on internal details of the opentelemetry gem to get what we need. In the
|
1626
|
+
// future maybe the otel ruby folks would be open to having a nice public way of getting this data that suits the
|
1627
|
+
// usecase of profilers.
|
1628
|
+
// Until then, the strategy below is to be extremely defensive, and if anything is out of place, we immediately return
|
1629
|
+
// and give up on getting trace data from opentelemetry. (Thus, worst case would be -- you upgrade opentelemetry and
|
1630
|
+
// profiling features relying on reading this data stop working, but you'll still get profiles and the app will be
|
1631
|
+
// otherwise undisturbed).
|
1632
|
+
//
|
1633
|
+
// Specifically, the way this works is:
|
1634
|
+
// 1. The latest entry in the opentelemetry context storage represents the current span (if any). We take the span id
|
1635
|
+
// and trace id from this span.
|
1636
|
+
// 2. To find the local root span id, we walk the context storage backwards from the current span, and find the earliest
|
1637
|
+
// entry in the context storage that has the same trace id as the current span; we use the found span as the local
|
1638
|
+
// root span id.
|
1639
|
+
// This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
|
1640
|
+
static void otel_without_ddtrace_trace_identifiers_for(
|
1641
|
+
struct thread_context_collector_state *state,
|
1642
|
+
VALUE thread,
|
1643
|
+
struct trace_identifiers *trace_identifiers_result
|
1644
|
+
) {
|
1645
|
+
VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
1646
|
+
|
1647
|
+
// If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
|
1648
|
+
if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
|
1649
|
+
|
1650
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
1651
|
+
if (otel_current_span_key == Qnil) return;
|
1652
|
+
|
1653
|
+
int active_context_index = RARRAY_LEN(context_storage) - 1;
|
1654
|
+
if (active_context_index < 0) return;
|
1655
|
+
|
1656
|
+
struct otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
1657
|
+
if (active_span.span == Qnil) return;
|
1658
|
+
|
1659
|
+
struct otel_span local_root_span = active_span;
|
1660
|
+
|
1661
|
+
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
1662
|
+
for (int i = active_context_index - 1; i >= 0; i--) {
|
1663
|
+
struct otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
1664
|
+
if (checking_span.span == Qnil) return;
|
1665
|
+
|
1666
|
+
if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
|
1667
|
+
|
1668
|
+
local_root_span = checking_span;
|
1669
|
+
}
|
1670
|
+
|
1671
|
+
// Convert the span ids into uint64_t to match what the Datadog tracer does
|
1672
|
+
trace_identifiers_result->span_id = otel_span_id_to_uint(active_span.span_id);
|
1673
|
+
trace_identifiers_result->local_root_span_id = otel_span_id_to_uint(local_root_span.span_id);
|
1674
|
+
|
1675
|
+
if (trace_identifiers_result->span_id == 0 || trace_identifiers_result->local_root_span_id == 0) return;
|
1676
|
+
|
1677
|
+
trace_identifiers_result->valid = true;
|
1678
|
+
|
1679
|
+
if (!state->endpoint_collection_enabled) return;
|
1680
|
+
|
1681
|
+
VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
|
1682
|
+
// We filter out spans that don't have `kind: :server`
|
1683
|
+
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || SYM2ID(root_span_type) != server_id) return;
|
1684
|
+
|
1685
|
+
VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
|
1686
|
+
if (!RB_TYPE_P(trace_resource, T_STRING)) return;
|
1687
|
+
|
1688
|
+
trace_identifiers_result->trace_endpoint = trace_resource;
|
1689
|
+
}
|
1690
|
+
|
1691
|
+
static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
|
1692
|
+
struct otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
|
1693
|
+
|
1694
|
+
if (otel_context == Qnil) return failed;
|
1695
|
+
|
1696
|
+
VALUE context_entries = rb_ivar_get(otel_context, at_entries_id /* @entries */);
|
1697
|
+
if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
|
1698
|
+
|
1699
|
+
// If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
|
1700
|
+
VALUE span = rb_hash_lookup(context_entries, otel_current_span_key);
|
1701
|
+
if (span == Qnil) return failed;
|
1702
|
+
|
1703
|
+
// If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
|
1704
|
+
VALUE span_context = rb_ivar_get(span, at_context_id /* @context */);
|
1705
|
+
if (span_context == Qnil) return failed;
|
1706
|
+
|
1707
|
+
VALUE span_id = rb_ivar_get(span_context, at_span_id_id /* @span_id */);
|
1708
|
+
VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
|
1709
|
+
if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
|
1710
|
+
|
1711
|
+
return (struct otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
|
1712
|
+
}
|
1713
|
+
|
1714
|
+
// Otel span ids are represented as a big-endian 8-byte string
|
1715
|
+
static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
1716
|
+
if (!RB_TYPE_P(otel_span_id, T_STRING) || RSTRING_LEN(otel_span_id) != 8) { return 0; }
|
1717
|
+
|
1718
|
+
unsigned char *span_bytes = (unsigned char*) StringValuePtr(otel_span_id);
|
1719
|
+
|
1720
|
+
return \
|
1721
|
+
((uint64_t)span_bytes[0] << 56) |
|
1722
|
+
((uint64_t)span_bytes[1] << 48) |
|
1723
|
+
((uint64_t)span_bytes[2] << 40) |
|
1724
|
+
((uint64_t)span_bytes[3] << 32) |
|
1725
|
+
((uint64_t)span_bytes[4] << 24) |
|
1726
|
+
((uint64_t)span_bytes[5] << 16) |
|
1727
|
+
((uint64_t)span_bytes[6] << 8) |
|
1728
|
+
((uint64_t)span_bytes[7]);
|
1729
|
+
}
|
1730
|
+
|
1731
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
1732
|
+
// This function can get called from outside the GVL and even on non-main Ractors
|
1733
|
+
void thread_context_collector_on_gvl_waiting(gvl_profiling_thread thread) {
|
1734
|
+
// Because this function gets called from a thread that is NOT holding the GVL, we avoid touching the
|
1735
|
+
// per-thread context directly.
|
1736
|
+
//
|
1737
|
+
// Instead, we ask Ruby to hold the data we need in Ruby's own special per-thread context area
|
1738
|
+
// that's thread-safe and built for this kind of use
|
1739
|
+
//
|
1740
|
+
// Also, this function can get called on the non-main Ractor. We deal with this by checking if the value in the context
|
1741
|
+
// is non-zero, since only `initialize_context` ever sets the value from 0 to non-zero for threads it sees.
|
1742
|
+
intptr_t thread_being_profiled = gvl_profiling_state_get(thread);
|
1743
|
+
if (!thread_being_profiled) return;
|
1744
|
+
|
1745
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
1746
|
+
if (current_monotonic_wall_time_ns <= 0 || current_monotonic_wall_time_ns > GVL_WAITING_ENABLED_EMPTY) return;
|
1747
|
+
|
1748
|
+
gvl_profiling_state_set(thread, current_monotonic_wall_time_ns);
|
1749
|
+
}
|
1750
|
+
|
1751
|
+
// This function can get called from outside the GVL and even on non-main Ractors
|
1752
|
+
__attribute__((warn_unused_result))
|
1753
|
+
on_gvl_running_result thread_context_collector_on_gvl_running_with_threshold(gvl_profiling_thread thread, uint32_t waiting_for_gvl_threshold_ns) {
|
1754
|
+
intptr_t gvl_waiting_at = gvl_profiling_state_get(thread);
|
1755
|
+
|
1756
|
+
// Thread was not being profiled / not waiting on gvl
|
1757
|
+
if (gvl_waiting_at == 0 || gvl_waiting_at == GVL_WAITING_ENABLED_EMPTY) return ON_GVL_RUNNING_UNKNOWN;
|
1758
|
+
|
1759
|
+
// @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
|
1760
|
+
if (gvl_waiting_at < 0) return ON_GVL_RUNNING_SAMPLE;
|
1761
|
+
|
1762
|
+
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
|
1763
|
+
|
1764
|
+
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
1765
|
+
|
1766
|
+
if (should_sample) {
|
1767
|
+
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
1768
|
+
intptr_t gvl_waiting_at_is_now_running = -gvl_waiting_at;
|
1769
|
+
|
1770
|
+
gvl_profiling_state_set(thread, gvl_waiting_at_is_now_running);
|
1771
|
+
} else {
|
1772
|
+
// We decided not to sample. Let's mark the thread back to the initial "enabled but empty" state
|
1773
|
+
gvl_profiling_state_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
1774
|
+
}
|
1775
|
+
|
1776
|
+
return should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE;
|
1777
|
+
}
|
1778
|
+
|
1779
|
+
__attribute__((warn_unused_result))
|
1780
|
+
on_gvl_running_result thread_context_collector_on_gvl_running(gvl_profiling_thread thread) {
|
1781
|
+
return thread_context_collector_on_gvl_running_with_threshold(thread, global_waiting_for_gvl_threshold_ns);
|
1782
|
+
}
|
1783
|
+
|
1784
|
+
// Why does this method need to exist?
|
1785
|
+
//
|
1786
|
+
// You may be surprised to see that if we never call this function (from cpu_and_wall_time_worker), Waiting for GVL
|
1787
|
+
// samples will still show up.
|
1788
|
+
// This is because regular cpu/wall-time samples also use `update_metrics_and_sample` which will do the right thing
|
1789
|
+
// and push "Waiting for GVL" samples as needed.
|
1790
|
+
//
|
1791
|
+
// The reason this method needs to exist and be called very shortly after thread_context_collector_on_gvl_running
|
1792
|
+
// returning true is to ensure accuracy of both the timing and stack for the Waiting for GVL sample.
|
1793
|
+
//
|
1794
|
+
// Timing:
|
1795
|
+
// Because we currently only record the timestamp when the Waiting for GVL started and not when the Waiting for GVL ended,
|
1796
|
+
// we rely on pushing a sample as soon as possible when the Waiting for GVL ends so that the timestamp of the sample
|
1797
|
+
// actually matches when we stopped waiting.
|
1798
|
+
//
|
1799
|
+
// Stack:
|
1800
|
+
// If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
|
1801
|
+
// via the regular cpu/wall-time samples mechanism, the stack can be be inaccurate (e.g. does not correctly pinpoint
|
1802
|
+
// where the waiting happened).
|
1803
|
+
//
|
1804
|
+
// Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
|
1805
|
+
// and not a regular cpu/wall-time sample BUT since all of these things are happening in parallel/concurrently I suspect
|
1806
|
+
// it's possible for a regular sample to kick in just before this one.
|
1807
|
+
//
|
1808
|
+
// ---
|
1809
|
+
//
|
1810
|
+
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
1811
|
+
// exists to enable testing.
|
1812
|
+
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
1813
|
+
struct thread_context_collector_state *state;
|
1814
|
+
TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1815
|
+
|
1816
|
+
if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
|
1817
|
+
|
1818
|
+
intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(current_thread);
|
1819
|
+
|
1820
|
+
if (gvl_waiting_at >= 0) {
|
1821
|
+
// @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
|
1822
|
+
// that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
|
1823
|
+
// triggered ahead of us.
|
1824
|
+
// We do nothing in this case.
|
1825
|
+
return Qfalse;
|
1826
|
+
}
|
1827
|
+
|
1828
|
+
struct per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
1829
|
+
|
1830
|
+
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
1831
|
+
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
1832
|
+
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
1833
|
+
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
1834
|
+
|
1835
|
+
// TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
|
1836
|
+
|
1837
|
+
update_metrics_and_sample(
|
1838
|
+
state,
|
1839
|
+
/* thread_being_sampled: */ current_thread,
|
1840
|
+
/* stack_from_thread: */ current_thread,
|
1841
|
+
thread_context,
|
1842
|
+
thread_context->sampling_buffer,
|
1843
|
+
cpu_time_for_thread,
|
1844
|
+
current_monotonic_wall_time_ns
|
1845
|
+
);
|
1846
|
+
|
1847
|
+
return Qtrue;
|
1848
|
+
}
|
1849
|
+
|
1850
|
+
// This method is intended to be called from update_metrics_and_sample. It exists to handle extra sampling steps we
|
1851
|
+
// need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
|
1852
|
+
__attribute__((warn_unused_result))
|
1853
|
+
static bool handle_gvl_waiting(
|
1854
|
+
struct thread_context_collector_state *state,
|
1855
|
+
VALUE thread_being_sampled,
|
1856
|
+
VALUE stack_from_thread,
|
1857
|
+
struct per_thread_context *thread_context,
|
1858
|
+
sampling_buffer* sampling_buffer,
|
1859
|
+
long current_cpu_time_ns
|
1860
|
+
) {
|
1861
|
+
intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread_being_sampled);
|
1862
|
+
|
1863
|
+
bool is_gvl_waiting_state = gvl_waiting_at != 0 && gvl_waiting_at != GVL_WAITING_ENABLED_EMPTY;
|
1864
|
+
|
1865
|
+
if (!is_gvl_waiting_state) return false;
|
1866
|
+
|
1867
|
+
// We can be in one of 2 situations here:
|
1868
|
+
//
|
1869
|
+
// 1. The current sample is the first one after we entered the "Waiting for GVL" state
|
1870
|
+
// (wall_time_at_previous_sample_ns < abs(gvl_waiting_at))
|
1871
|
+
//
|
1872
|
+
// time ─────►
|
1873
|
+
// ...──────────────┬───────────────────...
|
1874
|
+
// Other state │ Waiting for GVL
|
1875
|
+
// ...──────────────┴───────────────────...
|
1876
|
+
// ▲ ▲
|
1877
|
+
// └─ Previous sample └─ Regular sample (caller)
|
1878
|
+
//
|
1879
|
+
// In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
|
1880
|
+
// to represent the remaining cpu/wall time before the "Waiting for GVL" started:
|
1881
|
+
//
|
1882
|
+
// time ─────►
|
1883
|
+
// ...──────────────┬───────────────────...
|
1884
|
+
// Other state │ Waiting for GVL
|
1885
|
+
// ...──────────────┴───────────────────...
|
1886
|
+
// ▲ ▲ ▲
|
1887
|
+
// └─ Prev... └─ Extra sample └─ Regular sample (caller)
|
1888
|
+
//
|
1889
|
+
// 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
|
1890
|
+
// (wall_time_at_previous_sample_ns > abs(gvl_waiting_at))
|
1891
|
+
//
|
1892
|
+
// time ─────►
|
1893
|
+
// ...──────────────┬───────────────────────────────────────────────...
|
1894
|
+
// Other state │ Waiting for GVL
|
1895
|
+
// ...──────────────┴───────────────────────────────────────────────...
|
1896
|
+
// ▲ ▲ ▲
|
1897
|
+
// └─ Previous sample └─ Previous sample └─ Regular sample (caller)
|
1898
|
+
//
|
1899
|
+
// In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
|
1900
|
+
//
|
1901
|
+
// ---
|
1902
|
+
//
|
1903
|
+
// Overall, gvl_waiting_at will be > 0 if still in the "Waiting for GVL" state and < 0 if we actually reached the end of
|
1904
|
+
// the wait.
|
1905
|
+
//
|
1906
|
+
// It doesn't really matter if the thread is still waiting or just reached the end of the wait: each sample represents
|
1907
|
+
// a snapshot at time ending now, so if the state finished, it just means the next sample will be a regular one.
|
1908
|
+
|
1909
|
+
if (gvl_waiting_at < 0) {
|
1910
|
+
// Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
|
1911
|
+
gvl_profiling_state_thread_object_set(thread_being_sampled, GVL_WAITING_ENABLED_EMPTY);
|
1912
|
+
}
|
1913
|
+
|
1914
|
+
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
1915
|
+
|
1916
|
+
if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
|
1917
|
+
long cpu_time_elapsed_ns = update_time_since_previous_sample(
|
1918
|
+
&thread_context->cpu_time_at_previous_sample_ns,
|
1919
|
+
current_cpu_time_ns,
|
1920
|
+
thread_context->gc_tracking.cpu_time_at_start_ns,
|
1921
|
+
IS_NOT_WALL_TIME
|
1922
|
+
);
|
1923
|
+
|
1924
|
+
long duration_until_start_of_gvl_waiting_ns = update_time_since_previous_sample(
|
1925
|
+
&thread_context->wall_time_at_previous_sample_ns,
|
1926
|
+
gvl_waiting_started_wall_time_ns,
|
1927
|
+
INVALID_TIME,
|
1928
|
+
IS_WALL_TIME
|
1929
|
+
);
|
1930
|
+
|
1931
|
+
// Push extra sample
|
1932
|
+
trigger_sample_for_thread(
|
1933
|
+
state,
|
1934
|
+
thread_being_sampled,
|
1935
|
+
stack_from_thread,
|
1936
|
+
thread_context,
|
1937
|
+
sampling_buffer,
|
1938
|
+
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
1939
|
+
gvl_waiting_started_wall_time_ns,
|
1940
|
+
NULL,
|
1941
|
+
NULL,
|
1942
|
+
false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
|
1943
|
+
);
|
1944
|
+
}
|
1945
|
+
|
1946
|
+
return true;
|
1947
|
+
}
|
1948
|
+
|
1949
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1950
|
+
ENFORCE_THREAD(thread);
|
1951
|
+
|
1952
|
+
thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
|
1953
|
+
return Qnil;
|
1954
|
+
}
|
1955
|
+
|
1956
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1957
|
+
ENFORCE_THREAD(thread);
|
1958
|
+
|
1959
|
+
intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
|
1960
|
+
return LONG2NUM(gvl_waiting_at);
|
1961
|
+
}
|
1962
|
+
|
1963
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1964
|
+
ENFORCE_THREAD(thread);
|
1965
|
+
|
1966
|
+
return thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
1967
|
+
}
|
1968
|
+
|
1969
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
1970
|
+
ENFORCE_THREAD(thread);
|
1971
|
+
|
1972
|
+
return thread_context_collector_sample_after_gvl_running(
|
1973
|
+
collector_instance,
|
1974
|
+
thread,
|
1975
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
1976
|
+
);
|
1977
|
+
}
|
1978
|
+
|
1979
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
|
1980
|
+
ENFORCE_THREAD(thread);
|
1981
|
+
|
1982
|
+
struct thread_context_collector_state *state;
|
1983
|
+
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1984
|
+
|
1985
|
+
struct per_thread_context *thread_context = get_context_for(thread, state);
|
1986
|
+
if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
1987
|
+
|
1988
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
1989
|
+
|
1990
|
+
return Qtrue;
|
1991
|
+
}
|
1992
|
+
|
1993
|
+
#else
|
1994
|
+
static bool handle_gvl_waiting(
|
1995
|
+
DDTRACE_UNUSED struct thread_context_collector_state *state,
|
1996
|
+
DDTRACE_UNUSED VALUE thread_being_sampled,
|
1997
|
+
DDTRACE_UNUSED VALUE stack_from_thread,
|
1998
|
+
DDTRACE_UNUSED struct per_thread_context *thread_context,
|
1999
|
+
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
2000
|
+
DDTRACE_UNUSED long current_cpu_time_ns
|
2001
|
+
) { return false; }
|
2002
|
+
#endif // NO_GVL_INSTRUMENTATION
|