datadog 2.0.0 → 2.2.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 +66 -2
- data/README.md +1 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +19 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +41 -0
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1 -1
- data/ext/datadog_profiling_native_extension/crashtracker.c +1 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +47 -1
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +1 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +13 -6
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/lib/datadog/appsec/configuration/settings.rb +5 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +0 -1
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +1 -1
- data/lib/datadog/appsec/extensions.rb +1 -0
- data/lib/datadog/core/configuration/components.rb +6 -3
- data/lib/datadog/core/configuration/ext.rb +1 -0
- data/lib/datadog/core/configuration/option.rb +21 -14
- data/lib/datadog/core/configuration/options.rb +5 -1
- data/lib/datadog/core/configuration/settings.rb +68 -5
- data/lib/datadog/core/configuration.rb +3 -17
- data/lib/datadog/core/deprecations.rb +58 -0
- data/lib/datadog/core/environment/ext.rb +2 -0
- data/lib/datadog/core/environment/yjit.rb +5 -0
- data/lib/datadog/core/runtime/ext.rb +2 -0
- data/lib/datadog/core/runtime/metrics.rb +6 -0
- data/lib/datadog/core/telemetry/component.rb +107 -0
- data/lib/datadog/core/telemetry/event.rb +124 -31
- data/lib/datadog/core/telemetry/ext.rb +2 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
- data/lib/datadog/core/telemetry/metric.rb +167 -0
- data/lib/datadog/core/telemetry/metrics_collection.rb +81 -0
- data/lib/datadog/core/telemetry/metrics_manager.rb +81 -0
- data/lib/datadog/core/telemetry/request.rb +1 -1
- data/lib/datadog/core/telemetry/worker.rb +173 -0
- data/lib/datadog/core/utils/only_once_successful.rb +76 -0
- data/lib/datadog/core.rb +2 -19
- data/lib/datadog/opentelemetry/sdk/propagator.rb +5 -10
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +5 -2
- data/lib/datadog/profiling/collectors/code_provenance.rb +18 -5
- data/lib/datadog/profiling/component.rb +18 -1
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +410 -0
- data/lib/datadog/profiling.rb +1 -0
- data/lib/datadog/tracing/configuration/ext.rb +7 -0
- data/lib/datadog/tracing/configuration/settings.rb +52 -3
- data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
- data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +1 -1
- data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +1 -1
- data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +1 -1
- data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -6
- data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +9 -4
- data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +3 -2
- data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +1 -5
- data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/event.rb +32 -0
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +156 -0
- data/lib/datadog/tracing/contrib/active_support/cache/events.rb +34 -0
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +45 -41
- data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +17 -40
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +29 -6
- data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +16 -4
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +33 -29
- data/lib/datadog/tracing/contrib/analytics.rb +5 -0
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -2
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +166 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +25 -0
- data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/consumer_group_event.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/event.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +3 -3
- data/lib/datadog/tracing/contrib/racecar/event.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/ext.rb +9 -0
- data/lib/datadog/tracing/contrib/rails/patcher.rb +7 -0
- data/lib/datadog/tracing/contrib/rails/runner.rb +95 -0
- data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
- data/lib/datadog/tracing/distributed/b3_single.rb +3 -1
- data/lib/datadog/tracing/distributed/datadog.rb +2 -2
- data/lib/datadog/tracing/distributed/propagation.rb +39 -4
- data/lib/datadog/tracing/distributed/trace_context.rb +5 -3
- data/lib/datadog/tracing/metadata/ext.rb +1 -0
- data/lib/datadog/tracing/span_operation.rb +3 -2
- data/lib/datadog/tracing/trace_operation.rb +7 -3
- data/lib/datadog/tracing/trace_segment.rb +4 -1
- data/lib/datadog/tracing/tracer.rb +9 -2
- data/lib/datadog/tracing.rb +5 -1
- data/lib/datadog/version.rb +2 -2
- metadata +21 -8
- data/lib/datadog/core/telemetry/client.rb +0 -95
- data/lib/datadog/core/telemetry/heartbeat.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 895fe8f9fdd8391d5c6c86e8d39d0a8e241c85bbce42b35c9744e1f94095853f
|
4
|
+
data.tar.gz: d5a6e88ec35816de59a5d080f66896a4c85656216d2853b46bb1268dfa64df35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fef3c78c7835c47507a1f09d87c2ee84ddcc97303f9b9d7c6b4d601381ac62a47133f3450ddbc888cf54c56353ef4417ff337009a1e60d2ae239fb19093d721b
|
7
|
+
data.tar.gz: 061154162ab97a6e1cdc87f53c18d98fd0eea75b6cb7e71fd1e4e5c4a536ff7722c4e1202258760113ce5a7f0ce3838622e700858103fa97dddc33d0322275cc
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,46 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [2.2.0] - 2024-07-11
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
* Tracing: Add `Rails` Runner instrumentation ([#2509][])
|
10
|
+
* Tracing: Introduce a new, reworked `GraphQL` tracer to comply with span attributes specification ([#3672][])
|
11
|
+
* Tracing: Enhance `ActiveSupport::Cache` instrumentation with `ActiveSupport::Notifications` subscription ([#3772][])
|
12
|
+
* Profiling: Track unscaled allocation counts in allocation profiler ([#3770][])
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
* Core: Send Telemetry events in batches ([#3749][])
|
17
|
+
* Tracing: Populate spans from `ActiveSupport::Notifications` as early as possible ([#3725][])
|
18
|
+
* Profiling: Upgrade to `libdatadog` 10 ([#3753][])
|
19
|
+
* Profiling: Optimize `CodeProvenance#record_loaded_files` to avoid allocations ([#3757][])
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
* Core: Fix Telemetry events blocking main thread ([#3718][])
|
24
|
+
* Core: Fix deadlock from Telemetry threads ([#3743][])
|
25
|
+
* Tracing: Fix empty log correlation when tracing is disabled ([#3731][])
|
26
|
+
* Tracing: Fix HTTP propagation when missing parent span id ([#3730][])
|
27
|
+
* Tracing: Ensure `_dd.p.tid` tag with fixed size ([#3729][])
|
28
|
+
* OTel: Fix ids encoding/decoding for propagation ([#3709][])
|
29
|
+
* Profiling: Workaround Ruby `Dir` returning incorrect results ([#3720][])
|
30
|
+
* Profiling: Fix `Phusion Passenger` detection when missing from `Gemfile`/`gems.rb` ([#3750][])
|
31
|
+
* Profiling: Fix `rpath` for linking to libdatadog when loading extension ([#3706][])
|
32
|
+
* Profiling: Fix incorrect code provenance due to broken JSON monkey patch ([#3695][])
|
33
|
+
* Profiling: Fix aggregation of actionview template classes ([#3759][], [#3774][])
|
34
|
+
|
35
|
+
## [2.1.0] - 2024-06-10
|
36
|
+
|
37
|
+
### Added
|
38
|
+
|
39
|
+
* Tracing: Configuration by OpenTelemetry environment variables ([#3657][])
|
40
|
+
|
41
|
+
### Fixed
|
42
|
+
|
43
|
+
* Tracing: Improved compatibility with W3C Trace Context propagation ([#3631][])
|
44
|
+
|
5
45
|
## [2.0.0] - 2024-06-06
|
6
46
|
|
7
47
|
### Added
|
@@ -2894,7 +2934,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
2894
2934
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
2895
2935
|
|
2896
2936
|
|
2897
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
2937
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.2.0...master
|
2938
|
+
[2.2.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.1.0...v2.2.0
|
2939
|
+
[2.1.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0...v2.1.0
|
2898
2940
|
[2.0.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.rc1...v2.0.0
|
2899
2941
|
[2.0.0.rc1]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0.beta2...v2.0.0.rc1
|
2900
2942
|
[1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
|
@@ -3890,6 +3932,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3890
3932
|
[#2497]: https://github.com/DataDog/dd-trace-rb/issues/2497
|
3891
3933
|
[#2501]: https://github.com/DataDog/dd-trace-rb/issues/2501
|
3892
3934
|
[#2504]: https://github.com/DataDog/dd-trace-rb/issues/2504
|
3935
|
+
[#2509]: https://github.com/DataDog/dd-trace-rb/issues/2509
|
3893
3936
|
[#2512]: https://github.com/DataDog/dd-trace-rb/issues/2512
|
3894
3937
|
[#2513]: https://github.com/DataDog/dd-trace-rb/issues/2513
|
3895
3938
|
[#2522]: https://github.com/DataDog/dd-trace-rb/issues/2522
|
@@ -4267,9 +4310,30 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4267
4310
|
[#3624]: https://github.com/DataDog/dd-trace-rb/issues/3624
|
4268
4311
|
[#3627]: https://github.com/DataDog/dd-trace-rb/issues/3627
|
4269
4312
|
[#3630]: https://github.com/DataDog/dd-trace-rb/issues/3630
|
4313
|
+
[#3631]: https://github.com/DataDog/dd-trace-rb/issues/3631
|
4270
4314
|
[#3645]: https://github.com/DataDog/dd-trace-rb/issues/3645
|
4271
4315
|
[#3651]: https://github.com/DataDog/dd-trace-rb/issues/3651
|
4316
|
+
[#3657]: https://github.com/DataDog/dd-trace-rb/issues/3657
|
4272
4317
|
[#3664]: https://github.com/DataDog/dd-trace-rb/issues/3664
|
4318
|
+
[#3672]: https://github.com/DataDog/dd-trace-rb/issues/3672
|
4319
|
+
[#3695]: https://github.com/DataDog/dd-trace-rb/issues/3695
|
4320
|
+
[#3706]: https://github.com/DataDog/dd-trace-rb/issues/3706
|
4321
|
+
[#3709]: https://github.com/DataDog/dd-trace-rb/issues/3709
|
4322
|
+
[#3718]: https://github.com/DataDog/dd-trace-rb/issues/3718
|
4323
|
+
[#3720]: https://github.com/DataDog/dd-trace-rb/issues/3720
|
4324
|
+
[#3725]: https://github.com/DataDog/dd-trace-rb/issues/3725
|
4325
|
+
[#3729]: https://github.com/DataDog/dd-trace-rb/issues/3729
|
4326
|
+
[#3730]: https://github.com/DataDog/dd-trace-rb/issues/3730
|
4327
|
+
[#3731]: https://github.com/DataDog/dd-trace-rb/issues/3731
|
4328
|
+
[#3743]: https://github.com/DataDog/dd-trace-rb/issues/3743
|
4329
|
+
[#3749]: https://github.com/DataDog/dd-trace-rb/issues/3749
|
4330
|
+
[#3750]: https://github.com/DataDog/dd-trace-rb/issues/3750
|
4331
|
+
[#3753]: https://github.com/DataDog/dd-trace-rb/issues/3753
|
4332
|
+
[#3757]: https://github.com/DataDog/dd-trace-rb/issues/3757
|
4333
|
+
[#3759]: https://github.com/DataDog/dd-trace-rb/issues/3759
|
4334
|
+
[#3770]: https://github.com/DataDog/dd-trace-rb/issues/3770
|
4335
|
+
[#3772]: https://github.com/DataDog/dd-trace-rb/issues/3772
|
4336
|
+
[#3774]: https://github.com/DataDog/dd-trace-rb/issues/3774
|
4273
4337
|
[@AdrianLC]: https://github.com/AdrianLC
|
4274
4338
|
[@Azure7111]: https://github.com/Azure7111
|
4275
4339
|
[@BabyGroot]: https://github.com/BabyGroot
|
@@ -4421,4 +4485,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4421
4485
|
[@y-yagi]: https://github.com/y-yagi
|
4422
4486
|
[@yujideveloper]: https://github.com/yujideveloper
|
4423
4487
|
[@yukimurasawa]: https://github.com/yukimurasawa
|
4424
|
-
[@zachmccormick]: https://github.com/zachmccormick
|
4488
|
+
[@zachmccormick]: https://github.com/zachmccormick
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
## Getting started
|
10
10
|
|
11
|
-
**If you're upgrading from a
|
11
|
+
**If you're upgrading from a 1.x version, check out the [upgrade guide](https://github.com/DataDog/dd-trace-rb/blob/release/docs/UpgradeGuide2.md).**
|
12
12
|
|
13
13
|
For a product overview, installation, and configuration check out our [documentation][public docs].
|
14
14
|
|
@@ -222,6 +222,8 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
|
|
222
222
|
static VALUE rescued_sample_allocation(VALUE tracepoint_data);
|
223
223
|
static void delayed_error(struct cpu_and_wall_time_worker_state *state, const char *error);
|
224
224
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
|
225
|
+
static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
|
226
|
+
static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
|
225
227
|
|
226
228
|
// Note on sampler global state safety:
|
227
229
|
//
|
@@ -285,7 +287,9 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
285
287
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_allocation_count", _native_allocation_count, 0);
|
286
288
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_is_running?", _native_is_running, 1);
|
287
289
|
rb_define_singleton_method(testing_module, "_native_current_sigprof_signal_handler", _native_current_sigprof_signal_handler, 0);
|
288
|
-
|
290
|
+
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_hold_signals", _native_hold_signals, 0);
|
291
|
+
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_resume_signals", _native_resume_signals, 0);
|
292
|
+
// TODO: Remove `_native_is_running` from `testing_module` (should be in class) once `prof-correctness` has been updated to not need it
|
289
293
|
rb_define_singleton_method(testing_module, "_native_is_running?", _native_is_running, 1);
|
290
294
|
rb_define_singleton_method(testing_module, "_native_install_testing_signal_handler", _native_install_testing_signal_handler, 0);
|
291
295
|
rb_define_singleton_method(testing_module, "_native_remove_testing_signal_handler", _native_remove_testing_signal_handler, 0);
|
@@ -1159,3 +1163,17 @@ static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VA
|
|
1159
1163
|
|
1160
1164
|
return Qnil;
|
1161
1165
|
}
|
1166
|
+
|
1167
|
+
// Masks SIGPROF interruptions for the current thread. Please don't use this -- you may end up with incomplete
|
1168
|
+
// profiling data.
|
1169
|
+
static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self) {
|
1170
|
+
block_sigprof_signal_handler_from_running_in_current_thread();
|
1171
|
+
return Qtrue;
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
// Unmasks SIGPROF interruptions for the current thread. If there's a pending sample, it'll be triggered inside this
|
1175
|
+
// method.
|
1176
|
+
static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
1177
|
+
unblock_sigprof_signal_handler_from_running_in_current_thread();
|
1178
|
+
return Qtrue;
|
1179
|
+
}
|
@@ -34,6 +34,7 @@ static VALUE _native_sample(
|
|
34
34
|
);
|
35
35
|
static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer* buffer, char *frames_omitted_message, int frames_omitted_message_size);
|
36
36
|
static void record_placeholder_stack_in_native_code(sampling_buffer* buffer, VALUE recorder_instance, sample_values values, sample_labels labels);
|
37
|
+
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
|
37
38
|
|
38
39
|
void collectors_stack_init(VALUE profiling_module) {
|
39
40
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
@@ -69,6 +70,7 @@ static VALUE _native_sample(
|
|
69
70
|
.cpu_or_wall_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-samples"), zero)),
|
70
71
|
.wall_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("wall-time"), zero)),
|
71
72
|
.alloc_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples"), zero)),
|
73
|
+
.alloc_samples_unscaled = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples-unscaled"), zero)),
|
72
74
|
.timeline_wall_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("timeline"), zero)),
|
73
75
|
};
|
74
76
|
|
@@ -199,6 +201,8 @@ void sample_thread(
|
|
199
201
|
ddog_CharSlice name_slice = char_slice_from_ruby_string(name);
|
200
202
|
ddog_CharSlice filename_slice = char_slice_from_ruby_string(filename);
|
201
203
|
|
204
|
+
maybe_trim_template_random_ids(&name_slice, &filename_slice);
|
205
|
+
|
202
206
|
bool top_of_the_stack = i == 0;
|
203
207
|
|
204
208
|
// When there's only wall-time in a sample, this means that the thread was not active in the sampled period.
|
@@ -268,6 +272,43 @@ void sample_thread(
|
|
268
272
|
);
|
269
273
|
}
|
270
274
|
|
275
|
+
// Rails's ActionView likes to dynamically generate method names with suffixed hashes/ids, resulting in methods with
|
276
|
+
// names such as:
|
277
|
+
// * "_app_views_layouts_explore_html_haml__2304485752546535910_211320" (__number_number suffix -- two underscores)
|
278
|
+
// * "_app_views_articles_index_html_erb___2022809201779434309_12900" (___number_number suffix -- three underscores)
|
279
|
+
// This makes these stacks not aggregate well, as well as being not-very-useful data.
|
280
|
+
// (Reference:
|
281
|
+
// https://github.com/rails/rails/blob/4fa56814f18fd3da49c83931fa773caa727d8096/actionview/lib/action_view/template.rb#L389
|
282
|
+
// The two vs three underscores happen when @identifier.hash is negative in that method: the "-" gets replaced with
|
283
|
+
// the extra "_".)
|
284
|
+
//
|
285
|
+
// This method trims these suffixes, so that we keep less data + the names correctly aggregate together.
|
286
|
+
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice) {
|
287
|
+
// Check filename doesn't end with ".rb"; templates are usually along the lines of .html.erb/.html.haml/...
|
288
|
+
if (filename_slice->len < 3 || memcmp(filename_slice->ptr + filename_slice->len - 3, ".rb", 3) == 0) return;
|
289
|
+
|
290
|
+
int pos = name_slice->len - 1;
|
291
|
+
|
292
|
+
// Let's match on something__number_number:
|
293
|
+
// Find start of id suffix from the end...
|
294
|
+
if (name_slice->ptr[pos] < '0' || name_slice->ptr[pos] > '9') return;
|
295
|
+
|
296
|
+
// ...now match a bunch of numbers and interspersed underscores
|
297
|
+
for (int underscores = 0; pos >= 0 && underscores < 2; pos--) {
|
298
|
+
if (name_slice->ptr[pos] == '_') underscores++;
|
299
|
+
else if (name_slice->ptr[pos] < '0' || name_slice->ptr[pos] > '9') return;
|
300
|
+
}
|
301
|
+
|
302
|
+
// Make sure there's something left before the underscores (hence the <= instead of <) + match the last underscore
|
303
|
+
if (pos <= 0 || name_slice->ptr[pos] != '_') return;
|
304
|
+
|
305
|
+
// Does it have the optional third underscore? If so, remove it as well
|
306
|
+
if (pos > 1 && name_slice->ptr[pos-1] == '_') pos--;
|
307
|
+
|
308
|
+
// If we got here, we matched on our pattern. Let's slice the length of the string to exclude it.
|
309
|
+
name_slice->len = pos;
|
310
|
+
}
|
311
|
+
|
271
312
|
static void maybe_add_placeholder_frames_omitted(VALUE thread, sampling_buffer* buffer, char *frames_omitted_message, int frames_omitted_message_size) {
|
272
313
|
ptrdiff_t frames_omitted = stack_depth_for(thread) - buffer->max_frames;
|
273
314
|
|
@@ -1290,7 +1290,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1290
1290
|
/* thread: */ current_thread,
|
1291
1291
|
/* stack_from_thread: */ current_thread,
|
1292
1292
|
get_or_create_context_for(current_thread, state),
|
1293
|
-
(sample_values) {.alloc_samples = sample_weight},
|
1293
|
+
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1},
|
1294
1294
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
1295
1295
|
&ruby_vm_type,
|
1296
1296
|
optional_class_name
|
@@ -57,7 +57,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
|
|
57
57
|
// "Process.kill('SEGV', Process.pid)" gets run.
|
58
58
|
.create_alt_stack = false,
|
59
59
|
.endpoint = endpoint,
|
60
|
-
.resolve_frames =
|
60
|
+
.resolve_frames = DDOG_PROF_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER,
|
61
61
|
.timeout_secs = FIX2INT(upload_timeout_seconds),
|
62
62
|
};
|
63
63
|
|
@@ -211,12 +211,14 @@ unless have_type('atomic_int', ['stdatomic.h'])
|
|
211
211
|
skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILER_ATOMIC_MISSING)
|
212
212
|
end
|
213
213
|
|
214
|
-
# See comments on the helper
|
214
|
+
# See comments on the helper methods being used for why we need to additionally set this.
|
215
215
|
# The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
|
216
216
|
# experimentation. We need to get these special characters across a lot of tools untouched...
|
217
|
-
|
218
|
-
|
219
|
-
|
217
|
+
extra_relative_rpaths = [
|
218
|
+
Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder,
|
219
|
+
*Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
|
220
|
+
]
|
221
|
+
extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
|
220
222
|
Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
|
221
223
|
|
222
224
|
# Tag the native extension library with the Ruby version and Ruby platform.
|
@@ -15,7 +15,7 @@ module Datadog
|
|
15
15
|
# The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on debase-ruby_core_source
|
16
16
|
CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?('2.6', '2.7', '3.0.', '3.1.', '3.2.')
|
17
17
|
|
18
|
-
LIBDATADOG_VERSION = '~>
|
18
|
+
LIBDATADOG_VERSION = '~> 10.0.0.1.0'
|
19
19
|
|
20
20
|
def self.fail_install_if_missing_extension?
|
21
21
|
ENV[ENV_FAIL_INSTALL_IF_MISSING_EXTENSION].to_s.strip.downcase == 'true'
|
@@ -67,6 +67,52 @@ module Datadog
|
|
67
67
|
Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(profiling_native_lib_folder)).to_s
|
68
68
|
end
|
69
69
|
|
70
|
+
# In https://github.com/DataDog/dd-trace-rb/pull/3582 we got a report of a customer for which the native extension
|
71
|
+
# only got installed into the extensions folder.
|
72
|
+
#
|
73
|
+
# But then this fix was not enough to fully get them moving because then they started to see the issue from
|
74
|
+
# https://github.com/DataDog/dd-trace-rb/issues/2067 / https://github.com/DataDog/dd-trace-rb/pull/2125 :
|
75
|
+
#
|
76
|
+
# > Profiling was requested but is not supported, profiling disabled: There was an error loading the profiling
|
77
|
+
# > native extension due to 'RuntimeError Failure to load datadog_profiling_native_extension.3.2.2_x86_64-linux
|
78
|
+
# > due to libdatadog_profiling.so: cannot open shared object file: No such file or directory
|
79
|
+
#
|
80
|
+
# The problem is that when loading the native extension from the extensions directory, the relative rpath we add
|
81
|
+
# with the #libdatadog_folder_relative_to_native_lib_folder helper above is not correct, we need to add a relative
|
82
|
+
# rpath to the extensions directory.
|
83
|
+
#
|
84
|
+
# So how do we find the full path where the native extension is placed?
|
85
|
+
# * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/bundler/runtime.rb#L166
|
86
|
+
# `extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]`
|
87
|
+
# we get that's in one of two fixed subdirectories of `Gem.dir`
|
88
|
+
# * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/rubygems/basic_specification.rb#L111-L115
|
89
|
+
# we get the structure of the subdirectory (platform/extension_api_version/gem_and_version)
|
90
|
+
#
|
91
|
+
# Thus, `Gem.dir` of `/var/app/current/vendor/bundle/ruby/3.2.0` becomes (for instance)
|
92
|
+
# `/var/app/current/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/datadog-2.0.0/` or
|
93
|
+
# `/var/app/current/vendor/bundle/ruby/3.2.0/bundler/gems/extensions/x86_64-linux/3.2.0/datadog-2.0.0/`
|
94
|
+
#
|
95
|
+
# We then compute the relative path between these folders and the libdatadog folder, and use that as a relative path.
|
96
|
+
def self.libdatadog_folder_relative_to_ruby_extensions_folders(
|
97
|
+
gem_dir: Gem.dir,
|
98
|
+
libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
|
99
|
+
)
|
100
|
+
return unless libdatadog_pkgconfig_folder
|
101
|
+
|
102
|
+
# For the purposes of calculating a folder relative to the other, we don't actually NEED to fill in the
|
103
|
+
# platform, extension_api_version and gem version. We're basically just after how many folders it is deep from
|
104
|
+
# the Gem.dir.
|
105
|
+
expected_ruby_extensions_folders = [
|
106
|
+
"#{gem_dir}/extensions/platform/extension_api_version/datadog_version/",
|
107
|
+
"#{gem_dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/",
|
108
|
+
]
|
109
|
+
libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
|
110
|
+
|
111
|
+
expected_ruby_extensions_folders.map do |folder|
|
112
|
+
Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(folder)).to_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
70
116
|
# Used to check if profiler is supported, including user-visible clear messages explaining why their
|
71
117
|
# system may not be supported.
|
72
118
|
module Supported
|
@@ -91,7 +91,7 @@ void remove_sigprof_signal_handler(void) {
|
|
91
91
|
if (sigaction(SIGPROF, &signal_handler_config, NULL) != 0) rb_sys_fail("Failure while removing the signal handler");
|
92
92
|
}
|
93
93
|
|
94
|
-
static void toggle_sigprof_signal_handler_for_current_thread(int action) {
|
94
|
+
static inline void toggle_sigprof_signal_handler_for_current_thread(int action) {
|
95
95
|
sigset_t signals_to_toggle;
|
96
96
|
sigemptyset(&signals_to_toggle);
|
97
97
|
sigaddset(&signals_to_toggle, SIGPROF);
|
@@ -151,21 +151,23 @@ static VALUE error_symbol = Qnil; // :error in Ruby
|
|
151
151
|
#define WALL_TIME_VALUE_ID 2
|
152
152
|
#define ALLOC_SAMPLES_VALUE {.type_ = VALUE_STRING("alloc-samples"), .unit = VALUE_STRING("count")}
|
153
153
|
#define ALLOC_SAMPLES_VALUE_ID 3
|
154
|
+
#define ALLOC_SAMPLES_UNSCALED_VALUE {.type_ = VALUE_STRING("alloc-samples-unscaled"), .unit = VALUE_STRING("count")}
|
155
|
+
#define ALLOC_SAMPLES_UNSCALED_VALUE_ID 4
|
154
156
|
#define HEAP_SAMPLES_VALUE {.type_ = VALUE_STRING("heap-live-samples"), .unit = VALUE_STRING("count")}
|
155
|
-
#define HEAP_SAMPLES_VALUE_ID
|
157
|
+
#define HEAP_SAMPLES_VALUE_ID 5
|
156
158
|
#define HEAP_SIZE_VALUE {.type_ = VALUE_STRING("heap-live-size"), .unit = VALUE_STRING("bytes")}
|
157
|
-
#define HEAP_SIZE_VALUE_ID
|
159
|
+
#define HEAP_SIZE_VALUE_ID 6
|
158
160
|
#define TIMELINE_VALUE {.type_ = VALUE_STRING("timeline"), .unit = VALUE_STRING("nanoseconds")}
|
159
|
-
#define TIMELINE_VALUE_ID
|
161
|
+
#define TIMELINE_VALUE_ID 7
|
160
162
|
|
161
163
|
static const ddog_prof_ValueType all_value_types[] =
|
162
|
-
{CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE, ALLOC_SAMPLES_VALUE, HEAP_SAMPLES_VALUE, HEAP_SIZE_VALUE, TIMELINE_VALUE};
|
164
|
+
{CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE, ALLOC_SAMPLES_VALUE, ALLOC_SAMPLES_UNSCALED_VALUE, HEAP_SAMPLES_VALUE, HEAP_SIZE_VALUE, TIMELINE_VALUE};
|
163
165
|
|
164
166
|
// This array MUST be kept in sync with all_value_types above and is intended to act as a "hashmap" between VALUE_ID and the position it
|
165
167
|
// occupies on the all_value_types array.
|
166
168
|
// E.g. all_value_types_positions[CPU_TIME_VALUE_ID] => 0, means that CPU_TIME_VALUE was declared at position 0 of all_value_types.
|
167
169
|
static const uint8_t all_value_types_positions[] =
|
168
|
-
{CPU_TIME_VALUE_ID, CPU_SAMPLES_VALUE_ID, WALL_TIME_VALUE_ID, ALLOC_SAMPLES_VALUE_ID, HEAP_SAMPLES_VALUE_ID, HEAP_SIZE_VALUE_ID, TIMELINE_VALUE_ID};
|
170
|
+
{CPU_TIME_VALUE_ID, CPU_SAMPLES_VALUE_ID, WALL_TIME_VALUE_ID, ALLOC_SAMPLES_VALUE_ID, ALLOC_SAMPLES_UNSCALED_VALUE_ID, HEAP_SAMPLES_VALUE_ID, HEAP_SIZE_VALUE_ID, TIMELINE_VALUE_ID};
|
169
171
|
|
170
172
|
#define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(ddog_prof_ValueType))
|
171
173
|
|
@@ -429,7 +431,7 @@ static VALUE _native_initialize(
|
|
429
431
|
|
430
432
|
uint8_t requested_values_count = ALL_VALUE_TYPES_COUNT -
|
431
433
|
(cpu_time_enabled == Qtrue ? 0 : 1) -
|
432
|
-
(alloc_samples_enabled == Qtrue? 0 :
|
434
|
+
(alloc_samples_enabled == Qtrue? 0 : 2) -
|
433
435
|
(heap_samples_enabled == Qtrue ? 0 : 1) -
|
434
436
|
(heap_size_enabled == Qtrue ? 0 : 1) -
|
435
437
|
(timeline_enabled == Qtrue ? 0 : 1);
|
@@ -464,8 +466,12 @@ static VALUE _native_initialize(
|
|
464
466
|
if (alloc_samples_enabled == Qtrue) {
|
465
467
|
enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) ALLOC_SAMPLES_VALUE;
|
466
468
|
state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_enabled_pos++;
|
469
|
+
|
470
|
+
enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) ALLOC_SAMPLES_UNSCALED_VALUE;
|
471
|
+
state->position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID] = next_enabled_pos++;
|
467
472
|
} else {
|
468
473
|
state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_disabled_pos++;
|
474
|
+
state->position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID] = next_disabled_pos++;
|
469
475
|
}
|
470
476
|
|
471
477
|
if (heap_samples_enabled == Qtrue) {
|
@@ -603,6 +609,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
603
609
|
metric_values[position_for[CPU_SAMPLES_VALUE_ID]] = values.cpu_or_wall_samples;
|
604
610
|
metric_values[position_for[WALL_TIME_VALUE_ID]] = values.wall_time_ns;
|
605
611
|
metric_values[position_for[ALLOC_SAMPLES_VALUE_ID]] = values.alloc_samples;
|
612
|
+
metric_values[position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID]] = values.alloc_samples_unscaled;
|
606
613
|
metric_values[position_for[TIMELINE_VALUE_ID]] = values.timeline_wall_time_ns;
|
607
614
|
|
608
615
|
if (values.alloc_samples != 0) {
|
@@ -6,7 +6,7 @@ require_relative '../diagnostics/environment_logger'
|
|
6
6
|
require_relative '../diagnostics/health'
|
7
7
|
require_relative '../logger'
|
8
8
|
require_relative '../runtime/metrics'
|
9
|
-
require_relative '../telemetry/
|
9
|
+
require_relative '../telemetry/component'
|
10
10
|
require_relative '../workers/runtime_metrics'
|
11
11
|
|
12
12
|
require_relative '../remote/component'
|
@@ -62,9 +62,11 @@ module Datadog
|
|
62
62
|
logger.debug { "Telemetry disabled. Agent network adapter not supported: #{agent_settings.adapter}" }
|
63
63
|
end
|
64
64
|
|
65
|
-
Telemetry::
|
65
|
+
Telemetry::Component.new(
|
66
66
|
enabled: enabled,
|
67
|
+
metrics_enabled: enabled && settings.telemetry.metrics_enabled,
|
67
68
|
heartbeat_interval_seconds: settings.telemetry.heartbeat_interval_seconds,
|
69
|
+
metrics_aggregation_interval_seconds: settings.telemetry.metrics_aggregation_interval_seconds,
|
68
70
|
dependency_collection: settings.telemetry.dependency_collection
|
69
71
|
)
|
70
72
|
end
|
@@ -169,8 +171,9 @@ module Datadog
|
|
169
171
|
unused_statsd = (old_statsd - (old_statsd & new_statsd))
|
170
172
|
unused_statsd.each(&:close)
|
171
173
|
|
172
|
-
telemetry
|
174
|
+
# enqueue closing event before stopping telemetry so it will be send out on shutdown
|
173
175
|
telemetry.emit_closing! unless replacement
|
176
|
+
telemetry.stop!
|
174
177
|
end
|
175
178
|
end
|
176
179
|
end
|
@@ -14,7 +14,7 @@ module Datadog
|
|
14
14
|
# @!attribute [r] precedence_set
|
15
15
|
# When this option was last set, what was the value precedence used?
|
16
16
|
# @return [Precedence::Value]
|
17
|
-
attr_reader :definition, :precedence_set
|
17
|
+
attr_reader :definition, :precedence_set, :resolved_env
|
18
18
|
|
19
19
|
# Option setting precedence.
|
20
20
|
module Precedence
|
@@ -50,6 +50,7 @@ module Datadog
|
|
50
50
|
@context = context
|
51
51
|
@value = nil
|
52
52
|
@is_set = false
|
53
|
+
@resolved_env = nil
|
53
54
|
|
54
55
|
# One value is stored per precedence, to allow unsetting a higher
|
55
56
|
# precedence value and falling back to a lower precedence one.
|
@@ -65,7 +66,7 @@ module Datadog
|
|
65
66
|
#
|
66
67
|
# @param value [Object] the new value to be associated with this option
|
67
68
|
# @param precedence [Precedence] from what precedence order this new value comes from
|
68
|
-
def set(value, precedence: Precedence::PROGRAMMATIC)
|
69
|
+
def set(value, precedence: Precedence::PROGRAMMATIC, resolved_env: nil)
|
69
70
|
# Is there a higher precedence value set?
|
70
71
|
if @precedence_set > precedence
|
71
72
|
# This should be uncommon, as higher precedence values tend to
|
@@ -84,7 +85,7 @@ module Datadog
|
|
84
85
|
return @value
|
85
86
|
end
|
86
87
|
|
87
|
-
internal_set(value, precedence)
|
88
|
+
internal_set(value, precedence, resolved_env)
|
88
89
|
end
|
89
90
|
|
90
91
|
def unset(precedence)
|
@@ -102,7 +103,7 @@ module Datadog
|
|
102
103
|
# Look for value that is set.
|
103
104
|
# The hash `@value_per_precedence` has a custom default value of `UNSET`.
|
104
105
|
if (value = @value_per_precedence[p]) != UNSET
|
105
|
-
internal_set(value, p)
|
106
|
+
internal_set(value, p, nil)
|
106
107
|
return nil
|
107
108
|
end
|
108
109
|
end
|
@@ -260,11 +261,12 @@ module Datadog
|
|
260
261
|
end
|
261
262
|
|
262
263
|
# Directly manipulates the current value and currently set precedence.
|
263
|
-
def internal_set(value, precedence)
|
264
|
+
def internal_set(value, precedence, resolved_env)
|
264
265
|
old_value = @value
|
265
266
|
(@value = context_exec(validate_type(value), old_value, &definition.setter)).tap do |v|
|
266
267
|
@is_set = true
|
267
268
|
@precedence_set = precedence
|
269
|
+
@resolved_env = resolved_env
|
268
270
|
# Store original value to ensure we can always safely call `#internal_set`
|
269
271
|
# when restoring a value from `@value_per_precedence`, and we are only running `definition.setter`
|
270
272
|
# on the original value, not on a valud that has already been processed by `definition.setter`.
|
@@ -284,16 +286,21 @@ module Datadog
|
|
284
286
|
def set_value_from_env_or_default
|
285
287
|
value = nil
|
286
288
|
precedence = nil
|
287
|
-
|
289
|
+
resolved_env = nil
|
288
290
|
|
289
|
-
if definition.env
|
290
|
-
|
291
|
-
|
292
|
-
|
291
|
+
if definition.env
|
292
|
+
Array(definition.env).each do |env|
|
293
|
+
next if ENV[env].nil?
|
294
|
+
|
295
|
+
resolved_env = env
|
296
|
+
value = coerce_env_variable(ENV[env])
|
297
|
+
precedence = Precedence::PROGRAMMATIC
|
298
|
+
break
|
299
|
+
end
|
293
300
|
end
|
294
301
|
|
295
302
|
if value.nil? && definition.deprecated_env && ENV[definition.deprecated_env]
|
296
|
-
|
303
|
+
resolved_env = definition.deprecated_env
|
297
304
|
value = coerce_env_variable(ENV[definition.deprecated_env])
|
298
305
|
precedence = Precedence::PROGRAMMATIC
|
299
306
|
|
@@ -304,11 +311,11 @@ module Datadog
|
|
304
311
|
|
305
312
|
option_value = value.nil? ? default_value : value
|
306
313
|
|
307
|
-
set(option_value, precedence: precedence || Precedence::DEFAULT)
|
314
|
+
set(option_value, precedence: precedence || Precedence::DEFAULT, resolved_env: resolved_env)
|
308
315
|
rescue ArgumentError
|
309
316
|
raise ArgumentError,
|
310
|
-
"Expected environment variable #{
|
311
|
-
"but '#{ENV[
|
317
|
+
"Expected environment variable #{resolved_env} to be a #{@definition.type}, " \
|
318
|
+
"but '#{ENV[resolved_env]}' was provided"
|
312
319
|
end
|
313
320
|
|
314
321
|
# Anchor object that represents a value that is not set.
|
@@ -68,7 +68,7 @@ module Datadog
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def set_option(name, value, precedence: Configuration::Option::Precedence::PROGRAMMATIC)
|
71
|
-
resolve_option(name).set(value, precedence: precedence)
|
71
|
+
resolve_option(name).set(value, precedence: precedence, resolved_env: resolved_env(name))
|
72
72
|
end
|
73
73
|
|
74
74
|
def unset_option(name, precedence: Configuration::Option::Precedence::PROGRAMMATIC)
|
@@ -116,6 +116,10 @@ module Datadog
|
|
116
116
|
options[name] = definition.build(self)
|
117
117
|
end
|
118
118
|
|
119
|
+
def resolved_env(name)
|
120
|
+
return options[name].resolved_env if options.key?(name)
|
121
|
+
end
|
122
|
+
|
119
123
|
def assert_valid_option!(name)
|
120
124
|
raise(InvalidOptionError, "#{self.class.name} doesn't define the option: #{name}") unless option_defined?(name)
|
121
125
|
end
|