datadog 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|