ddtrace 1.22.0 → 1.23.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 +15 -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/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 +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5e8a3ccb932af75df7d30d6fe59edc8277e21ee34a9562f7c2c8bfba1ea7521
|
4
|
+
data.tar.gz: 1508bf7e56a24af598aad666665be6cd0a7b97c9cbf3bbcc95a3fda36b67c07a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afccca8d070d1dfd472af2390da1b343dff3199c42a71e742f10e29e91ddb27a6fa709490adcd639e6914f792b8e8565f667ff5d916f9f0f6690d69b8d8b0b54
|
7
|
+
data.tar.gz: 581022bd8d146fe01e9dac70a309e58d503d48ed8420b845a6b91200796ce9a0b1c2bc29fb58382ddc546abd00785d2f6ee42863e1bc6df517052a566352604e
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [1.23.0] - 2024-05-09
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
* Profiling: Enable endpoint profiling for Sidekiq and similar background job processors ([#3619][])
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
* Fix no such file or directory issue when using single step instrumentation ([#3623][])
|
14
|
+
* Fix error during telemetry debug logging attempt ([#3618][])
|
15
|
+
|
5
16
|
## [1.22.0] - 2024-04-16
|
6
17
|
|
7
18
|
### Added
|
@@ -2804,6 +2815,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
2804
2815
|
|
2805
2816
|
|
2806
2817
|
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...master
|
2818
|
+
[1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
|
2807
2819
|
[1.22.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v1.22.0
|
2808
2820
|
[2.0.0.beta1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v2.0.0.beta1
|
2809
2821
|
[1.21.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.0...v1.21.1
|
@@ -4111,6 +4123,9 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
4111
4123
|
[#3582]: https://github.com/DataDog/dd-trace-rb/issues/3582
|
4112
4124
|
[#3585]: https://github.com/DataDog/dd-trace-rb/issues/3585
|
4113
4125
|
[#3587]: https://github.com/DataDog/dd-trace-rb/issues/3587
|
4126
|
+
[#3618]: https://github.com/DataDog/dd-trace-rb/issues/3618
|
4127
|
+
[#3619]: https://github.com/DataDog/dd-trace-rb/issues/3619
|
4128
|
+
[#3623]: https://github.com/DataDog/dd-trace-rb/issues/3623
|
4114
4129
|
[@AdrianLC]: https://github.com/AdrianLC
|
4115
4130
|
[@Azure7111]: https://github.com/Azure7111
|
4116
4131
|
[@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
|
+
}
|
@@ -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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Datadog, Inc.
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -879,7 +879,7 @@ licenses:
|
|
879
879
|
metadata:
|
880
880
|
allowed_push_host: https://rubygems.org
|
881
881
|
changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/master/CHANGELOG.md
|
882
|
-
post_install_message:
|
882
|
+
post_install_message:
|
883
883
|
rdoc_options: []
|
884
884
|
require_paths:
|
885
885
|
- lib
|
@@ -897,8 +897,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
897
897
|
- !ruby/object:Gem::Version
|
898
898
|
version: 2.0.0
|
899
899
|
requirements: []
|
900
|
-
rubygems_version: 3.
|
901
|
-
signing_key:
|
900
|
+
rubygems_version: 3.4.10
|
901
|
+
signing_key:
|
902
902
|
specification_version: 4
|
903
903
|
summary: Datadog tracing code for your Ruby applications
|
904
904
|
test_files: []
|