datadog 2.4.0 → 2.6.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 +40 -1
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +57 -18
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +93 -106
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +8 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +8 -8
- data/ext/datadog_profiling_native_extension/heap_recorder.c +174 -28
- data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
- data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/libdatadog_api/crashtracker.c +3 -5
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/configuration/settings.rb +8 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -5
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
- data/lib/datadog/appsec/event.rb +24 -0
- data/lib/datadog/appsec/ext.rb +4 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
- data/lib/datadog/appsec/processor/context.rb +107 -0
- data/lib/datadog/appsec/processor.rb +7 -71
- data/lib/datadog/appsec/scope.rb +1 -4
- data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
- data/lib/datadog/appsec/utils.rb +2 -0
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
- data/lib/datadog/core/configuration/settings.rb +12 -0
- data/lib/datadog/core/configuration.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +8 -5
- data/lib/datadog/core/environment/yjit.rb +5 -0
- data/lib/datadog/core/remote/transport/http.rb +5 -0
- data/lib/datadog/core/remote/worker.rb +1 -1
- data/lib/datadog/core/runtime/ext.rb +1 -0
- data/lib/datadog/core/runtime/metrics.rb +4 -0
- data/lib/datadog/core/semaphore.rb +35 -0
- data/lib/datadog/core/telemetry/logging.rb +10 -10
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/workers/async.rb +1 -1
- data/lib/datadog/di/code_tracker.rb +11 -13
- data/lib/datadog/di/instrumenter.rb +301 -0
- data/lib/datadog/di/probe.rb +29 -0
- data/lib/datadog/di/probe_builder.rb +7 -1
- data/lib/datadog/di/probe_notification_builder.rb +207 -0
- data/lib/datadog/di/probe_notifier_worker.rb +244 -0
- data/lib/datadog/di/serializer.rb +23 -1
- data/lib/datadog/di/transport.rb +67 -0
- data/lib/datadog/di/utils.rb +39 -0
- data/lib/datadog/di.rb +43 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +9 -11
- data/lib/datadog/profiling/component.rb +1 -0
- data/lib/datadog/profiling/stack_recorder.rb +37 -9
- data/lib/datadog/tracing/component.rb +13 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +3 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -2
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +4 -0
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +4 -0
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +4 -0
- data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
- data/lib/datadog/tracing/sampling/rule_sampler.rb +6 -4
- data/lib/datadog/tracing/tracer.rb +15 -10
- data/lib/datadog/tracing/transport/http.rb +4 -0
- data/lib/datadog/tracing/workers.rb +1 -1
- data/lib/datadog/tracing/writer.rb +26 -28
- data/lib/datadog/version.rb +1 -1
- metadata +22 -14
@@ -256,21 +256,21 @@ if Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER
|
|
256
256
|
create_makefile EXTENSION_NAME
|
257
257
|
else
|
258
258
|
# The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
|
259
|
-
# the
|
259
|
+
# the datadog-ruby_core_source gem to get access to private VM headers.
|
260
260
|
# This gem ships source code copies of these VM headers for the different Ruby VM versions;
|
261
|
-
# see https://github.com/
|
261
|
+
# see https://github.com/DataDog/datadog-ruby_core_source for details
|
262
262
|
|
263
263
|
create_header
|
264
264
|
|
265
|
-
require "
|
265
|
+
require "datadog/ruby_core_source"
|
266
266
|
dir_config("ruby") # allow user to pass in non-standard core include directory
|
267
267
|
|
268
268
|
# This is a workaround for a weird issue...
|
269
269
|
#
|
270
|
-
# The mkmf tool defines a `with_cppflags` helper that
|
270
|
+
# The mkmf tool defines a `with_cppflags` helper that datadog-ruby_core_source uses. This helper temporarily
|
271
271
|
# replaces `$CPPFLAGS` (aka the C pre-processor [not c++!] flags) with a different set when doing something.
|
272
272
|
#
|
273
|
-
# The
|
273
|
+
# The datadog-ruby_core_source gem uses `with_cppflags` during makefile generation to inject extra headers into the
|
274
274
|
# path. But because `with_cppflags` replaces `$CPPFLAGS`, well, the default `$CPPFLAGS` are not included in the
|
275
275
|
# makefile.
|
276
276
|
#
|
@@ -281,12 +281,12 @@ else
|
|
281
281
|
# `VM_CHECK_MODE=1` when building Ruby will trigger this issue (because somethings in structures the profiler reads
|
282
282
|
# are ifdef'd out using this setting).
|
283
283
|
#
|
284
|
-
# To workaround this issue, we override `with_cppflags` for
|
285
|
-
|
284
|
+
# To workaround this issue, we override `with_cppflags` for datadog-ruby_core_source to still include `$CPPFLAGS`.
|
285
|
+
Datadog::RubyCoreSource.define_singleton_method(:with_cppflags) do |newflags, &block|
|
286
286
|
super("#{newflags} #{$CPPFLAGS}", &block)
|
287
287
|
end
|
288
288
|
|
289
|
-
|
289
|
+
Datadog::RubyCoreSource
|
290
290
|
.create_makefile_with_core(
|
291
291
|
proc do
|
292
292
|
headers_available =
|
@@ -5,6 +5,7 @@
|
|
5
5
|
#include <errno.h>
|
6
6
|
#include "collectors_stack.h"
|
7
7
|
#include "libdatadog_helpers.h"
|
8
|
+
#include "time_helpers.h"
|
8
9
|
|
9
10
|
#if (defined(HAVE_WORKING_RB_GC_FORCE_RECYCLE) && ! defined(NO_SEEN_OBJ_ID_FLAG))
|
10
11
|
#define CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
|
@@ -16,6 +17,16 @@
|
|
16
17
|
// relevant for heap profiles as the great majority should be trivially reclaimed
|
17
18
|
// during the next GC.
|
18
19
|
#define ITERATION_MIN_AGE 1
|
20
|
+
// Copied from https://github.com/ruby/ruby/blob/15135030e5808d527325feaaaf04caeb1b44f8b5/gc/default.c#L725C1-L725C27
|
21
|
+
// to align with Ruby's GC definition of what constitutes an old object which are only
|
22
|
+
// supposed to be reclaimed in major GCs.
|
23
|
+
#define OLD_AGE 3
|
24
|
+
// Wait at least 2 seconds before asking heap recorder to explicitly update itself. Heap recorder
|
25
|
+
// data will only materialize at profile serialization time but updating often helps keep our
|
26
|
+
// heap tracking data small since every GC should get rid of a bunch of temporary objects. The
|
27
|
+
// more we clean up before profile flush, the less work we'll have to do all-at-once when preparing
|
28
|
+
// to flush heap data and holding the GVL which should hopefully help with reducing latency impact.
|
29
|
+
#define MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS SECONDS_AS_NS(2)
|
19
30
|
|
20
31
|
// A compact representation of a stacktrace frame for a heap allocation.
|
21
32
|
typedef struct {
|
@@ -144,11 +155,18 @@ struct heap_recorder {
|
|
144
155
|
// mutation of the data so iteration can occur without acquiring a lock.
|
145
156
|
// NOTE: Contrary to object_records, this table has no ownership of its data.
|
146
157
|
st_table *object_records_snapshot;
|
147
|
-
//
|
158
|
+
// Are we currently updating or not?
|
159
|
+
bool updating;
|
160
|
+
// The GC gen/epoch/count in which we are updating (or last updated if not currently updating).
|
148
161
|
//
|
149
|
-
// This enables us to calculate the age of
|
150
|
-
//
|
151
|
-
size_t
|
162
|
+
// This enables us to calculate the age of objects considered in the update by comparing it
|
163
|
+
// against an object's alloc_gen.
|
164
|
+
size_t update_gen;
|
165
|
+
// Whether the current update (or last update if not currently updating) is including old
|
166
|
+
// objects or not.
|
167
|
+
bool update_include_old;
|
168
|
+
// When did we do the last update of heap recorder?
|
169
|
+
long last_update_ns;
|
152
170
|
|
153
171
|
// Data for a heap recording that was started but not yet ended
|
154
172
|
recording active_recording;
|
@@ -165,6 +183,21 @@ struct heap_recorder {
|
|
165
183
|
size_t objects_skipped;
|
166
184
|
size_t objects_frozen;
|
167
185
|
} stats_last_update;
|
186
|
+
|
187
|
+
struct stats_lifetime {
|
188
|
+
unsigned long updates_successful;
|
189
|
+
unsigned long updates_skipped_concurrent;
|
190
|
+
unsigned long updates_skipped_gcgen;
|
191
|
+
unsigned long updates_skipped_time;
|
192
|
+
|
193
|
+
double ewma_young_objects_alive;
|
194
|
+
double ewma_young_objects_dead;
|
195
|
+
double ewma_young_objects_skipped; // Note: Here "young" refers to the young update; objects skipped includes non-young objects
|
196
|
+
|
197
|
+
double ewma_objects_alive;
|
198
|
+
double ewma_objects_dead;
|
199
|
+
double ewma_objects_skipped;
|
200
|
+
} stats_lifetime;
|
168
201
|
};
|
169
202
|
|
170
203
|
struct end_heap_allocation_args {
|
@@ -183,6 +216,8 @@ static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t ext
|
|
183
216
|
static int update_object_record_entry(st_data_t*, st_data_t*, st_data_t, int);
|
184
217
|
static void commit_recording(heap_recorder*, heap_record*, recording);
|
185
218
|
static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
|
219
|
+
static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
|
220
|
+
static inline double ewma_stat(double previous, double current);
|
186
221
|
|
187
222
|
// ==========================
|
188
223
|
// Heap Recorder External API
|
@@ -280,6 +315,9 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
|
|
280
315
|
if (heap_recorder->object_records_snapshot != NULL) {
|
281
316
|
heap_recorder_finish_iteration(heap_recorder);
|
282
317
|
}
|
318
|
+
|
319
|
+
// Clear lifetime stats since this is essentially a new heap recorder
|
320
|
+
heap_recorder->stats_lifetime = (struct stats_lifetime) {0};
|
283
321
|
}
|
284
322
|
|
285
323
|
void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice *alloc_class) {
|
@@ -394,23 +432,94 @@ static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args) {
|
|
394
432
|
return Qnil;
|
395
433
|
}
|
396
434
|
|
397
|
-
void
|
435
|
+
void heap_recorder_update_young_objects(heap_recorder *heap_recorder) {
|
398
436
|
if (heap_recorder == NULL) {
|
399
437
|
return;
|
400
438
|
}
|
401
439
|
|
402
|
-
heap_recorder
|
440
|
+
heap_recorder_update(heap_recorder, /* full_update: */ false);
|
441
|
+
}
|
442
|
+
|
443
|
+
static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update) {
|
444
|
+
if (heap_recorder->updating) {
|
445
|
+
if (full_update) rb_raise(rb_eRuntimeError, "BUG: full_update should not be triggered during another update");
|
446
|
+
|
447
|
+
// If we try to update while another update is still running, short-circuit.
|
448
|
+
// NOTE: This runs while holding the GVL. But since updates may be triggered from GC activity, there's still
|
449
|
+
// a chance for updates to be attempted concurrently if scheduling gods so determine.
|
450
|
+
heap_recorder->stats_lifetime.updates_skipped_concurrent++;
|
451
|
+
return;
|
452
|
+
}
|
403
453
|
|
404
454
|
if (heap_recorder->object_records_snapshot != NULL) {
|
405
|
-
//
|
406
|
-
|
455
|
+
// While serialization is happening, it runs without the GVL and uses the object_records_snapshot.
|
456
|
+
// Although we iterate on a snapshot of object_records, these records point to other data that has not been
|
457
|
+
// snapshotted for efficiency reasons (e.g. heap_records). Since updating may invalidate
|
458
|
+
// some of that non-snapshotted data, let's refrain from doing updates during iteration. This also enforces the
|
459
|
+
// semantic that iteration will operate as a point-in-time snapshot.
|
460
|
+
return;
|
407
461
|
}
|
408
462
|
|
463
|
+
size_t current_gc_gen = rb_gc_count();
|
464
|
+
long now_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
465
|
+
|
466
|
+
if (!full_update) {
|
467
|
+
if (current_gc_gen == heap_recorder->update_gen) {
|
468
|
+
// Are we still in the same GC gen as last update? If so, skip updating since things should not have
|
469
|
+
// changed significantly since last time.
|
470
|
+
// NOTE: This is mostly a performance decision. I suppose some objects may be cleaned up in intermediate
|
471
|
+
// GC steps and sizes may change. But because we have to iterate through all our tracked
|
472
|
+
// object records to do an update, let's wait until all steps for a particular GC generation
|
473
|
+
// have finished to do so. We may revisit this once we have a better liveness checking mechanism.
|
474
|
+
heap_recorder->stats_lifetime.updates_skipped_gcgen++;
|
475
|
+
return;
|
476
|
+
}
|
477
|
+
|
478
|
+
if (now_ns > 0 && (now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
|
479
|
+
// We did an update not too long ago. Let's skip this one to avoid over-taxing the system.
|
480
|
+
heap_recorder->stats_lifetime.updates_skipped_time++;
|
481
|
+
return;
|
482
|
+
}
|
483
|
+
}
|
484
|
+
|
485
|
+
heap_recorder->updating = true;
|
409
486
|
// Reset last update stats, we'll be building them from scratch during the st_foreach call below
|
410
|
-
heap_recorder->stats_last_update = (struct stats_last_update) {};
|
487
|
+
heap_recorder->stats_last_update = (struct stats_last_update) {0};
|
488
|
+
|
489
|
+
heap_recorder->update_gen = current_gc_gen;
|
490
|
+
heap_recorder->update_include_old = full_update;
|
411
491
|
|
412
492
|
st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
|
413
493
|
|
494
|
+
heap_recorder->last_update_ns = now_ns;
|
495
|
+
heap_recorder->stats_lifetime.updates_successful++;
|
496
|
+
|
497
|
+
// Lifetime stats updating
|
498
|
+
if (!full_update) {
|
499
|
+
heap_recorder->stats_lifetime.ewma_young_objects_alive = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_alive, heap_recorder->stats_last_update.objects_alive);
|
500
|
+
heap_recorder->stats_lifetime.ewma_young_objects_dead = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_dead, heap_recorder->stats_last_update.objects_dead);
|
501
|
+
heap_recorder->stats_lifetime.ewma_young_objects_skipped = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_skipped, heap_recorder->stats_last_update.objects_skipped);
|
502
|
+
} else {
|
503
|
+
heap_recorder->stats_lifetime.ewma_objects_alive = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_alive, heap_recorder->stats_last_update.objects_alive);
|
504
|
+
heap_recorder->stats_lifetime.ewma_objects_dead = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_dead, heap_recorder->stats_last_update.objects_dead);
|
505
|
+
heap_recorder->stats_lifetime.ewma_objects_skipped = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_skipped, heap_recorder->stats_last_update.objects_skipped);
|
506
|
+
}
|
507
|
+
|
508
|
+
heap_recorder->updating = false;
|
509
|
+
}
|
510
|
+
|
511
|
+
void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
|
512
|
+
if (heap_recorder == NULL) {
|
513
|
+
return;
|
514
|
+
}
|
515
|
+
|
516
|
+
if (heap_recorder->object_records_snapshot != NULL) {
|
517
|
+
// we could trivially handle this but we raise to highlight and catch unexpected usages.
|
518
|
+
rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
|
519
|
+
}
|
520
|
+
|
521
|
+
heap_recorder_update(heap_recorder, /* full_update: */ true);
|
522
|
+
|
414
523
|
heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
|
415
524
|
if (heap_recorder->object_records_snapshot == NULL) {
|
416
525
|
rb_raise(rb_eRuntimeError, "Failed to create heap snapshot.");
|
@@ -474,6 +583,19 @@ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
|
|
474
583
|
ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
|
475
584
|
ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
|
476
585
|
ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
|
586
|
+
|
587
|
+
// Lifetime stats
|
588
|
+
ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_successful),
|
589
|
+
ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
|
590
|
+
ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
|
591
|
+
ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
|
592
|
+
ID2SYM(rb_intern("lifetime_ewma_young_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_alive),
|
593
|
+
ID2SYM(rb_intern("lifetime_ewma_young_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_dead),
|
594
|
+
// Note: Here "young" refers to the young update; objects skipped includes non-young objects
|
595
|
+
ID2SYM(rb_intern("lifetime_ewma_young_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_skipped),
|
596
|
+
ID2SYM(rb_intern("lifetime_ewma_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_alive),
|
597
|
+
ID2SYM(rb_intern("lifetime_ewma_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_dead),
|
598
|
+
ID2SYM(rb_intern("lifetime_ewma_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_skipped),
|
477
599
|
};
|
478
600
|
VALUE hash = rb_hash_new();
|
479
601
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
@@ -503,11 +625,14 @@ void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locatio
|
|
503
625
|
|
504
626
|
VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
505
627
|
if (heap_recorder == NULL) {
|
506
|
-
|
628
|
+
rb_raise(rb_eArgError, "heap_recorder is NULL");
|
507
629
|
}
|
508
630
|
|
509
631
|
VALUE debug_str = rb_str_new2("object records:\n");
|
510
632
|
st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) debug_str);
|
633
|
+
|
634
|
+
rb_str_catf(debug_str, "state snapshot: %"PRIsVALUE"\n------\n", heap_recorder_state_snapshot(heap_recorder));
|
635
|
+
|
511
636
|
return debug_str;
|
512
637
|
}
|
513
638
|
|
@@ -526,13 +651,6 @@ static int st_object_record_entry_free(DDTRACE_UNUSED st_data_t key, st_data_t v
|
|
526
651
|
return ST_DELETE;
|
527
652
|
}
|
528
653
|
|
529
|
-
// Check to see if an object should not be included in a heap recorder iteration.
|
530
|
-
// This centralizes the checking logic to ensure it's equally applied between
|
531
|
-
// preparation and iteration codepaths.
|
532
|
-
static inline bool should_exclude_from_iteration(object_record *obj_record) {
|
533
|
-
return obj_record->object_data.gen_age < ITERATION_MIN_AGE;
|
534
|
-
}
|
535
|
-
|
536
654
|
static int st_object_record_update(st_data_t key, st_data_t value, st_data_t extra_arg) {
|
537
655
|
long obj_id = (long) key;
|
538
656
|
object_record *record = (object_record*) value;
|
@@ -540,16 +658,20 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
540
658
|
|
541
659
|
VALUE ref;
|
542
660
|
|
543
|
-
size_t
|
661
|
+
size_t update_gen = recorder->update_gen;
|
544
662
|
size_t alloc_gen = record->object_data.alloc_gen;
|
545
663
|
// Guard against potential overflows given unsigned types here.
|
546
|
-
record->object_data.gen_age = alloc_gen <
|
664
|
+
record->object_data.gen_age = alloc_gen < update_gen ? update_gen - alloc_gen : 0;
|
665
|
+
|
666
|
+
if (record->object_data.gen_age == 0) {
|
667
|
+
// Objects that belong to the current GC gen have not had a chance to be cleaned up yet
|
668
|
+
// and won't show up in the iteration anyway so no point in checking their liveness/sizes.
|
669
|
+
recorder->stats_last_update.objects_skipped++;
|
670
|
+
return ST_CONTINUE;
|
671
|
+
}
|
547
672
|
|
548
|
-
if (
|
549
|
-
//
|
550
|
-
// no point checking for liveness or updating its size, so exit early.
|
551
|
-
// NOTE: This means that there should be an equivalent check during actual
|
552
|
-
// iteration otherwise we'd iterate/expose stale object data.
|
673
|
+
if (!recorder->update_include_old && record->object_data.gen_age >= OLD_AGE) {
|
674
|
+
// The current update is not including old objects but this record is for an old object, skip its update.
|
553
675
|
recorder->stats_last_update.objects_skipped++;
|
554
676
|
return ST_CONTINUE;
|
555
677
|
}
|
@@ -598,7 +720,11 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
598
720
|
|
599
721
|
#endif
|
600
722
|
|
601
|
-
if (
|
723
|
+
if (
|
724
|
+
recorder->size_enabled &&
|
725
|
+
recorder->update_include_old && // We only update sizes when doing a full update
|
726
|
+
!record->object_data.is_frozen
|
727
|
+
) {
|
602
728
|
// if we were asked to update sizes and this object was not already seen as being frozen,
|
603
729
|
// update size again.
|
604
730
|
record->object_data.size = ruby_obj_memsize_of(ref);
|
@@ -622,10 +748,8 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
622
748
|
|
623
749
|
const heap_recorder *recorder = context->heap_recorder;
|
624
750
|
|
625
|
-
if (
|
751
|
+
if (record->object_data.gen_age < ITERATION_MIN_AGE) {
|
626
752
|
// Skip objects that should not be included in iteration
|
627
|
-
// NOTE: This matches the short-circuiting condition in st_object_record_update
|
628
|
-
// and prevents iteration over stale objects.
|
629
753
|
return ST_CONTINUE;
|
630
754
|
}
|
631
755
|
|
@@ -1087,3 +1211,25 @@ st_index_t heap_record_key_hash_st(st_data_t key) {
|
|
1087
1211
|
return ddog_location_slice_hash(*record_key->location_slice, FNV1_32A_INIT);
|
1088
1212
|
}
|
1089
1213
|
}
|
1214
|
+
|
1215
|
+
static inline double ewma_stat(double previous, double current) {
|
1216
|
+
double alpha = 0.3;
|
1217
|
+
return (1 - alpha) * previous + alpha * current;
|
1218
|
+
}
|
1219
|
+
|
1220
|
+
VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VALUE obj_id) {
|
1221
|
+
if (heap_recorder == NULL) {
|
1222
|
+
rb_raise(rb_eArgError, "heap_recorder is NULL");
|
1223
|
+
}
|
1224
|
+
|
1225
|
+
// Check if object records contains an object with this object_id
|
1226
|
+
return st_is_member(heap_recorder->object_records, FIX2LONG(obj_id)) ? Qtrue : Qfalse;
|
1227
|
+
}
|
1228
|
+
|
1229
|
+
void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder) {
|
1230
|
+
if (heap_recorder == NULL) {
|
1231
|
+
rb_raise(rb_eArgError, "heap_recorder is NULL");
|
1232
|
+
}
|
1233
|
+
|
1234
|
+
heap_recorder->last_update_ns = 0;
|
1235
|
+
}
|
@@ -118,6 +118,11 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
|
|
118
118
|
__attribute__((warn_unused_result))
|
119
119
|
int end_heap_allocation_recording_with_rb_protect(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations);
|
120
120
|
|
121
|
+
// Update the heap recorder, **checking young objects only**. The idea here is to align with GC: most young objects never
|
122
|
+
// survive enough GC generations, and thus periodically running this method reduces memory usage (we get rid of
|
123
|
+
// these objects quicker) and hopefully reduces tail latency (because there's less objects at serialization time to check).
|
124
|
+
void heap_recorder_update_young_objects(heap_recorder *heap_recorder);
|
125
|
+
|
121
126
|
// Update the heap recorder to reflect the latest state of the VM and prepare internal structures
|
122
127
|
// for efficient iteration.
|
123
128
|
//
|
@@ -166,3 +171,9 @@ void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locatio
|
|
166
171
|
// Returns a Ruby string with a representation of internal data helpful to
|
167
172
|
// troubleshoot issues such as unexpected test failures.
|
168
173
|
VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder);
|
174
|
+
|
175
|
+
// Check if a given object_id is being tracked or not
|
176
|
+
VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VALUE obj_id);
|
177
|
+
|
178
|
+
// Used to ensure that a GC actually triggers an update of the objects
|
179
|
+
void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder);
|
@@ -9,7 +9,7 @@ module Datadog
|
|
9
9
|
# Can be set to force rubygems to fail gem installation when profiling extension could not be built
|
10
10
|
ENV_FAIL_INSTALL_IF_MISSING_EXTENSION = "DD_PROFILING_FAIL_INSTALL_IF_MISSING_EXTENSION"
|
11
11
|
|
12
|
-
# The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
|
12
|
+
# The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on datadog-ruby_core_source
|
13
13
|
CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?("2.6", "2.7", "3.0.", "3.1.", "3.2.")
|
14
14
|
|
15
15
|
def self.fail_install_if_missing_extension?
|
@@ -13,7 +13,7 @@
|
|
13
13
|
#include RUBY_MJIT_HEADER
|
14
14
|
#else
|
15
15
|
// The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
|
16
|
-
// the
|
16
|
+
// the datadog-ruby_core_source gem to get access to private VM headers.
|
17
17
|
|
18
18
|
// We can't do anything about warnings in VM headers, so we just use this technique to suppress them.
|
19
19
|
// See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
|
@@ -219,16 +219,19 @@ static bool ruby_is_obj_with_class(VALUE obj) {
|
|
219
219
|
return false;
|
220
220
|
}
|
221
221
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
}
|
222
|
+
// These two functions are not present in the VM headers, but are public symbols that can be invoked.
|
223
|
+
int rb_objspace_internal_object_p(VALUE obj);
|
224
|
+
const char *rb_obj_info(VALUE obj);
|
226
225
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
226
|
+
VALUE ruby_safe_inspect(VALUE obj) {
|
227
|
+
if (!ruby_is_obj_with_class(obj)) return rb_str_new_cstr("(Not an object)");
|
228
|
+
if (rb_objspace_internal_object_p(obj)) return rb_sprintf("(VM Internal, %s)", rb_obj_info(obj));
|
229
|
+
// @ivoanjo: I saw crashes on Ruby 3.1.4 when trying to #inspect matchdata objects. I'm not entirely sure why this
|
230
|
+
// is needed, but since we only use this method for debug purposes I put in this alternative and decided not to
|
231
|
+
// dig deeper.
|
232
|
+
if (rb_type(obj) == RUBY_T_MATCH) return rb_sprintf("(MatchData, %s)", rb_obj_info(obj));
|
233
|
+
if (rb_respond_to(obj, inspect_id)) return rb_sprintf("%+"PRIsVALUE, obj);
|
234
|
+
if (rb_respond_to(obj, to_s_id)) return rb_sprintf("%"PRIsVALUE, obj);
|
235
|
+
|
236
|
+
return rb_str_new_cstr("(Not inspectable)");
|
234
237
|
}
|
@@ -187,6 +187,7 @@ typedef struct profile_slot {
|
|
187
187
|
struct stack_recorder_state {
|
188
188
|
// Heap recorder instance
|
189
189
|
heap_recorder *heap_recorder;
|
190
|
+
bool heap_clean_after_gc_enabled;
|
190
191
|
|
191
192
|
pthread_mutex_t mutex_slot_one;
|
192
193
|
profile_slot profile_slot_one;
|
@@ -236,16 +237,7 @@ static VALUE _native_new(VALUE klass);
|
|
236
237
|
static void initialize_slot_concurrency_control(struct stack_recorder_state *state);
|
237
238
|
static void initialize_profiles(struct stack_recorder_state *state, ddog_prof_Slice_ValueType sample_types);
|
238
239
|
static void stack_recorder_typed_data_free(void *data);
|
239
|
-
static VALUE _native_initialize(
|
240
|
-
DDTRACE_UNUSED VALUE _self,
|
241
|
-
VALUE recorder_instance,
|
242
|
-
VALUE cpu_time_enabled,
|
243
|
-
VALUE alloc_samples_enabled,
|
244
|
-
VALUE heap_samples_enabled,
|
245
|
-
VALUE heap_size_enabled,
|
246
|
-
VALUE heap_sample_every,
|
247
|
-
VALUE timeline_enabled
|
248
|
-
);
|
240
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
249
241
|
static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
|
250
242
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time);
|
251
243
|
static void *call_serialize_without_gvl(void *call_args);
|
@@ -270,7 +262,9 @@ static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
|
270
262
|
static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
271
263
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance);
|
272
264
|
static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns);
|
273
|
-
|
265
|
+
static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE object_id);
|
266
|
+
static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
267
|
+
static VALUE _native_recorder_after_gc_step(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
274
268
|
|
275
269
|
void stack_recorder_init(VALUE profiling_module) {
|
276
270
|
VALUE stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
|
@@ -287,7 +281,7 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
287
281
|
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
288
282
|
rb_define_alloc_func(stack_recorder_class, _native_new);
|
289
283
|
|
290
|
-
rb_define_singleton_method(stack_recorder_class, "_native_initialize", _native_initialize,
|
284
|
+
rb_define_singleton_method(stack_recorder_class, "_native_initialize", _native_initialize, -1);
|
291
285
|
rb_define_singleton_method(stack_recorder_class, "_native_serialize", _native_serialize, 1);
|
292
286
|
rb_define_singleton_method(stack_recorder_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
293
287
|
rb_define_singleton_method(stack_recorder_class, "_native_stats", _native_stats, 1);
|
@@ -307,6 +301,9 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
307
301
|
_native_gc_force_recycle, 1);
|
308
302
|
rb_define_singleton_method(testing_module, "_native_has_seen_id_flag",
|
309
303
|
_native_has_seen_id_flag, 1);
|
304
|
+
rb_define_singleton_method(testing_module, "_native_is_object_recorded?", _native_is_object_recorded, 2);
|
305
|
+
rb_define_singleton_method(testing_module, "_native_heap_recorder_reset_last_update", _native_heap_recorder_reset_last_update, 1);
|
306
|
+
rb_define_singleton_method(testing_module, "_native_recorder_after_gc_step", _native_recorder_after_gc_step, 1);
|
310
307
|
|
311
308
|
ok_symbol = ID2SYM(rb_intern_const("ok"));
|
312
309
|
error_symbol = ID2SYM(rb_intern_const("error"));
|
@@ -330,6 +327,8 @@ static VALUE _native_new(VALUE klass) {
|
|
330
327
|
// Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
|
331
328
|
// being leaked.
|
332
329
|
|
330
|
+
state->heap_clean_after_gc_enabled = false;
|
331
|
+
|
333
332
|
ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT};
|
334
333
|
|
335
334
|
initialize_slot_concurrency_control(state);
|
@@ -411,26 +410,33 @@ static void stack_recorder_typed_data_free(void *state_ptr) {
|
|
411
410
|
ruby_xfree(state);
|
412
411
|
}
|
413
412
|
|
414
|
-
static VALUE _native_initialize(
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
VALUE
|
420
|
-
VALUE
|
421
|
-
VALUE
|
422
|
-
VALUE
|
423
|
-
)
|
413
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
414
|
+
VALUE options;
|
415
|
+
rb_scan_args(argc, argv, "0:", &options);
|
416
|
+
if (options == Qnil) options = rb_hash_new();
|
417
|
+
|
418
|
+
VALUE recorder_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
|
419
|
+
VALUE cpu_time_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("cpu_time_enabled")));
|
420
|
+
VALUE alloc_samples_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("alloc_samples_enabled")));
|
421
|
+
VALUE heap_samples_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("heap_samples_enabled")));
|
422
|
+
VALUE heap_size_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("heap_size_enabled")));
|
423
|
+
VALUE heap_sample_every = rb_hash_fetch(options, ID2SYM(rb_intern("heap_sample_every")));
|
424
|
+
VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
|
425
|
+
VALUE heap_clean_after_gc_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("heap_clean_after_gc_enabled")));
|
426
|
+
|
424
427
|
ENFORCE_BOOLEAN(cpu_time_enabled);
|
425
428
|
ENFORCE_BOOLEAN(alloc_samples_enabled);
|
426
429
|
ENFORCE_BOOLEAN(heap_samples_enabled);
|
427
430
|
ENFORCE_BOOLEAN(heap_size_enabled);
|
428
431
|
ENFORCE_TYPE(heap_sample_every, T_FIXNUM);
|
429
432
|
ENFORCE_BOOLEAN(timeline_enabled);
|
433
|
+
ENFORCE_BOOLEAN(heap_clean_after_gc_enabled);
|
430
434
|
|
431
435
|
struct stack_recorder_state *state;
|
432
436
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
433
437
|
|
438
|
+
state->heap_clean_after_gc_enabled = (heap_clean_after_gc_enabled == Qtrue);
|
439
|
+
|
434
440
|
heap_recorder_set_sample_rate(state->heap_recorder, NUM2INT(heap_sample_every));
|
435
441
|
|
436
442
|
uint8_t requested_values_count = ALL_VALUE_TYPES_COUNT -
|
@@ -675,6 +681,13 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
675
681
|
}
|
676
682
|
}
|
677
683
|
|
684
|
+
void recorder_after_gc_step(VALUE recorder_instance) {
|
685
|
+
struct stack_recorder_state *state;
|
686
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
687
|
+
|
688
|
+
if (state->heap_clean_after_gc_enabled) heap_recorder_update_young_objects(state->heap_recorder);
|
689
|
+
}
|
690
|
+
|
678
691
|
#define MAX_LEN_HEAP_ITERATION_ERROR_MSG 256
|
679
692
|
|
680
693
|
// Heap recorder iteration context allows us access to stack recorder state and profile being serialized
|
@@ -1057,3 +1070,26 @@ static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns,
|
|
1057
1070
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
1058
1071
|
return stats_as_hash;
|
1059
1072
|
}
|
1073
|
+
|
1074
|
+
static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE obj_id) {
|
1075
|
+
ENFORCE_TYPE(obj_id, T_FIXNUM);
|
1076
|
+
|
1077
|
+
struct stack_recorder_state *state;
|
1078
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
1079
|
+
|
1080
|
+
return heap_recorder_testonly_is_object_recorded(state->heap_recorder, obj_id);
|
1081
|
+
}
|
1082
|
+
|
1083
|
+
static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
1084
|
+
struct stack_recorder_state *state;
|
1085
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
1086
|
+
|
1087
|
+
heap_recorder_testonly_reset_last_update(state->heap_recorder);
|
1088
|
+
|
1089
|
+
return Qtrue;
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
static VALUE _native_recorder_after_gc_step(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
1093
|
+
recorder_after_gc_step(recorder_instance);
|
1094
|
+
return Qtrue;
|
1095
|
+
}
|
@@ -27,4 +27,5 @@ typedef struct sample_labels {
|
|
27
27
|
void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations, sample_values values, sample_labels labels);
|
28
28
|
void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
|
29
29
|
void track_object(VALUE recorder_instance, VALUE new_object, unsigned int sample_weight, ddog_CharSlice *alloc_class);
|
30
|
+
void recorder_after_gc_step(VALUE recorder_instance);
|
30
31
|
VALUE enforce_recorder_instance(VALUE object);
|
@@ -67,12 +67,10 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
|
|
67
67
|
// The Ruby crash handler also seems to get confused when this option is enabled and
|
68
68
|
// "Process.kill('SEGV', Process.pid)" gets run.
|
69
69
|
.create_alt_stack = false,
|
70
|
+
.use_alt_stack = true, // NOTE: This is a no-op in libdatadog 14.0; should be fixed in a future version
|
70
71
|
.endpoint = endpoint,
|
71
72
|
.resolve_frames = DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER,
|
72
|
-
.
|
73
|
-
// Waits for crash tracker to finish reporting the issue before letting the Ruby process die; see
|
74
|
-
// https://github.com/DataDog/libdatadog/pull/477 for details
|
75
|
-
.wait_for_receiver = true,
|
73
|
+
.timeout_ms = FIX2INT(upload_timeout_seconds) * 1000,
|
76
74
|
};
|
77
75
|
|
78
76
|
ddog_crasht_Metadata metadata = {
|
@@ -97,7 +95,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
|
|
97
95
|
|
98
96
|
ddog_crasht_Result result =
|
99
97
|
action == start_action ?
|
100
|
-
|
98
|
+
ddog_crasht_init(config, receiver_config, metadata) :
|
101
99
|
ddog_crasht_update_on_fork(config, receiver_config, metadata);
|
102
100
|
|
103
101
|
// Clean up before potentially raising any exceptions
|
@@ -8,7 +8,7 @@ module Datadog
|
|
8
8
|
module LibdatadogExtconfHelpers
|
9
9
|
# Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
|
10
10
|
# may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
|
11
|
-
LIBDATADOG_VERSION = '~>
|
11
|
+
LIBDATADOG_VERSION = '~> 14.0.0.1.0'
|
12
12
|
|
13
13
|
# Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
|
14
14
|
# libdatadog are moved after the extension gets compiled.
|
@@ -197,6 +197,14 @@ module Datadog
|
|
197
197
|
o.type :bool, nilable: true
|
198
198
|
o.env 'DD_APPSEC_SCA_ENABLED'
|
199
199
|
end
|
200
|
+
|
201
|
+
settings :standalone do
|
202
|
+
option :enabled do |o|
|
203
|
+
o.type :bool
|
204
|
+
o.env 'DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED'
|
205
|
+
o.default false
|
206
|
+
end
|
207
|
+
end
|
200
208
|
end
|
201
209
|
end
|
202
210
|
end
|
@@ -38,11 +38,7 @@ module Datadog
|
|
38
38
|
actions: result.actions
|
39
39
|
}
|
40
40
|
|
41
|
-
|
42
|
-
scope.service_entry_span.set_tag('appsec.blocked', 'true') if result.actions.include?('block')
|
43
|
-
scope.service_entry_span.set_tag('appsec.event', 'true')
|
44
|
-
end
|
45
|
-
|
41
|
+
Datadog::AppSec::Event.tag_and_keep!(scope, result)
|
46
42
|
scope.processor_context.events << event
|
47
43
|
end
|
48
44
|
|