datadog 2.2.0 → 2.3.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 +51 -2
- data/ext/datadog_profiling_loader/extconf.rb +15 -15
- data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +113 -43
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
- data/ext/datadog_profiling_native_extension/collectors_stack.c +49 -37
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
- data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
- data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/helpers.h +6 -17
- data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
- data/ext/datadog_profiling_native_extension/profiling.c +0 -2
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
- data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
- data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +19 -6
- data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
- data/ext/libdatadog_api/extconf.rb +108 -0
- data/ext/libdatadog_api/macos_development.md +26 -0
- data/ext/libdatadog_extconf_helpers.rb +130 -0
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
- data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
- data/lib/datadog/appsec/processor/actions.rb +1 -1
- data/lib/datadog/appsec/response.rb +15 -1
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/components.rb +14 -12
- data/lib/datadog/core/configuration/settings.rb +54 -7
- data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
- data/lib/datadog/core/crashtracking/component.rb +111 -0
- data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
- data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
- data/lib/datadog/core/telemetry/component.rb +49 -2
- data/lib/datadog/core/telemetry/emitter.rb +9 -11
- data/lib/datadog/core/telemetry/event.rb +32 -1
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
- data/lib/datadog/core/telemetry/http/ext.rb +3 -0
- data/lib/datadog/core/telemetry/http/transport.rb +38 -9
- data/lib/datadog/core/telemetry/logging.rb +35 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
- data/lib/datadog/kit/appsec/events.rb +2 -4
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
- data/lib/datadog/profiling/collectors/info.rb +3 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
- data/lib/datadog/profiling/component.rb +69 -91
- data/lib/datadog/profiling/exporter.rb +3 -3
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/profiling/ext.rb +21 -21
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/http_transport.rb +8 -6
- data/lib/datadog/profiling/load_native_extension.rb +5 -5
- data/lib/datadog/profiling/preload.rb +1 -1
- data/lib/datadog/profiling/profiler.rb +5 -8
- data/lib/datadog/profiling/scheduler.rb +31 -25
- data/lib/datadog/profiling/tag_builder.rb +2 -2
- data/lib/datadog/profiling/tasks/exec.rb +5 -5
- data/lib/datadog/profiling/tasks/setup.rb +16 -35
- data/lib/datadog/profiling.rb +4 -5
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/ext.rb +14 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
- data/lib/datadog/tracing/metadata/errors.rb +9 -1
- data/lib/datadog/tracing/metadata/ext.rb +4 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
- data/lib/datadog/tracing/span.rb +9 -2
- data/lib/datadog/tracing/span_event.rb +41 -0
- data/lib/datadog/tracing/span_operation.rb +6 -2
- data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +28 -10
- data/lib/datadog/profiling/crashtracker.rb +0 -91
- data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -20,6 +20,9 @@
|
|
20
20
|
|
21
21
|
#define EMA_SMOOTHING_FACTOR 0.6
|
22
22
|
|
23
|
+
static void maybe_readjust(discrete_dynamic_sampler *sampler, long now_ns);
|
24
|
+
static inline bool should_readjust(discrete_dynamic_sampler *sampler, coarse_instant now);
|
25
|
+
|
23
26
|
void discrete_dynamic_sampler_init(discrete_dynamic_sampler *sampler, const char *debug_name, long now_ns) {
|
24
27
|
sampler->debug_name = debug_name;
|
25
28
|
discrete_dynamic_sampler_set_overhead_target_percentage(sampler, BASE_OVERHEAD_PCT, now_ns);
|
@@ -54,27 +57,25 @@ void discrete_dynamic_sampler_set_overhead_target_percentage(discrete_dynamic_sa
|
|
54
57
|
return discrete_dynamic_sampler_reset(sampler, now_ns);
|
55
58
|
}
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler
|
60
|
+
// NOTE: See header for an explanation of when this should get used
|
61
|
+
__attribute__((warn_unused_result))
|
62
|
+
bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler) {
|
60
63
|
// For efficiency reasons we don't do true random sampling but rather systematic
|
61
64
|
// sampling following a sample interval/skip. This can be biased and hide patterns
|
62
|
-
// but the dynamic interval and rather
|
65
|
+
// but the dynamic interval and rather nondeterministic pattern of allocations in
|
63
66
|
// most real applications should help reduce the bias impact.
|
64
67
|
sampler->events_since_last_sample++;
|
65
68
|
sampler->events_since_last_readjustment++;
|
66
|
-
bool should_sample = sampler->sampling_interval > 0 && sampler->events_since_last_sample >= sampler->sampling_interval;
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
} else {
|
71
|
-
// check if we should readjust our sampler after this event, even if we didn't sample it
|
72
|
-
maybe_readjust(sampler, now_ns);
|
73
|
-
}
|
70
|
+
return sampler->sampling_interval > 0 && sampler->events_since_last_sample >= sampler->sampling_interval;
|
71
|
+
}
|
74
72
|
|
75
|
-
|
73
|
+
// NOTE: See header for an explanation of when this should get used
|
74
|
+
void discrete_dynamic_sampler_before_sample(discrete_dynamic_sampler *sampler, long now_ns) {
|
75
|
+
sampler->sample_start_time_ns = now_ns;
|
76
76
|
}
|
77
77
|
|
78
|
+
// NOTE: See header for an explanation of when this should get used
|
78
79
|
long discrete_dynamic_sampler_after_sample(discrete_dynamic_sampler *sampler, long now_ns) {
|
79
80
|
long last_sampling_time_ns = sampler->sample_start_time_ns == 0 ? 0 : long_max_of(0, now_ns - sampler->sample_start_time_ns);
|
80
81
|
sampler->samples_since_last_readjustment++;
|
@@ -95,6 +96,11 @@ size_t discrete_dynamic_sampler_events_since_last_sample(discrete_dynamic_sample
|
|
95
96
|
return sampler->events_since_last_sample;
|
96
97
|
}
|
97
98
|
|
99
|
+
// NOTE: See header for an explanation of when this should get used
|
100
|
+
bool discrete_dynamic_sampler_skipped_sample(discrete_dynamic_sampler *sampler, coarse_instant now) {
|
101
|
+
return should_readjust(sampler, now);
|
102
|
+
}
|
103
|
+
|
98
104
|
static double ewma_adj_window(double latest_value, double avg, long current_window_time_ns, bool is_first) {
|
99
105
|
if (is_first) {
|
100
106
|
return latest_value;
|
@@ -109,19 +115,27 @@ static double ewma_adj_window(double latest_value, double avg, long current_wind
|
|
109
115
|
return (1-alpha) * avg + alpha * latest_value;
|
110
116
|
}
|
111
117
|
|
112
|
-
static void maybe_readjust(discrete_dynamic_sampler *sampler, long
|
113
|
-
|
118
|
+
static void maybe_readjust(discrete_dynamic_sampler *sampler, long now_ns) {
|
119
|
+
if (should_readjust(sampler, to_coarse_instant(now_ns))) discrete_dynamic_sampler_readjust(sampler, now_ns);
|
120
|
+
}
|
121
|
+
|
122
|
+
static inline bool should_readjust(discrete_dynamic_sampler *sampler, coarse_instant now) {
|
123
|
+
long this_window_time_ns =
|
124
|
+
sampler->last_readjust_time_ns == 0 ? ADJUSTMENT_WINDOW_NS : now.timestamp_ns - sampler->last_readjust_time_ns;
|
114
125
|
|
115
126
|
bool should_readjust_based_on_time = this_window_time_ns >= ADJUSTMENT_WINDOW_NS;
|
116
127
|
bool should_readjust_based_on_samples = sampler->samples_since_last_readjustment >= ADJUSTMENT_WINDOW_SAMPLES;
|
117
128
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
129
|
+
return should_readjust_based_on_time || should_readjust_based_on_samples;
|
130
|
+
}
|
131
|
+
|
132
|
+
// NOTE: This method ASSUMES should_readjust == true
|
133
|
+
// NOTE: See header for an explanation of when this should get used
|
134
|
+
void discrete_dynamic_sampler_readjust(discrete_dynamic_sampler *sampler, long now_ns) {
|
135
|
+
long this_window_time_ns = sampler->last_readjust_time_ns == 0 ? ADJUSTMENT_WINDOW_NS : now_ns - sampler->last_readjust_time_ns;
|
122
136
|
|
123
|
-
if (this_window_time_ns
|
124
|
-
// should not be possible given
|
137
|
+
if (this_window_time_ns <= 0) {
|
138
|
+
// should not be possible given should_readjust but lets protect against div by 0 below.
|
125
139
|
return;
|
126
140
|
}
|
127
141
|
|
@@ -143,7 +157,7 @@ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
|
|
143
157
|
// Lets update our average sampling time per event
|
144
158
|
long avg_sampling_time_in_window_ns = sampler->samples_since_last_readjustment == 0 ? 0 : sampler->sampling_time_since_last_readjustment_ns / sampler->samples_since_last_readjustment;
|
145
159
|
if (avg_sampling_time_in_window_ns > sampler->max_sampling_time_ns) {
|
146
|
-
// If the average sampling time in the previous window was deemed
|
160
|
+
// If the average sampling time in the previous window was deemed unacceptable, clamp it to the
|
147
161
|
// maximum acceptable value and register this operation in our counter.
|
148
162
|
// NOTE: This is important so that events like suspensions or system overloads do not lead us to
|
149
163
|
// learn arbitrarily big sampling times which may then result in us not sampling anything
|
@@ -255,9 +269,7 @@ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
|
|
255
269
|
double num_this_windows_in_60s = 60 * 1e9 / this_window_time_ns;
|
256
270
|
double real_total_sampling_time_in_60s = sampler->sampling_time_since_last_readjustment_ns * num_this_windows_in_60s / 1e9;
|
257
271
|
|
258
|
-
|
259
|
-
|
260
|
-
fprintf(stderr, "[dds.%s] readjusting due to %s...\n", sampler->debug_name, readjustment_reason);
|
272
|
+
fprintf(stderr, "[dds.%s] readjusting...\n", sampler->debug_name);
|
261
273
|
fprintf(stderr, "events_since_last_readjustment=%ld\n", sampler->events_since_last_readjustment);
|
262
274
|
fprintf(stderr, "samples_since_last_readjustment=%ld\n", sampler->samples_since_last_readjustment);
|
263
275
|
fprintf(stderr, "this_window_time=%ld\n", this_window_time_ns);
|
@@ -286,7 +298,7 @@ static void maybe_readjust(discrete_dynamic_sampler *sampler, long now) {
|
|
286
298
|
sampler->events_since_last_readjustment = 0;
|
287
299
|
sampler->samples_since_last_readjustment = 0;
|
288
300
|
sampler->sampling_time_since_last_readjustment_ns = 0;
|
289
|
-
sampler->last_readjust_time_ns =
|
301
|
+
sampler->last_readjust_time_ns = now_ns;
|
290
302
|
sampler->has_completed_full_adjustment_window = true;
|
291
303
|
}
|
292
304
|
|
@@ -359,6 +371,10 @@ static VALUE _native_new(VALUE klass) {
|
|
359
371
|
}
|
360
372
|
discrete_dynamic_sampler_init(&state->sampler, "test sampler", now_ns);
|
361
373
|
|
374
|
+
// Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
|
375
|
+
// to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
|
376
|
+
// since the instance representing the state does not yet exist, such objects will not get marked.
|
377
|
+
|
362
378
|
return TypedData_Wrap_Struct(klass, &sampler_typed_data, state);
|
363
379
|
}
|
364
380
|
|
@@ -402,7 +418,14 @@ VALUE _native_should_sample(VALUE self, VALUE now_ns) {
|
|
402
418
|
sampler_state *state;
|
403
419
|
TypedData_Get_Struct(self, sampler_state, &sampler_typed_data, state);
|
404
420
|
|
405
|
-
|
421
|
+
if (discrete_dynamic_sampler_should_sample(&state->sampler)) {
|
422
|
+
discrete_dynamic_sampler_before_sample(&state->sampler, NUM2LONG(now_ns));
|
423
|
+
return Qtrue;
|
424
|
+
} else {
|
425
|
+
bool needs_readjust = discrete_dynamic_sampler_skipped_sample(&state->sampler, to_coarse_instant(NUM2LONG(now_ns)));
|
426
|
+
if (needs_readjust) discrete_dynamic_sampler_readjust(&state->sampler, NUM2LONG(now_ns));
|
427
|
+
return Qfalse;
|
428
|
+
}
|
406
429
|
}
|
407
430
|
|
408
431
|
VALUE _native_after_sample(VALUE self, VALUE now_ns) {
|
@@ -5,6 +5,8 @@
|
|
5
5
|
|
6
6
|
#include <ruby.h>
|
7
7
|
|
8
|
+
#include "time_helpers.h"
|
9
|
+
|
8
10
|
// A sampler that will sample discrete events based on the overhead of their
|
9
11
|
// sampling.
|
10
12
|
//
|
@@ -62,7 +64,6 @@ typedef struct discrete_dynamic_sampler {
|
|
62
64
|
unsigned long sampling_time_clamps;
|
63
65
|
} discrete_dynamic_sampler;
|
64
66
|
|
65
|
-
|
66
67
|
// Init a new sampler with sane defaults.
|
67
68
|
void discrete_dynamic_sampler_init(discrete_dynamic_sampler *sampler, const char *debug_name, long now_ns);
|
68
69
|
|
@@ -80,9 +81,38 @@ void discrete_dynamic_sampler_set_overhead_target_percentage(discrete_dynamic_sa
|
|
80
81
|
// @return True if the event associated with this decision should be sampled, false
|
81
82
|
// otherwise.
|
82
83
|
//
|
83
|
-
//
|
84
|
-
//
|
85
|
-
|
84
|
+
// IMPORTANT: A call to this method MUST be followed by a call to either
|
85
|
+
// `discrete_dynamic_sampler_before_sample` if return is `true` or
|
86
|
+
// `discrete_dynamic_sampler_skipped_sample` if return is `false`.
|
87
|
+
//
|
88
|
+
// In the past, this method took care of before_sample/skipped_sample/readjust as well.
|
89
|
+
// We've had to split it up because we wanted to both use coarse time and regular monotonic time depending on the
|
90
|
+
// situation, but also wanted time to come as an argument from the outside.
|
91
|
+
//
|
92
|
+
// TL;DR here's how they should be used as Ruby code:
|
93
|
+
// if discrete_dynamic_sampler_should_sample
|
94
|
+
// discrete_dynamic_sampler_before_sample(monotonic_wall_time_now_ns())
|
95
|
+
// else
|
96
|
+
// needs_readjust = discrete_dynamic_sampler_skipped_sample(monotonic_coarse_wall_time_now_ns())
|
97
|
+
// discrete_dynamic_sampler_readjust(monotonic_wall_time_now_ns()) if needs_readjust
|
98
|
+
// end
|
99
|
+
__attribute__((warn_unused_result))
|
100
|
+
bool discrete_dynamic_sampler_should_sample(discrete_dynamic_sampler *sampler);
|
101
|
+
|
102
|
+
// Signal the start of a sampling operation.
|
103
|
+
// MUST be called after `discrete_dynamic_sampler_should_sample` returns `true`.
|
104
|
+
void discrete_dynamic_sampler_before_sample(discrete_dynamic_sampler *sampler, long now_ns);
|
105
|
+
|
106
|
+
// Signals that sampling did not happen
|
107
|
+
// MUST be called after `discrete_dynamic_sampler_skipped_sample` returns `false`.
|
108
|
+
//
|
109
|
+
// @return True if the sampler needs to be readjusted.
|
110
|
+
//
|
111
|
+
// IMPORTANT: A call to this method MUST be followed by a call to `discrete_dynamic_sampler_readjust` if return is `true`.
|
112
|
+
__attribute__((warn_unused_result))
|
113
|
+
bool discrete_dynamic_sampler_skipped_sample(discrete_dynamic_sampler *sampler, coarse_instant now);
|
114
|
+
|
115
|
+
void discrete_dynamic_sampler_readjust(discrete_dynamic_sampler *sampler, long now_ns);
|
86
116
|
|
87
117
|
// Signal the end of a sampling operation.
|
88
118
|
//
|
@@ -83,6 +83,10 @@ static VALUE _native_new(VALUE klass) {
|
|
83
83
|
|
84
84
|
reset_state(state);
|
85
85
|
|
86
|
+
// Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
|
87
|
+
// to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
|
88
|
+
// since the instance representing the state does not yet exist, such objects will not get marked.
|
89
|
+
|
86
90
|
return TypedData_Wrap_Struct(klass, &idle_sampling_helper_typed_data, state);
|
87
91
|
}
|
88
92
|
|
@@ -15,11 +15,9 @@ static VALUE missing_string = Qnil;
|
|
15
15
|
|
16
16
|
// Used as scratch space during sampling
|
17
17
|
struct sampling_buffer {
|
18
|
-
|
19
|
-
VALUE *stack_buffer;
|
20
|
-
int *lines_buffer;
|
21
|
-
bool *is_ruby_frame;
|
18
|
+
uint16_t max_frames;
|
22
19
|
ddog_prof_Location *locations;
|
20
|
+
frame_info *stack_buffer;
|
23
21
|
}; // Note: typedef'd in the header to sampling_buffer
|
24
22
|
|
25
23
|
static VALUE _native_sample(
|
@@ -33,9 +31,14 @@ static VALUE _native_sample(
|
|
33
31
|
VALUE in_gc
|
34
32
|
);
|
35
33
|
static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer* buffer, char *frames_omitted_message, int frames_omitted_message_size);
|
36
|
-
static void record_placeholder_stack_in_native_code(
|
34
|
+
static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
|
37
35
|
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
|
38
36
|
|
37
|
+
// These two functions are exposed as symbols by the VM but are not in any header.
|
38
|
+
// Their signatures actually take a `const rb_iseq_t *iseq` but it gets casted back and forth between VALUE.
|
39
|
+
extern VALUE rb_iseq_path(const VALUE);
|
40
|
+
extern VALUE rb_iseq_base_label(const VALUE);
|
41
|
+
|
39
42
|
void collectors_stack_init(VALUE profiling_module) {
|
40
43
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
41
44
|
VALUE collectors_stack_class = rb_define_class_under(collectors_module, "Stack", rb_cObject);
|
@@ -65,6 +68,8 @@ static VALUE _native_sample(
|
|
65
68
|
ENFORCE_TYPE(numeric_labels_array, T_ARRAY);
|
66
69
|
|
67
70
|
VALUE zero = INT2NUM(0);
|
71
|
+
VALUE heap_sample = rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("heap_sample"), Qfalse);
|
72
|
+
ENFORCE_BOOLEAN(heap_sample);
|
68
73
|
sample_values values = {
|
69
74
|
.cpu_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-time"), zero)),
|
70
75
|
.cpu_or_wall_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-samples"), zero)),
|
@@ -72,6 +77,7 @@ static VALUE _native_sample(
|
|
72
77
|
.alloc_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples"), zero)),
|
73
78
|
.alloc_samples_unscaled = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples-unscaled"), zero)),
|
74
79
|
.timeline_wall_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("timeline"), zero)),
|
80
|
+
.heap_sample = heap_sample == Qtrue,
|
75
81
|
};
|
76
82
|
|
77
83
|
long labels_count = RARRAY_LEN(labels_array) + RARRAY_LEN(numeric_labels_array);
|
@@ -102,13 +108,13 @@ static VALUE _native_sample(
|
|
102
108
|
int max_frames_requested = NUM2INT(max_frames);
|
103
109
|
if (max_frames_requested < 0) rb_raise(rb_eArgError, "Invalid max_frames: value must not be negative");
|
104
110
|
|
105
|
-
|
111
|
+
ddog_prof_Location *locations = ruby_xcalloc(max_frames_requested, sizeof(ddog_prof_Location));
|
112
|
+
sampling_buffer *buffer = sampling_buffer_new(max_frames_requested, locations);
|
106
113
|
|
107
114
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = labels_count};
|
108
115
|
|
109
116
|
if (in_gc == Qtrue) {
|
110
117
|
record_placeholder_stack(
|
111
|
-
buffer,
|
112
118
|
recorder_instance,
|
113
119
|
values,
|
114
120
|
(sample_labels) {.labels = slice_labels, .state_label = state_label},
|
@@ -124,6 +130,7 @@ static VALUE _native_sample(
|
|
124
130
|
);
|
125
131
|
}
|
126
132
|
|
133
|
+
ruby_xfree(locations);
|
127
134
|
sampling_buffer_free(buffer);
|
128
135
|
|
129
136
|
return Qtrue;
|
@@ -151,22 +158,28 @@ void sample_thread(
|
|
151
158
|
thread,
|
152
159
|
0 /* stack starting depth */,
|
153
160
|
buffer->max_frames,
|
154
|
-
buffer->stack_buffer
|
155
|
-
buffer->lines_buffer,
|
156
|
-
buffer->is_ruby_frame
|
161
|
+
buffer->stack_buffer
|
157
162
|
);
|
158
163
|
|
159
164
|
if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
|
160
|
-
record_placeholder_stack_in_native_code(
|
165
|
+
record_placeholder_stack_in_native_code(recorder_instance, values, labels);
|
161
166
|
return;
|
162
167
|
}
|
163
168
|
|
169
|
+
// if (captured_frames > 0) {
|
170
|
+
// int cache_hits = 0;
|
171
|
+
// for (int i = 0; i < captured_frames; i++) {
|
172
|
+
// if (buffer->stack_buffer[i].same_frame) cache_hits++;
|
173
|
+
// }
|
174
|
+
// fprintf(stderr, "Sampling cache hits: %f\n", ((double) cache_hits / captured_frames) * 100);
|
175
|
+
// }
|
176
|
+
|
164
177
|
// Ruby does not give us path and line number for methods implemented using native code.
|
165
178
|
// The convention in Kernel#caller_locations is to instead use the path and line number of the first Ruby frame
|
166
179
|
// on the stack that is below (e.g. directly or indirectly has called) the native method.
|
167
180
|
// Thus, we keep that frame here to able to replicate that behavior.
|
168
|
-
// (This is why we also iterate the sampling buffers backwards below -- so that it's easier to keep the
|
169
|
-
VALUE
|
181
|
+
// (This is why we also iterate the sampling buffers backwards below -- so that it's easier to keep the last_ruby_frame_filename)
|
182
|
+
VALUE last_ruby_frame_filename = Qnil;
|
170
183
|
int last_ruby_line = 0;
|
171
184
|
|
172
185
|
ddog_prof_Label *state_label = labels.state_label;
|
@@ -182,16 +195,16 @@ void sample_thread(
|
|
182
195
|
VALUE name, filename;
|
183
196
|
int line;
|
184
197
|
|
185
|
-
if (buffer->
|
186
|
-
|
187
|
-
|
198
|
+
if (buffer->stack_buffer[i].is_ruby_frame) {
|
199
|
+
name = rb_iseq_base_label(buffer->stack_buffer[i].as.ruby_frame.iseq);
|
200
|
+
filename = rb_iseq_path(buffer->stack_buffer[i].as.ruby_frame.iseq);
|
201
|
+
line = buffer->stack_buffer[i].as.ruby_frame.line;
|
188
202
|
|
189
|
-
|
190
|
-
|
191
|
-
line = buffer->lines_buffer[i];
|
203
|
+
last_ruby_frame_filename = filename;
|
204
|
+
last_ruby_line = line;
|
192
205
|
} else {
|
193
|
-
name =
|
194
|
-
filename =
|
206
|
+
name = rb_id2str(buffer->stack_buffer[i].as.native_frame.method_id);
|
207
|
+
filename = last_ruby_frame_filename;
|
195
208
|
line = last_ruby_line;
|
196
209
|
}
|
197
210
|
|
@@ -211,7 +224,7 @@ void sample_thread(
|
|
211
224
|
// approximation, and in the future we hope to replace this with a more accurate approach (such as using the
|
212
225
|
// GVL instrumentation API.)
|
213
226
|
if (top_of_the_stack && only_wall_time) {
|
214
|
-
if (!buffer->
|
227
|
+
if (!buffer->stack_buffer[i].is_ruby_frame) {
|
215
228
|
// We know that known versions of Ruby implement these using native code; thus if we find a method with the
|
216
229
|
// same name that is not native code, we ignore it, as it's probably a user method that coincidentally
|
217
230
|
// has the same name. Thus, even though "matching just by method name" is kinda weak,
|
@@ -352,13 +365,11 @@ static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer*
|
|
352
365
|
// with one containing a placeholder frame, so that these threads are properly represented in the UX.
|
353
366
|
|
354
367
|
static void record_placeholder_stack_in_native_code(
|
355
|
-
sampling_buffer* buffer,
|
356
368
|
VALUE recorder_instance,
|
357
369
|
sample_values values,
|
358
370
|
sample_labels labels
|
359
371
|
) {
|
360
372
|
record_placeholder_stack(
|
361
|
-
buffer,
|
362
373
|
recorder_instance,
|
363
374
|
values,
|
364
375
|
labels,
|
@@ -367,36 +378,39 @@ static void record_placeholder_stack_in_native_code(
|
|
367
378
|
}
|
368
379
|
|
369
380
|
void record_placeholder_stack(
|
370
|
-
sampling_buffer* buffer,
|
371
381
|
VALUE recorder_instance,
|
372
382
|
sample_values values,
|
373
383
|
sample_labels labels,
|
374
384
|
ddog_CharSlice placeholder_stack
|
375
385
|
) {
|
376
|
-
|
377
|
-
|
386
|
+
ddog_prof_Location placeholder_location = {
|
387
|
+
.function = {.name = DDOG_CHARSLICE_C(""), .filename = placeholder_stack},
|
388
|
+
.line = 0,
|
389
|
+
};
|
378
390
|
|
379
391
|
record_sample(
|
380
392
|
recorder_instance,
|
381
|
-
(ddog_prof_Slice_Location) {.ptr =
|
393
|
+
(ddog_prof_Slice_Location) {.ptr = &placeholder_location, .len = 1},
|
382
394
|
values,
|
383
395
|
labels
|
384
396
|
);
|
385
397
|
}
|
386
398
|
|
387
|
-
|
399
|
+
uint16_t sampling_buffer_check_max_frames(int max_frames) {
|
388
400
|
if (max_frames < 5) rb_raise(rb_eArgError, "Invalid max_frames: value must be >= 5");
|
389
401
|
if (max_frames > MAX_FRAMES_LIMIT) rb_raise(rb_eArgError, "Invalid max_frames: value must be <= " MAX_FRAMES_LIMIT_AS_STRING);
|
402
|
+
return max_frames;
|
403
|
+
}
|
404
|
+
|
405
|
+
sampling_buffer *sampling_buffer_new(uint16_t max_frames, ddog_prof_Location *locations) {
|
406
|
+
sampling_buffer_check_max_frames(max_frames);
|
390
407
|
|
391
408
|
// Note: never returns NULL; if out of memory, it calls the Ruby out-of-memory handlers
|
392
409
|
sampling_buffer* buffer = ruby_xcalloc(1, sizeof(sampling_buffer));
|
393
410
|
|
394
411
|
buffer->max_frames = max_frames;
|
395
|
-
|
396
|
-
buffer->stack_buffer
|
397
|
-
buffer->lines_buffer = ruby_xcalloc(max_frames, sizeof(int));
|
398
|
-
buffer->is_ruby_frame = ruby_xcalloc(max_frames, sizeof(bool));
|
399
|
-
buffer->locations = ruby_xcalloc(max_frames, sizeof(ddog_prof_Location));
|
412
|
+
buffer->locations = locations;
|
413
|
+
buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(frame_info));
|
400
414
|
|
401
415
|
return buffer;
|
402
416
|
}
|
@@ -404,10 +418,8 @@ sampling_buffer *sampling_buffer_new(unsigned int max_frames) {
|
|
404
418
|
void sampling_buffer_free(sampling_buffer *buffer) {
|
405
419
|
if (buffer == NULL) rb_raise(rb_eArgError, "sampling_buffer_free called with NULL buffer");
|
406
420
|
|
421
|
+
// buffer->locations are owned by whoever called sampling_buffer_new, not us
|
407
422
|
ruby_xfree(buffer->stack_buffer);
|
408
|
-
ruby_xfree(buffer->lines_buffer);
|
409
|
-
ruby_xfree(buffer->is_ruby_frame);
|
410
|
-
ruby_xfree(buffer->locations);
|
411
423
|
|
412
424
|
ruby_xfree(buffer);
|
413
425
|
}
|
@@ -17,11 +17,11 @@ void sample_thread(
|
|
17
17
|
sample_labels labels
|
18
18
|
);
|
19
19
|
void record_placeholder_stack(
|
20
|
-
sampling_buffer* buffer,
|
21
20
|
VALUE recorder_instance,
|
22
21
|
sample_values values,
|
23
22
|
sample_labels labels,
|
24
23
|
ddog_CharSlice placeholder_stack
|
25
24
|
);
|
26
|
-
|
25
|
+
uint16_t sampling_buffer_check_max_frames(int max_frames);
|
26
|
+
sampling_buffer *sampling_buffer_new(uint16_t max_frames, ddog_prof_Location *locations);
|
27
27
|
void sampling_buffer_free(sampling_buffer *buffer);
|