datadog 2.24.0 → 2.26.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 +41 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +93 -23
- data/ext/datadog_profiling_native_extension/http_transport.c +1 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +82 -0
- data/lib/datadog/ai_guard/component.rb +42 -0
- data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
- data/lib/datadog/ai_guard/configuration/settings.rb +98 -0
- data/lib/datadog/ai_guard/configuration.rb +11 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +34 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +81 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +43 -0
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
- data/lib/datadog/ai_guard/evaluation.rb +72 -0
- data/lib/datadog/ai_guard/ext.rb +16 -0
- data/lib/datadog/ai_guard.rb +153 -0
- data/lib/datadog/appsec/remote.rb +4 -3
- data/lib/datadog/appsec/security_engine/engine.rb +3 -3
- data/lib/datadog/appsec/security_engine/runner.rb +2 -2
- data/lib/datadog/core/configuration/components.rb +7 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +6 -0
- data/lib/datadog/core/error.rb +6 -6
- data/lib/datadog/core/pin.rb +4 -0
- data/lib/datadog/core/rate_limiter.rb +1 -1
- data/lib/datadog/core/runtime/metrics.rb +11 -1
- data/lib/datadog/core/semaphore.rb +1 -4
- data/lib/datadog/core/telemetry/event/app_started.rb +2 -1
- data/lib/datadog/core/transport/response.rb +3 -1
- data/lib/datadog/core/utils/safe_dup.rb +2 -2
- data/lib/datadog/core/utils/sequence.rb +2 -0
- data/lib/datadog/di/boot.rb +4 -2
- data/lib/datadog/di/contrib/active_record.rb +4 -5
- data/lib/datadog/di/instrumenter.rb +9 -3
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
- data/lib/datadog/di/probe_notifier_worker.rb +5 -5
- data/lib/datadog/error_tracking/filters.rb +2 -2
- data/lib/datadog/kit/appsec/events/v2.rb +2 -3
- data/lib/datadog/profiling/collectors/code_provenance.rb +1 -1
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -2
- data/lib/datadog/profiling/collectors/info.rb +3 -3
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +18 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +4 -0
- data/lib/datadog/tracing/distributed/baggage.rb +3 -2
- data/lib/datadog/tracing/sampling/priority_sampler.rb +3 -1
- data/lib/datadog/tracing/span.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +15 -9
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +1 -0
- metadata +23 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbf89a9582401ec9fcab36ed1c165ccd447e286085668c768c4df83b5dd27d81
|
|
4
|
+
data.tar.gz: 0c915a22c6ba56f7d39cd03a278466100e72769ac013bb26e9bd2204b4f280ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d2fefe85cab7cff7273196e57638a62da64b6ab0f84b0a350266b0adbc64ae57249d1ab6beb74bfe0d42a233f5b36855a4210ffeb69573fa4cafa2decc12820
|
|
7
|
+
data.tar.gz: 6b41c594384690eed2a84f1e8046046a72bb29c276e87d6cf048b34df2d24cf54725526f0d6eebed0a523f4334385a1f796cba8a6c1112e46e5d58787410611d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.26.0] - 2026-01-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Core: Add process tags to runtime metrics when `DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED` is enabled. ([#5210][])
|
|
10
|
+
* SSI: Add experimental dependency injection validation.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
* Profiling: Improve profiler error reporting. ([#5237][])
|
|
15
|
+
* SSI: Improve injection debug error reporting. ([#5238][])
|
|
16
|
+
|
|
17
|
+
## [2.25.0] - 2026-01-13
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
AI Guard: Add SDK for evaluating the safety of user messages and assistant commands for LLM session ([#5144][])
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
Core: Bump minimum version of `datadog-ruby_core_source` dependency ([#5215][])
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
AppSec: Fix processing of numeric data for WAF and RASP checks ([#5222][])
|
|
30
|
+
|
|
5
31
|
## [2.24.0] - 2026-01-08
|
|
6
32
|
|
|
7
33
|
### Added
|
|
@@ -9,6 +35,11 @@
|
|
|
9
35
|
* Core: Add support for installing the gem on Ruby 4.0.x stable ([#5157][])
|
|
10
36
|
* Tracing: Add origin detection using extra headers and the `DD_EXTERNAL_ENV` variable. ([#5028][])
|
|
11
37
|
* Dynamic Instrumentation: Add one-click enablement support ([#5150][])
|
|
38
|
+
* SSI: Add support for Bundler deployment mode ([#5053][])
|
|
39
|
+
* SSI: Report UI-oriented injection results ([#5053][])
|
|
40
|
+
* SSI: Guard against Bundler global force_ruby_platform ([#5053][])
|
|
41
|
+
* SSI: Guard against Bundler 4.0 and Bundler 2.7 in 4.0 mode ([#5053][])
|
|
42
|
+
* SSI: Guard against Ruby 3.5+ ([#5053][])
|
|
12
43
|
|
|
13
44
|
### Changed
|
|
14
45
|
|
|
@@ -3418,7 +3449,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3418
3449
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3419
3450
|
|
|
3420
3451
|
|
|
3421
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3452
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.26.0...master
|
|
3453
|
+
[2.26.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.25.0...v2.26.0
|
|
3454
|
+
[2.25.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.24.0...v2.25.0
|
|
3422
3455
|
[2.24.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.23.0...v2.24.0
|
|
3423
3456
|
[2.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.22.0...v2.23.0
|
|
3424
3457
|
[2.22.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.21.0...v2.22.0
|
|
@@ -5059,12 +5092,14 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5059
5092
|
[#5044]: https://github.com/DataDog/dd-trace-rb/issues/5044
|
|
5060
5093
|
[#5045]: https://github.com/DataDog/dd-trace-rb/issues/5045
|
|
5061
5094
|
[#5049]: https://github.com/DataDog/dd-trace-rb/issues/5049
|
|
5095
|
+
[#5053]: https://github.com/DataDog/dd-trace-rb/issues/5053
|
|
5062
5096
|
[#5054]: https://github.com/DataDog/dd-trace-rb/issues/5054
|
|
5063
5097
|
[#5058]: https://github.com/DataDog/dd-trace-rb/issues/5058
|
|
5064
5098
|
[#5073]: https://github.com/DataDog/dd-trace-rb/issues/5073
|
|
5065
5099
|
[#5086]: https://github.com/DataDog/dd-trace-rb/issues/5086
|
|
5066
5100
|
[#5091]: https://github.com/DataDog/dd-trace-rb/issues/5091
|
|
5067
5101
|
[#5122]: https://github.com/DataDog/dd-trace-rb/issues/5122
|
|
5102
|
+
[#5144]: https://github.com/DataDog/dd-trace-rb/issues/5144
|
|
5068
5103
|
[#5145]: https://github.com/DataDog/dd-trace-rb/issues/5145
|
|
5069
5104
|
[#5146]: https://github.com/DataDog/dd-trace-rb/issues/5146
|
|
5070
5105
|
[#5148]: https://github.com/DataDog/dd-trace-rb/issues/5148
|
|
@@ -5078,6 +5113,11 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5078
5113
|
[#5176]: https://github.com/DataDog/dd-trace-rb/issues/5176
|
|
5079
5114
|
[#5194]: https://github.com/DataDog/dd-trace-rb/issues/5194
|
|
5080
5115
|
[#5197]: https://github.com/DataDog/dd-trace-rb/issues/5197
|
|
5116
|
+
[#5210]: https://github.com/DataDog/dd-trace-rb/issues/5210
|
|
5117
|
+
[#5215]: https://github.com/DataDog/dd-trace-rb/issues/5215
|
|
5118
|
+
[#5222]: https://github.com/DataDog/dd-trace-rb/issues/5222
|
|
5119
|
+
[#5237]: https://github.com/DataDog/dd-trace-rb/issues/5237
|
|
5120
|
+
[#5238]: https://github.com/DataDog/dd-trace-rb/issues/5238
|
|
5081
5121
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5082
5122
|
[@Azure7111]: https://github.com/Azure7111
|
|
5083
5123
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -76,8 +76,6 @@
|
|
|
76
76
|
//
|
|
77
77
|
// ---
|
|
78
78
|
|
|
79
|
-
#define ERR_CLOCK_FAIL "failed to get clock time"
|
|
80
|
-
|
|
81
79
|
// Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
|
|
82
80
|
// See https://docs.google.com/document/d/1lWLB714wlLBBq6T4xZyAc4a5wtWhSmr4-hgiPKeErlA/edit#heading=h.ugp0zxcj5iqh
|
|
83
81
|
// (Datadog-only link) for research backing the choice of this value.
|
|
@@ -117,6 +115,7 @@ typedef struct {
|
|
|
117
115
|
// When something goes wrong during sampling, we record the Ruby exception here, so that it can be "re-raised" on
|
|
118
116
|
// the CpuAndWallTimeWorker thread
|
|
119
117
|
VALUE failure_exception;
|
|
118
|
+
const char *failure_exception_during_operation;
|
|
120
119
|
// Used by `_native_stop` to flag the worker thread to start (see comment on `_native_sampling_loop`)
|
|
121
120
|
VALUE stop_thread;
|
|
122
121
|
|
|
@@ -191,17 +190,17 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
191
190
|
static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
|
|
192
191
|
static VALUE _native_sampling_loop(VALUE self, VALUE instance);
|
|
193
192
|
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
|
|
194
|
-
static VALUE stop(VALUE self_instance, VALUE optional_exception);
|
|
195
|
-
static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception);
|
|
193
|
+
static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation);
|
|
194
|
+
static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_operation_name);
|
|
196
195
|
static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
|
|
197
196
|
static void *run_sampling_trigger_loop(void *state_ptr);
|
|
198
197
|
static void interrupt_sampling_trigger_loop(void *state_ptr);
|
|
199
198
|
static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused);
|
|
200
199
|
static VALUE rescued_sample_from_postponed_job(VALUE self_instance);
|
|
201
|
-
static VALUE handle_sampling_failure(VALUE self_instance, VALUE exception);
|
|
202
200
|
static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
203
201
|
static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance);
|
|
204
202
|
static VALUE _native_is_running(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
203
|
+
static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
205
204
|
static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
|
|
206
205
|
static VALUE _native_install_testing_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
207
206
|
static VALUE _native_remove_testing_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
@@ -209,7 +208,12 @@ static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self);
|
|
|
209
208
|
static VALUE _native_gc_tracepoint(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
210
209
|
static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
211
210
|
static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused);
|
|
212
|
-
static VALUE safely_call(
|
|
211
|
+
static VALUE safely_call(
|
|
212
|
+
VALUE (*function_to_call_safely)(VALUE),
|
|
213
|
+
VALUE function_to_call_safely_arg,
|
|
214
|
+
VALUE instance,
|
|
215
|
+
VALUE (*handle_sampling_failure)(VALUE, VALUE)
|
|
216
|
+
);
|
|
213
217
|
static VALUE _native_simulate_handle_sampling_signal(DDTRACE_UNUSED VALUE self);
|
|
214
218
|
static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE self);
|
|
215
219
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
@@ -226,6 +230,7 @@ static void disable_tracepoints(cpu_and_wall_time_worker_state *state);
|
|
|
226
230
|
static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
|
|
227
231
|
static VALUE rescued_sample_allocation(VALUE tracepoint_data);
|
|
228
232
|
static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error);
|
|
233
|
+
static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state);
|
|
229
234
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
|
|
230
235
|
static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
|
|
231
236
|
static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
|
|
@@ -235,6 +240,10 @@ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
|
|
|
235
240
|
#endif
|
|
236
241
|
static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance);
|
|
237
242
|
static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
243
|
+
static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception);
|
|
244
|
+
static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception);
|
|
245
|
+
static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception);
|
|
246
|
+
static VALUE handle_sampling_failure_rescued_after_gvl_running_from_postponed_job(VALUE self_instance, VALUE exception);
|
|
238
247
|
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state);
|
|
239
248
|
static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
|
|
240
249
|
|
|
@@ -262,6 +271,7 @@ static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
|
|
|
262
271
|
// (e.g. signal handler) where it's impossible or just awkward to pass it as an argument.
|
|
263
272
|
static VALUE active_sampler_instance = Qnil;
|
|
264
273
|
static cpu_and_wall_time_worker_state *active_sampler_instance_state = NULL;
|
|
274
|
+
static VALUE clock_failure_exception_class = Qnil;
|
|
265
275
|
|
|
266
276
|
// See handle_sampling_signal for details on what this does
|
|
267
277
|
#ifdef NO_POSTPONED_TRIGGER
|
|
@@ -299,6 +309,8 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
|
299
309
|
VALUE collectors_cpu_and_wall_time_worker_class = rb_define_class_under(collectors_module, "CpuAndWallTimeWorker", rb_cObject);
|
|
300
310
|
// Hosts methods used for testing the native code using RSpec
|
|
301
311
|
VALUE testing_module = rb_define_module_under(collectors_cpu_and_wall_time_worker_class, "Testing");
|
|
312
|
+
clock_failure_exception_class = rb_define_class_under(collectors_cpu_and_wall_time_worker_class, "ClockFailure", rb_eRuntimeError);
|
|
313
|
+
rb_gc_register_mark_object(clock_failure_exception_class);
|
|
302
314
|
|
|
303
315
|
// Instances of the CpuAndWallTimeWorker class are "TypedData" objects.
|
|
304
316
|
// "TypedData" objects are special objects in the Ruby VM that can wrap C structs.
|
|
@@ -318,6 +330,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
|
318
330
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stats_reset_not_thread_safe", _native_stats_reset_not_thread_safe, 1);
|
|
319
331
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_allocation_count", _native_allocation_count, 0);
|
|
320
332
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_is_running?", _native_is_running, 1);
|
|
333
|
+
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_failure_exception_during_operation", _native_failure_exception_during_operation, 1);
|
|
321
334
|
rb_define_singleton_method(testing_module, "_native_current_sigprof_signal_handler", _native_current_sigprof_signal_handler, 0);
|
|
322
335
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_hold_signals", _native_hold_signals, 0);
|
|
323
336
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_resume_signals", _native_resume_signals, 0);
|
|
@@ -370,6 +383,7 @@ static VALUE _native_new(VALUE klass) {
|
|
|
370
383
|
|
|
371
384
|
atomic_init(&state->should_run, false);
|
|
372
385
|
state->failure_exception = Qnil;
|
|
386
|
+
state->failure_exception_during_operation = NULL;
|
|
373
387
|
state->stop_thread = Qnil;
|
|
374
388
|
|
|
375
389
|
during_sample_exit(state);
|
|
@@ -554,22 +568,24 @@ static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE
|
|
|
554
568
|
|
|
555
569
|
state->stop_thread = worker_thread;
|
|
556
570
|
|
|
557
|
-
return stop(self_instance,
|
|
571
|
+
return stop(self_instance, Qnil, NULL);
|
|
558
572
|
}
|
|
559
573
|
|
|
560
|
-
|
|
574
|
+
// When providing an `optional_exception`, `optional_exception_during_operation` should be provided as well
|
|
575
|
+
static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_exception_during_operation) {
|
|
561
576
|
atomic_store(&state->should_run, false);
|
|
562
577
|
state->failure_exception = optional_exception;
|
|
578
|
+
state->failure_exception_during_operation = optional_exception_during_operation;
|
|
563
579
|
|
|
564
580
|
// Disable the tracepoints as soon as possible, so the VM doesn't keep on calling them
|
|
565
581
|
disable_tracepoints(state);
|
|
566
582
|
}
|
|
567
583
|
|
|
568
|
-
static VALUE stop(VALUE self_instance, VALUE optional_exception) {
|
|
584
|
+
static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation) {
|
|
569
585
|
cpu_and_wall_time_worker_state *state;
|
|
570
586
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
571
587
|
|
|
572
|
-
stop_state(state, optional_exception);
|
|
588
|
+
stop_state(state, optional_exception, optional_exception_during_operation);
|
|
573
589
|
|
|
574
590
|
return Qtrue;
|
|
575
591
|
}
|
|
@@ -726,7 +742,12 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
726
742
|
during_sample_enter(state);
|
|
727
743
|
|
|
728
744
|
// Rescue against any exceptions that happen during sampling
|
|
729
|
-
safely_call(
|
|
745
|
+
safely_call(
|
|
746
|
+
rescued_sample_from_postponed_job,
|
|
747
|
+
state->self_instance,
|
|
748
|
+
state->self_instance,
|
|
749
|
+
handle_sampling_failure_rescued_sample_from_postponed_job
|
|
750
|
+
);
|
|
730
751
|
|
|
731
752
|
during_sample_exit(state);
|
|
732
753
|
}
|
|
@@ -763,11 +784,6 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
763
784
|
return Qnil;
|
|
764
785
|
}
|
|
765
786
|
|
|
766
|
-
static VALUE handle_sampling_failure(VALUE self_instance, VALUE exception) {
|
|
767
|
-
stop(self_instance, exception);
|
|
768
|
-
return Qnil;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
787
|
// This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
|
|
772
788
|
// It SHOULD NOT be used for other purposes.
|
|
773
789
|
static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
@@ -844,6 +860,15 @@ static VALUE _native_is_running(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
|
844
860
|
return (state != NULL && is_thread_alive(state->owner_thread) && state->self_instance == instance) ? Qtrue : Qfalse;
|
|
845
861
|
}
|
|
846
862
|
|
|
863
|
+
static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
864
|
+
cpu_and_wall_time_worker_state *state;
|
|
865
|
+
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
866
|
+
|
|
867
|
+
if (state->failure_exception_during_operation == NULL) return Qnil;
|
|
868
|
+
|
|
869
|
+
return rb_str_new_cstr(state->failure_exception_during_operation);
|
|
870
|
+
}
|
|
871
|
+
|
|
847
872
|
static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
|
|
848
873
|
/* Does nothing on purpose */
|
|
849
874
|
}
|
|
@@ -936,14 +961,24 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
936
961
|
|
|
937
962
|
during_sample_enter(state);
|
|
938
963
|
|
|
939
|
-
safely_call(
|
|
964
|
+
safely_call(
|
|
965
|
+
thread_context_collector_sample_after_gc,
|
|
966
|
+
state->thread_context_collector_instance,
|
|
967
|
+
state->self_instance,
|
|
968
|
+
handle_sampling_failure_thread_context_collector_sample_after_gc
|
|
969
|
+
);
|
|
940
970
|
|
|
941
971
|
during_sample_exit(state);
|
|
942
972
|
}
|
|
943
973
|
|
|
944
974
|
// Equivalent to Ruby begin/rescue call, where we call a C function and jump to the exception handler if an
|
|
945
975
|
// exception gets raised within
|
|
946
|
-
static VALUE safely_call(
|
|
976
|
+
static VALUE safely_call(
|
|
977
|
+
VALUE (*function_to_call_safely)(VALUE),
|
|
978
|
+
VALUE function_to_call_safely_arg,
|
|
979
|
+
VALUE instance,
|
|
980
|
+
VALUE (*handle_sampling_failure)(VALUE, VALUE)
|
|
981
|
+
) {
|
|
947
982
|
VALUE exception_handler_function_arg = instance;
|
|
948
983
|
return rb_rescue2(
|
|
949
984
|
function_to_call_safely,
|
|
@@ -1119,7 +1154,7 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
|
|
|
1119
1154
|
#define HANDLE_CLOCK_FAILURE(call) ({ \
|
|
1120
1155
|
long _result = (call); \
|
|
1121
1156
|
if (_result == 0) { \
|
|
1122
|
-
|
|
1157
|
+
delayed_error_clock_failure(state); \
|
|
1123
1158
|
return; \
|
|
1124
1159
|
} \
|
|
1125
1160
|
_result; \
|
|
@@ -1203,12 +1238,17 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1203
1238
|
during_sample_enter(state);
|
|
1204
1239
|
|
|
1205
1240
|
// Rescue against any exceptions that happen during sampling
|
|
1206
|
-
safely_call(
|
|
1241
|
+
safely_call(
|
|
1242
|
+
rescued_sample_allocation,
|
|
1243
|
+
Qnil,
|
|
1244
|
+
state->self_instance,
|
|
1245
|
+
handle_sampling_failure_rescued_sample_allocation
|
|
1246
|
+
);
|
|
1207
1247
|
|
|
1208
1248
|
if (state->dynamic_sampling_rate_enabled) {
|
|
1209
1249
|
long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
1210
1250
|
if (now == 0) {
|
|
1211
|
-
|
|
1251
|
+
delayed_error_clock_failure(state);
|
|
1212
1252
|
// NOTE: Not short-circuiting here to make sure cleanup happens
|
|
1213
1253
|
}
|
|
1214
1254
|
uint64_t sampling_time_ns = discrete_dynamic_sampler_after_sample(&state->allocation_sampler, now);
|
|
@@ -1284,7 +1324,12 @@ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
|
|
|
1284
1324
|
|
|
1285
1325
|
static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error) {
|
|
1286
1326
|
// If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
|
|
1287
|
-
stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error));
|
|
1327
|
+
stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error), "delayed_error");
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state) {
|
|
1331
|
+
// If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
|
|
1332
|
+
stop_state(state, rb_exc_new_cstr(clock_failure_exception_class, "failed to get clock time"), "delayed_error_clock_failure");
|
|
1288
1333
|
}
|
|
1289
1334
|
|
|
1290
1335
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg) {
|
|
@@ -1365,7 +1410,12 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1365
1410
|
during_sample_enter(state);
|
|
1366
1411
|
|
|
1367
1412
|
// Rescue against any exceptions that happen during sampling
|
|
1368
|
-
safely_call(
|
|
1413
|
+
safely_call(
|
|
1414
|
+
rescued_after_gvl_running_from_postponed_job,
|
|
1415
|
+
state->self_instance,
|
|
1416
|
+
state->self_instance,
|
|
1417
|
+
handle_sampling_failure_rescued_after_gvl_running_from_postponed_job
|
|
1418
|
+
);
|
|
1369
1419
|
|
|
1370
1420
|
during_sample_exit(state);
|
|
1371
1421
|
}
|
|
@@ -1404,6 +1454,26 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1404
1454
|
}
|
|
1405
1455
|
#endif
|
|
1406
1456
|
|
|
1457
|
+
static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception) {
|
|
1458
|
+
stop(self_instance, exception, "rescued_sample_from_postponed_job");
|
|
1459
|
+
return Qnil;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception) {
|
|
1463
|
+
stop(self_instance, exception, "thread_context_collector_sample_after_gc");
|
|
1464
|
+
return Qnil;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception) {
|
|
1468
|
+
stop(self_instance, exception, "rescued_sample_allocation");
|
|
1469
|
+
return Qnil;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
static VALUE handle_sampling_failure_rescued_after_gvl_running_from_postponed_job(VALUE self_instance, VALUE exception) {
|
|
1473
|
+
stop(self_instance, exception, "rescued_after_gvl_running_from_postponed_job");
|
|
1474
|
+
return Qnil;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1407
1477
|
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state) {
|
|
1408
1478
|
// Tell the compiler it's not allowed to reorder the `during_sample` flag with anything that happens after.
|
|
1409
1479
|
//
|
|
@@ -136,6 +136,7 @@ static VALUE perform_export(
|
|
|
136
136
|
files_to_compress_and_export,
|
|
137
137
|
/* files_to_export_unmodified: */ ddog_prof_Exporter_Slice_File_empty(),
|
|
138
138
|
/* optional_additional_tags: */ NULL,
|
|
139
|
+
/* optional_process_tags: */ NULL,
|
|
139
140
|
&internal_metadata,
|
|
140
141
|
&info
|
|
141
142
|
);
|
|
@@ -10,7 +10,7 @@ module Datadog
|
|
|
10
10
|
module LibdatadogExtconfHelpers
|
|
11
11
|
# Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
|
|
12
12
|
# may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
|
|
13
|
-
LIBDATADOG_VERSION = '~>
|
|
13
|
+
LIBDATADOG_VERSION = '~> 25.0.0.1.0'
|
|
14
14
|
|
|
15
15
|
# Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
|
|
16
16
|
# libdatadog are moved after the extension gets compiled.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Datadog
|
|
8
|
+
module AIGuard
|
|
9
|
+
# API Client for AI Guard API.
|
|
10
|
+
# Uses net/http to perform request. Raises on client and server errors.
|
|
11
|
+
class APIClient
|
|
12
|
+
DEFAULT_SITE = "app.datadoghq.com"
|
|
13
|
+
DEFAULT_PATH = "/api/v2/ai-guard"
|
|
14
|
+
|
|
15
|
+
def initialize(endpoint:, api_key:, application_key:, timeout:)
|
|
16
|
+
@timeout = timeout
|
|
17
|
+
|
|
18
|
+
@endpoint_uri = if endpoint
|
|
19
|
+
URI(endpoint) #: URI::HTTP
|
|
20
|
+
else
|
|
21
|
+
URI::HTTPS.build(
|
|
22
|
+
host: Datadog.configuration.site || DEFAULT_SITE,
|
|
23
|
+
path: DEFAULT_PATH
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@headers = {
|
|
28
|
+
"DD-API-KEY": api_key.to_s,
|
|
29
|
+
"DD-APPLICATION-KEY": application_key.to_s,
|
|
30
|
+
"DD-AI-GUARD-VERSION": Datadog::VERSION::STRING,
|
|
31
|
+
"DD-AI-GUARD-SOURCE": "SDK",
|
|
32
|
+
"DD-AI-GUARD-LANGUAGE": "ruby",
|
|
33
|
+
"content-type": "application/json"
|
|
34
|
+
}.freeze
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def post(path, body:)
|
|
38
|
+
Net::HTTP.start(@endpoint_uri.host.to_s, @endpoint_uri.port, use_ssl: use_ssl?, read_timeout: @timeout) do |http|
|
|
39
|
+
request = Net::HTTP::Post.new(@endpoint_uri.request_uri + path, @headers)
|
|
40
|
+
request.body = body.to_json
|
|
41
|
+
|
|
42
|
+
response = http.request(request)
|
|
43
|
+
raise_on_http_error!(response)
|
|
44
|
+
|
|
45
|
+
parse_response_body(response.body)
|
|
46
|
+
end
|
|
47
|
+
rescue Net::ReadTimeout
|
|
48
|
+
raise AIGuardClientError, "Request to AI Guard timed out"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def raise_on_http_error!(response)
|
|
54
|
+
case response
|
|
55
|
+
when Net::HTTPSuccess
|
|
56
|
+
# do nothing
|
|
57
|
+
when Net::HTTPRedirection
|
|
58
|
+
raise AIGuardClientError, "Redirects for AI Guard API are not supported"
|
|
59
|
+
else
|
|
60
|
+
error_message = begin
|
|
61
|
+
parsed_body = JSON.parse(response.body)
|
|
62
|
+
Array(parsed_body.fetch('errors')).join(', ')
|
|
63
|
+
rescue JSON::ParserError, KeyError
|
|
64
|
+
response.body
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
raise AIGuardClientError, error_message
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def parse_response_body(body)
|
|
72
|
+
JSON.parse(body)
|
|
73
|
+
rescue JSON::ParserError
|
|
74
|
+
raise AIGuardClientError, "Could not parse response body"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def use_ssl?
|
|
78
|
+
@endpoint_uri.scheme == 'https'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'api_client'
|
|
4
|
+
require_relative 'evaluation'
|
|
5
|
+
require_relative 'evaluation/request'
|
|
6
|
+
require_relative 'evaluation/result'
|
|
7
|
+
require_relative 'evaluation/no_op_result'
|
|
8
|
+
require_relative 'evaluation/message'
|
|
9
|
+
require_relative 'evaluation/tool_call'
|
|
10
|
+
require_relative 'ext'
|
|
11
|
+
|
|
12
|
+
module Datadog
|
|
13
|
+
module AIGuard
|
|
14
|
+
# Component for API Guard product
|
|
15
|
+
class Component
|
|
16
|
+
attr_reader :api_client, :logger
|
|
17
|
+
|
|
18
|
+
def self.build(settings, logger:, telemetry:)
|
|
19
|
+
return unless settings.respond_to?(:ai_guard) && settings.ai_guard.enabled
|
|
20
|
+
|
|
21
|
+
api_client = APIClient.new(
|
|
22
|
+
endpoint: settings.ai_guard.endpoint,
|
|
23
|
+
api_key: settings.api_key,
|
|
24
|
+
application_key: settings.ai_guard.app_key,
|
|
25
|
+
timeout: settings.ai_guard.timeout_ms / 1_000
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
new(api_client, logger: logger, telemetry: telemetry)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(api_client, logger:, telemetry:)
|
|
32
|
+
@api_client = api_client
|
|
33
|
+
@logger = logger
|
|
34
|
+
@telemetry = telemetry
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shutdown!
|
|
38
|
+
# no-op
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
module Configuration
|
|
6
|
+
# This module contains constants for AI Guard component
|
|
7
|
+
module Ext
|
|
8
|
+
ENV_AI_GUARD_ENABLED = "DD_AI_GUARD_ENABLED"
|
|
9
|
+
ENV_AI_GUARD_ENDPOINT = "DD_AI_GUARD_ENDPOINT"
|
|
10
|
+
ENV_AI_GUARD_TIMEOUT = "DD_AI_GUARD_TIMEOUT"
|
|
11
|
+
ENV_AI_GUARD_MAX_CONTENT_SIZE = "DD_AI_GUARD_MAX_CONTENT_SIZE"
|
|
12
|
+
ENV_AI_GUARD_MAX_MESSAGES_LENGTH = "DD_AI_GUARD_MAX_MESSAGES_LENGTH"
|
|
13
|
+
ENV_APP_KEY = "DD_APP_KEY"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require_relative "ext"
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module AIGuard
|
|
8
|
+
module Configuration
|
|
9
|
+
# AI Guard specific settings
|
|
10
|
+
module Settings
|
|
11
|
+
def self.extended(base)
|
|
12
|
+
base = base.singleton_class unless base.is_a?(Class)
|
|
13
|
+
add_settings!(base)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.add_settings!(base)
|
|
17
|
+
base.class_eval do
|
|
18
|
+
# AI Guard specific configurations.
|
|
19
|
+
# @public_api
|
|
20
|
+
settings :ai_guard do
|
|
21
|
+
# Enable AI Guard.
|
|
22
|
+
#
|
|
23
|
+
# You can use this option to skip calls to AI Guard API without having to remove library as a whole.
|
|
24
|
+
#
|
|
25
|
+
# @default `DD_AI_GUARD_ENABLED`, otherwise `false`
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
option :enabled do |o|
|
|
28
|
+
o.type :bool
|
|
29
|
+
o.env Ext::ENV_AI_GUARD_ENABLED
|
|
30
|
+
o.default false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# AI Guard API endpoint path.
|
|
34
|
+
#
|
|
35
|
+
# @default `DD_AI_GUARD_ENDPOINT`, otherwise `nil`
|
|
36
|
+
# @return [String, nil]
|
|
37
|
+
option :endpoint do |o|
|
|
38
|
+
o.type :string, nilable: true
|
|
39
|
+
o.env Ext::ENV_AI_GUARD_ENDPOINT
|
|
40
|
+
|
|
41
|
+
o.setter do |value|
|
|
42
|
+
next unless value
|
|
43
|
+
|
|
44
|
+
uri = URI(value.to_s)
|
|
45
|
+
raise ArgumentError, "Please provide an absolute URI that includes a protocol" unless uri.absolute?
|
|
46
|
+
|
|
47
|
+
uri.to_s.delete_suffix("/")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Datadog Application key.
|
|
52
|
+
#
|
|
53
|
+
# @default `DD_APP_KEY` environment variable, otherwise `nil`
|
|
54
|
+
# @return [String, nil]
|
|
55
|
+
option :app_key do |o|
|
|
56
|
+
o.type :string, nilable: true
|
|
57
|
+
o.env Ext::ENV_APP_KEY
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Request timeout in milliseconds.
|
|
61
|
+
#
|
|
62
|
+
# @default `DD_AI_GUARD_TIMEOUT`, otherwise 10 000 ms
|
|
63
|
+
# @return [Integer]
|
|
64
|
+
option :timeout_ms do |o|
|
|
65
|
+
o.type :int
|
|
66
|
+
o.env Ext::ENV_AI_GUARD_TIMEOUT
|
|
67
|
+
o.default 10_000
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Maximum content size in bytes.
|
|
71
|
+
# Content that exceeds the maximum allowed size is truncated before
|
|
72
|
+
# being stored in the current span context.
|
|
73
|
+
#
|
|
74
|
+
# @default `DD_AI_GUARD_MAX_CONTENT_SIZE`, otherwise 524 228 bytes
|
|
75
|
+
# @return [Integer]
|
|
76
|
+
option :max_content_size_bytes do |o|
|
|
77
|
+
o.type :int
|
|
78
|
+
o.env Ext::ENV_AI_GUARD_MAX_CONTENT_SIZE
|
|
79
|
+
o.default 512 * 1024
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Maximum number of messages.
|
|
83
|
+
# Older messages are omitted once the message limit is reached.
|
|
84
|
+
#
|
|
85
|
+
# @default `DD_AI_GUARD_MAX_MESSAGES_LENGTH`, otherwise 16 messages
|
|
86
|
+
# @return [Integer]
|
|
87
|
+
option :max_messages_length do |o|
|
|
88
|
+
o.type :int
|
|
89
|
+
o.env Ext::ENV_AI_GUARD_MAX_MESSAGES_LENGTH
|
|
90
|
+
o.default 16
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
module Evaluation
|
|
6
|
+
# Message class for AI Guard
|
|
7
|
+
class Message
|
|
8
|
+
attr_reader :role, :content, :tool_call, :tool_call_id
|
|
9
|
+
|
|
10
|
+
def initialize(role:, content: nil, tool_call: nil, tool_call_id: nil)
|
|
11
|
+
raise ArgumentError, "Role must be set to a non-empty value" if role.to_s.empty?
|
|
12
|
+
|
|
13
|
+
@role = role.to_sym
|
|
14
|
+
@content = content
|
|
15
|
+
@tool_call = tool_call
|
|
16
|
+
@tool_call_id = tool_call_id
|
|
17
|
+
|
|
18
|
+
if @tool_call && !@tool_call.is_a?(ToolCall)
|
|
19
|
+
raise ArgumentError, "Expected an instance of #{ToolCall.name} for :tool_call argument"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|