ddtrace 1.22.0 → 1.23.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +8 -20
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +18 -10
- data/ext/datadog_profiling_native_extension/heap_recorder.c +38 -3
- data/ext/datadog_profiling_native_extension/heap_recorder.h +5 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +156 -55
- data/lib/datadog/appsec/contrib/devise/tracking.rb +8 -0
- data/lib/datadog/core/telemetry/emitter.rb +1 -1
- data/lib/datadog/core/telemetry/http/response.rb +4 -0
- data/lib/datadog/profiling/exporter.rb +6 -3
- data/lib/datadog/profiling/stack_recorder.rb +6 -2
- data/lib/ddtrace/version.rb +2 -2
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1957f7ac42439a5ec9b5c6c1d1700a91b226ccc06def53d0bf3fdd701c55436e
|
4
|
+
data.tar.gz: 3a9ad0d54e59350c3718eb69ee854a756819567b7bd6d4218f0539c87156078e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60ce1f6e6e60cfbfe6f2a3241fc431b9357eefe3866912a82ea672838af323ba6ab441fe50f483a1bc6f26510d500fb00ab62107e770d4c427c6a5952225feac
|
7
|
+
data.tar.gz: 90c4037291c16fa50614509bab19bf45f6283937466e6f9ee6b28fbe7cde73d39ffd14ecc8fb082104aa1d70c7c748ebccff1683dcda772a3665e4f7c94ad619
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,23 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [1.23.1] - 2024-06-06
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
AppSec: Fix undefined method error when Tracing disabled ([#3650][])
|
10
|
+
|
11
|
+
## [1.23.0] - 2024-05-09
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
* Profiling: Enable endpoint profiling for Sidekiq and similar background job processors ([#3619][])
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
|
19
|
+
* Fix no such file or directory issue when using single step instrumentation ([#3623][])
|
20
|
+
* Fix error during telemetry debug logging attempt ([#3618][])
|
21
|
+
|
5
22
|
## [1.22.0] - 2024-04-16
|
6
23
|
|
7
24
|
### Added
|
@@ -2804,6 +2821,8 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
2804
2821
|
|
2805
2822
|
|
2806
2823
|
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...master
|
2824
|
+
[1.23.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.0...v1.23.1
|
2825
|
+
[1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
|
2807
2826
|
[1.22.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v1.22.0
|
2808
2827
|
[2.0.0.beta1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v2.0.0.beta1
|
2809
2828
|
[1.21.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.0...v1.21.1
|
@@ -4111,6 +4130,10 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4111
4130
|
[#3582]: https://github.com/DataDog/dd-trace-rb/issues/3582
|
4112
4131
|
[#3585]: https://github.com/DataDog/dd-trace-rb/issues/3585
|
4113
4132
|
[#3587]: https://github.com/DataDog/dd-trace-rb/issues/3587
|
4133
|
+
[#3618]: https://github.com/DataDog/dd-trace-rb/issues/3618
|
4134
|
+
[#3619]: https://github.com/DataDog/dd-trace-rb/issues/3619
|
4135
|
+
[#3623]: https://github.com/DataDog/dd-trace-rb/issues/3623
|
4136
|
+
[#3650]: https://github.com/DataDog/dd-trace-rb/issues/3650
|
4114
4137
|
[@AdrianLC]: https://github.com/AdrianLC
|
4115
4138
|
[@Azure7111]: https://github.com/Azure7111
|
4116
4139
|
[@BabyGroot]: https://github.com/BabyGroot
|
@@ -929,18 +929,6 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
929
929
|
struct cpu_and_wall_time_worker_state *state;
|
930
930
|
TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
931
931
|
|
932
|
-
VALUE pretty_cpu_sampling_time_ns_min = state->stats.cpu_sampling_time_ns_min == UINT64_MAX ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_min);
|
933
|
-
VALUE pretty_cpu_sampling_time_ns_max = state->stats.cpu_sampling_time_ns_max == 0 ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_max);
|
934
|
-
VALUE pretty_cpu_sampling_time_ns_total = state->stats.cpu_sampling_time_ns_total == 0 ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_total);
|
935
|
-
VALUE pretty_cpu_sampling_time_ns_avg =
|
936
|
-
state->stats.cpu_sampled == 0 ? Qnil : DBL2NUM(((double) state->stats.cpu_sampling_time_ns_total) / state->stats.cpu_sampled);
|
937
|
-
|
938
|
-
VALUE pretty_allocation_sampling_time_ns_min = state->stats.allocation_sampling_time_ns_min == UINT64_MAX ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_min);
|
939
|
-
VALUE pretty_allocation_sampling_time_ns_max = state->stats.allocation_sampling_time_ns_max == 0 ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_max);
|
940
|
-
VALUE pretty_allocation_sampling_time_ns_total = state->stats.allocation_sampling_time_ns_total == 0 ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_total);
|
941
|
-
VALUE pretty_allocation_sampling_time_ns_avg =
|
942
|
-
state->stats.allocation_sampled == 0 ? Qnil : DBL2NUM(((double) state->stats.allocation_sampling_time_ns_total) / state->stats.allocation_sampled);
|
943
|
-
|
944
932
|
unsigned long total_cpu_samples_attempted = state->stats.cpu_sampled + state->stats.cpu_skipped;
|
945
933
|
VALUE effective_cpu_sample_rate =
|
946
934
|
total_cpu_samples_attempted == 0 ? Qnil : DBL2NUM(((double) state->stats.cpu_sampled) / total_cpu_samples_attempted);
|
@@ -968,19 +956,19 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
968
956
|
ID2SYM(rb_intern("cpu_sampled")), /* => */ UINT2NUM(state->stats.cpu_sampled),
|
969
957
|
ID2SYM(rb_intern("cpu_skipped")), /* => */ UINT2NUM(state->stats.cpu_skipped),
|
970
958
|
ID2SYM(rb_intern("cpu_effective_sample_rate")), /* => */ effective_cpu_sample_rate,
|
971
|
-
ID2SYM(rb_intern("cpu_sampling_time_ns_min")), /* => */
|
972
|
-
ID2SYM(rb_intern("cpu_sampling_time_ns_max")), /* => */
|
973
|
-
ID2SYM(rb_intern("cpu_sampling_time_ns_total")), /* => */
|
974
|
-
ID2SYM(rb_intern("cpu_sampling_time_ns_avg")), /* => */
|
959
|
+
ID2SYM(rb_intern("cpu_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
|
960
|
+
ID2SYM(rb_intern("cpu_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_max, > 0, ULL2NUM),
|
961
|
+
ID2SYM(rb_intern("cpu_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_total, > 0, ULL2NUM),
|
962
|
+
ID2SYM(rb_intern("cpu_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.cpu_sampling_time_ns_total, state->stats.cpu_sampled),
|
975
963
|
|
976
964
|
// Allocation stats
|
977
965
|
ID2SYM(rb_intern("allocation_sampled")), /* => */ state->allocation_profiling_enabled ? ULONG2NUM(state->stats.allocation_sampled) : Qnil,
|
978
966
|
ID2SYM(rb_intern("allocation_skipped")), /* => */ state->allocation_profiling_enabled ? ULONG2NUM(state->stats.allocation_skipped) : Qnil,
|
979
967
|
ID2SYM(rb_intern("allocation_effective_sample_rate")), /* => */ effective_allocation_sample_rate,
|
980
|
-
ID2SYM(rb_intern("allocation_sampling_time_ns_min")), /* => */
|
981
|
-
ID2SYM(rb_intern("allocation_sampling_time_ns_max")), /* => */
|
982
|
-
ID2SYM(rb_intern("allocation_sampling_time_ns_total")), /* => */
|
983
|
-
ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */
|
968
|
+
ID2SYM(rb_intern("allocation_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
|
969
|
+
ID2SYM(rb_intern("allocation_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_max, > 0, ULL2NUM),
|
970
|
+
ID2SYM(rb_intern("allocation_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_total, > 0, ULL2NUM),
|
971
|
+
ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.allocation_sampling_time_ns_total, state->stats.allocation_sampled),
|
984
972
|
ID2SYM(rb_intern("allocation_sampler_snapshot")), /* => */ allocation_sampler_snapshot,
|
985
973
|
ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
|
986
974
|
};
|
@@ -217,7 +217,7 @@ static long thread_id_for(VALUE thread);
|
|
217
217
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
218
218
|
static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
|
219
219
|
static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result);
|
220
|
-
static bool should_collect_resource(VALUE
|
220
|
+
static bool should_collect_resource(VALUE root_span);
|
221
221
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
222
222
|
static VALUE thread_list(struct thread_context_collector_state *state);
|
223
223
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
|
@@ -1146,10 +1146,7 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
|
|
1146
1146
|
|
1147
1147
|
trace_identifiers_result->valid = true;
|
1148
1148
|
|
1149
|
-
if (!state->endpoint_collection_enabled) return;
|
1150
|
-
|
1151
|
-
VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
|
1152
|
-
if (root_span_type == Qnil || !should_collect_resource(root_span_type)) return;
|
1149
|
+
if (!state->endpoint_collection_enabled || !should_collect_resource(root_span)) return;
|
1153
1150
|
|
1154
1151
|
VALUE trace_resource = rb_ivar_get(active_trace, at_resource_id /* @resource */);
|
1155
1152
|
if (RB_TYPE_P(trace_resource, T_STRING)) {
|
@@ -1160,21 +1157,32 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
|
|
1160
1157
|
}
|
1161
1158
|
}
|
1162
1159
|
|
1163
|
-
// We
|
1160
|
+
// We opt-in to collecting the resource for spans of types:
|
1164
1161
|
// * 'web', for web requests
|
1165
|
-
// * proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
|
1162
|
+
// * 'proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
|
1163
|
+
// * 'worker', used for sidekiq and similar background job processors
|
1166
1164
|
//
|
1167
|
-
//
|
1165
|
+
// Over time, this list may be expanded.
|
1168
1166
|
// Resources MUST NOT include personal identifiable information (PII); this should not be the case with
|
1169
1167
|
// ddtrace integrations, but worth mentioning just in case :)
|
1170
|
-
static bool should_collect_resource(VALUE
|
1168
|
+
static bool should_collect_resource(VALUE root_span) {
|
1169
|
+
VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
|
1170
|
+
if (root_span_type == Qnil) return false;
|
1171
1171
|
ENFORCE_TYPE(root_span_type, T_STRING);
|
1172
1172
|
|
1173
1173
|
int root_span_type_length = RSTRING_LEN(root_span_type);
|
1174
1174
|
const char *root_span_type_value = StringValuePtr(root_span_type);
|
1175
1175
|
|
1176
|
-
|
1176
|
+
bool is_web_request =
|
1177
|
+
(root_span_type_length == strlen("web") && (memcmp("web", root_span_type_value, strlen("web")) == 0)) ||
|
1177
1178
|
(root_span_type_length == strlen("proxy") && (memcmp("proxy", root_span_type_value, strlen("proxy")) == 0));
|
1179
|
+
|
1180
|
+
if (is_web_request) return true;
|
1181
|
+
|
1182
|
+
bool is_worker_request =
|
1183
|
+
(root_span_type_length == strlen("worker") && (memcmp("worker", root_span_type_value, strlen("worker")) == 0));
|
1184
|
+
|
1185
|
+
return is_worker_request;
|
1178
1186
|
}
|
1179
1187
|
|
1180
1188
|
// After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
|
@@ -158,6 +158,13 @@ struct heap_recorder {
|
|
158
158
|
|
159
159
|
// Sampling state
|
160
160
|
uint num_recordings_skipped;
|
161
|
+
|
162
|
+
struct stats_last_update {
|
163
|
+
size_t objects_alive;
|
164
|
+
size_t objects_dead;
|
165
|
+
size_t objects_skipped;
|
166
|
+
size_t objects_frozen;
|
167
|
+
} stats_last_update;
|
161
168
|
};
|
162
169
|
static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
|
163
170
|
static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
|
@@ -372,6 +379,9 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
|
|
372
379
|
rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
|
373
380
|
}
|
374
381
|
|
382
|
+
// Reset last update stats, we'll be building them from scratch during the st_foreach call below
|
383
|
+
heap_recorder->stats_last_update = (struct stats_last_update) {};
|
384
|
+
|
375
385
|
st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
|
376
386
|
|
377
387
|
heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
|
@@ -427,6 +437,22 @@ bool heap_recorder_for_each_live_object(
|
|
427
437
|
return true;
|
428
438
|
}
|
429
439
|
|
440
|
+
VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
|
441
|
+
VALUE arguments[] = {
|
442
|
+
ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
|
443
|
+
ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
|
444
|
+
|
445
|
+
// Stats as of last update
|
446
|
+
ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
|
447
|
+
ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
|
448
|
+
ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
|
449
|
+
ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
|
450
|
+
};
|
451
|
+
VALUE hash = rb_hash_new();
|
452
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
453
|
+
return hash;
|
454
|
+
}
|
455
|
+
|
430
456
|
void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations) {
|
431
457
|
heap_stack *stack = heap_stack_new(locations);
|
432
458
|
heap_record_key stack_based_key = (heap_record_key) {
|
@@ -497,12 +523,14 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
497
523
|
// no point checking for liveness or updating its size, so exit early.
|
498
524
|
// NOTE: This means that there should be an equivalent check during actual
|
499
525
|
// iteration otherwise we'd iterate/expose stale object data.
|
526
|
+
recorder->stats_last_update.objects_skipped++;
|
500
527
|
return ST_CONTINUE;
|
501
528
|
}
|
502
529
|
|
503
530
|
if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
|
504
531
|
// Id no longer associated with a valid ref. Need to delete this object record!
|
505
532
|
on_committed_object_record_cleanup(recorder, record);
|
533
|
+
recorder->stats_last_update.objects_dead++;
|
506
534
|
return ST_DELETE;
|
507
535
|
}
|
508
536
|
|
@@ -537,6 +565,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
537
565
|
RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
|
538
566
|
|
539
567
|
on_committed_object_record_cleanup(recorder, record);
|
568
|
+
recorder->stats_last_update.objects_dead++;
|
540
569
|
return ST_DELETE;
|
541
570
|
}
|
542
571
|
|
@@ -550,6 +579,11 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
550
579
|
record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
|
551
580
|
}
|
552
581
|
|
582
|
+
recorder->stats_last_update.objects_alive++;
|
583
|
+
if (record->object_data.is_frozen) {
|
584
|
+
recorder->stats_last_update.objects_frozen++;
|
585
|
+
}
|
586
|
+
|
553
587
|
return ST_CONTINUE;
|
554
588
|
}
|
555
589
|
|
@@ -767,9 +801,10 @@ void object_record_free(object_record *record) {
|
|
767
801
|
|
768
802
|
VALUE object_record_inspect(object_record *record) {
|
769
803
|
heap_frame top_frame = record->heap_record->stack->frames[0];
|
770
|
-
|
771
|
-
|
772
|
-
|
804
|
+
live_object_data object_data = record->object_data;
|
805
|
+
VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu frozen=%d ",
|
806
|
+
record->obj_id, object_data.weight, object_data.size, top_frame.filename,
|
807
|
+
(int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
|
773
808
|
|
774
809
|
const char *class = record->object_data.class;
|
775
810
|
if (class != NULL) {
|
@@ -150,6 +150,11 @@ bool heap_recorder_for_each_live_object(
|
|
150
150
|
bool (*for_each_callback)(heap_recorder_iteration_data data, void* extra_arg),
|
151
151
|
void *for_each_callback_extra_arg);
|
152
152
|
|
153
|
+
// Return a Ruby hash containing a snapshot of this recorder's interesting state at calling time.
|
154
|
+
// WARN: This allocates in the Ruby VM and therefore should not be called without the
|
155
|
+
// VM lock or during GC.
|
156
|
+
VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder);
|
157
|
+
|
153
158
|
// v--- TEST-ONLY APIs ---v
|
154
159
|
|
155
160
|
// Assert internal hashing logic is valid for the provided locations and its
|
@@ -82,6 +82,9 @@ NORETURN(
|
|
82
82
|
#define ENFORCE_SUCCESS_HELPER(expression, have_gvl) \
|
83
83
|
{ int result_syserr_errno = expression; if (RB_UNLIKELY(result_syserr_errno)) raise_syserr(result_syserr_errno, have_gvl, ADD_QUOTES(expression), __FILE__, __LINE__, __func__); }
|
84
84
|
|
85
|
+
#define RUBY_NUM_OR_NIL(val, condition, conv) ((val condition) ? conv(val) : Qnil)
|
86
|
+
#define RUBY_AVG_OR_NIL(total, count) ((count == 0) ? Qnil : DBL2NUM(((double) total) / count))
|
87
|
+
|
85
88
|
// Called by ENFORCE_SUCCESS_HELPER; should not be used directly
|
86
89
|
NORETURN(void raise_syserr(
|
87
90
|
int syserr_errno,
|
@@ -169,28 +169,51 @@ static const uint8_t all_value_types_positions[] =
|
|
169
169
|
|
170
170
|
#define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(ddog_prof_ValueType))
|
171
171
|
|
172
|
+
// Struct for storing stats related to a profile in a particular slot.
|
173
|
+
// These stats will share the same lifetime as the data in that profile slot.
|
174
|
+
typedef struct slot_stats {
|
175
|
+
// How many individual samples were recorded into this slot (un-weighted)
|
176
|
+
uint64_t recorded_samples;
|
177
|
+
} stats_slot;
|
178
|
+
|
179
|
+
typedef struct profile_slot {
|
180
|
+
ddog_prof_Profile profile;
|
181
|
+
stats_slot stats;
|
182
|
+
} profile_slot;
|
183
|
+
|
172
184
|
// Contains native state for each instance
|
173
185
|
struct stack_recorder_state {
|
174
186
|
// Heap recorder instance
|
175
187
|
heap_recorder *heap_recorder;
|
176
188
|
|
177
|
-
pthread_mutex_t
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
ddog_prof_Profile slot_two_profile;
|
189
|
+
pthread_mutex_t mutex_slot_one;
|
190
|
+
profile_slot profile_slot_one;
|
191
|
+
pthread_mutex_t mutex_slot_two;
|
192
|
+
profile_slot profile_slot_two;
|
182
193
|
|
183
194
|
short active_slot; // MUST NEVER BE ACCESSED FROM record_sample; this is NOT for the sampler thread to use.
|
184
195
|
|
185
196
|
uint8_t position_for[ALL_VALUE_TYPES_COUNT];
|
186
197
|
uint8_t enabled_values_count;
|
198
|
+
|
199
|
+
// Struct for storing stats related to behaviour of a stack recorder instance during its entire lifetime.
|
200
|
+
struct lifetime_stats {
|
201
|
+
// How many profiles have we serialized successfully so far
|
202
|
+
uint64_t serialization_successes;
|
203
|
+
// How many profiles have we serialized unsuccessfully so far
|
204
|
+
uint64_t serialization_failures;
|
205
|
+
// Stats on profile serialization time
|
206
|
+
long serialization_time_ns_min;
|
207
|
+
long serialization_time_ns_max;
|
208
|
+
uint64_t serialization_time_ns_total;
|
209
|
+
} stats_lifetime;
|
187
210
|
};
|
188
211
|
|
189
|
-
// Used to
|
190
|
-
struct
|
212
|
+
// Used to group mutex and the corresponding profile slot for easy unlocking after work is done.
|
213
|
+
typedef struct locked_profile_slot {
|
191
214
|
pthread_mutex_t *mutex;
|
192
|
-
|
193
|
-
};
|
215
|
+
profile_slot *data;
|
216
|
+
} locked_profile_slot;
|
194
217
|
|
195
218
|
struct call_serialize_without_gvl_arguments {
|
196
219
|
// Set by caller
|
@@ -198,8 +221,10 @@ struct call_serialize_without_gvl_arguments {
|
|
198
221
|
ddog_Timespec finish_timestamp;
|
199
222
|
|
200
223
|
// Set by callee
|
201
|
-
|
224
|
+
profile_slot *slot;
|
202
225
|
ddog_prof_Profile_SerializeResult result;
|
226
|
+
long heap_profile_build_time_ns;
|
227
|
+
long serialize_no_gvl_time_ns;
|
203
228
|
|
204
229
|
// Set by both
|
205
230
|
bool serialize_ran;
|
@@ -222,9 +247,9 @@ static VALUE _native_initialize(
|
|
222
247
|
static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
|
223
248
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time);
|
224
249
|
static void *call_serialize_without_gvl(void *call_args);
|
225
|
-
static
|
226
|
-
static void sampler_unlock_active_profile(
|
227
|
-
static
|
250
|
+
static locked_profile_slot sampler_lock_active_profile(struct stack_recorder_state *state);
|
251
|
+
static void sampler_unlock_active_profile(locked_profile_slot active_slot);
|
252
|
+
static profile_slot* serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state);
|
228
253
|
static VALUE _native_active_slot(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
229
254
|
static VALUE _native_is_slot_one_mutex_locked(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
230
255
|
static VALUE _native_is_slot_two_mutex_locked(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
@@ -233,7 +258,7 @@ static ddog_Timespec system_epoch_now_timespec(void);
|
|
233
258
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_instance);
|
234
259
|
static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec start_time);
|
235
260
|
static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint);
|
236
|
-
static void
|
261
|
+
static void reset_profile_slot(profile_slot *slot, ddog_Timespec *start_time /* Can be null */);
|
237
262
|
static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class);
|
238
263
|
static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locations);
|
239
264
|
static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
@@ -241,6 +266,8 @@ static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self
|
|
241
266
|
static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
242
267
|
static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
243
268
|
static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj);
|
269
|
+
static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance);
|
270
|
+
static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns);
|
244
271
|
|
245
272
|
|
246
273
|
void stack_recorder_init(VALUE profiling_module) {
|
@@ -261,6 +288,7 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
261
288
|
rb_define_singleton_method(stack_recorder_class, "_native_initialize", _native_initialize, 7);
|
262
289
|
rb_define_singleton_method(stack_recorder_class, "_native_serialize", _native_serialize, 1);
|
263
290
|
rb_define_singleton_method(stack_recorder_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
291
|
+
rb_define_singleton_method(stack_recorder_class, "_native_stats", _native_stats, 1);
|
264
292
|
rb_define_singleton_method(testing_module, "_native_active_slot", _native_active_slot, 1);
|
265
293
|
rb_define_singleton_method(testing_module, "_native_slot_one_mutex_locked?", _native_is_slot_one_mutex_locked, 1);
|
266
294
|
rb_define_singleton_method(testing_module, "_native_slot_two_mutex_locked?", _native_is_slot_two_mutex_locked, 1);
|
@@ -305,6 +333,9 @@ static VALUE _native_new(VALUE klass) {
|
|
305
333
|
initialize_slot_concurrency_control(state);
|
306
334
|
for (uint8_t i = 0; i < ALL_VALUE_TYPES_COUNT; i++) { state->position_for[i] = all_value_types_positions[i]; }
|
307
335
|
state->enabled_values_count = ALL_VALUE_TYPES_COUNT;
|
336
|
+
state->stats_lifetime = (struct lifetime_stats) {
|
337
|
+
.serialization_time_ns_min = INT64_MAX,
|
338
|
+
};
|
308
339
|
|
309
340
|
// Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
|
310
341
|
// before using them so it's ok for us to go ahead and create the StackRecorder object.
|
@@ -325,11 +356,11 @@ static VALUE _native_new(VALUE klass) {
|
|
325
356
|
}
|
326
357
|
|
327
358
|
static void initialize_slot_concurrency_control(struct stack_recorder_state *state) {
|
328
|
-
state->
|
329
|
-
state->
|
359
|
+
state->mutex_slot_one = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
|
360
|
+
state->mutex_slot_two = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
|
330
361
|
|
331
362
|
// A newly-created StackRecorder starts with slot one being active for samples, so let's lock slot two
|
332
|
-
ENFORCE_SUCCESS_GVL(pthread_mutex_lock(&state->
|
363
|
+
ENFORCE_SUCCESS_GVL(pthread_mutex_lock(&state->mutex_slot_two));
|
333
364
|
|
334
365
|
state->active_slot = 1;
|
335
366
|
}
|
@@ -352,18 +383,22 @@ static void initialize_profiles(struct stack_recorder_state *state, ddog_prof_Sl
|
|
352
383
|
rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err));
|
353
384
|
}
|
354
385
|
|
355
|
-
state->
|
356
|
-
|
386
|
+
state->profile_slot_one = (profile_slot) {
|
387
|
+
.profile = slot_one_profile_result.ok,
|
388
|
+
};
|
389
|
+
state->profile_slot_two = (profile_slot) {
|
390
|
+
.profile = slot_two_profile_result.ok,
|
391
|
+
};
|
357
392
|
}
|
358
393
|
|
359
394
|
static void stack_recorder_typed_data_free(void *state_ptr) {
|
360
395
|
struct stack_recorder_state *state = (struct stack_recorder_state *) state_ptr;
|
361
396
|
|
362
|
-
pthread_mutex_destroy(&state->
|
363
|
-
ddog_prof_Profile_drop(&state->
|
397
|
+
pthread_mutex_destroy(&state->mutex_slot_one);
|
398
|
+
ddog_prof_Profile_drop(&state->profile_slot_one.profile);
|
364
399
|
|
365
|
-
pthread_mutex_destroy(&state->
|
366
|
-
ddog_prof_Profile_drop(&state->
|
400
|
+
pthread_mutex_destroy(&state->mutex_slot_two);
|
401
|
+
ddog_prof_Profile_drop(&state->profile_slot_two.profile);
|
367
402
|
|
368
403
|
heap_recorder_free(state->heap_recorder);
|
369
404
|
|
@@ -462,8 +497,8 @@ static VALUE _native_initialize(
|
|
462
497
|
state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++;
|
463
498
|
}
|
464
499
|
|
465
|
-
ddog_prof_Profile_drop(&state->
|
466
|
-
ddog_prof_Profile_drop(&state->
|
500
|
+
ddog_prof_Profile_drop(&state->profile_slot_one.profile);
|
501
|
+
ddog_prof_Profile_drop(&state->profile_slot_two.profile);
|
467
502
|
|
468
503
|
ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count};
|
469
504
|
initialize_profiles(state, sample_types);
|
@@ -479,9 +514,11 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
479
514
|
// Need to do this while still holding on to the Global VM Lock; see comments on method for why
|
480
515
|
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
481
516
|
|
517
|
+
long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
482
518
|
// Prepare the iteration on heap recorder we'll be doing outside the GVL. The preparation needs to
|
483
519
|
// happen while holding on to the GVL.
|
484
520
|
heap_recorder_prepare_iteration(state->heap_recorder);
|
521
|
+
long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - heap_iteration_prep_start_time_ns;
|
485
522
|
|
486
523
|
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
|
487
524
|
// is pending
|
@@ -508,12 +545,27 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
508
545
|
// Cleanup after heap recorder iteration. This needs to happen while holding on to the GVL.
|
509
546
|
heap_recorder_finish_iteration(state->heap_recorder);
|
510
547
|
|
548
|
+
// NOTE: We are focusing on the serialization time outside of the GVL in this stat here. This doesn't
|
549
|
+
// really cover the full serialization process but it gives a more useful number since it bypasses
|
550
|
+
// the noise of acquiring GVLs and dealing with interruptions which is highly specific to runtime
|
551
|
+
// conditions and over which we really have no control about.
|
552
|
+
long serialization_time_ns = args.serialize_no_gvl_time_ns;
|
553
|
+
if (serialization_time_ns >= 0) {
|
554
|
+
// Only update stats if our serialization time is valid.
|
555
|
+
state->stats_lifetime.serialization_time_ns_max = long_max_of(state->stats_lifetime.serialization_time_ns_max, serialization_time_ns);
|
556
|
+
state->stats_lifetime.serialization_time_ns_min = long_min_of(state->stats_lifetime.serialization_time_ns_min, serialization_time_ns);
|
557
|
+
state->stats_lifetime.serialization_time_ns_total += serialization_time_ns;
|
558
|
+
}
|
559
|
+
|
511
560
|
ddog_prof_Profile_SerializeResult serialized_profile = args.result;
|
512
561
|
|
513
562
|
if (serialized_profile.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
|
563
|
+
state->stats_lifetime.serialization_failures++;
|
514
564
|
return rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&serialized_profile.err));
|
515
565
|
}
|
516
566
|
|
567
|
+
state->stats_lifetime.serialization_successes++;
|
568
|
+
|
517
569
|
VALUE encoded_pprof = ruby_string_from_vec_u8(serialized_profile.ok.buffer);
|
518
570
|
|
519
571
|
ddog_Timespec ddprof_start = serialized_profile.ok.start;
|
@@ -523,8 +575,9 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
523
575
|
|
524
576
|
VALUE start = ruby_time_from(ddprof_start);
|
525
577
|
VALUE finish = ruby_time_from(ddprof_finish);
|
578
|
+
VALUE profile_stats = build_profile_stats(args.slot, serialization_time_ns, heap_iteration_prep_time_ns, args.heap_profile_build_time_ns);
|
526
579
|
|
527
|
-
return rb_ary_new_from_args(2, ok_symbol, rb_ary_new_from_args(
|
580
|
+
return rb_ary_new_from_args(2, ok_symbol, rb_ary_new_from_args(4, start, finish, encoded_pprof, profile_stats));
|
528
581
|
}
|
529
582
|
|
530
583
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time) {
|
@@ -537,7 +590,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
537
590
|
struct stack_recorder_state *state;
|
538
591
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
539
592
|
|
540
|
-
|
593
|
+
locked_profile_slot active_slot = sampler_lock_active_profile(state);
|
541
594
|
|
542
595
|
// Note: We initialize this array to have ALL_VALUE_TYPES_COUNT but only tell libdatadog to use the first
|
543
596
|
// state->enabled_values_count values. This simplifies handling disabled value types -- we still put them on the
|
@@ -561,7 +614,7 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
561
614
|
}
|
562
615
|
|
563
616
|
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
564
|
-
active_slot.profile,
|
617
|
+
&active_slot.data->profile,
|
565
618
|
(ddog_prof_Sample) {
|
566
619
|
.locations = locations,
|
567
620
|
.values = (ddog_Slice_I64) {.ptr = metric_values, .len = state->enabled_values_count},
|
@@ -570,6 +623,8 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
570
623
|
labels.end_timestamp_ns
|
571
624
|
);
|
572
625
|
|
626
|
+
active_slot.data->stats.recorded_samples++;
|
627
|
+
|
573
628
|
sampler_unlock_active_profile(active_slot);
|
574
629
|
|
575
630
|
if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
@@ -590,9 +645,9 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
590
645
|
struct stack_recorder_state *state;
|
591
646
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
592
647
|
|
593
|
-
|
648
|
+
locked_profile_slot active_slot = sampler_lock_active_profile(state);
|
594
649
|
|
595
|
-
ddog_prof_Profile_Result result = ddog_prof_Profile_set_endpoint(active_slot.profile, local_root_span_id, endpoint);
|
650
|
+
ddog_prof_Profile_Result result = ddog_prof_Profile_set_endpoint(&active_slot.data->profile, local_root_span_id, endpoint);
|
596
651
|
|
597
652
|
sampler_unlock_active_profile(active_slot);
|
598
653
|
|
@@ -607,7 +662,7 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
607
662
|
// during iteration of heap recorder live objects.
|
608
663
|
typedef struct heap_recorder_iteration_context {
|
609
664
|
struct stack_recorder_state *state;
|
610
|
-
|
665
|
+
profile_slot *slot;
|
611
666
|
|
612
667
|
bool error;
|
613
668
|
char error_msg[MAX_LEN_HEAP_ITERATION_ERROR_MSG];
|
@@ -643,7 +698,7 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
|
|
643
698
|
};
|
644
699
|
|
645
700
|
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
646
|
-
context->profile,
|
701
|
+
&context->slot->profile,
|
647
702
|
(ddog_prof_Sample) {
|
648
703
|
.locations = iteration_data.locations,
|
649
704
|
.values = (ddog_Slice_I64) {.ptr = metric_values, .len = context->state->enabled_values_count},
|
@@ -655,6 +710,8 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
|
|
655
710
|
0
|
656
711
|
);
|
657
712
|
|
713
|
+
context->slot->stats.recorded_samples++;
|
714
|
+
|
658
715
|
if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
659
716
|
read_ddogerr_string_and_drop(&result.err, context->error_msg, MAX_LEN_HEAP_ITERATION_ERROR_MSG);
|
660
717
|
context->error = true;
|
@@ -666,10 +723,10 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
|
|
666
723
|
return true;
|
667
724
|
}
|
668
725
|
|
669
|
-
static void build_heap_profile_without_gvl(struct stack_recorder_state *state,
|
726
|
+
static void build_heap_profile_without_gvl(struct stack_recorder_state *state, profile_slot *slot) {
|
670
727
|
heap_recorder_iteration_context iteration_context = {
|
671
728
|
.state = state,
|
672
|
-
.
|
729
|
+
.slot = slot,
|
673
730
|
.error = false,
|
674
731
|
.error_msg = {0},
|
675
732
|
};
|
@@ -689,15 +746,21 @@ static void build_heap_profile_without_gvl(struct stack_recorder_state *state, d
|
|
689
746
|
static void *call_serialize_without_gvl(void *call_args) {
|
690
747
|
struct call_serialize_without_gvl_arguments *args = (struct call_serialize_without_gvl_arguments *) call_args;
|
691
748
|
|
692
|
-
|
749
|
+
long serialize_no_gvl_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
750
|
+
|
751
|
+
profile_slot *slot_now_inactive = serializer_flip_active_and_inactive_slots(args->state);
|
752
|
+
|
753
|
+
args->slot = slot_now_inactive;
|
693
754
|
|
694
755
|
// Now that we have the inactive profile with all but heap samples, lets fill it with heap data
|
695
756
|
// without needing to race with the active sampler
|
696
|
-
build_heap_profile_without_gvl(args->state, args->
|
757
|
+
build_heap_profile_without_gvl(args->state, args->slot);
|
758
|
+
args->heap_profile_build_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns;
|
697
759
|
|
698
760
|
// Note: The profile gets reset by the serialize call
|
699
|
-
args->result = ddog_prof_Profile_serialize(args->profile, &args->finish_timestamp, NULL /* duration_nanos is optional */, NULL /* start_time is optional */);
|
761
|
+
args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->finish_timestamp, NULL /* duration_nanos is optional */, NULL /* start_time is optional */);
|
700
762
|
args->serialize_ran = true;
|
763
|
+
args->serialize_no_gvl_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns;
|
701
764
|
|
702
765
|
return NULL; // Unused
|
703
766
|
}
|
@@ -707,42 +770,42 @@ VALUE enforce_recorder_instance(VALUE object) {
|
|
707
770
|
return object;
|
708
771
|
}
|
709
772
|
|
710
|
-
static
|
773
|
+
static locked_profile_slot sampler_lock_active_profile(struct stack_recorder_state *state) {
|
711
774
|
int error;
|
712
775
|
|
713
776
|
for (int attempts = 0; attempts < 2; attempts++) {
|
714
|
-
error = pthread_mutex_trylock(&state->
|
777
|
+
error = pthread_mutex_trylock(&state->mutex_slot_one);
|
715
778
|
if (error && error != EBUSY) ENFORCE_SUCCESS_GVL(error);
|
716
779
|
|
717
780
|
// Slot one is active
|
718
|
-
if (!error) return (
|
781
|
+
if (!error) return (locked_profile_slot) {.mutex = &state->mutex_slot_one, .data = &state->profile_slot_one};
|
719
782
|
|
720
783
|
// If we got here, slot one was not active, let's try slot two
|
721
784
|
|
722
|
-
error = pthread_mutex_trylock(&state->
|
785
|
+
error = pthread_mutex_trylock(&state->mutex_slot_two);
|
723
786
|
if (error && error != EBUSY) ENFORCE_SUCCESS_GVL(error);
|
724
787
|
|
725
788
|
// Slot two is active
|
726
|
-
if (!error) return (
|
789
|
+
if (!error) return (locked_profile_slot) {.mutex = &state->mutex_slot_two, .data = &state->profile_slot_two};
|
727
790
|
}
|
728
791
|
|
729
792
|
// We already tried both multiple times, and we did not succeed. This is not expected to happen. Let's stop sampling.
|
730
793
|
rb_raise(rb_eRuntimeError, "Failed to grab either mutex in sampler_lock_active_profile");
|
731
794
|
}
|
732
795
|
|
733
|
-
static void sampler_unlock_active_profile(
|
796
|
+
static void sampler_unlock_active_profile(locked_profile_slot active_slot) {
|
734
797
|
ENFORCE_SUCCESS_GVL(pthread_mutex_unlock(active_slot.mutex));
|
735
798
|
}
|
736
799
|
|
737
|
-
static
|
800
|
+
static profile_slot* serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state) {
|
738
801
|
int previously_active_slot = state->active_slot;
|
739
802
|
|
740
803
|
if (previously_active_slot != 1 && previously_active_slot != 2) {
|
741
804
|
grab_gvl_and_raise(rb_eRuntimeError, "Unexpected active_slot state %d in serializer_flip_active_and_inactive_slots", previously_active_slot);
|
742
805
|
}
|
743
806
|
|
744
|
-
pthread_mutex_t *previously_active = (previously_active_slot == 1) ? &state->
|
745
|
-
pthread_mutex_t *previously_inactive = (previously_active_slot == 1) ? &state->
|
807
|
+
pthread_mutex_t *previously_active = (previously_active_slot == 1) ? &state->mutex_slot_one : &state->mutex_slot_two;
|
808
|
+
pthread_mutex_t *previously_inactive = (previously_active_slot == 1) ? &state->mutex_slot_two : &state->mutex_slot_one;
|
746
809
|
|
747
810
|
// Release the lock, thus making this slot active
|
748
811
|
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_unlock(previously_inactive));
|
@@ -753,8 +816,8 @@ static ddog_prof_Profile *serializer_flip_active_and_inactive_slots(struct stack
|
|
753
816
|
// Update active_slot
|
754
817
|
state->active_slot = (previously_active_slot == 1) ? 2 : 1;
|
755
818
|
|
756
|
-
// Return
|
757
|
-
return (previously_active_slot == 1) ? &state->
|
819
|
+
// Return pointer to previously active slot (now inactive)
|
820
|
+
return (previously_active_slot == 1) ? &state->profile_slot_one : &state->profile_slot_two;
|
758
821
|
}
|
759
822
|
|
760
823
|
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
@@ -778,7 +841,7 @@ static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot) {
|
|
778
841
|
struct stack_recorder_state *state;
|
779
842
|
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
780
843
|
|
781
|
-
pthread_mutex_t *slot_mutex = (slot == 1) ? &state->
|
844
|
+
pthread_mutex_t *slot_mutex = (slot == 1) ? &state->mutex_slot_one : &state->mutex_slot_two;
|
782
845
|
|
783
846
|
// Like Heisenberg's uncertainty principle, we can't observe without affecting...
|
784
847
|
int error = pthread_mutex_trylock(slot_mutex);
|
@@ -813,8 +876,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
813
876
|
// resulting state is inconsistent, we make sure to reset it back to the initial state.
|
814
877
|
initialize_slot_concurrency_control(state);
|
815
878
|
|
816
|
-
|
817
|
-
|
879
|
+
reset_profile_slot(&state->profile_slot_one, /* start_time: */ NULL);
|
880
|
+
reset_profile_slot(&state->profile_slot_two, /* start_time: */ NULL);
|
818
881
|
|
819
882
|
heap_recorder_after_fork(state->heap_recorder);
|
820
883
|
|
@@ -825,8 +888,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
825
888
|
// not be interrupted part-way through by a VM fork.
|
826
889
|
static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec start_time) {
|
827
890
|
// Before making this profile active, we reset it so that it uses the correct start_time for its start
|
828
|
-
|
829
|
-
|
891
|
+
profile_slot *next_profile_slot = (state->active_slot == 1) ? &state->profile_slot_two : &state->profile_slot_one;
|
892
|
+
reset_profile_slot(next_profile_slot, &start_time);
|
830
893
|
}
|
831
894
|
|
832
895
|
static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint) {
|
@@ -872,11 +935,12 @@ static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locatio
|
|
872
935
|
return Qnil;
|
873
936
|
}
|
874
937
|
|
875
|
-
static void
|
876
|
-
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(profile, start_time);
|
938
|
+
static void reset_profile_slot(profile_slot *slot, ddog_Timespec *start_time /* Can be null */) {
|
939
|
+
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(&slot->profile, start_time);
|
877
940
|
if (reset_result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
878
941
|
rb_raise(rb_eRuntimeError, "Failed to reset profile: %"PRIsVALUE, get_error_details_and_drop(&reset_result.err));
|
879
942
|
}
|
943
|
+
slot->stats = (stats_slot) {};
|
880
944
|
}
|
881
945
|
|
882
946
|
// This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
|
@@ -937,3 +1001,40 @@ static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj) {
|
|
937
1001
|
return Qfalse;
|
938
1002
|
#endif
|
939
1003
|
}
|
1004
|
+
|
1005
|
+
static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE recorder_instance) {
|
1006
|
+
struct stack_recorder_state *state;
|
1007
|
+
TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
|
1008
|
+
|
1009
|
+
uint64_t total_serializations = state->stats_lifetime.serialization_successes + state->stats_lifetime.serialization_failures;
|
1010
|
+
|
1011
|
+
VALUE heap_recorder_snapshot = state->heap_recorder ?
|
1012
|
+
heap_recorder_state_snapshot(state->heap_recorder) : Qnil;
|
1013
|
+
|
1014
|
+
VALUE stats_as_hash = rb_hash_new();
|
1015
|
+
VALUE arguments[] = {
|
1016
|
+
ID2SYM(rb_intern("serialization_successes")), /* => */ ULL2NUM(state->stats_lifetime.serialization_successes),
|
1017
|
+
ID2SYM(rb_intern("serialization_failures")), /* => */ ULL2NUM(state->stats_lifetime.serialization_failures),
|
1018
|
+
|
1019
|
+
ID2SYM(rb_intern("serialization_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats_lifetime.serialization_time_ns_min, != INT64_MAX, LONG2NUM),
|
1020
|
+
ID2SYM(rb_intern("serialization_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats_lifetime.serialization_time_ns_max, > 0, LONG2NUM),
|
1021
|
+
ID2SYM(rb_intern("serialization_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats_lifetime.serialization_time_ns_total, > 0, LONG2NUM),
|
1022
|
+
ID2SYM(rb_intern("serialization_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats_lifetime.serialization_time_ns_total, total_serializations),
|
1023
|
+
|
1024
|
+
ID2SYM(rb_intern("heap_recorder_snapshot")), /* => */ heap_recorder_snapshot,
|
1025
|
+
};
|
1026
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
1027
|
+
return stats_as_hash;
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns) {
|
1031
|
+
VALUE stats_as_hash = rb_hash_new();
|
1032
|
+
VALUE arguments[] = {
|
1033
|
+
ID2SYM(rb_intern("recorded_samples")), /* => */ ULL2NUM(slot->stats.recorded_samples),
|
1034
|
+
ID2SYM(rb_intern("serialization_time_ns")), /* => */ LONG2NUM(serialization_time_ns),
|
1035
|
+
ID2SYM(rb_intern("heap_iteration_prep_time_ns")), /* => */ LONG2NUM(heap_iteration_prep_time_ns),
|
1036
|
+
ID2SYM(rb_intern("heap_profile_build_time_ns")), /* => */ LONG2NUM(heap_profile_build_time_ns),
|
1037
|
+
};
|
1038
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
1039
|
+
return stats_as_hash;
|
1040
|
+
}
|
@@ -13,12 +13,16 @@ module Datadog
|
|
13
13
|
SIGNUP_EVENT = 'users.signup'
|
14
14
|
|
15
15
|
def self.track_login_success(trace, span, user_id:, **others)
|
16
|
+
return if trace.nil? || span.nil?
|
17
|
+
|
16
18
|
track(LOGIN_SUCCESS_EVENT, trace, span, **others)
|
17
19
|
|
18
20
|
Kit::Identity.set_user(trace, span, id: user_id.to_s, **others) if user_id
|
19
21
|
end
|
20
22
|
|
21
23
|
def self.track_login_failure(trace, span, user_id:, user_exists:, **others)
|
24
|
+
return if trace.nil? || span.nil?
|
25
|
+
|
22
26
|
track(LOGIN_FAILURE_EVENT, trace, span, **others)
|
23
27
|
|
24
28
|
span.set_tag('appsec.events.users.login.failure.usr.id', user_id) if user_id
|
@@ -26,11 +30,15 @@ module Datadog
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def self.track_signup(trace, span, user_id:, **others)
|
33
|
+
return if trace.nil? || span.nil?
|
34
|
+
|
29
35
|
track(SIGNUP_EVENT, trace, span, **others)
|
30
36
|
Kit::Identity.set_user(trace, id: user_id.to_s, **others) if user_id
|
31
37
|
end
|
32
38
|
|
33
39
|
def self.track(event, trace, span, **others)
|
40
|
+
return if trace.nil? || span.nil?
|
41
|
+
|
34
42
|
span.set_tag("appsec.events.#{event}.track", 'true')
|
35
43
|
span.set_tag("_dd.appsec.events.#{event}.auto.mode", Datadog.configuration.appsec.track_user_events.mode)
|
36
44
|
|
@@ -24,7 +24,7 @@ module Datadog
|
|
24
24
|
seq_id = self.class.sequence.next
|
25
25
|
payload = Request.build_payload(event, seq_id)
|
26
26
|
res = @http_transport.request(request_type: event.type, payload: payload.to_json)
|
27
|
-
Datadog.logger.debug { "Telemetry sent for event `#{event.type}` (
|
27
|
+
Datadog.logger.debug { "Telemetry sent for event `#{event.type}` (code: #{res.code.inspect})" }
|
28
28
|
res
|
29
29
|
rescue => e
|
30
30
|
Datadog.logger.debug("Unable to send telemetry request for event `#{event.type rescue 'unknown'}`: #{e}")
|
@@ -52,10 +52,11 @@ module Datadog
|
|
52
52
|
|
53
53
|
def flush
|
54
54
|
worker_stats = @worker.stats_and_reset_not_thread_safe
|
55
|
-
|
56
|
-
|
55
|
+
serialization_result = pprof_recorder.serialize
|
56
|
+
return if serialization_result.nil?
|
57
57
|
|
58
|
-
|
58
|
+
start, finish, compressed_pprof, profile_stats = serialization_result
|
59
|
+
@last_flush_finish_at = finish
|
59
60
|
|
60
61
|
if duration_below_threshold?(start, finish)
|
61
62
|
Datadog.logger.debug('Skipped exporting profiling events as profile duration is below minimum')
|
@@ -75,6 +76,8 @@ module Datadog
|
|
75
76
|
internal_metadata: internal_metadata.merge(
|
76
77
|
{
|
77
78
|
worker_stats: worker_stats,
|
79
|
+
profile_stats: profile_stats,
|
80
|
+
recorder_stats: pprof_recorder.stats,
|
78
81
|
gc: GC.stat,
|
79
82
|
}
|
80
83
|
),
|
@@ -31,11 +31,11 @@ module Datadog
|
|
31
31
|
status, result = @no_concurrent_synchronize_mutex.synchronize { self.class._native_serialize(self) }
|
32
32
|
|
33
33
|
if status == :ok
|
34
|
-
start, finish, encoded_pprof = result
|
34
|
+
start, finish, encoded_pprof, profile_stats = result
|
35
35
|
|
36
36
|
Datadog.logger.debug { "Encoded profile covering #{start.iso8601} to #{finish.iso8601}" }
|
37
37
|
|
38
|
-
[start, finish, encoded_pprof]
|
38
|
+
[start, finish, encoded_pprof, profile_stats]
|
39
39
|
else
|
40
40
|
error_message = result
|
41
41
|
|
@@ -62,6 +62,10 @@ module Datadog
|
|
62
62
|
def reset_after_fork
|
63
63
|
self.class._native_reset_after_fork(self)
|
64
64
|
end
|
65
|
+
|
66
|
+
def stats
|
67
|
+
self.class._native_stats(self)
|
68
|
+
end
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
data/lib/ddtrace/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ddtrace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.23.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Datadog, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -876,9 +876,11 @@ files:
|
|
876
876
|
homepage: https://github.com/DataDog/dd-trace-rb
|
877
877
|
licenses:
|
878
878
|
- BSD-3-Clause
|
879
|
+
- Apache-2.0
|
879
880
|
metadata:
|
880
881
|
allowed_push_host: https://rubygems.org
|
881
|
-
changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/
|
882
|
+
changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v1.23.1/CHANGELOG.md
|
883
|
+
source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v1.23.1
|
882
884
|
post_install_message:
|
883
885
|
rdoc_options: []
|
884
886
|
require_paths:
|
@@ -897,7 +899,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
897
899
|
- !ruby/object:Gem::Version
|
898
900
|
version: 2.0.0
|
899
901
|
requirements: []
|
900
|
-
rubygems_version: 3.
|
902
|
+
rubygems_version: 3.4.21
|
901
903
|
signing_key:
|
902
904
|
specification_version: 4
|
903
905
|
summary: Datadog tracing code for your Ruby applications
|