datadog 2.39.0 → 2.40.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 +27 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +218 -147
- data/ext/datadog_profiling_native_extension/collectors_stack.h +16 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +12 -7
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +12 -7
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +259 -65
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +16 -10
- data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
- data/ext/libdatadog_api/datadog_ruby_common.h +7 -0
- data/ext/libdatadog_api/init.c +4 -0
- data/ext/libdatadog_api/trace_exporter.c +974 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/lib/datadog/ai_guard/evaluation.rb +1 -0
- data/lib/datadog/ai_guard/ext.rb +2 -0
- data/lib/datadog/appsec/configuration.rb +9 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
- data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
- data/lib/datadog/core/configuration/settings.rb +33 -41
- data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
- data/lib/datadog/core/environment/gc.rb +1 -1
- data/lib/datadog/core/environment/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +2 -2
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +80 -12
- data/lib/datadog/core/utils/sequence.rb +3 -6
- data/lib/datadog/core/utils/time.rb +16 -54
- data/lib/datadog/di/el/compiler.rb +65 -12
- data/lib/datadog/di/el/evaluator.rb +82 -2
- data/lib/datadog/di/el/expression.rb +10 -2
- data/lib/datadog/di/probe_builder.rb +6 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +6 -13
- data/lib/datadog/profiling/component.rb +1 -0
- data/lib/datadog/profiling.rb +4 -0
- data/lib/datadog/tracing/component.rb +21 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +16 -0
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
- data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
- data/lib/datadog/tracing/sync_writer.rb +8 -3
- data/lib/datadog/tracing/tracer.rb +8 -0
- data/lib/datadog/tracing/transport/native/response.rb +71 -0
- data/lib/datadog/tracing/transport/native.rb +363 -0
- data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/tracing/writer.rb +16 -3
- data/lib/datadog/version.rb +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4894d3cc2404bab418412c2a96b2f15c2337f6e31dc567c6fd8d1ceafd372230
|
|
4
|
+
data.tar.gz: e31bdb882385e48f731b925496b75964a02e17c4eb9c7b4a858083740009954c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 488fc94df9bda0fd8485f42934a08660a342617602e61a0fb999cb337239f98f226fc51e021df954ab74af985a6abfa38cc0b17069c00b0541c5100e8c961810
|
|
7
|
+
data.tar.gz: '0197b5995f7861ddae8677caa96d419c89e2b25bb702d3c05e99e594562771c2c3193d8e9cc367bde5ebbd3fbd78362f55ea25fb1c071442b01d37a22c8d631e'
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.40.0] - 2026-08-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Profiling: Add experimental profiling setting to show class/module names in stack frames. Fix crash in `rb_iseq_path()` caused by incorrect GC marking. ([#6024][])
|
|
10
|
+
* Tracing: Add an experimental native (`libdatadog`-backed) trace transport, selectable via `tracing.native_transport` ([#5971][])
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
* Core: Deprecate `time_now_provider` setting for removal; it no longer does anything. Please remove it from your `Datadog.configure block`. The datadog gem always uses the real non-mocked time, even when the timecop gem monkey-patches `Time` ([#6010][])
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* AppSec: Fix standalone billing issues for partially flushed traces ([#6116][])
|
|
19
|
+
* Dynamic Instrumentation: Limit dynamic instrumentation regular expression evaluation time to 500ms ([#5908][])
|
|
20
|
+
* Profiling: Fix missing native filename for static Ruby binaries ([#6097][])
|
|
21
|
+
* Tracing: Fix missing or zero Kafka producer and connection span tags like `kafka.message_count` and `kafka.request_size` in the `ruby-kafka` integration. ([#6104][])
|
|
22
|
+
|
|
5
23
|
## [2.39.0] - 2026-07-22
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -3716,7 +3734,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3716
3734
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3717
3735
|
|
|
3718
3736
|
|
|
3719
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3737
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.40.0...master
|
|
3738
|
+
[2.40.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.39.0...v2.40.0
|
|
3720
3739
|
[2.39.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.38.0...v2.39.0
|
|
3721
3740
|
[2.38.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.37.0...v2.38.0
|
|
3722
3741
|
[2.37.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.36.0...v2.37.0
|
|
@@ -5496,6 +5515,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5496
5515
|
[#5890]: https://github.com/DataDog/dd-trace-rb/issues/5890
|
|
5497
5516
|
[#5894]: https://github.com/DataDog/dd-trace-rb/issues/5894
|
|
5498
5517
|
[#5907]: https://github.com/DataDog/dd-trace-rb/issues/5907
|
|
5518
|
+
[#5908]: https://github.com/DataDog/dd-trace-rb/issues/5908
|
|
5499
5519
|
[#5911]: https://github.com/DataDog/dd-trace-rb/issues/5911
|
|
5500
5520
|
[#5916]: https://github.com/DataDog/dd-trace-rb/issues/5916
|
|
5501
5521
|
[#5920]: https://github.com/DataDog/dd-trace-rb/issues/5920
|
|
@@ -5506,6 +5526,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5506
5526
|
[#5955]: https://github.com/DataDog/dd-trace-rb/issues/5955
|
|
5507
5527
|
[#5960]: https://github.com/DataDog/dd-trace-rb/issues/5960
|
|
5508
5528
|
[#5970]: https://github.com/DataDog/dd-trace-rb/issues/5970
|
|
5529
|
+
[#5971]: https://github.com/DataDog/dd-trace-rb/issues/5971
|
|
5509
5530
|
[#5973]: https://github.com/DataDog/dd-trace-rb/issues/5973
|
|
5510
5531
|
[#5975]: https://github.com/DataDog/dd-trace-rb/issues/5975
|
|
5511
5532
|
[#5981]: https://github.com/DataDog/dd-trace-rb/issues/5981
|
|
@@ -5513,16 +5534,21 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5513
5534
|
[#5990]: https://github.com/DataDog/dd-trace-rb/issues/5990
|
|
5514
5535
|
[#5991]: https://github.com/DataDog/dd-trace-rb/issues/5991
|
|
5515
5536
|
[#5993]: https://github.com/DataDog/dd-trace-rb/issues/5993
|
|
5537
|
+
[#6010]: https://github.com/DataDog/dd-trace-rb/issues/6010
|
|
5516
5538
|
[#6014]: https://github.com/DataDog/dd-trace-rb/issues/6014
|
|
5517
5539
|
[#6020]: https://github.com/DataDog/dd-trace-rb/issues/6020
|
|
5518
5540
|
[#6022]: https://github.com/DataDog/dd-trace-rb/issues/6022
|
|
5519
5541
|
[#6023]: https://github.com/DataDog/dd-trace-rb/issues/6023
|
|
5542
|
+
[#6024]: https://github.com/DataDog/dd-trace-rb/issues/6024
|
|
5520
5543
|
[#6047]: https://github.com/DataDog/dd-trace-rb/issues/6047
|
|
5521
5544
|
[#6050]: https://github.com/DataDog/dd-trace-rb/issues/6050
|
|
5522
5545
|
[#6056]: https://github.com/DataDog/dd-trace-rb/issues/6056
|
|
5523
5546
|
[#6060]: https://github.com/DataDog/dd-trace-rb/issues/6060
|
|
5524
5547
|
[#6064]: https://github.com/DataDog/dd-trace-rb/issues/6064
|
|
5525
5548
|
[#6072]: https://github.com/DataDog/dd-trace-rb/issues/6072
|
|
5549
|
+
[#6097]: https://github.com/DataDog/dd-trace-rb/issues/6097
|
|
5550
|
+
[#6104]: https://github.com/DataDog/dd-trace-rb/issues/6104
|
|
5551
|
+
[#6116]: https://github.com/DataDog/dd-trace-rb/issues/6116
|
|
5526
5552
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5527
5553
|
[@Azure7111]: https://github.com/Azure7111
|
|
5528
5554
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -3,17 +3,10 @@
|
|
|
3
3
|
#include <ruby/st.h>
|
|
4
4
|
#include <stdatomic.h>
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
|
9
|
-
#ifndef _GNU_SOURCE
|
|
10
|
-
#define _GNU_SOURCE
|
|
11
|
-
#endif
|
|
12
|
-
#include <dlfcn.h>
|
|
13
|
-
#if defined(HAVE_DLADDR1) && HAVE_DLADDR1
|
|
14
|
-
#include <link.h>
|
|
15
|
-
#endif
|
|
6
|
+
#ifndef _GNU_SOURCE
|
|
7
|
+
#define _GNU_SOURCE // Needed for dlfcn.h/dladdr
|
|
16
8
|
#endif
|
|
9
|
+
#include <dlfcn.h>
|
|
17
10
|
|
|
18
11
|
#include "datadog_ruby_common.h"
|
|
19
12
|
#include "private_vm_api_access.h"
|
|
@@ -21,11 +14,16 @@
|
|
|
21
14
|
#include "stack_recorder.h"
|
|
22
15
|
#include "collectors_stack.h"
|
|
23
16
|
|
|
17
|
+
// An estimation of how much bytes we need on average for qualified method names.
|
|
18
|
+
// This is an "average" and not a "max" because frames can use more than that,
|
|
19
|
+
// we just fall back to unqualified method names if the buffer is full.
|
|
20
|
+
#define QUALIFIED_NAME_AVG_SIZE 64
|
|
21
|
+
|
|
24
22
|
// Gathers stack traces from running threads, storing them in a StackRecorder instance
|
|
25
23
|
// This file implements the native bits of the Datadog::Profiling::Collectors::Stack class
|
|
26
24
|
|
|
27
|
-
static VALUE _native_filenames_available(DDTRACE_UNUSED VALUE self);
|
|
28
25
|
static VALUE _native_ruby_native_filename(DDTRACE_UNUSED VALUE self);
|
|
26
|
+
static VALUE _native_set_file_info_for_cfunc(DDTRACE_UNUSED VALUE self, VALUE new_static_ruby_actual_filename);
|
|
29
27
|
static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
|
30
28
|
static VALUE native_sample_do(VALUE args);
|
|
31
29
|
static VALUE native_sample_ensure(VALUE args);
|
|
@@ -40,56 +38,51 @@ static void set_file_info_for_cfunc(
|
|
|
40
38
|
st_table *native_filenames_cache
|
|
41
39
|
);
|
|
42
40
|
static const char *get_or_compute_native_filename(void *function, st_table *native_filenames_cache);
|
|
41
|
+
static void initialize_static_ruby_actual_filename(VALUE profiling_module);
|
|
43
42
|
static void add_truncated_frames_placeholder(ddog_prof_Location *locations);
|
|
44
43
|
static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
|
|
45
44
|
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
|
|
46
45
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
extern VALUE rb_iseq_base_label(const VALUE);
|
|
51
|
-
|
|
52
|
-
// NULL if dladdr is not available or we weren't able to get the native filename for the Ruby VM
|
|
46
|
+
// NULL if we weren't able to get the native filename for the Ruby VM.
|
|
47
|
+
// On a static Ruby without libruby.so, `get_or_compute_native_filename` returns `ruby_native_filename` but
|
|
48
|
+
// `static_ruby_actual_filename` (below) is what we want to show instead.
|
|
53
49
|
static const char *ruby_native_filename = NULL;
|
|
50
|
+
// Qnil unless we're running on a static Ruby
|
|
51
|
+
static VALUE static_ruby_actual_filename = Qnil;
|
|
54
52
|
|
|
55
53
|
void collectors_stack_init(VALUE profiling_module) {
|
|
56
54
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
57
55
|
VALUE collectors_stack_class = rb_define_class_under(collectors_module, "Stack", rb_cObject);
|
|
58
56
|
|
|
59
|
-
rb_define_singleton_method(collectors_stack_class, "_native_filenames_available?", _native_filenames_available, 0);
|
|
60
57
|
rb_define_singleton_method(collectors_stack_class, "_native_ruby_native_filename", _native_ruby_native_filename, 0);
|
|
61
58
|
|
|
62
59
|
// Hosts methods used for testing the native code using RSpec
|
|
63
60
|
VALUE testing_module = rb_define_module_under(collectors_stack_class, "Testing");
|
|
64
61
|
|
|
65
62
|
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, -1);
|
|
63
|
+
rb_define_singleton_method(testing_module, "_native_set_file_info_for_cfunc", _native_set_file_info_for_cfunc, 1);
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#endif
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
static VALUE _native_filenames_available(DDTRACE_UNUSED VALUE self) {
|
|
84
|
-
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
|
85
|
-
return ruby_native_filename != NULL ? Qtrue : Qfalse;
|
|
86
|
-
#else
|
|
87
|
-
return Qfalse;
|
|
88
|
-
#endif
|
|
65
|
+
// To be able to detect when a frame is coming from Ruby, we record here its filename as returned by dladdr.
|
|
66
|
+
// We expect this same pointer to be returned by dladdr for all frames coming from Ruby.
|
|
67
|
+
//
|
|
68
|
+
// Small note: Creating/deleting the cache is a bit awkward here, but it seems like a bigger footgun to allow
|
|
69
|
+
// `get_or_compute_native_filename` to run without a cache, since we never expect that to happen during sampling. So it seems
|
|
70
|
+
// like a reasonable trade-off to force callers to always figure that out.
|
|
71
|
+
st_table *temporary_cache = st_init_numtable();
|
|
72
|
+
const char *native_filename = get_or_compute_native_filename(rb_ary_new, temporary_cache);
|
|
73
|
+
if (native_filename[0] != '\0') {
|
|
74
|
+
ruby_native_filename = native_filename;
|
|
75
|
+
initialize_static_ruby_actual_filename(profiling_module);
|
|
76
|
+
}
|
|
77
|
+
st_free_table(temporary_cache);
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
static VALUE _native_ruby_native_filename(DDTRACE_UNUSED VALUE self) {
|
|
92
|
-
|
|
81
|
+
if (static_ruby_actual_filename != Qnil) {
|
|
82
|
+
return static_ruby_actual_filename;
|
|
83
|
+
} else {
|
|
84
|
+
return ruby_native_filename != NULL ? rb_utf8_str_new_cstr(ruby_native_filename) : Qnil;
|
|
85
|
+
}
|
|
93
86
|
}
|
|
94
87
|
|
|
95
88
|
typedef struct {
|
|
@@ -102,6 +95,7 @@ typedef struct {
|
|
|
102
95
|
sampling_buffer *buffer;
|
|
103
96
|
bool native_filenames_enabled;
|
|
104
97
|
st_table *native_filenames_cache;
|
|
98
|
+
bool show_classes;
|
|
105
99
|
} native_sample_args;
|
|
106
100
|
|
|
107
101
|
// This method exists only to enable testing Datadog::Profiling::Collectors::Stack behavior using RSpec.
|
|
@@ -124,6 +118,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
124
118
|
VALUE in_gc = rb_hash_lookup2(options, ID2SYM(rb_intern("in_gc")), Qfalse);
|
|
125
119
|
VALUE is_gvl_waiting_state = rb_hash_lookup2(options, ID2SYM(rb_intern("is_gvl_waiting_state")), Qfalse);
|
|
126
120
|
VALUE native_filenames_enabled = rb_hash_lookup2(options, ID2SYM(rb_intern("native_filenames_enabled")), Qfalse);
|
|
121
|
+
VALUE show_classes = rb_hash_lookup2(options, ID2SYM(rb_intern("show_classes")), Qtrue);
|
|
127
122
|
|
|
128
123
|
ENFORCE_TYPE(metric_values_hash, T_HASH);
|
|
129
124
|
ENFORCE_TYPE(labels_array, T_ARRAY);
|
|
@@ -132,6 +127,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
132
127
|
ENFORCE_BOOLEAN(in_gc);
|
|
133
128
|
ENFORCE_BOOLEAN(is_gvl_waiting_state);
|
|
134
129
|
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
130
|
+
ENFORCE_BOOLEAN(show_classes);
|
|
135
131
|
|
|
136
132
|
VALUE zero = INT2NUM(0);
|
|
137
133
|
VALUE heap_sample = rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("heap_sample"), Qfalse);
|
|
@@ -173,7 +169,8 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
173
169
|
|
|
174
170
|
int max_frames_requested = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
175
171
|
|
|
176
|
-
|
|
172
|
+
sample_locations locations;
|
|
173
|
+
sample_locations_initialize(&locations, max_frames_requested, show_classes == Qtrue);
|
|
177
174
|
sampling_buffer buffer;
|
|
178
175
|
sampling_buffer_initialize(&buffer, max_frames_requested);
|
|
179
176
|
|
|
@@ -185,10 +182,11 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
185
182
|
.values = values,
|
|
186
183
|
.labels = (sample_labels) {.labels = slice_labels, .state_label = state_label, .is_gvl_waiting_state = is_gvl_waiting_state == Qtrue},
|
|
187
184
|
.thread = thread,
|
|
188
|
-
.locations =
|
|
185
|
+
.locations = locations,
|
|
189
186
|
.buffer = &buffer,
|
|
190
187
|
.native_filenames_enabled = native_filenames_enabled == Qtrue,
|
|
191
188
|
.native_filenames_cache = st_init_numtable(),
|
|
189
|
+
.show_classes = show_classes == Qtrue,
|
|
192
190
|
};
|
|
193
191
|
|
|
194
192
|
return rb_ensure(native_sample_do, (VALUE) &args_struct, native_sample_ensure, (VALUE) &args_struct);
|
|
@@ -213,7 +211,8 @@ static VALUE native_sample_do(VALUE args) {
|
|
|
213
211
|
args_struct->values,
|
|
214
212
|
args_struct->labels,
|
|
215
213
|
args_struct->native_filenames_enabled,
|
|
216
|
-
args_struct->native_filenames_cache
|
|
214
|
+
args_struct->native_filenames_cache,
|
|
215
|
+
args_struct->show_classes
|
|
217
216
|
);
|
|
218
217
|
}
|
|
219
218
|
|
|
@@ -223,7 +222,7 @@ static VALUE native_sample_do(VALUE args) {
|
|
|
223
222
|
static VALUE native_sample_ensure(VALUE args) {
|
|
224
223
|
native_sample_args *args_struct = (native_sample_args *) args;
|
|
225
224
|
|
|
226
|
-
|
|
225
|
+
sample_locations_free(&args_struct->locations);
|
|
227
226
|
sampling_buffer_free(args_struct->buffer);
|
|
228
227
|
st_free_table(args_struct->native_filenames_cache);
|
|
229
228
|
|
|
@@ -249,7 +248,8 @@ void sample_thread(
|
|
|
249
248
|
sample_values values,
|
|
250
249
|
sample_labels labels,
|
|
251
250
|
bool native_filenames_enabled,
|
|
252
|
-
st_table *native_filenames_cache
|
|
251
|
+
st_table *native_filenames_cache,
|
|
252
|
+
bool show_classes
|
|
253
253
|
) {
|
|
254
254
|
if (buffer->max_frames != locations.len) {
|
|
255
255
|
// This shouldn't happen as thread_context_collector_reset_all_per_thread_contexts (which resizes every
|
|
@@ -268,11 +268,13 @@ void sample_thread(
|
|
|
268
268
|
prepare_sample_thread(thread, buffer);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
buffer->pending_sample = false;
|
|
272
271
|
int captured_frames = buffer->pending_sample_result;
|
|
273
272
|
|
|
274
273
|
if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
|
|
275
274
|
record_placeholder_stack_in_native_code(recorder_instance, values, labels);
|
|
275
|
+
|
|
276
|
+
// Done reading from the buffer -- allow GC to stop marking it.
|
|
277
|
+
buffer->pending_sample = false;
|
|
276
278
|
return;
|
|
277
279
|
}
|
|
278
280
|
|
|
@@ -309,45 +311,58 @@ void sample_thread(
|
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
int top_of_stack_position = captured_frames - 1;
|
|
314
|
+
char *qualified_name_buf = locations.qualified_name_buf;
|
|
315
|
+
size_t qualified_name_buf_size = locations.qualified_name_buf_size;
|
|
312
316
|
|
|
313
317
|
for (int i = 0; i <= top_of_stack_position; i++) {
|
|
314
|
-
ddog_CharSlice
|
|
318
|
+
ddog_CharSlice filename_slice;
|
|
315
319
|
int line;
|
|
316
320
|
bool top_of_the_stack = i == top_of_stack_position;
|
|
317
321
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
pathobj_is_null(iseq) ? Qnil : rb_iseq_path(iseq);
|
|
326
|
-
|
|
327
|
-
name_slice = NIL_P(name) ? DDOG_CHARSLICE_C("") : char_slice_from_ruby_string(name);
|
|
322
|
+
frame_info frame = buffer->stack_buffer[i];
|
|
323
|
+
const rb_callable_method_entry_t *cme = frame.cme;
|
|
324
|
+
const rb_iseq_t* iseq;
|
|
325
|
+
if (frame.is_ruby_frame) {
|
|
326
|
+
iseq = frame.as.ruby_frame.iseq;
|
|
327
|
+
|
|
328
|
+
VALUE filename = ddtrace_iseq_path(iseq);
|
|
328
329
|
filename_slice = NIL_P(filename) ? DDOG_CHARSLICE_C("") : char_slice_from_ruby_string(filename);
|
|
329
|
-
line =
|
|
330
|
+
line = frame.as.ruby_frame.line;
|
|
330
331
|
|
|
331
332
|
last_ruby_frame_filename = filename_slice;
|
|
332
333
|
last_ruby_line = line;
|
|
333
334
|
} else {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
name_slice = NIL_P(name) ? DDOG_CHARSLICE_C("") : char_slice_from_ruby_string(name);
|
|
335
|
+
iseq = NULL;
|
|
337
336
|
|
|
338
337
|
set_file_info_for_cfunc(
|
|
339
338
|
&filename_slice,
|
|
340
339
|
&line,
|
|
341
340
|
last_ruby_frame_filename,
|
|
342
341
|
last_ruby_line,
|
|
343
|
-
|
|
342
|
+
ddtrace_cme_cfunc_func(cme),
|
|
344
343
|
top_of_the_stack,
|
|
345
344
|
native_filenames_enabled,
|
|
346
345
|
native_filenames_cache
|
|
347
346
|
);
|
|
348
347
|
}
|
|
349
348
|
|
|
350
|
-
|
|
349
|
+
VALUE name = ddtrace_location_base_label(cme, iseq);
|
|
350
|
+
// TODO: name_slice is currently the unqualified method name
|
|
351
|
+
// because we don't always use qualified method names yet, so we don't always compute it,
|
|
352
|
+
// and checks below need to compare to something stable until we always qualified method names.
|
|
353
|
+
ddog_CharSlice name_slice = char_slice_from_ruby_string(name);
|
|
354
|
+
|
|
355
|
+
ddog_CharSlice qualified_name = DDOG_CHARSLICE_C("");
|
|
356
|
+
if (show_classes) {
|
|
357
|
+
ssize_t written = ddtrace_location_label(cme, iseq, qualified_name_buf, qualified_name_buf_size);
|
|
358
|
+
if (written > 0) {
|
|
359
|
+
qualified_name = (ddog_CharSlice) {.ptr = qualified_name_buf, .len = written};
|
|
360
|
+
qualified_name_buf += written;
|
|
361
|
+
qualified_name_buf_size -= written;
|
|
362
|
+
} else {
|
|
363
|
+
qualified_name = name_slice; // just the method name
|
|
364
|
+
}
|
|
365
|
+
}
|
|
351
366
|
|
|
352
367
|
// When there's only wall-time in a sample, this means that the thread was not active in the sampled period.
|
|
353
368
|
if (top_of_the_stack && only_wall_time) {
|
|
@@ -358,7 +373,7 @@ void sample_thread(
|
|
|
358
373
|
// Otherwise, we try to categorize what the thread was doing based on what we observe at the top of the stack. This is a very rough
|
|
359
374
|
// approximation, and in the future we hope to replace this with a more accurate approach (such as using the
|
|
360
375
|
// GVL instrumentation API.)
|
|
361
|
-
} else if (!
|
|
376
|
+
} else if (!frame.is_ruby_frame) {
|
|
362
377
|
// We know that known versions of Ruby implement these using native code; thus if we find a method with the
|
|
363
378
|
// same name that is not native code, we ignore it, as it's probably a user method that coincidentally
|
|
364
379
|
// has the same name. Thus, even though "matching just by method name" is kinda weak,
|
|
@@ -408,11 +423,20 @@ void sample_thread(
|
|
|
408
423
|
}
|
|
409
424
|
}
|
|
410
425
|
|
|
426
|
+
ddog_CharSlice frame_name;
|
|
427
|
+
if (show_classes) {
|
|
428
|
+
frame_name = qualified_name;
|
|
429
|
+
} else {
|
|
430
|
+
frame_name = name_slice;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
maybe_trim_template_random_ids(&frame_name, &filename_slice);
|
|
434
|
+
|
|
411
435
|
int libdatadog_stores_stacks_flipped_from_rb_profile_frames_index = top_of_stack_position - i;
|
|
412
436
|
|
|
413
437
|
locations.ptr[libdatadog_stores_stacks_flipped_from_rb_profile_frames_index] = (ddog_prof_Location) {
|
|
414
438
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
415
|
-
.function = (ddog_prof_Function) {.name =
|
|
439
|
+
.function = (ddog_prof_Function) {.name = frame_name, .filename = filename_slice},
|
|
416
440
|
.line = line,
|
|
417
441
|
};
|
|
418
442
|
}
|
|
@@ -429,95 +453,81 @@ void sample_thread(
|
|
|
429
453
|
values,
|
|
430
454
|
labels
|
|
431
455
|
);
|
|
456
|
+
|
|
457
|
+
// Done reading from the buffer -- allow GC to stop marking it.
|
|
458
|
+
// This needs to be done last in this function in case anything above triggers a GC
|
|
459
|
+
// (remember that `ruby_xmalloc` for instance can do that too...)
|
|
460
|
+
buffer->pending_sample = false;
|
|
432
461
|
}
|
|
433
462
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
463
|
+
static void set_file_info_for_cfunc(
|
|
464
|
+
ddog_CharSlice *filename_slice,
|
|
465
|
+
int *line,
|
|
466
|
+
ddog_CharSlice last_ruby_frame_filename,
|
|
467
|
+
int last_ruby_line,
|
|
468
|
+
void *function,
|
|
469
|
+
bool top_of_the_stack,
|
|
470
|
+
bool native_filenames_enabled,
|
|
471
|
+
st_table *native_filenames_cache
|
|
472
|
+
) {
|
|
473
|
+
if (native_filenames_enabled) {
|
|
474
|
+
const char *native_filename = get_or_compute_native_filename(function, native_filenames_cache);
|
|
475
|
+
if (native_filename[0] != '\0' &&
|
|
476
|
+
// TODO: Using the ruby_native_filename at the top of the stack has a weird effect on the "top methods" table because
|
|
477
|
+
// e.g. we don't have classnames for methods. This is especially visible in the allocations profile, e.g.
|
|
478
|
+
// what a surprise, you're telling me "libruby.so:new" is the top method always?
|
|
479
|
+
// Until we have a better way of dealing with that, we don't do this replacement for the top frame.
|
|
480
|
+
// (Bonus TODO note: This might be fixed by using full class/module names in frames?)
|
|
481
|
+
//
|
|
482
|
+
// dladdr is expected to always return the same pointer to the ruby_native_filename, so that's why we're
|
|
483
|
+
// comparing only pointer values and not the string contents.
|
|
484
|
+
(native_filename != ruby_native_filename || !top_of_the_stack)
|
|
485
|
+
) {
|
|
486
|
+
// On a static Ruby without libruby.so, we show `static_ruby_actual_filename` instead of `ruby_native_filename`
|
|
487
|
+
if (native_filename == ruby_native_filename && static_ruby_actual_filename != Qnil) {
|
|
488
|
+
*filename_slice = char_slice_from_ruby_string(static_ruby_actual_filename);
|
|
489
|
+
} else {
|
|
458
490
|
*filename_slice = (ddog_CharSlice) {.ptr = native_filename, .len = strlen(native_filename)};
|
|
459
|
-
// Explicitly set the line to 0 as it has no meaning on a native library (e.g. an .so is built of many source files)
|
|
460
|
-
// and anyway often that debug info is not available.
|
|
461
|
-
*line = 0;
|
|
462
|
-
return;
|
|
463
491
|
}
|
|
492
|
+
// Explicitly set the line to 0 as it has no meaning on a native library (e.g. an .so is built of many source files)
|
|
493
|
+
// and anyway often that debug info is not available.
|
|
494
|
+
*line = 0;
|
|
495
|
+
return;
|
|
464
496
|
}
|
|
465
|
-
|
|
466
|
-
*filename_slice = last_ruby_frame_filename;
|
|
467
|
-
*line = last_ruby_line;
|
|
468
497
|
}
|
|
469
498
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// frame on the **Ruby** stack with a given `function`, then that `function` was registered with the Ruby VM and
|
|
474
|
-
// belongs to a Ruby extension, so a lot of other bad things would happen if it was dlclosed.
|
|
475
|
-
static const char *get_or_compute_native_filename(void *function, st_table *native_filenames_cache) {
|
|
476
|
-
const char *cached_filename = NULL;
|
|
477
|
-
st_lookup(native_filenames_cache, (st_data_t) function, (st_data_t *) &cached_filename);
|
|
478
|
-
if (cached_filename != NULL) return cached_filename;
|
|
479
|
-
|
|
480
|
-
Dl_info info;
|
|
481
|
-
const char *native_filename = NULL;
|
|
482
|
-
#if defined(HAVE_DLADDR1) && HAVE_DLADDR1
|
|
483
|
-
struct link_map *extra_info = NULL;
|
|
484
|
-
if (dladdr1(function, &info, (void **) &extra_info, RTLD_DL_LINKMAP) != 0 && extra_info != NULL) {
|
|
485
|
-
native_filename = extra_info->l_name != NULL ? extra_info->l_name : info.dli_fname;
|
|
486
|
-
}
|
|
487
|
-
#elif defined(HAVE_DLADDR) && HAVE_DLADDR
|
|
488
|
-
if (dladdr(function, &info) != 0) {
|
|
489
|
-
native_filename = info.dli_fname;
|
|
490
|
-
}
|
|
491
|
-
#endif
|
|
492
|
-
|
|
493
|
-
// We explicitly use an empty string here so as to cache lookups that somehow "failed". Otherwise we would keep trying them every time.
|
|
494
|
-
if (native_filename == NULL) native_filename = "";
|
|
495
|
-
|
|
496
|
-
// An st_table is what Ruby uses for its own hashtables. This allows us to get an easy estimate of the size of the cache:
|
|
497
|
-
// `ObjectSpace.memsize_of((0..100000).map { |it| [it, nil] }.to_h)` => 4194400 bytes as of Ruby 3.2 so that seems reasonable?
|
|
498
|
-
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
499
|
-
if (native_filenames_cache->num_entries >= 100000) {
|
|
500
|
-
st_clear(native_filenames_cache);
|
|
501
|
-
}
|
|
499
|
+
*filename_slice = last_ruby_frame_filename;
|
|
500
|
+
*line = last_ruby_line;
|
|
501
|
+
}
|
|
502
502
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
|
|
503
|
+
// `native_filenames_cache` is used to cache native filename lookup results (Map[void *function_pointer, char *filename])
|
|
504
|
+
//
|
|
505
|
+
// Caching this information is safe because there's no API in Ruby to "unrequire" a native extension. Thus, if we see a
|
|
506
|
+
// frame on the **Ruby** stack with a given `function`, then that `function` was registered with the Ruby VM and
|
|
507
|
+
// belongs to a Ruby extension, so a lot of other bad things would happen if it was dlclosed.
|
|
508
|
+
//
|
|
509
|
+
// Returns "" if not found. Never returns NULL.
|
|
510
|
+
static const char *get_or_compute_native_filename(void *function, st_table *native_filenames_cache) {
|
|
511
|
+
const char *cached_filename = NULL;
|
|
512
|
+
st_lookup(native_filenames_cache, (st_data_t) function, (st_data_t *) &cached_filename);
|
|
513
|
+
if (cached_filename != NULL) return cached_filename;
|
|
514
|
+
|
|
515
|
+
Dl_info info;
|
|
516
|
+
const char *native_filename = dladdr(function, &info) != 0 ? info.dli_fname : NULL;
|
|
517
|
+
|
|
518
|
+
// We explicitly use an empty string here so as to cache lookups that somehow "failed". Otherwise we would keep trying them every time.
|
|
519
|
+
if (native_filename == NULL) native_filename = "";
|
|
520
|
+
|
|
521
|
+
// An st_table is what Ruby uses for its own hashtables. This allows us to get an easy estimate of the size of the cache:
|
|
522
|
+
// `ObjectSpace.memsize_of((0..100000).map { |it| [it, nil] }.to_h)` => 4194400 bytes as of Ruby 3.2 so that seems reasonable?
|
|
523
|
+
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
524
|
+
if (native_filenames_cache->num_entries >= 100000) {
|
|
525
|
+
st_clear(native_filenames_cache);
|
|
519
526
|
}
|
|
520
|
-
|
|
527
|
+
|
|
528
|
+
st_insert(native_filenames_cache, (st_data_t) function, (st_data_t) native_filename);
|
|
529
|
+
return native_filename;
|
|
530
|
+
}
|
|
521
531
|
|
|
522
532
|
// Rails's ActionView likes to dynamically generate method names with suffixed hashes/ids, resulting in methods with
|
|
523
533
|
// names such as:
|
|
@@ -633,6 +643,28 @@ bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer) {
|
|
|
633
643
|
return true;
|
|
634
644
|
}
|
|
635
645
|
|
|
646
|
+
void sample_locations_initialize(sample_locations *locations, uint16_t max_frames, bool show_classes) {
|
|
647
|
+
locations->len = max_frames;
|
|
648
|
+
locations->ptr = ruby_xcalloc(max_frames, sizeof(ddog_prof_Location));
|
|
649
|
+
if (show_classes) {
|
|
650
|
+
locations->qualified_name_buf_size = QUALIFIED_NAME_AVG_SIZE * max_frames;
|
|
651
|
+
locations->qualified_name_buf = ruby_xmalloc(locations->qualified_name_buf_size);
|
|
652
|
+
} else {
|
|
653
|
+
locations->qualified_name_buf_size = 0;
|
|
654
|
+
locations->qualified_name_buf = NULL;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
void sample_locations_free(sample_locations *locations) {
|
|
659
|
+
ruby_xfree(locations->ptr);
|
|
660
|
+
ruby_xfree(locations->qualified_name_buf);
|
|
661
|
+
|
|
662
|
+
locations->ptr = NULL;
|
|
663
|
+
locations->len = 0;
|
|
664
|
+
locations->qualified_name_buf = NULL;
|
|
665
|
+
locations->qualified_name_buf_size = 0;
|
|
666
|
+
}
|
|
667
|
+
|
|
636
668
|
uint16_t sampling_buffer_check_max_frames(int max_frames) {
|
|
637
669
|
if (max_frames < 5) raise_error(rb_eArgError, "Invalid max_frames: value must be >= 5");
|
|
638
670
|
if (max_frames > MAX_FRAMES_LIMIT) raise_error(rb_eArgError, "Invalid max_frames: value must be <= " MAX_FRAMES_LIMIT_AS_STRING);
|
|
@@ -675,8 +707,9 @@ void sampling_buffer_mark(sampling_buffer *buffer) {
|
|
|
675
707
|
atomic_signal_fence(memory_order_seq_cst);
|
|
676
708
|
|
|
677
709
|
for (int i = 0; i < buffer->pending_sample_result; i++) {
|
|
710
|
+
rb_gc_mark((VALUE) buffer->stack_buffer[i].cme);
|
|
678
711
|
if (buffer->stack_buffer[i].is_ruby_frame) {
|
|
679
|
-
rb_gc_mark(buffer->stack_buffer[i].as.ruby_frame.iseq);
|
|
712
|
+
rb_gc_mark((VALUE) buffer->stack_buffer[i].as.ruby_frame.iseq);
|
|
680
713
|
}
|
|
681
714
|
}
|
|
682
715
|
|
|
@@ -684,3 +717,41 @@ void sampling_buffer_mark(sampling_buffer *buffer) {
|
|
|
684
717
|
atomic_signal_fence(memory_order_seq_cst);
|
|
685
718
|
buffer->is_marking = false;
|
|
686
719
|
}
|
|
720
|
+
|
|
721
|
+
// On a static Ruby without libruby.so the `ruby_native_filename` returned from `dladdr` is in practice `argv[0]`
|
|
722
|
+
// (e.g. can be rspec, rake, etc), so here we find the actual Ruby binary to use instead
|
|
723
|
+
static void initialize_static_ruby_actual_filename(VALUE profiling_module) {
|
|
724
|
+
static_ruby_actual_filename = rb_const_get(profiling_module, rb_intern("STATIC_RUBY_PATH"));
|
|
725
|
+
if (NIL_P(static_ruby_actual_filename)) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
// Makes this object immortal + pinned in-place (no moving)
|
|
729
|
+
rb_gc_register_mark_object(static_ruby_actual_filename);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
static VALUE _native_set_file_info_for_cfunc(DDTRACE_UNUSED VALUE self, VALUE new_static_ruby_actual_filename) {
|
|
733
|
+
if (new_static_ruby_actual_filename != Qnil) ENFORCE_TYPE(new_static_ruby_actual_filename, T_STRING);
|
|
734
|
+
|
|
735
|
+
VALUE previous_static_ruby_actual_filename = static_ruby_actual_filename;
|
|
736
|
+
static_ruby_actual_filename = new_static_ruby_actual_filename;
|
|
737
|
+
|
|
738
|
+
ddog_CharSlice filename_slice;
|
|
739
|
+
int line;
|
|
740
|
+
st_table *native_filenames_cache = st_init_numtable();
|
|
741
|
+
|
|
742
|
+
set_file_info_for_cfunc(
|
|
743
|
+
&filename_slice,
|
|
744
|
+
&line,
|
|
745
|
+
DDOG_CHARSLICE_C("(not used when testing)"),
|
|
746
|
+
0,
|
|
747
|
+
rb_ary_new, // Symbol inside Ruby VM to trigger comparison logic
|
|
748
|
+
false,
|
|
749
|
+
true,
|
|
750
|
+
native_filenames_cache
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
st_free_table(native_filenames_cache);
|
|
754
|
+
static_ruby_actual_filename = previous_static_ruby_actual_filename;
|
|
755
|
+
|
|
756
|
+
return rb_utf8_str_new(filename_slice.ptr, filename_slice.len);
|
|
757
|
+
}
|