datadog 2.18.0 → 2.19.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 +47 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -10
- data/ext/datadog_profiling_native_extension/collectors_stack.c +58 -49
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -6
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +37 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +0 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -1
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +20 -0
- data/lib/datadog/core/telemetry/component.rb +8 -4
- data/lib/datadog/core/telemetry/event/app_started.rb +21 -3
- data/lib/datadog/di/instrumenter.rb +11 -18
- data/lib/datadog/di/probe_notification_builder.rb +21 -16
- data/lib/datadog/di/serializer.rb +6 -2
- data/lib/datadog/di.rb +0 -6
- data/lib/datadog/kit/appsec/events/v2.rb +195 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -0
- data/lib/datadog/profiling/collectors/info.rb +41 -0
- data/lib/datadog/profiling/component.rb +1 -0
- data/lib/datadog/profiling/exporter.rb +9 -3
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/tag_builder.rb +2 -0
- data/lib/datadog/profiling.rb +1 -0
- data/lib/datadog/single_step_instrument.rb +9 -0
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
- data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/tracing/span_event.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +22 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +7 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c533682b9a96989e1ad8d4eb96339af4bffdb5fd9cbfe447bd0a034bc387c03
|
4
|
+
data.tar.gz: 5d7808aa6b7fd5f9c68e453fef4fff8c5345c62fb26a7f7839d630ec0da2fe9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b35d7ef1ce2f9e565727037f9e5b329d019202ff20c01a15bb5096cf14253b3ab9812d4fc84fc23e265ec2e7e0096e34acc6a845ad510b215ef7df4093107e7
|
7
|
+
data.tar.gz: 72fff247b51de201e4373638627fa419a3a679979f880dfa44c73d1c9a7c062991746045d41cbe5fa47d824b025fb9cad2f34c0139e034df1b85ddb4a07bccd9
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,35 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [2.19.0] - 2025-07-24
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
* AppSec: Added Business Logic Events SDK v2. ([#4802][])
|
10
|
+
* Tracing: Add `record_exception` API to capture and attach error information to spans via span events. ([#4771][])
|
11
|
+
* Tracing: Add `:cache_store` option to ActiveSupport integration to allow tracing only specified cache backends. ([#4693][])
|
12
|
+
* SSI: Rework SSI from the ground up. ([#4366][])
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
* Profiling: Switch profiler stack truncation strategy and improve sampling performance ([#4819][])
|
17
|
+
* Profiling: Report GC tuning environment variables with profiles ([#4813][])
|
18
|
+
* Profiling: Tag profiles with sequence number ([#4794][])
|
19
|
+
* Profiling: Enable sample from inside signal handler by default on modern Rubies ([#4786][], [#4785][])
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
* Core: Fix emitting duplicate warnings on agent configuration mismatch ([#4814][])
|
24
|
+
* Appsec: Fix an error in AppSec route extractor for not-found routes in Rails 8 ([#4793][])
|
25
|
+
* Profiling: Add workaround for Ruby VM bug ([#4787][])
|
26
|
+
* Profiling: Fix checking for dladdr in profiling ([#4783][])
|
27
|
+
* Profiling: Fix potential profiler compilation issue. ([#4783][])
|
28
|
+
* Tracing: The mysql integration now only sets the `db.name` tag if there is a valid value ([#4776][])
|
29
|
+
* Tracing: The Rails Runner instrumentation should now create Rails Runner spans. ([#4681][])
|
30
|
+
* Tracing: Fix sampling rules and sample rate reporting in environment logger. ([#4772][])
|
31
|
+
|
32
|
+
### Removed
|
33
|
+
|
5
34
|
## [2.18.0] - 2025-07-03
|
6
35
|
|
7
36
|
### Added
|
@@ -3268,7 +3297,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
3268
3297
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
3269
3298
|
|
3270
3299
|
|
3271
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
3300
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.19.0...master
|
3301
|
+
[2.19.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.18.0...v2.19.0
|
3272
3302
|
[2.18.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.17.0...v2.18.0
|
3273
3303
|
[2.17.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.16.0...v2.17.0
|
3274
3304
|
[2.16.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.15.0...v2.16.0
|
@@ -4770,6 +4800,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4770
4800
|
[#4353]: https://github.com/DataDog/dd-trace-rb/issues/4353
|
4771
4801
|
[#4360]: https://github.com/DataDog/dd-trace-rb/issues/4360
|
4772
4802
|
[#4363]: https://github.com/DataDog/dd-trace-rb/issues/4363
|
4803
|
+
[#4366]: https://github.com/DataDog/dd-trace-rb/issues/4366
|
4773
4804
|
[#4391]: https://github.com/DataDog/dd-trace-rb/issues/4391
|
4774
4805
|
[#4398]: https://github.com/DataDog/dd-trace-rb/issues/4398
|
4775
4806
|
[#4399]: https://github.com/DataDog/dd-trace-rb/issues/4399
|
@@ -4827,7 +4858,9 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4827
4858
|
[#4673]: https://github.com/DataDog/dd-trace-rb/issues/4673
|
4828
4859
|
[#4678]: https://github.com/DataDog/dd-trace-rb/issues/4678
|
4829
4860
|
[#4679]: https://github.com/DataDog/dd-trace-rb/issues/4679
|
4861
|
+
[#4681]: https://github.com/DataDog/dd-trace-rb/issues/4681
|
4830
4862
|
[#4688]: https://github.com/DataDog/dd-trace-rb/issues/4688
|
4863
|
+
[#4693]: https://github.com/DataDog/dd-trace-rb/issues/4693
|
4831
4864
|
[#4697]: https://github.com/DataDog/dd-trace-rb/issues/4697
|
4832
4865
|
[#4699]: https://github.com/DataDog/dd-trace-rb/issues/4699
|
4833
4866
|
[#4718]: https://github.com/DataDog/dd-trace-rb/issues/4718
|
@@ -4838,6 +4871,19 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4838
4871
|
[#4745]: https://github.com/DataDog/dd-trace-rb/issues/4745
|
4839
4872
|
[#4756]: https://github.com/DataDog/dd-trace-rb/issues/4756
|
4840
4873
|
[#4757]: https://github.com/DataDog/dd-trace-rb/issues/4757
|
4874
|
+
[#4771]: https://github.com/DataDog/dd-trace-rb/issues/4771
|
4875
|
+
[#4772]: https://github.com/DataDog/dd-trace-rb/issues/4772
|
4876
|
+
[#4776]: https://github.com/DataDog/dd-trace-rb/issues/4776
|
4877
|
+
[#4783]: https://github.com/DataDog/dd-trace-rb/issues/4783
|
4878
|
+
[#4785]: https://github.com/DataDog/dd-trace-rb/issues/4785
|
4879
|
+
[#4786]: https://github.com/DataDog/dd-trace-rb/issues/4786
|
4880
|
+
[#4787]: https://github.com/DataDog/dd-trace-rb/issues/4787
|
4881
|
+
[#4793]: https://github.com/DataDog/dd-trace-rb/issues/4793
|
4882
|
+
[#4794]: https://github.com/DataDog/dd-trace-rb/issues/4794
|
4883
|
+
[#4802]: https://github.com/DataDog/dd-trace-rb/issues/4802
|
4884
|
+
[#4813]: https://github.com/DataDog/dd-trace-rb/issues/4813
|
4885
|
+
[#4814]: https://github.com/DataDog/dd-trace-rb/issues/4814
|
4886
|
+
[#4819]: https://github.com/DataDog/dd-trace-rb/issues/4819
|
4841
4887
|
[@AdrianLC]: https://github.com/AdrianLC
|
4842
4888
|
[@Azure7111]: https://github.com/Azure7111
|
4843
4889
|
[@BabyGroot]: https://github.com/BabyGroot
|
@@ -102,6 +102,7 @@ typedef struct {
|
|
102
102
|
bool allocation_counting_enabled;
|
103
103
|
bool gvl_profiling_enabled;
|
104
104
|
bool skip_idle_samples_for_testing;
|
105
|
+
bool sighandler_sampling_enabled;
|
105
106
|
VALUE self_instance;
|
106
107
|
VALUE thread_context_collector_instance;
|
107
108
|
VALUE idle_sampling_helper_instance;
|
@@ -142,8 +143,10 @@ typedef struct {
|
|
142
143
|
unsigned int trigger_simulated_signal_delivery_attempts;
|
143
144
|
// How many times we actually simulated signal delivery
|
144
145
|
unsigned int simulated_signal_delivery;
|
145
|
-
// How many times we actually called rb_postponed_job_register_one from
|
146
|
+
// How many times we actually called rb_postponed_job_register_one from the signal handler
|
146
147
|
unsigned int signal_handler_enqueued_sample;
|
148
|
+
// How many times we prepared a sample (sampled directly) from the signal handler
|
149
|
+
unsigned int signal_handler_prepared_sample;
|
147
150
|
// How many times the signal handler was called from the wrong thread
|
148
151
|
unsigned int signal_handler_wrong_thread;
|
149
152
|
// How many times we actually tried to interrupt a thread for sampling
|
@@ -232,6 +235,8 @@ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
|
|
232
235
|
#endif
|
233
236
|
static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance);
|
234
237
|
static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
|
238
|
+
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state);
|
239
|
+
static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
|
235
240
|
|
236
241
|
// We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
|
237
242
|
// with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
|
@@ -356,6 +361,7 @@ static VALUE _native_new(VALUE klass) {
|
|
356
361
|
state->allocation_counting_enabled = false;
|
357
362
|
state->gvl_profiling_enabled = false;
|
358
363
|
state->skip_idle_samples_for_testing = false;
|
364
|
+
state->sighandler_sampling_enabled = false;
|
359
365
|
state->thread_context_collector_instance = Qnil;
|
360
366
|
state->idle_sampling_helper_instance = Qnil;
|
361
367
|
state->owner_thread = Qnil;
|
@@ -366,7 +372,7 @@ static VALUE _native_new(VALUE klass) {
|
|
366
372
|
state->failure_exception = Qnil;
|
367
373
|
state->stop_thread = Qnil;
|
368
374
|
|
369
|
-
state
|
375
|
+
during_sample_exit(state);
|
370
376
|
|
371
377
|
#ifndef NO_GVL_INSTRUMENTATION
|
372
378
|
state->gvl_profiling_hook = NULL;
|
@@ -398,6 +404,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
398
404
|
VALUE allocation_counting_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_counting_enabled")));
|
399
405
|
VALUE gvl_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gvl_profiling_enabled")));
|
400
406
|
VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
|
407
|
+
VALUE sighandler_sampling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("sighandler_sampling_enabled")));
|
401
408
|
|
402
409
|
ENFORCE_BOOLEAN(gc_profiling_enabled);
|
403
410
|
ENFORCE_BOOLEAN(no_signals_workaround_enabled);
|
@@ -407,6 +414,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
407
414
|
ENFORCE_BOOLEAN(allocation_counting_enabled);
|
408
415
|
ENFORCE_BOOLEAN(gvl_profiling_enabled);
|
409
416
|
ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
|
417
|
+
ENFORCE_BOOLEAN(sighandler_sampling_enabled)
|
410
418
|
|
411
419
|
cpu_and_wall_time_worker_state *state;
|
412
420
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
@@ -418,6 +426,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
418
426
|
state->allocation_counting_enabled = (allocation_counting_enabled == Qtrue);
|
419
427
|
state->gvl_profiling_enabled = (gvl_profiling_enabled == Qtrue);
|
420
428
|
state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
|
429
|
+
state->sighandler_sampling_enabled = (sighandler_sampling_enabled == Qtrue);
|
421
430
|
|
422
431
|
double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
|
423
432
|
if (!state->allocation_profiling_enabled) {
|
@@ -590,6 +599,19 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
|
|
590
599
|
|
591
600
|
state->stats.signal_handler_enqueued_sample++;
|
592
601
|
|
602
|
+
bool sample_from_signal_handler =
|
603
|
+
state->sighandler_sampling_enabled &&
|
604
|
+
// Don't sample if we're already in the middle of processing a sample
|
605
|
+
!state->during_sample;
|
606
|
+
|
607
|
+
if (sample_from_signal_handler) {
|
608
|
+
// Buffer current stack trace. Note that this will not actually record the sample, for that we still need to wait
|
609
|
+
// until the postponed job below gets run.
|
610
|
+
bool prepared = thread_context_collector_prepare_sample_inside_signal_handler(state->thread_context_collector_instance);
|
611
|
+
|
612
|
+
if (prepared) state->stats.signal_handler_prepared_sample++;
|
613
|
+
}
|
614
|
+
|
593
615
|
#ifndef NO_POSTPONED_TRIGGER // Ruby 3.3+
|
594
616
|
rb_postponed_job_trigger(sample_from_postponed_job_handle);
|
595
617
|
#else
|
@@ -701,12 +723,12 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
701
723
|
return; // We're not on the main Ractor; we currently don't support profiling non-main Ractors
|
702
724
|
}
|
703
725
|
|
704
|
-
state
|
726
|
+
during_sample_enter(state);
|
705
727
|
|
706
728
|
// Rescue against any exceptions that happen during sampling
|
707
729
|
safely_call(rescued_sample_from_postponed_job, state->self_instance, state->self_instance);
|
708
730
|
|
709
|
-
state
|
731
|
+
during_sample_exit(state);
|
710
732
|
}
|
711
733
|
|
712
734
|
static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
@@ -912,11 +934,11 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
912
934
|
return; // We're not on the main Ractor; we currently don't support profiling non-main Ractors
|
913
935
|
}
|
914
936
|
|
915
|
-
state
|
937
|
+
during_sample_enter(state);
|
916
938
|
|
917
939
|
safely_call(thread_context_collector_sample_after_gc, state->thread_context_collector_instance, state->self_instance);
|
918
940
|
|
919
|
-
state
|
941
|
+
during_sample_exit(state);
|
920
942
|
}
|
921
943
|
|
922
944
|
// Equivalent to Ruby begin/rescue call, where we call a C function and jump to the exception handler if an
|
@@ -994,6 +1016,7 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
994
1016
|
ID2SYM(rb_intern("trigger_simulated_signal_delivery_attempts")), /* => */ UINT2NUM(state->stats.trigger_simulated_signal_delivery_attempts),
|
995
1017
|
ID2SYM(rb_intern("simulated_signal_delivery")), /* => */ UINT2NUM(state->stats.simulated_signal_delivery),
|
996
1018
|
ID2SYM(rb_intern("signal_handler_enqueued_sample")), /* => */ UINT2NUM(state->stats.signal_handler_enqueued_sample),
|
1019
|
+
ID2SYM(rb_intern("signal_handler_prepared_sample")), /* => */ UINT2NUM(state->stats.signal_handler_prepared_sample),
|
997
1020
|
ID2SYM(rb_intern("signal_handler_wrong_thread")), /* => */ UINT2NUM(state->stats.signal_handler_wrong_thread),
|
998
1021
|
ID2SYM(rb_intern("interrupt_thread_attempts")), /* => */ UINT2NUM(state->stats.interrupt_thread_attempts),
|
999
1022
|
|
@@ -1177,7 +1200,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
1177
1200
|
&state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
|
1178
1201
|
);
|
1179
1202
|
|
1180
|
-
state
|
1203
|
+
during_sample_enter(state);
|
1181
1204
|
|
1182
1205
|
// Rescue against any exceptions that happen during sampling
|
1183
1206
|
safely_call(rescued_sample_allocation, Qnil, state->self_instance);
|
@@ -1198,7 +1221,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
1198
1221
|
|
1199
1222
|
state->stats.allocation_sampled++;
|
1200
1223
|
|
1201
|
-
state
|
1224
|
+
during_sample_exit(state);
|
1202
1225
|
}
|
1203
1226
|
|
1204
1227
|
static void disable_tracepoints(cpu_and_wall_time_worker_state *state) {
|
@@ -1339,12 +1362,12 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
1339
1362
|
// This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
|
1340
1363
|
if (state == NULL) return;
|
1341
1364
|
|
1342
|
-
state
|
1365
|
+
during_sample_enter(state);
|
1343
1366
|
|
1344
1367
|
// Rescue against any exceptions that happen during sampling
|
1345
1368
|
safely_call(rescued_after_gvl_running_from_postponed_job, state->self_instance, state->self_instance);
|
1346
1369
|
|
1347
|
-
state
|
1370
|
+
during_sample_exit(state);
|
1348
1371
|
}
|
1349
1372
|
|
1350
1373
|
static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance) {
|
@@ -1380,3 +1403,21 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
1380
1403
|
return Qfalse;
|
1381
1404
|
}
|
1382
1405
|
#endif
|
1406
|
+
|
1407
|
+
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state) {
|
1408
|
+
// Tell the compiler it's not allowed to reorder the `during_sample` flag with anything that happens after.
|
1409
|
+
//
|
1410
|
+
// In a few cases, we may be checking this flag from a signal handler, so we need to make sure the compiler didn't
|
1411
|
+
// get clever and reordered things in such a way that makes us miss the flag update.
|
1412
|
+
//
|
1413
|
+
// See https://github.com/ruby/ruby/pull/11036 for a similar change made to the Ruby VM with more context.
|
1414
|
+
state->during_sample = true;
|
1415
|
+
atomic_signal_fence(memory_order_seq_cst);
|
1416
|
+
}
|
1417
|
+
|
1418
|
+
static inline void during_sample_exit(cpu_and_wall_time_worker_state* state) {
|
1419
|
+
// See `during_sample_enter` for more context; in this case we set the fence before to make sure anything that
|
1420
|
+
// happens before the fence is not reordered with the flag update.
|
1421
|
+
atomic_signal_fence(memory_order_seq_cst);
|
1422
|
+
state->during_sample = false;
|
1423
|
+
}
|
@@ -1,16 +1,16 @@
|
|
1
1
|
#include <ruby.h>
|
2
2
|
#include <ruby/debug.h>
|
3
3
|
#include <ruby/st.h>
|
4
|
+
#include <stdatomic.h>
|
4
5
|
|
5
6
|
#include "extconf.h" // This is needed for the HAVE_DLADDR and friends below
|
6
7
|
|
7
|
-
|
8
|
-
#if defined(HAVE_DLADDR1) || defined(HAVE_DLADDR)
|
8
|
+
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
9
9
|
#ifndef _GNU_SOURCE
|
10
10
|
#define _GNU_SOURCE
|
11
11
|
#endif
|
12
12
|
#include <dlfcn.h>
|
13
|
-
#
|
13
|
+
#if defined(HAVE_DLADDR1) && HAVE_DLADDR1
|
14
14
|
#include <link.h>
|
15
15
|
#endif
|
16
16
|
#endif
|
@@ -39,7 +39,7 @@ static void set_file_info_for_cfunc(
|
|
39
39
|
st_table *native_filenames_cache
|
40
40
|
);
|
41
41
|
static const char *get_or_compute_native_filename(void *function, st_table *native_filenames_cache);
|
42
|
-
static void
|
42
|
+
static void add_truncated_frames_placeholder(sampling_buffer* buffer);
|
43
43
|
static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
|
44
44
|
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
|
45
45
|
|
@@ -63,24 +63,24 @@ void collectors_stack_init(VALUE profiling_module) {
|
|
63
63
|
|
64
64
|
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, -1);
|
65
65
|
|
66
|
-
#if defined(HAVE_DLADDR1) || defined(HAVE_DLADDR)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
66
|
+
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
67
|
+
// To be able to detect when a frame is coming from Ruby, we record here its filename as returned by dladdr.
|
68
|
+
// We expect this same pointer to be returned by dladdr for all frames coming from Ruby.
|
69
|
+
//
|
70
|
+
// Small note: Creating/deleting the cache is a bit awkward here, but it seems like a bigger footgun to allow
|
71
|
+
// `get_or_compute_native_filename` to run without a cache, since we never expect that to happen during sampling. So it seems
|
72
|
+
// like a reasonable trade-off to force callers to always figure that out.
|
73
|
+
st_table *temporary_cache = st_init_numtable();
|
74
|
+
const char *native_filename = get_or_compute_native_filename(rb_ary_new, temporary_cache);
|
75
|
+
if (native_filename != NULL && native_filename[0] != '\0') {
|
76
|
+
ruby_native_filename = native_filename;
|
77
|
+
}
|
78
|
+
st_free_table(temporary_cache);
|
79
79
|
#endif
|
80
80
|
}
|
81
81
|
|
82
82
|
static VALUE _native_filenames_available(DDTRACE_UNUSED VALUE self) {
|
83
|
-
#if defined(HAVE_DLADDR1) || defined(HAVE_DLADDR)
|
83
|
+
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
84
84
|
return ruby_native_filename != NULL ? Qtrue : Qfalse;
|
85
85
|
#else
|
86
86
|
return Qfalse;
|
@@ -271,7 +271,8 @@ void sample_thread(
|
|
271
271
|
// The convention in Kernel#caller_locations is to instead use the path and line number of the first Ruby frame
|
272
272
|
// on the stack that is below (e.g. directly or indirectly has called) the native method.
|
273
273
|
// Thus, we keep that frame here to able to replicate that behavior.
|
274
|
-
// (This is why we also iterate the sampling buffers backwards below -- so that it's easier
|
274
|
+
// (This is why we also iterate the sampling buffers backwards from what libdatadog uses below -- so that it's easier
|
275
|
+
// to keep the last_ruby_frame_filename)
|
275
276
|
ddog_CharSlice last_ruby_frame_filename = DDOG_CHARSLICE_C("");
|
276
277
|
int last_ruby_line = 0;
|
277
278
|
|
@@ -290,10 +291,12 @@ void sample_thread(
|
|
290
291
|
if (labels.is_gvl_waiting_state) rb_raise(rb_eRuntimeError, "BUG: Unexpected combination of cpu-time with is_gvl_waiting");
|
291
292
|
}
|
292
293
|
|
293
|
-
|
294
|
+
int top_of_stack_position = captured_frames - 1;
|
295
|
+
|
296
|
+
for (int i = 0; i <= top_of_stack_position; i++) {
|
294
297
|
ddog_CharSlice name_slice, filename_slice;
|
295
298
|
int line;
|
296
|
-
bool top_of_the_stack = i ==
|
299
|
+
bool top_of_the_stack = i == top_of_stack_position;
|
297
300
|
|
298
301
|
if (buffer->stack_buffer[i].is_ruby_frame) {
|
299
302
|
VALUE name = rb_iseq_base_label(buffer->stack_buffer[i].as.ruby_frame.iseq);
|
@@ -324,7 +327,6 @@ void sample_thread(
|
|
324
327
|
|
325
328
|
maybe_trim_template_random_ids(&name_slice, &filename_slice);
|
326
329
|
|
327
|
-
|
328
330
|
// When there's only wall-time in a sample, this means that the thread was not active in the sampled period.
|
329
331
|
if (top_of_the_stack && only_wall_time) {
|
330
332
|
// Did the caller already provide the state?
|
@@ -368,21 +370,19 @@ void sample_thread(
|
|
368
370
|
}
|
369
371
|
}
|
370
372
|
|
371
|
-
|
373
|
+
int libdatadog_stores_stacks_flipped_from_rb_profile_frames_index = top_of_stack_position - i;
|
374
|
+
|
375
|
+
buffer->locations[libdatadog_stores_stacks_flipped_from_rb_profile_frames_index] = (ddog_prof_Location) {
|
372
376
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
373
377
|
.function = (ddog_prof_Function) {.name = name_slice, .filename = filename_slice},
|
374
378
|
.line = line,
|
375
379
|
};
|
376
380
|
}
|
377
381
|
|
378
|
-
// Used below; since we want to stack-allocate this, we must do it here rather than in maybe_add_placeholder_frames_omitted
|
379
|
-
const int frames_omitted_message_size = sizeof(MAX_FRAMES_LIMIT_AS_STRING " frames omitted");
|
380
|
-
char frames_omitted_message[frames_omitted_message_size];
|
381
|
-
|
382
382
|
// If we filled up the buffer, some frames may have been omitted. In that case, we'll add a placeholder frame
|
383
383
|
// with that info.
|
384
384
|
if (captured_frames == (long) buffer->max_frames) {
|
385
|
-
|
385
|
+
add_truncated_frames_placeholder(buffer);
|
386
386
|
}
|
387
387
|
|
388
388
|
record_sample(
|
@@ -393,7 +393,7 @@ void sample_thread(
|
|
393
393
|
);
|
394
394
|
}
|
395
395
|
|
396
|
-
#if defined(HAVE_DLADDR1) || defined(HAVE_DLADDR)
|
396
|
+
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
397
397
|
static void set_file_info_for_cfunc(
|
398
398
|
ddog_CharSlice *filename_slice,
|
399
399
|
int *line,
|
@@ -441,12 +441,12 @@ void sample_thread(
|
|
441
441
|
|
442
442
|
Dl_info info;
|
443
443
|
const char *native_filename = NULL;
|
444
|
-
#
|
444
|
+
#if defined(HAVE_DLADDR1) && HAVE_DLADDR1
|
445
445
|
struct link_map *extra_info = NULL;
|
446
446
|
if (dladdr1(function, &info, (void **) &extra_info, RTLD_DL_LINKMAP) != 0 && extra_info != NULL) {
|
447
447
|
native_filename = extra_info->l_name != NULL ? extra_info->l_name : info.dli_fname;
|
448
448
|
}
|
449
|
-
#elif defined(HAVE_DLADDR)
|
449
|
+
#elif defined(HAVE_DLADDR) && HAVE_DLADDR
|
450
450
|
if (dladdr(function, &info) != 0) {
|
451
451
|
native_filename = info.dli_fname;
|
452
452
|
}
|
@@ -521,24 +521,12 @@ static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_Char
|
|
521
521
|
name_slice->len = pos;
|
522
522
|
}
|
523
523
|
|
524
|
-
static void
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
// The placeholder frame takes over a space, so if 10 frames were left out and we consume one other space for the
|
530
|
-
// placeholder, then 11 frames are omitted in total
|
531
|
-
frames_omitted++;
|
532
|
-
|
533
|
-
snprintf(frames_omitted_message, frames_omitted_message_size, "%td frames omitted", frames_omitted);
|
534
|
-
|
535
|
-
// Important note: `frames_omitted_message` MUST have a lifetime that is at least as long as the call to
|
536
|
-
// `record_sample`. So be careful where it gets allocated. (We do have tests for this, at least!)
|
537
|
-
ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
|
538
|
-
ddog_CharSlice function_filename = {.ptr = frames_omitted_message, .len = strlen(frames_omitted_message)};
|
539
|
-
buffer->locations[buffer->max_frames - 1] = (ddog_prof_Location) {
|
524
|
+
static void add_truncated_frames_placeholder(sampling_buffer* buffer) {
|
525
|
+
// Important note: The strings below are static so we don't need to worry about their lifetime. If we ever want to change
|
526
|
+
// this to non-static strings, don't forget to check that lifetimes are properly respected.
|
527
|
+
buffer->locations[0] = (ddog_prof_Location) {
|
540
528
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
541
|
-
.function =
|
529
|
+
.function = {.name = DDOG_CHARSLICE_C("Truncated Frames"), .filename = DDOG_CHARSLICE_C(""), .filename_id = {}},
|
542
530
|
.line = 0,
|
543
531
|
};
|
544
532
|
}
|
@@ -597,9 +585,14 @@ void record_placeholder_stack(
|
|
597
585
|
);
|
598
586
|
}
|
599
587
|
|
600
|
-
|
588
|
+
bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer) {
|
589
|
+
// Since this can get called from inside a signal handler, we don't want to touch the buffer if
|
590
|
+
// the thread was actually in the middle of marking it.
|
591
|
+
if (buffer->is_marking) return false;
|
592
|
+
|
601
593
|
buffer->pending_sample = true;
|
602
594
|
buffer->pending_sample_result = ddtrace_rb_profile_frames(thread, 0, buffer->max_frames, buffer->stack_buffer);
|
595
|
+
return true;
|
603
596
|
}
|
604
597
|
|
605
598
|
uint16_t sampling_buffer_check_max_frames(int max_frames) {
|
@@ -615,6 +608,7 @@ void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, dd
|
|
615
608
|
buffer->locations = locations;
|
616
609
|
buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(frame_info));
|
617
610
|
buffer->pending_sample = false;
|
611
|
+
buffer->is_marking = false;
|
618
612
|
buffer->pending_sample_result = 0;
|
619
613
|
}
|
620
614
|
|
@@ -630,6 +624,7 @@ void sampling_buffer_free(sampling_buffer *buffer) {
|
|
630
624
|
buffer->locations = NULL;
|
631
625
|
buffer->stack_buffer = NULL;
|
632
626
|
buffer->pending_sample = false;
|
627
|
+
buffer->is_marking = false;
|
633
628
|
buffer->pending_sample_result = 0;
|
634
629
|
}
|
635
630
|
|
@@ -638,9 +633,23 @@ void sampling_buffer_mark(sampling_buffer *buffer) {
|
|
638
633
|
rb_bug("sampling_buffer_mark called with no pending sample. `sampling_buffer_needs_marking` should be used before calling mark.");
|
639
634
|
}
|
640
635
|
|
636
|
+
buffer->is_marking = true;
|
637
|
+
// Tell the compiler it's not allowed to reorder the `is_marking` flag with the iteration below.
|
638
|
+
//
|
639
|
+
// Specifically, in the middle of `sampling_buffer_mark` a signal handler may execute and call
|
640
|
+
// `prepare_sample_thread` to add a new sample to the buffer. This flag is here to prevent that BUT we need to
|
641
|
+
// make sure the signal handler actually sees the flag being set.
|
642
|
+
//
|
643
|
+
// See https://github.com/ruby/ruby/pull/11036 for a similar change made to the Ruby VM with more context.
|
644
|
+
atomic_signal_fence(memory_order_seq_cst);
|
645
|
+
|
641
646
|
for (int i = 0; i < buffer->pending_sample_result; i++) {
|
642
647
|
if (buffer->stack_buffer[i].is_ruby_frame) {
|
643
648
|
rb_gc_mark(buffer->stack_buffer[i].as.ruby_frame.iseq);
|
644
649
|
}
|
645
650
|
}
|
651
|
+
|
652
|
+
// Make sure iteration completes before `is_marking` is unset...
|
653
|
+
atomic_signal_fence(memory_order_seq_cst);
|
654
|
+
buffer->is_marking = false;
|
646
655
|
}
|
@@ -14,6 +14,7 @@ typedef struct {
|
|
14
14
|
ddog_prof_Location *locations;
|
15
15
|
frame_info *stack_buffer;
|
16
16
|
bool pending_sample;
|
17
|
+
bool is_marking; // Used to avoid recording a sample when marking
|
17
18
|
int pending_sample_result;
|
18
19
|
} sampling_buffer;
|
19
20
|
|
@@ -32,7 +33,7 @@ void record_placeholder_stack(
|
|
32
33
|
sample_labels labels,
|
33
34
|
ddog_CharSlice placeholder_stack
|
34
35
|
);
|
35
|
-
|
36
|
+
bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer);
|
36
37
|
|
37
38
|
uint16_t sampling_buffer_check_max_frames(int max_frames);
|
38
39
|
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, ddog_prof_Location *locations);
|
@@ -1469,17 +1469,17 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
1469
1469
|
// expected to be called from a signal handler and to be async-signal-safe.
|
1470
1470
|
//
|
1471
1471
|
// Also, no allocation (Ruby or malloc) can happen.
|
1472
|
-
|
1472
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(VALUE self_instance) {
|
1473
1473
|
thread_context_collector_state *state;
|
1474
|
-
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
1474
|
+
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
1475
1475
|
// This should never fail if the above check passes
|
1476
1476
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1477
1477
|
|
1478
1478
|
VALUE current_thread = rb_thread_current();
|
1479
1479
|
per_thread_context *thread_context = get_context_for(current_thread, state);
|
1480
|
-
if (thread_context == NULL) return;
|
1480
|
+
if (thread_context == NULL) return false;
|
1481
1481
|
|
1482
|
-
prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
1482
|
+
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
1483
1483
|
}
|
1484
1484
|
|
1485
1485
|
void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
|
@@ -2217,6 +2217,5 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
2217
2217
|
}
|
2218
2218
|
|
2219
2219
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
2220
|
-
thread_context_collector_prepare_sample_inside_signal_handler(collector_instance);
|
2221
|
-
return Qtrue;
|
2220
|
+
return thread_context_collector_prepare_sample_inside_signal_handler(collector_instance) ? Qtrue : Qfalse;
|
2222
2221
|
}
|
@@ -10,7 +10,7 @@ void thread_context_collector_sample(
|
|
10
10
|
long current_monotonic_wall_time_ns,
|
11
11
|
VALUE profiler_overhead_stack_thread
|
12
12
|
);
|
13
|
-
|
13
|
+
__attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(VALUE self_instance);
|
14
14
|
void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object);
|
15
15
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
|
16
16
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
|