ddtrace 1.7.0 → 1.9.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 +100 -1
- data/README.md +2 -2
- data/ext/ddtrace_profiling_loader/extconf.rb +4 -1
- data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +1 -1
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +3 -2
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +24 -50
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +1 -1
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +284 -74
- data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.c +142 -0
- data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.h +14 -0
- data/ext/ddtrace_profiling_native_extension/collectors_idle_sampling_helper.c +241 -0
- data/ext/ddtrace_profiling_native_extension/collectors_idle_sampling_helper.h +3 -0
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +32 -32
- data/ext/ddtrace_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/ddtrace_profiling_native_extension/extconf.rb +21 -7
- data/ext/ddtrace_profiling_native_extension/helpers.h +5 -0
- data/ext/ddtrace_profiling_native_extension/http_transport.c +50 -49
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +5 -1
- data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +42 -12
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +116 -22
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +9 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +205 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +86 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +28 -6
- data/ext/ddtrace_profiling_native_extension/setup_signal_handler.c +23 -4
- data/ext/ddtrace_profiling_native_extension/setup_signal_handler.h +4 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +47 -50
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +4 -4
- data/ext/ddtrace_profiling_native_extension/time_helpers.c +17 -0
- data/ext/ddtrace_profiling_native_extension/time_helpers.h +10 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +75 -8
- data/lib/datadog/appsec/assets/waf_rules/risky.json +1 -1
- data/lib/datadog/appsec/assets/waf_rules/strict.json +1 -1
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/configuration/settings.rb +35 -22
- data/lib/datadog/appsec/configuration.rb +4 -2
- data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
- data/lib/datadog/appsec/contrib/configuration/settings.rb +1 -1
- data/lib/datadog/appsec/contrib/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/configuration/settings.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/response.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/configuration/settings.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/ext.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/framework.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/request.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/request_middleware.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/configuration/settings.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/framework.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +1 -1
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/appsec/extensions.rb +36 -26
- data/lib/datadog/appsec/instrumentation/gateway.rb +3 -3
- data/lib/datadog/appsec/processor.rb +15 -19
- data/lib/datadog/appsec/rate_limiter.rb +1 -1
- data/lib/datadog/appsec/reactive/address_hash.rb +1 -1
- data/lib/datadog/appsec/reactive/engine.rb +1 -1
- data/lib/datadog/appsec/reactive/operation.rb +2 -2
- data/lib/datadog/appsec/reactive/subscriber.rb +1 -1
- data/lib/datadog/appsec/response.rb +18 -9
- data/lib/datadog/appsec/utils/http/media_range.rb +201 -0
- data/lib/datadog/appsec/utils/http/media_type.rb +87 -0
- data/lib/datadog/appsec/utils/http.rb +9 -0
- data/lib/datadog/appsec/utils.rb +7 -0
- data/lib/datadog/appsec.rb +1 -1
- data/lib/datadog/ci/ext/environment.rb +57 -13
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +2 -2
- data/lib/datadog/core/configuration/base.rb +3 -0
- data/lib/datadog/core/configuration/components.rb +27 -6
- data/lib/datadog/core/configuration/ext.rb +26 -0
- data/lib/datadog/core/configuration/option_definition.rb +11 -2
- data/lib/datadog/core/configuration/settings.rb +16 -341
- data/lib/datadog/core/diagnostics/environment_logger.rb +4 -3
- data/lib/datadog/core/diagnostics/health.rb +4 -22
- data/lib/datadog/core/environment/variable_helpers.rb +58 -10
- data/lib/datadog/core/metrics/client.rb +3 -2
- data/lib/datadog/core/metrics/ext.rb +0 -2
- data/lib/datadog/core/telemetry/collector.rb +1 -0
- data/lib/datadog/core/utils.rb +0 -21
- data/lib/datadog/core.rb +21 -1
- data/lib/datadog/kit/appsec/events.rb +75 -0
- data/lib/datadog/kit/enable_core_dumps.rb +1 -0
- data/lib/datadog/kit/identity.rb +8 -7
- data/lib/datadog/opentelemetry/api/context.rb +187 -0
- data/lib/datadog/opentelemetry/api/trace/span.rb +15 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +38 -0
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +27 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +91 -0
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +92 -0
- data/lib/datadog/opentelemetry.rb +48 -0
- data/lib/datadog/opentracer/distributed_headers.rb +2 -2
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +16 -5
- data/lib/datadog/profiling/collectors/dynamic_sampling_rate.rb +14 -0
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +68 -0
- data/lib/datadog/profiling/stack_recorder.rb +14 -0
- data/lib/datadog/profiling.rb +2 -0
- data/lib/datadog/tracing/configuration/ext.rb +33 -4
- data/lib/datadog/tracing/configuration/settings.rb +433 -0
- data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +4 -1
- data/lib/datadog/tracing/contrib/aws/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +4 -1
- data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +6 -1
- data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +6 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +9 -4
- data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -1
- data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +10 -3
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +9 -4
- data/lib/datadog/tracing/contrib/http/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +3 -6
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -1
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +3 -4
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -1
- data/lib/datadog/tracing/contrib/httprb/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +3 -4
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +4 -1
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/patcher.rb +3 -2
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +4 -1
- data/lib/datadog/tracing/contrib/pg/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +56 -33
- data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +4 -1
- data/lib/datadog/tracing/contrib/presto/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +10 -12
- data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +4 -1
- data/lib/datadog/tracing/contrib/redis/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/redis/instrumentation.rb +30 -23
- data/lib/datadog/tracing/contrib/redis/integration.rb +34 -2
- data/lib/datadog/tracing/contrib/redis/patcher.rb +18 -14
- data/lib/datadog/tracing/contrib/redis/quantize.rb +12 -9
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -6
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +72 -0
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +6 -1
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +33 -0
- data/lib/datadog/tracing/contrib/stripe/ext.rb +26 -0
- data/lib/datadog/tracing/contrib/stripe/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/stripe/patcher.rb +29 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +67 -0
- data/lib/datadog/tracing/contrib.rb +1 -0
- data/lib/datadog/{core → tracing}/diagnostics/ext.rb +1 -6
- data/lib/datadog/tracing/diagnostics/health.rb +40 -0
- data/lib/datadog/tracing/distributed/{b3.rb → b3_multi.rb} +2 -2
- data/lib/datadog/tracing/distributed/helpers.rb +2 -1
- data/lib/datadog/tracing/distributed/none.rb +19 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +378 -0
- data/lib/datadog/tracing/metadata/ext.rb +1 -1
- data/lib/datadog/tracing/metadata/tagging.rb +6 -0
- data/lib/datadog/tracing/sampling/priority_sampler.rb +11 -0
- data/lib/datadog/tracing/sampling/rate_sampler.rb +3 -3
- data/lib/datadog/tracing/span.rb +3 -19
- data/lib/datadog/tracing/span_operation.rb +5 -4
- data/lib/datadog/tracing/trace_digest.rb +85 -2
- data/lib/datadog/tracing/trace_operation.rb +13 -4
- data/lib/datadog/tracing/utils.rb +50 -0
- data/lib/ddtrace/version.rb +1 -1
- metadata +41 -9
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
|
|
3
|
+
#include "collectors_dynamic_sampling_rate.h"
|
|
4
|
+
#include "helpers.h"
|
|
5
|
+
#include "ruby_helpers.h"
|
|
6
|
+
#include "time_helpers.h"
|
|
7
|
+
|
|
8
|
+
// Used to pace the rate of profiling samples based on the last observed time for a sample.
|
|
9
|
+
//
|
|
10
|
+
// This file implements the native bits of the Datadog::Profiling::Collectors::DynamicSamplingRate module, and is
|
|
11
|
+
// only exposed to Ruby for testing (it's always and only invoked by other C code in production).
|
|
12
|
+
|
|
13
|
+
// ---
|
|
14
|
+
// ## Dynamic Sampling Rate
|
|
15
|
+
//
|
|
16
|
+
// Our profilers get deployed in quite unpredictable situations in terms of system resources. While they can provide key
|
|
17
|
+
// information to help customers solve their performance problems, the profilers must always be careful not to make
|
|
18
|
+
// performance problems worse. This is where the idea of a dynamic sampling rate comes in.
|
|
19
|
+
//
|
|
20
|
+
// Instead of sampling at a fixed sample rate, the actual sampling rate should be decided by also observing the impact
|
|
21
|
+
// that running the profiler is having. This protects against issues such as the profiler being deployed in very busy
|
|
22
|
+
//machines or containers with unrealistic CPU restrictions.
|
|
23
|
+
//
|
|
24
|
+
// ### Implementation
|
|
25
|
+
//
|
|
26
|
+
// The APIs exposed by this file are used by the `CpuAndWallTimeWorker`.
|
|
27
|
+
//
|
|
28
|
+
// The main idea of the implementation below is the following: whenever the profiler takes a sample, the time we spent
|
|
29
|
+
// sampling and the current wall-time are recorded by calling `dynamic_sampling_rate_after_sample()`.
|
|
30
|
+
//
|
|
31
|
+
// Inside `dynamic_sampling_rate_after_sample()`, both values are combined to decide a future wall-time before which
|
|
32
|
+
// we should not sample. That is, we may decide that the next sample should happen no less than 200ms from now.
|
|
33
|
+
//
|
|
34
|
+
// Before taking a sample, the profiler checks using `dynamic_sampling_rate_should_sample()`, if it's time or not to
|
|
35
|
+
// sample. If it's not, it will skip sampling.
|
|
36
|
+
//
|
|
37
|
+
// Finally, as an additional optimization, there's a `dynamic_sampling_rate_get_sleep()` which, given the current
|
|
38
|
+
// wall-time, will return the time remaining (*there's an exception, check below) until the next sample.
|
|
39
|
+
//
|
|
40
|
+
// ---
|
|
41
|
+
|
|
42
|
+
// This is the wall-time overhead we're targeting. E.g. we target to spend no more than 2%, or 1.2 seconds per minute,
|
|
43
|
+
// taking profiling samples.
|
|
44
|
+
#define WALL_TIME_OVERHEAD_TARGET_PERCENTAGE 2.0 // %
|
|
45
|
+
// See `dynamic_sampling_rate_get_sleep()` for details
|
|
46
|
+
#define MAX_SLEEP_TIME_NS MILLIS_AS_NS(100)
|
|
47
|
+
// See `dynamic_sampling_rate_after_sample()` for details
|
|
48
|
+
#define MAX_TIME_UNTIL_NEXT_SAMPLE_NS SECONDS_AS_NS(10)
|
|
49
|
+
|
|
50
|
+
void dynamic_sampling_rate_init(dynamic_sampling_rate_state *state) {
|
|
51
|
+
atomic_init(&state->next_sample_after_monotonic_wall_time_ns, 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void dynamic_sampling_rate_reset(dynamic_sampling_rate_state *state) {
|
|
55
|
+
atomic_store(&state->next_sample_after_monotonic_wall_time_ns, 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
uint64_t dynamic_sampling_rate_get_sleep(dynamic_sampling_rate_state *state, long current_monotonic_wall_time_ns) {
|
|
59
|
+
long next_sample_after_ns = atomic_load(&state->next_sample_after_monotonic_wall_time_ns);
|
|
60
|
+
long delta_ns = next_sample_after_ns - current_monotonic_wall_time_ns;
|
|
61
|
+
|
|
62
|
+
if (delta_ns > 0 && next_sample_after_ns > 0) {
|
|
63
|
+
// We don't want to sleep for too long as the profiler may be trying to stop.
|
|
64
|
+
//
|
|
65
|
+
// Instead, here we sleep for at most this time. Worst case, the profiler will still try to sample before
|
|
66
|
+
// `next_sample_after_monotonic_wall_time_ns`, BUT `dynamic_sampling_rate_should_sample()` will still be false
|
|
67
|
+
// so we still get the intended behavior.
|
|
68
|
+
return uint64_min_of(delta_ns, MAX_SLEEP_TIME_NS);
|
|
69
|
+
} else {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
bool dynamic_sampling_rate_should_sample(dynamic_sampling_rate_state *state, long wall_time_ns_before_sample) {
|
|
75
|
+
return wall_time_ns_before_sample >= atomic_load(&state->next_sample_after_monotonic_wall_time_ns);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void dynamic_sampling_rate_after_sample(dynamic_sampling_rate_state *state, long wall_time_ns_after_sample, uint64_t sampling_time_ns) {
|
|
79
|
+
double overhead_target = (double) WALL_TIME_OVERHEAD_TARGET_PERCENTAGE;
|
|
80
|
+
|
|
81
|
+
// The idea here is that we're targeting a maximum % of wall-time spent sampling.
|
|
82
|
+
// So for instance, if sampling_time_ns is 2% of the time we spend working, how much is the 98% we should spend
|
|
83
|
+
// sleeping? As an example, if the last sample took 1ms and the target overhead is 2%, we should sleep for 49ms.
|
|
84
|
+
uint64_t time_to_sleep_ns = sampling_time_ns * ((100.0 - overhead_target)/overhead_target);
|
|
85
|
+
|
|
86
|
+
// In case a sample took an unexpected long time (e.g. maybe a VM was paused, or a laptop was suspended), we clamp the
|
|
87
|
+
// value so it doesn't get too crazy
|
|
88
|
+
time_to_sleep_ns = uint64_min_of(time_to_sleep_ns, MAX_TIME_UNTIL_NEXT_SAMPLE_NS);
|
|
89
|
+
|
|
90
|
+
atomic_store(&state->next_sample_after_monotonic_wall_time_ns, wall_time_ns_after_sample + time_to_sleep_ns);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---
|
|
94
|
+
// Below here is boilerplate to expose the above code to Ruby so that we can test it with RSpec as usual.
|
|
95
|
+
|
|
96
|
+
VALUE _native_get_sleep(DDTRACE_UNUSED VALUE self, VALUE simulated_next_sample_after_monotonic_wall_time_ns, VALUE current_monotonic_wall_time_ns);
|
|
97
|
+
VALUE _native_should_sample(DDTRACE_UNUSED VALUE self, VALUE simulated_next_sample_after_monotonic_wall_time_ns, VALUE wall_time_ns_before_sample);
|
|
98
|
+
VALUE _native_after_sample(DDTRACE_UNUSED VALUE self, VALUE wall_time_ns_after_sample, VALUE sampling_time_ns);
|
|
99
|
+
|
|
100
|
+
void collectors_dynamic_sampling_rate_init(VALUE profiling_module) {
|
|
101
|
+
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
102
|
+
VALUE dynamic_sampling_rate_module = rb_define_module_under(collectors_module, "DynamicSamplingRate");
|
|
103
|
+
VALUE testing_module = rb_define_module_under(dynamic_sampling_rate_module, "Testing");
|
|
104
|
+
|
|
105
|
+
rb_define_singleton_method(testing_module, "_native_get_sleep", _native_get_sleep, 2);
|
|
106
|
+
rb_define_singleton_method(testing_module, "_native_should_sample", _native_should_sample, 2);
|
|
107
|
+
rb_define_singleton_method(testing_module, "_native_after_sample", _native_after_sample, 2);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
VALUE _native_get_sleep(DDTRACE_UNUSED VALUE self, VALUE simulated_next_sample_after_monotonic_wall_time_ns, VALUE current_monotonic_wall_time_ns) {
|
|
111
|
+
ENFORCE_TYPE(simulated_next_sample_after_monotonic_wall_time_ns, T_FIXNUM);
|
|
112
|
+
ENFORCE_TYPE(current_monotonic_wall_time_ns, T_FIXNUM);
|
|
113
|
+
|
|
114
|
+
dynamic_sampling_rate_state state;
|
|
115
|
+
dynamic_sampling_rate_init(&state);
|
|
116
|
+
atomic_store(&state.next_sample_after_monotonic_wall_time_ns, NUM2LONG(simulated_next_sample_after_monotonic_wall_time_ns));
|
|
117
|
+
|
|
118
|
+
return ULL2NUM(dynamic_sampling_rate_get_sleep(&state, NUM2LONG(current_monotonic_wall_time_ns)));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
VALUE _native_should_sample(DDTRACE_UNUSED VALUE self, VALUE simulated_next_sample_after_monotonic_wall_time_ns, VALUE wall_time_ns_before_sample) {
|
|
122
|
+
ENFORCE_TYPE(simulated_next_sample_after_monotonic_wall_time_ns, T_FIXNUM);
|
|
123
|
+
ENFORCE_TYPE(wall_time_ns_before_sample, T_FIXNUM);
|
|
124
|
+
|
|
125
|
+
dynamic_sampling_rate_state state;
|
|
126
|
+
dynamic_sampling_rate_init(&state);
|
|
127
|
+
atomic_store(&state.next_sample_after_monotonic_wall_time_ns, NUM2LONG(simulated_next_sample_after_monotonic_wall_time_ns));
|
|
128
|
+
|
|
129
|
+
return dynamic_sampling_rate_should_sample(&state, NUM2LONG(wall_time_ns_before_sample)) ? Qtrue : Qfalse;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
VALUE _native_after_sample(DDTRACE_UNUSED VALUE self, VALUE wall_time_ns_after_sample, VALUE sampling_time_ns) {
|
|
133
|
+
ENFORCE_TYPE(wall_time_ns_after_sample, T_FIXNUM);
|
|
134
|
+
ENFORCE_TYPE(sampling_time_ns, T_FIXNUM);
|
|
135
|
+
|
|
136
|
+
dynamic_sampling_rate_state state;
|
|
137
|
+
dynamic_sampling_rate_init(&state);
|
|
138
|
+
|
|
139
|
+
dynamic_sampling_rate_after_sample(&state, NUM2LONG(wall_time_ns_after_sample), NUM2ULL(sampling_time_ns));
|
|
140
|
+
|
|
141
|
+
return ULL2NUM(atomic_load(&state.next_sample_after_monotonic_wall_time_ns));
|
|
142
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <stdatomic.h>
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
|
|
6
|
+
typedef struct {
|
|
7
|
+
atomic_long next_sample_after_monotonic_wall_time_ns;
|
|
8
|
+
} dynamic_sampling_rate_state;
|
|
9
|
+
|
|
10
|
+
void dynamic_sampling_rate_init(dynamic_sampling_rate_state *state);
|
|
11
|
+
void dynamic_sampling_rate_reset(dynamic_sampling_rate_state *state);
|
|
12
|
+
uint64_t dynamic_sampling_rate_get_sleep(dynamic_sampling_rate_state *state, long current_monotonic_wall_time_ns);
|
|
13
|
+
bool dynamic_sampling_rate_should_sample(dynamic_sampling_rate_state *state, long wall_time_ns_before_sample);
|
|
14
|
+
void dynamic_sampling_rate_after_sample(dynamic_sampling_rate_state *state, long wall_time_ns_after_sample, uint64_t sampling_time_ns);
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <ruby/thread.h>
|
|
3
|
+
#include <pthread.h>
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
|
|
6
|
+
#include "helpers.h"
|
|
7
|
+
#include "ruby_helpers.h"
|
|
8
|
+
#include "collectors_idle_sampling_helper.h"
|
|
9
|
+
|
|
10
|
+
// Used by the Collectors::CpuAndWallTimeWorker to gather samples when the Ruby process is idle.
|
|
11
|
+
//
|
|
12
|
+
// Specifically, the IdleSamplingHelper is expected to be triggered by the CpuAndWallTimeWorker whenever it needs to
|
|
13
|
+
// trigger a sample, but the VM is otherwise idle. See implementation of CpuAndWallTimeWorker for details.
|
|
14
|
+
//
|
|
15
|
+
// The IdleSamplingHelper keeps a background thread that waits for functions to run on a single-element "queue".
|
|
16
|
+
// Other threads communicate with it by asking it to ACTION_RUN a `requested_action` or ACTION_STOP to terminate.
|
|
17
|
+
//
|
|
18
|
+
// The state is protected by the `wakeup_mutex`, and the background thread is woken up after changes using the
|
|
19
|
+
// `wakeup` condition variable.
|
|
20
|
+
|
|
21
|
+
typedef enum { ACTION_WAIT, ACTION_RUN, ACTION_STOP } action;
|
|
22
|
+
|
|
23
|
+
// Contains state for a single CpuAndWallTimeWorker instance
|
|
24
|
+
struct idle_sampling_loop_state {
|
|
25
|
+
pthread_mutex_t wakeup_mutex;
|
|
26
|
+
pthread_cond_t wakeup;
|
|
27
|
+
action requested_action;
|
|
28
|
+
void (*run_action_function)(void);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
static VALUE _native_new(VALUE klass);
|
|
32
|
+
static void reset_state(struct idle_sampling_loop_state *state);
|
|
33
|
+
static VALUE _native_idle_sampling_loop(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
34
|
+
static VALUE _native_stop(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
35
|
+
static void *run_idle_sampling_loop(void *state_ptr);
|
|
36
|
+
static void interrupt_idle_sampling_loop(void *state_ptr);
|
|
37
|
+
static VALUE _native_reset(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
38
|
+
static VALUE _native_idle_sampling_helper_request_action(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
39
|
+
static void *request_testing_action(void *self_instance_ptr);
|
|
40
|
+
static void grab_gvl_and_run_testing_action(void);
|
|
41
|
+
static void *run_testing_action(DDTRACE_UNUSED void *unused);
|
|
42
|
+
|
|
43
|
+
void collectors_idle_sampling_helper_init(VALUE profiling_module) {
|
|
44
|
+
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
45
|
+
VALUE collectors_idle_sampling_helper_class = rb_define_class_under(collectors_module, "IdleSamplingHelper", rb_cObject);
|
|
46
|
+
// Hosts methods used for testing the native code using RSpec
|
|
47
|
+
VALUE testing_module = rb_define_module_under(collectors_idle_sampling_helper_class, "Testing");
|
|
48
|
+
|
|
49
|
+
// Instances of the IdleSamplingHelper class are "TypedData" objects.
|
|
50
|
+
// "TypedData" objects are special objects in the Ruby VM that can wrap C structs.
|
|
51
|
+
// In this case, it wraps the idle_sampling_loop_state.
|
|
52
|
+
//
|
|
53
|
+
// Because Ruby doesn't know how to initialize native-level structs, we MUST override the allocation function for objects
|
|
54
|
+
// of this class so that we can manage this part. Not overriding or disabling the allocation function is a common
|
|
55
|
+
// gotcha for "TypedData" objects that can very easily lead to VM crashes, see for instance
|
|
56
|
+
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
|
57
|
+
rb_define_alloc_func(collectors_idle_sampling_helper_class, _native_new);
|
|
58
|
+
|
|
59
|
+
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_idle_sampling_loop", _native_idle_sampling_loop, 1);
|
|
60
|
+
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_stop", _native_stop, 1);
|
|
61
|
+
rb_define_singleton_method(collectors_idle_sampling_helper_class, "_native_reset", _native_reset, 1);
|
|
62
|
+
rb_define_singleton_method(testing_module, "_native_idle_sampling_helper_request_action", _native_idle_sampling_helper_request_action, 1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// This structure is used to define a Ruby object that stores a pointer to a struct idle_sampling_loop_state
|
|
66
|
+
// See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
|
|
67
|
+
static const rb_data_type_t idle_sampling_helper_typed_data = {
|
|
68
|
+
.wrap_struct_name = "Datadog::Profiling::Collectors::IdleSamplingHelper",
|
|
69
|
+
.function = {
|
|
70
|
+
.dmark = NULL, // We don't store references to Ruby objects so we don't need to mark any of them
|
|
71
|
+
.dfree = RUBY_DEFAULT_FREE,
|
|
72
|
+
.dsize = NULL, // We don't track memory usage (although it'd be cool if we did!)
|
|
73
|
+
//.dcompact = NULL, // Not needed -- we don't store references to Ruby objects
|
|
74
|
+
},
|
|
75
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
static VALUE _native_new(VALUE klass) {
|
|
79
|
+
struct idle_sampling_loop_state *state = ruby_xcalloc(1, sizeof(struct idle_sampling_loop_state));
|
|
80
|
+
|
|
81
|
+
reset_state(state);
|
|
82
|
+
|
|
83
|
+
return TypedData_Wrap_Struct(klass, &idle_sampling_helper_typed_data, state);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static void reset_state(struct idle_sampling_loop_state *state) {
|
|
87
|
+
state->wakeup_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
|
|
88
|
+
state->wakeup = (pthread_cond_t) PTHREAD_COND_INITIALIZER;
|
|
89
|
+
state->requested_action = ACTION_WAIT;
|
|
90
|
+
state->run_action_function = NULL;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The same instance of the IdleSamplingHelper can be reused multiple times, and this resets it back to
|
|
94
|
+
// a pristine state before recreating the worker thread (this includes resetting the mutex in case it was left
|
|
95
|
+
// locked halfway through the VM forking)
|
|
96
|
+
static VALUE _native_reset(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
97
|
+
struct idle_sampling_loop_state *state;
|
|
98
|
+
TypedData_Get_Struct(self_instance, struct idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
99
|
+
|
|
100
|
+
reset_state(state);
|
|
101
|
+
|
|
102
|
+
return Qtrue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static VALUE _native_idle_sampling_loop(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
106
|
+
struct idle_sampling_loop_state *state;
|
|
107
|
+
TypedData_Get_Struct(self_instance, struct idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
108
|
+
|
|
109
|
+
// Release GVL and run the loop waiting for requests
|
|
110
|
+
rb_thread_call_without_gvl(run_idle_sampling_loop, state, interrupt_idle_sampling_loop, state);
|
|
111
|
+
|
|
112
|
+
return Qtrue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static void *run_idle_sampling_loop(void *state_ptr) {
|
|
116
|
+
struct idle_sampling_loop_state *state = (struct idle_sampling_loop_state *) state_ptr;
|
|
117
|
+
int error = 0;
|
|
118
|
+
|
|
119
|
+
while (true) {
|
|
120
|
+
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_lock(&state->wakeup_mutex));
|
|
121
|
+
|
|
122
|
+
action next_action;
|
|
123
|
+
void (*run_action_function)(void);
|
|
124
|
+
|
|
125
|
+
// Await for an action
|
|
126
|
+
while ((next_action = state->requested_action) == ACTION_WAIT) {
|
|
127
|
+
error = pthread_cond_wait(&state->wakeup, &state->wakeup_mutex);
|
|
128
|
+
if (error) {
|
|
129
|
+
// If something went wrong, try to leave the mutex unlocked at least
|
|
130
|
+
pthread_mutex_unlock(&state->wakeup_mutex);
|
|
131
|
+
ENFORCE_SUCCESS_NO_GVL(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// There's an action to be taken!
|
|
136
|
+
|
|
137
|
+
// Record function, if any
|
|
138
|
+
run_action_function = state->run_action_function;
|
|
139
|
+
|
|
140
|
+
// Reset buffer for next request
|
|
141
|
+
state->requested_action = ACTION_WAIT;
|
|
142
|
+
|
|
143
|
+
// Unlock the mutex immediately so other threads can continue to request actions without blocking
|
|
144
|
+
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_unlock(&state->wakeup_mutex));
|
|
145
|
+
|
|
146
|
+
// Process pending action
|
|
147
|
+
if (next_action == ACTION_RUN) {
|
|
148
|
+
if (run_action_function == NULL) {
|
|
149
|
+
grab_gvl_and_raise(rb_eRuntimeError, "Unexpected NULL run_action_function in run_idle_sampling_loop");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
run_action_function();
|
|
153
|
+
} else { // ACTION_STOP
|
|
154
|
+
return NULL;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static void interrupt_idle_sampling_loop(void *state_ptr) {
|
|
160
|
+
struct idle_sampling_loop_state *state = (struct idle_sampling_loop_state *) state_ptr;
|
|
161
|
+
int error = 0;
|
|
162
|
+
|
|
163
|
+
// Note about the error handling in this situation: Something bad happening at this stage is really really awkward to
|
|
164
|
+
// handle because we get called by the VM in a situation where we can't really raise exceptions, and the VM really really
|
|
165
|
+
// just wants us to stop what we're doing and return control of the thread to it.
|
|
166
|
+
//
|
|
167
|
+
// So if we return immediately on error, we may leave the VM hanging because we didn't actually interrupt the thread.
|
|
168
|
+
// We're also not at a great location to flag errors.
|
|
169
|
+
// That's why: a) I chose to log to stderr, as a last-ditch effort; b) even if something goes wrong we still try to
|
|
170
|
+
// ask the thread to stop, instead of exiting early.
|
|
171
|
+
|
|
172
|
+
error = pthread_mutex_lock(&state->wakeup_mutex);
|
|
173
|
+
if (error) { fprintf(stderr, "[DDTRACE] Error during pthread_mutex_lock in interrupt_idle_sampling_loop (%s)\n", strerror(error)); }
|
|
174
|
+
|
|
175
|
+
state->requested_action = ACTION_STOP;
|
|
176
|
+
|
|
177
|
+
error = pthread_mutex_unlock(&state->wakeup_mutex);
|
|
178
|
+
if (error) { fprintf(stderr, "[DDTRACE] Error during pthread_mutex_unlock in interrupt_idle_sampling_loop (%s)\n", strerror(error)); }
|
|
179
|
+
|
|
180
|
+
error = pthread_cond_broadcast(&state->wakeup);
|
|
181
|
+
if (error) { fprintf(stderr, "[DDTRACE] Error during pthread_cond_broadcast in interrupt_idle_sampling_loop (%s)\n", strerror(error)); }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static VALUE _native_stop(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
185
|
+
struct idle_sampling_loop_state *state;
|
|
186
|
+
TypedData_Get_Struct(self_instance, struct idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
187
|
+
|
|
188
|
+
ENFORCE_SUCCESS_GVL(pthread_mutex_lock(&state->wakeup_mutex));
|
|
189
|
+
state->requested_action = ACTION_STOP;
|
|
190
|
+
ENFORCE_SUCCESS_GVL(pthread_mutex_unlock(&state->wakeup_mutex));
|
|
191
|
+
|
|
192
|
+
// Wake up worker thread, if needed; It's OK to call broadcast after releasing the mutex
|
|
193
|
+
ENFORCE_SUCCESS_GVL(pthread_cond_broadcast(&state->wakeup));
|
|
194
|
+
|
|
195
|
+
return Qtrue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Assumption: Function gets called without the global VM lock
|
|
199
|
+
void idle_sampling_helper_request_action(VALUE self_instance, void (*run_action_function)(void)) {
|
|
200
|
+
struct idle_sampling_loop_state *state;
|
|
201
|
+
if (!rb_typeddata_is_kind_of(self_instance, &idle_sampling_helper_typed_data)) {
|
|
202
|
+
grab_gvl_and_raise(rb_eTypeError, "Wrong argument for idle_sampling_helper_request_action");
|
|
203
|
+
}
|
|
204
|
+
// This should never fail the the above check passes
|
|
205
|
+
TypedData_Get_Struct(self_instance, struct idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
206
|
+
|
|
207
|
+
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_lock(&state->wakeup_mutex));
|
|
208
|
+
if (state->requested_action == ACTION_WAIT) {
|
|
209
|
+
state->requested_action = ACTION_RUN;
|
|
210
|
+
state->run_action_function = run_action_function;
|
|
211
|
+
}
|
|
212
|
+
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_unlock(&state->wakeup_mutex));
|
|
213
|
+
|
|
214
|
+
// Wake up worker thread, if needed; It's OK to call broadcast after releasing the mutex
|
|
215
|
+
ENFORCE_SUCCESS_NO_GVL(pthread_cond_broadcast(&state->wakeup));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Because the idle_sampling_helper_request_action is built to be called without the global VM lock, here we release it
|
|
219
|
+
// to be able to call that API.
|
|
220
|
+
static VALUE _native_idle_sampling_helper_request_action(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
221
|
+
rb_thread_call_without_gvl(request_testing_action, (void *) self_instance, NULL, NULL);
|
|
222
|
+
return Qtrue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static void *request_testing_action(void *self_instance_ptr) {
|
|
226
|
+
VALUE self_instance = (VALUE) self_instance_ptr;
|
|
227
|
+
idle_sampling_helper_request_action(self_instance, grab_gvl_and_run_testing_action);
|
|
228
|
+
return NULL;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// This gets called by the worker thread, which is not holding the global VM lock. To be able to actually run the action,
|
|
232
|
+
// we need to acquire it
|
|
233
|
+
static void grab_gvl_and_run_testing_action(void) {
|
|
234
|
+
rb_thread_call_with_gvl(run_testing_action, NULL);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
static void *run_testing_action(DDTRACE_UNUSED void *unused) {
|
|
238
|
+
VALUE idle_sampling_helper_testing_action = rb_gv_get("$idle_sampling_helper_testing_action");
|
|
239
|
+
rb_funcall(idle_sampling_helper_testing_action, rb_intern("call"), 0);
|
|
240
|
+
return NULL;
|
|
241
|
+
}
|
|
@@ -22,8 +22,8 @@ struct sampling_buffer {
|
|
|
22
22
|
VALUE *stack_buffer;
|
|
23
23
|
int *lines_buffer;
|
|
24
24
|
bool *is_ruby_frame;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
ddog_prof_Location *locations;
|
|
26
|
+
ddog_prof_Line *lines;
|
|
27
27
|
}; // Note: typedef'd in the header to sampling_buffer
|
|
28
28
|
|
|
29
29
|
static VALUE _native_sample(
|
|
@@ -39,8 +39,8 @@ static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer*
|
|
|
39
39
|
static void record_placeholder_stack_in_native_code(
|
|
40
40
|
sampling_buffer* buffer,
|
|
41
41
|
VALUE recorder_instance,
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
ddog_Slice_I64 metric_values,
|
|
43
|
+
ddog_prof_Slice_Label labels,
|
|
44
44
|
sampling_buffer *record_buffer,
|
|
45
45
|
int extra_frames_in_record_buffer
|
|
46
46
|
);
|
|
@@ -48,8 +48,8 @@ static void sample_thread_internal(
|
|
|
48
48
|
VALUE thread,
|
|
49
49
|
sampling_buffer* buffer,
|
|
50
50
|
VALUE recorder_instance,
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
ddog_Slice_I64 metric_values,
|
|
52
|
+
ddog_prof_Slice_Label labels,
|
|
53
53
|
sampling_buffer *record_buffer,
|
|
54
54
|
int extra_frames_in_record_buffer
|
|
55
55
|
);
|
|
@@ -96,12 +96,12 @@ static VALUE _native_sample(
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
long labels_count = RARRAY_LEN(labels_array);
|
|
99
|
-
|
|
99
|
+
ddog_prof_Label labels[labels_count];
|
|
100
100
|
|
|
101
101
|
for (int i = 0; i < labels_count; i++) {
|
|
102
102
|
VALUE key_str_pair = rb_ary_entry(labels_array, i);
|
|
103
103
|
|
|
104
|
-
labels[i] = (
|
|
104
|
+
labels[i] = (ddog_prof_Label) {
|
|
105
105
|
.key = char_slice_from_ruby_string(rb_ary_entry(key_str_pair, 0)),
|
|
106
106
|
.str = char_slice_from_ruby_string(rb_ary_entry(key_str_pair, 1))
|
|
107
107
|
};
|
|
@@ -116,8 +116,8 @@ static VALUE _native_sample(
|
|
|
116
116
|
thread,
|
|
117
117
|
buffer,
|
|
118
118
|
recorder_instance,
|
|
119
|
-
(
|
|
120
|
-
(
|
|
119
|
+
(ddog_Slice_I64) {.ptr = metric_values, .len = ENABLED_VALUE_TYPES_COUNT},
|
|
120
|
+
(ddog_prof_Slice_Label) {.ptr = labels, .len = labels_count},
|
|
121
121
|
RTEST(in_gc) ? SAMPLE_IN_GC : SAMPLE_REGULAR
|
|
122
122
|
);
|
|
123
123
|
|
|
@@ -130,8 +130,8 @@ void sample_thread(
|
|
|
130
130
|
VALUE thread,
|
|
131
131
|
sampling_buffer* buffer,
|
|
132
132
|
VALUE recorder_instance,
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
ddog_Slice_I64 metric_values,
|
|
134
|
+
ddog_prof_Slice_Label labels,
|
|
135
135
|
sample_type type
|
|
136
136
|
) {
|
|
137
137
|
// Samples thread into recorder
|
|
@@ -146,8 +146,8 @@ void sample_thread(
|
|
|
146
146
|
if (type == SAMPLE_IN_GC) {
|
|
147
147
|
ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
|
|
148
148
|
ddog_CharSlice function_filename = DDOG_CHARSLICE_C("Garbage Collection");
|
|
149
|
-
buffer->lines[0] = (
|
|
150
|
-
.function = (
|
|
149
|
+
buffer->lines[0] = (ddog_prof_Line) {
|
|
150
|
+
.function = (ddog_prof_Function) {.name = function_name, .filename = function_filename},
|
|
151
151
|
.line = 0
|
|
152
152
|
};
|
|
153
153
|
// To avoid changing sample_thread_internal, we just prepare a new buffer struct that uses the same underlying storage as the
|
|
@@ -192,8 +192,8 @@ static void sample_thread_internal(
|
|
|
192
192
|
VALUE thread,
|
|
193
193
|
sampling_buffer* buffer,
|
|
194
194
|
VALUE recorder_instance,
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
ddog_Slice_I64 metric_values,
|
|
196
|
+
ddog_prof_Slice_Label labels,
|
|
197
197
|
sampling_buffer *record_buffer,
|
|
198
198
|
int extra_frames_in_record_buffer
|
|
199
199
|
) {
|
|
@@ -257,8 +257,8 @@ static void sample_thread_internal(
|
|
|
257
257
|
name = NIL_P(name) ? missing_string : name;
|
|
258
258
|
filename = NIL_P(filename) ? missing_string : filename;
|
|
259
259
|
|
|
260
|
-
buffer->lines[i] = (
|
|
261
|
-
.function = (
|
|
260
|
+
buffer->lines[i] = (ddog_prof_Line) {
|
|
261
|
+
.function = (ddog_prof_Function) {
|
|
262
262
|
.name = char_slice_from_ruby_string(name),
|
|
263
263
|
.filename = char_slice_from_ruby_string(filename)
|
|
264
264
|
},
|
|
@@ -278,8 +278,8 @@ static void sample_thread_internal(
|
|
|
278
278
|
|
|
279
279
|
record_sample(
|
|
280
280
|
recorder_instance,
|
|
281
|
-
(
|
|
282
|
-
.locations = (
|
|
281
|
+
(ddog_prof_Sample) {
|
|
282
|
+
.locations = (ddog_prof_Slice_Location) {.ptr = record_buffer->locations, .len = captured_frames + extra_frames_in_record_buffer},
|
|
283
283
|
.values = metric_values,
|
|
284
284
|
.labels = labels,
|
|
285
285
|
}
|
|
@@ -301,8 +301,8 @@ static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer*
|
|
|
301
301
|
// `record_sample`. So be careful where it gets allocated. (We do have tests for this, at least!)
|
|
302
302
|
ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
|
|
303
303
|
ddog_CharSlice function_filename = {.ptr = frames_omitted_message, .len = strlen(frames_omitted_message)};
|
|
304
|
-
buffer->lines[buffer->max_frames - 1] = (
|
|
305
|
-
.function = (
|
|
304
|
+
buffer->lines[buffer->max_frames - 1] = (ddog_prof_Line) {
|
|
305
|
+
.function = (ddog_prof_Function) {.name = function_name, .filename = function_filename},
|
|
306
306
|
.line = 0,
|
|
307
307
|
};
|
|
308
308
|
}
|
|
@@ -330,22 +330,22 @@ static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer*
|
|
|
330
330
|
static void record_placeholder_stack_in_native_code(
|
|
331
331
|
sampling_buffer* buffer,
|
|
332
332
|
VALUE recorder_instance,
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
ddog_Slice_I64 metric_values,
|
|
334
|
+
ddog_prof_Slice_Label labels,
|
|
335
335
|
sampling_buffer *record_buffer,
|
|
336
336
|
int extra_frames_in_record_buffer
|
|
337
337
|
) {
|
|
338
338
|
ddog_CharSlice function_name = DDOG_CHARSLICE_C("");
|
|
339
339
|
ddog_CharSlice function_filename = DDOG_CHARSLICE_C("In native code");
|
|
340
|
-
buffer->lines[0] = (
|
|
341
|
-
.function = (
|
|
340
|
+
buffer->lines[0] = (ddog_prof_Line) {
|
|
341
|
+
.function = (ddog_prof_Function) {.name = function_name, .filename = function_filename},
|
|
342
342
|
.line = 0
|
|
343
343
|
};
|
|
344
344
|
|
|
345
345
|
record_sample(
|
|
346
346
|
recorder_instance,
|
|
347
|
-
(
|
|
348
|
-
.locations = (
|
|
347
|
+
(ddog_prof_Sample) {
|
|
348
|
+
.locations = (ddog_prof_Slice_Location) {.ptr = record_buffer->locations, .len = 1 + extra_frames_in_record_buffer},
|
|
349
349
|
.values = metric_values,
|
|
350
350
|
.labels = labels,
|
|
351
351
|
}
|
|
@@ -364,14 +364,14 @@ sampling_buffer *sampling_buffer_new(unsigned int max_frames) {
|
|
|
364
364
|
buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(VALUE));
|
|
365
365
|
buffer->lines_buffer = ruby_xcalloc(max_frames, sizeof(int));
|
|
366
366
|
buffer->is_ruby_frame = ruby_xcalloc(max_frames, sizeof(bool));
|
|
367
|
-
buffer->locations = ruby_xcalloc(max_frames, sizeof(
|
|
368
|
-
buffer->lines = ruby_xcalloc(max_frames, sizeof(
|
|
367
|
+
buffer->locations = ruby_xcalloc(max_frames, sizeof(ddog_prof_Location));
|
|
368
|
+
buffer->lines = ruby_xcalloc(max_frames, sizeof(ddog_prof_Line));
|
|
369
369
|
|
|
370
370
|
// Currently we have a 1-to-1 correspondence between lines and locations, so we just initialize the locations once
|
|
371
371
|
// here and then only mutate the contents of the lines.
|
|
372
372
|
for (unsigned int i = 0; i < max_frames; i++) {
|
|
373
|
-
|
|
374
|
-
buffer->locations[i] = (
|
|
373
|
+
ddog_prof_Slice_Line lines = (ddog_prof_Slice_Line) {.ptr = &buffer->lines[i], .len = 1};
|
|
374
|
+
buffer->locations[i] = (ddog_prof_Location) {.lines = lines};
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
return buffer;
|
|
@@ -10,8 +10,8 @@ void sample_thread(
|
|
|
10
10
|
VALUE thread,
|
|
11
11
|
sampling_buffer* buffer,
|
|
12
12
|
VALUE recorder_instance,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
ddog_Slice_I64 metric_values,
|
|
14
|
+
ddog_prof_Slice_Label labels,
|
|
15
15
|
sample_type type
|
|
16
16
|
);
|
|
17
17
|
sampling_buffer *sampling_buffer_new(unsigned int max_frames);
|
|
@@ -67,6 +67,10 @@ $stderr.puts(
|
|
|
67
67
|
# that may fail on an environment not properly setup for building Ruby extensions.
|
|
68
68
|
require 'mkmf'
|
|
69
69
|
|
|
70
|
+
Logging.message(" [ddtrace] Using compiler:\n")
|
|
71
|
+
xsystem("#{CONFIG['CC']} -v")
|
|
72
|
+
Logging.message(" [ddtrace] End of compiler information\n")
|
|
73
|
+
|
|
70
74
|
# mkmf on modern Rubies actually has an append_cflags that does something similar
|
|
71
75
|
# (see https://github.com/ruby/ruby/pull/5760), but as usual we need a bit more boilerplate to deal with legacy Rubies
|
|
72
76
|
def add_compiler_flag(flag)
|
|
@@ -87,7 +91,7 @@ add_compiler_flag '-Werror' if ENV['DDTRACE_CI'] == 'true'
|
|
|
87
91
|
# (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
|
|
88
92
|
add_compiler_flag '-std=gnu99'
|
|
89
93
|
|
|
90
|
-
# Gets really noisy when we include the MJIT header, let's omit it
|
|
94
|
+
# Gets really noisy when we include the MJIT header, let's omit it (TODO: Use #pragma GCC diagnostic instead?)
|
|
91
95
|
add_compiler_flag '-Wno-unused-function'
|
|
92
96
|
|
|
93
97
|
# Allow defining variables at any point in a function
|
|
@@ -108,6 +112,9 @@ add_compiler_flag '-Wunused-parameter'
|
|
|
108
112
|
# For more details see https://gcc.gnu.org/wiki/Visibility
|
|
109
113
|
add_compiler_flag '-fvisibility=hidden'
|
|
110
114
|
|
|
115
|
+
# Avoid legacy C definitions
|
|
116
|
+
add_compiler_flag '-Wold-style-definition'
|
|
117
|
+
|
|
111
118
|
# Enable all other compiler warnings
|
|
112
119
|
add_compiler_flag '-Wall'
|
|
113
120
|
add_compiler_flag '-Wextra'
|
|
@@ -126,6 +133,9 @@ end
|
|
|
126
133
|
# On older Rubies, there was no struct rb_native_thread. See private_vm_api_acccess.c for details.
|
|
127
134
|
$defs << '-DNO_RB_NATIVE_THREAD' if RUBY_VERSION < '3.2'
|
|
128
135
|
|
|
136
|
+
# On older Rubies, there was no struct rb_thread_sched (it was struct rb_global_vm_lock_struct)
|
|
137
|
+
$defs << '-DNO_RB_THREAD_SCHED' if RUBY_VERSION < '3.2'
|
|
138
|
+
|
|
129
139
|
# On older Rubies, there was no tid member in the internal thread structure
|
|
130
140
|
$defs << '-DNO_THREAD_TID' if RUBY_VERSION < '3.1'
|
|
131
141
|
|
|
@@ -135,9 +145,15 @@ $defs << '-DUSE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME' if RUBY_VERSION < '3'
|
|
|
135
145
|
# On older Rubies, there are no Ractors
|
|
136
146
|
$defs << '-DNO_RACTORS' if RUBY_VERSION < '3'
|
|
137
147
|
|
|
148
|
+
# On older Rubies, rb_global_vm_lock_struct did not include the owner field
|
|
149
|
+
$defs << '-DNO_GVL_OWNER' if RUBY_VERSION < '2.6'
|
|
150
|
+
|
|
138
151
|
# On older Rubies, we need to use rb_thread_t instead of rb_execution_context_t
|
|
139
152
|
$defs << '-DUSE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT' if RUBY_VERSION < '2.5'
|
|
140
153
|
|
|
154
|
+
# On older Rubies, extensions can't use GET_VM()
|
|
155
|
+
$defs << '-DNO_GET_VM' if RUBY_VERSION < '2.5'
|
|
156
|
+
|
|
141
157
|
# On older Rubies...
|
|
142
158
|
if RUBY_VERSION < '2.4'
|
|
143
159
|
# ...we need to use RUBY_VM_NORMAL_ISEQ_P instead of VM_FRAME_RUBYFRAME_P
|
|
@@ -154,8 +170,6 @@ if RUBY_VERSION < '2.3'
|
|
|
154
170
|
$defs << '-DUSE_LEGACY_RB_PROFILE_FRAMES'
|
|
155
171
|
# ... you couldn't name threads
|
|
156
172
|
$defs << '-DNO_THREAD_NAMES'
|
|
157
|
-
# ...the ruby_thread_has_gvl_p function was not exposed to users outside of the VM
|
|
158
|
-
$defs << '-DNO_THREAD_HAS_GVL'
|
|
159
173
|
end
|
|
160
174
|
|
|
161
175
|
# If we got here, libdatadog is available and loaded
|
|
@@ -173,6 +187,10 @@ unless pkg_config('datadog_profiling_with_rpath')
|
|
|
173
187
|
)
|
|
174
188
|
end
|
|
175
189
|
|
|
190
|
+
unless have_type('atomic_int', ['stdatomic.h'])
|
|
191
|
+
skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILER_ATOMIC_MISSING)
|
|
192
|
+
end
|
|
193
|
+
|
|
176
194
|
# See comments on the helper method being used for why we need to additionally set this.
|
|
177
195
|
# The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
|
|
178
196
|
# experimentation. We need to get these special characters across a lot of tools untouched...
|
|
@@ -181,10 +199,6 @@ $LDFLAGS += \
|
|
|
181
199
|
"#{Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder}"
|
|
182
200
|
Logging.message(" [ddtrace] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
|
|
183
201
|
|
|
184
|
-
Logging.message(" [ddtrace] Using compiler:\n")
|
|
185
|
-
xsystem("#{CONFIG['CC']} --version")
|
|
186
|
-
Logging.message(" [ddtrace] End of compiler information\n")
|
|
187
|
-
|
|
188
202
|
# Tag the native extension library with the Ruby version and Ruby platform.
|
|
189
203
|
# This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
|
|
190
204
|
# the wrong library is never loaded.
|