datadog 2.16.0 → 2.18.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 +72 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +12 -46
- data/ext/datadog_profiling_native_extension/collectors_stack.c +227 -49
- data/ext/datadog_profiling_native_extension/collectors_stack.h +19 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +63 -12
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.c +22 -12
- data/ext/datadog_profiling_native_extension/encoded_profile.h +1 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +7 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +239 -363
- data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -6
- data/ext/datadog_profiling_native_extension/http_transport.c +45 -72
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +22 -0
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -5
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -3
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -13
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +2 -10
- data/ext/datadog_profiling_native_extension/stack_recorder.c +156 -60
- data/ext/libdatadog_api/crashtracker.c +10 -3
- data/ext/libdatadog_api/extconf.rb +2 -2
- data/ext/libdatadog_api/library_config.c +54 -12
- data/ext/libdatadog_api/library_config.h +6 -0
- data/ext/libdatadog_api/macos_development.md +3 -3
- data/ext/libdatadog_api/process_discovery.c +2 -7
- data/ext/libdatadog_extconf_helpers.rb +2 -2
- data/lib/datadog/appsec/api_security/lru_cache.rb +56 -0
- data/lib/datadog/appsec/api_security/route_extractor.rb +65 -0
- data/lib/datadog/appsec/api_security/sampler.rb +59 -0
- data/lib/datadog/appsec/api_security.rb +23 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +257 -85
- data/lib/datadog/appsec/assets/waf_rules/strict.json +10 -78
- data/lib/datadog/appsec/component.rb +30 -54
- data/lib/datadog/appsec/configuration/settings.rb +60 -2
- data/lib/datadog/appsec/context.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +27 -16
- data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
- data/lib/datadog/appsec/remote.rb +15 -55
- data/lib/datadog/appsec/security_engine/engine.rb +194 -0
- data/lib/datadog/appsec/security_engine/runner.rb +10 -11
- data/lib/datadog/appsec.rb +4 -7
- data/lib/datadog/core/buffer/random.rb +18 -2
- data/lib/datadog/core/configuration/agent_settings.rb +52 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +4 -46
- data/lib/datadog/core/configuration/components.rb +31 -24
- data/lib/datadog/core/configuration/components_state.rb +23 -0
- data/lib/datadog/core/configuration/option.rb +27 -27
- data/lib/datadog/core/configuration/option_definition.rb +4 -4
- data/lib/datadog/core/configuration/options.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +32 -20
- data/lib/datadog/core/configuration/stable_config.rb +1 -2
- data/lib/datadog/core/configuration.rb +16 -16
- data/lib/datadog/core/crashtracking/component.rb +2 -1
- data/lib/datadog/core/crashtracking/tag_builder.rb +4 -22
- data/lib/datadog/core/encoding.rb +1 -1
- data/lib/datadog/core/environment/cgroup.rb +10 -12
- data/lib/datadog/core/environment/container.rb +38 -40
- data/lib/datadog/core/environment/ext.rb +6 -6
- data/lib/datadog/core/environment/identity.rb +3 -3
- data/lib/datadog/core/environment/platform.rb +3 -3
- data/lib/datadog/core/error.rb +11 -9
- data/lib/datadog/core/logger.rb +2 -2
- data/lib/datadog/core/metrics/client.rb +12 -14
- data/lib/datadog/core/metrics/logging.rb +5 -5
- data/lib/datadog/core/process_discovery/tracer_memfd.rb +15 -0
- data/lib/datadog/core/process_discovery.rb +5 -1
- data/lib/datadog/core/rate_limiter.rb +4 -2
- data/lib/datadog/core/remote/client.rb +32 -31
- data/lib/datadog/core/remote/component.rb +3 -3
- data/lib/datadog/core/remote/configuration/digest.rb +7 -7
- data/lib/datadog/core/remote/configuration/path.rb +1 -1
- data/lib/datadog/core/remote/configuration/repository.rb +12 -0
- data/lib/datadog/core/remote/transport/http/client.rb +1 -1
- data/lib/datadog/core/remote/transport/http/config.rb +21 -5
- data/lib/datadog/core/remote/transport/http/negotiation.rb +1 -1
- data/lib/datadog/core/runtime/metrics.rb +3 -3
- data/lib/datadog/core/tag_builder.rb +56 -0
- data/lib/datadog/core/telemetry/component.rb +39 -24
- data/lib/datadog/core/telemetry/emitter.rb +7 -1
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +66 -0
- data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
- data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
- data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
- data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +269 -0
- data/lib/datadog/core/telemetry/event/base.rb +40 -0
- data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
- data/lib/datadog/core/telemetry/event/log.rb +76 -0
- data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +43 -0
- data/lib/datadog/core/telemetry/event.rb +17 -475
- data/lib/datadog/core/telemetry/logger.rb +5 -4
- data/lib/datadog/core/telemetry/logging.rb +11 -5
- data/lib/datadog/core/telemetry/metric.rb +3 -3
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -2
- data/lib/datadog/core/telemetry/transport/telemetry.rb +0 -1
- data/lib/datadog/core/telemetry/worker.rb +48 -27
- data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
- data/lib/datadog/core/transport/http/adapters/test.rb +2 -1
- data/lib/datadog/core/transport/http/builder.rb +14 -14
- data/lib/datadog/core/transport/http/env.rb +8 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +6 -6
- data/lib/datadog/core/utils/duration.rb +32 -32
- data/lib/datadog/core/utils/forking.rb +2 -2
- data/lib/datadog/core/utils/network.rb +6 -6
- data/lib/datadog/core/utils/only_once_successful.rb +16 -5
- data/lib/datadog/core/utils/time.rb +10 -2
- data/lib/datadog/core/utils/truncation.rb +21 -0
- data/lib/datadog/core/utils.rb +7 -0
- data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +1 -1
- data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +8 -8
- data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +7 -7
- data/lib/datadog/core/worker.rb +1 -1
- data/lib/datadog/core/workers/async.rb +9 -10
- data/lib/datadog/di/instrumenter.rb +52 -2
- data/lib/datadog/di/probe_notification_builder.rb +31 -41
- data/lib/datadog/di/probe_notifier_worker.rb +9 -1
- data/lib/datadog/di/serializer.rb +6 -2
- data/lib/datadog/di/transport/http/input.rb +10 -0
- data/lib/datadog/di/transport/input.rb +10 -2
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/profiling/collectors/code_provenance.rb +18 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +4 -0
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +16 -1
- data/lib/datadog/profiling/component.rb +7 -9
- data/lib/datadog/profiling/ext.rb +0 -13
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/http_transport.rb +3 -8
- data/lib/datadog/profiling/profiler.rb +2 -0
- data/lib/datadog/profiling/scheduler.rb +10 -2
- data/lib/datadog/profiling/stack_recorder.rb +5 -5
- data/lib/datadog/profiling/tag_builder.rb +5 -41
- data/lib/datadog/profiling/tasks/setup.rb +2 -0
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +19 -12
- data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +4 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +33 -0
- data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +4 -0
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +2 -4
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +10 -0
- data/lib/datadog/tracing/contrib/aws/parsed_context.rb +5 -1
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +1 -5
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +1 -5
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +1 -5
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
- data/lib/datadog/tracing/contrib/patcher.rb +5 -2
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -2
- data/lib/datadog/tracing/contrib/support.rb +28 -0
- data/lib/datadog/tracing/metadata/errors.rb +4 -4
- data/lib/datadog/tracing/sync_writer.rb +1 -1
- data/lib/datadog/tracing/trace_operation.rb +12 -4
- data/lib/datadog/tracing/tracer.rb +6 -2
- data/lib/datadog/version.rb +1 -1
- metadata +31 -12
- data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
- data/lib/datadog/appsec/processor.rb +0 -107
@@ -5,6 +5,18 @@
|
|
5
5
|
#include "libdatadog_helpers.h"
|
6
6
|
#include "time_helpers.h"
|
7
7
|
|
8
|
+
// Note on calloc vs ruby_xcalloc use:
|
9
|
+
// * Whenever we're allocating memory after being called by the Ruby VM in a "regular" situation (e.g. initializer)
|
10
|
+
// we should use `ruby_xcalloc` to give the VM visibility into what we're doing + give it a chance to manage GC
|
11
|
+
// * BUT, when we're being called during a sample, being in the middle of an object allocation is a very special
|
12
|
+
// situation for the VM to be in, and we've found the hard way (e.g. https://bugs.ruby-lang.org/issues/20629 and
|
13
|
+
// https://github.com/DataDog/dd-trace-rb/pull/4240 ) that it can be easy to do things the VM didn't expect.
|
14
|
+
// * Thus, out of caution and to avoid future potential issues such as the ones above, whenever we allocate memory
|
15
|
+
// during **sampling** we use `calloc` instead of `ruby_xcalloc`. Note that we've never seen issues from using
|
16
|
+
// `ruby_xcalloc` at any time, so this is a **precaution** not a "we've seen it break". But it seems a harmless
|
17
|
+
// one to use.
|
18
|
+
// This applies to both heap_recorder.c and collectors_thread_context.c
|
19
|
+
|
8
20
|
// Minimum age (in GC generations) of heap objects we want to include in heap
|
9
21
|
// recorder iterations. Object with age 0 represent objects that have yet to undergo
|
10
22
|
// a GC and, thus, may just be noise/trash at instant of iteration and are usually not
|
@@ -24,80 +36,37 @@
|
|
24
36
|
|
25
37
|
// A compact representation of a stacktrace frame for a heap allocation.
|
26
38
|
typedef struct {
|
27
|
-
|
28
|
-
|
39
|
+
ddog_prof_ManagedStringId name;
|
40
|
+
ddog_prof_ManagedStringId filename;
|
29
41
|
int32_t line;
|
30
42
|
} heap_frame;
|
31
43
|
|
44
|
+
// We use memcmp/st_hash below to compare/hash an entire array of heap_frames, so want to make sure no padding is added
|
45
|
+
// We could define the structure to be packed, but that seems even weirder across compilers, and this seems more portable?
|
46
|
+
_Static_assert(
|
47
|
+
sizeof(heap_frame) == sizeof(ddog_prof_ManagedStringId) * 2 + sizeof(int32_t),
|
48
|
+
"Size of heap_frame does not match the sum of its members. Padding detected."
|
49
|
+
);
|
50
|
+
|
32
51
|
// A compact representation of a stacktrace for a heap allocation.
|
33
|
-
//
|
34
|
-
// We could use a ddog_prof_Slice_Location instead but it has a lot of
|
35
|
-
// unused fields. Because we have to keep these stacks around for at
|
36
|
-
// least the lifetime of the objects allocated therein, we would be
|
37
|
-
// incurring a non-negligible memory overhead for little purpose.
|
52
|
+
// Used to dedup heap allocation stacktraces across multiple objects sharing the same allocation location.
|
38
53
|
typedef struct {
|
54
|
+
// How many objects are currently tracked in object_records recorder for this heap record.
|
55
|
+
uint32_t num_tracked_objects;
|
56
|
+
|
39
57
|
uint16_t frames_len;
|
40
58
|
heap_frame frames[];
|
41
|
-
}
|
42
|
-
static
|
43
|
-
static void
|
44
|
-
static st_index_t heap_stack_hash(heap_stack*, st_index_t);
|
59
|
+
} heap_record;
|
60
|
+
static heap_record* heap_record_new(heap_recorder*, ddog_prof_Slice_Location);
|
61
|
+
static void heap_record_free(heap_recorder*, heap_record*);
|
45
62
|
|
46
63
|
#if MAX_FRAMES_LIMIT > UINT16_MAX
|
47
64
|
#error Frames len type not compatible with MAX_FRAMES_LIMIT
|
48
65
|
#endif
|
49
66
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
};
|
54
|
-
// This struct allows us to use two different types of stacks when
|
55
|
-
// interacting with a heap_record hash.
|
56
|
-
//
|
57
|
-
// The idea is that we'll always want to use heap_stack-keys when
|
58
|
-
// adding new entries to the hash since that's the compact stack
|
59
|
-
// representation we rely on internally.
|
60
|
-
//
|
61
|
-
// However, when querying for an existing heap record, we'd save a
|
62
|
-
// lot of allocations if we could query with the
|
63
|
-
// ddog_prof_Slice_Location we receive in our external API.
|
64
|
-
//
|
65
|
-
// To allow this interchange, we need a union and need to ensure
|
66
|
-
// that whatever shape of the union, the heap_record_key_cmp_st
|
67
|
-
// and heap_record_hash_st functions return the same results for
|
68
|
-
// equivalent stacktraces.
|
69
|
-
typedef struct {
|
70
|
-
enum heap_record_key_type type;
|
71
|
-
union {
|
72
|
-
// key never owns this if set
|
73
|
-
heap_stack *heap_stack;
|
74
|
-
// key never owns this if set
|
75
|
-
ddog_prof_Slice_Location *location_slice;
|
76
|
-
};
|
77
|
-
} heap_record_key;
|
78
|
-
static heap_record_key* heap_record_key_new(heap_stack*);
|
79
|
-
static void heap_record_key_free(heap_record_key*);
|
80
|
-
static int heap_record_key_cmp_st(st_data_t, st_data_t);
|
81
|
-
static st_index_t heap_record_key_hash_st(st_data_t);
|
82
|
-
static const struct st_hash_type st_hash_type_heap_record_key = {
|
83
|
-
heap_record_key_cmp_st,
|
84
|
-
heap_record_key_hash_st,
|
85
|
-
};
|
86
|
-
|
87
|
-
// Need to implement these functions to support the location-slice based keys
|
88
|
-
static st_index_t ddog_location_hash(ddog_prof_Location, st_index_t seed);
|
89
|
-
static st_index_t ddog_location_slice_hash(ddog_prof_Slice_Location, st_index_t seed);
|
90
|
-
|
91
|
-
// A heap record is used for deduping heap allocation stacktraces across multiple
|
92
|
-
// objects sharing the same allocation location.
|
93
|
-
typedef struct {
|
94
|
-
// How many objects are currently tracked by the heap recorder for this heap record.
|
95
|
-
uint32_t num_tracked_objects;
|
96
|
-
// stack is owned by the associated record and gets cleaned up alongside it
|
97
|
-
heap_stack *stack;
|
98
|
-
} heap_record;
|
99
|
-
static heap_record* heap_record_new(heap_stack*);
|
100
|
-
static void heap_record_free(heap_record*);
|
67
|
+
static int heap_record_cmp_st(st_data_t, st_data_t);
|
68
|
+
static st_index_t heap_record_hash_st(st_data_t);
|
69
|
+
static const struct st_hash_type st_hash_type_heap_record = { .compare = heap_record_cmp_st, .hash = heap_record_hash_st };
|
101
70
|
|
102
71
|
// An object record is used for storing data about currently tracked live objects
|
103
72
|
typedef struct {
|
@@ -106,8 +75,8 @@ typedef struct {
|
|
106
75
|
live_object_data object_data;
|
107
76
|
} object_record;
|
108
77
|
static object_record* object_record_new(long, heap_record*, live_object_data);
|
109
|
-
static void object_record_free(object_record*);
|
110
|
-
static VALUE object_record_inspect(object_record*);
|
78
|
+
static void object_record_free(heap_recorder*, object_record*);
|
79
|
+
static VALUE object_record_inspect(heap_recorder*, object_record*);
|
111
80
|
static object_record SKIPPED_RECORD = {0};
|
112
81
|
|
113
82
|
struct heap_recorder {
|
@@ -116,12 +85,15 @@ struct heap_recorder {
|
|
116
85
|
bool size_enabled;
|
117
86
|
uint sample_rate;
|
118
87
|
|
119
|
-
// Map[key:
|
120
|
-
// NOTE: We always use heap_record_key.type == HEAP_STACK for storage but support lookups
|
121
|
-
// via heap_record_key.type == LOCATION_SLICE to allow for allocation-free fast-paths.
|
88
|
+
// Map[key: heap_record*, record: nothing] (This is a set, basically)
|
122
89
|
// NOTE: This table is currently only protected by the GVL since we never interact with it
|
123
90
|
// outside the GVL.
|
124
|
-
// NOTE: This table has ownership of
|
91
|
+
// NOTE: This table has ownership of its heap_records.
|
92
|
+
//
|
93
|
+
// This is a cpu/memory trade-off: Maintaining the "heap_records" map means we spend extra CPU when sampling as we need
|
94
|
+
// to do de-duplication, but we reduce the memory footprint of the heap profiler.
|
95
|
+
// In the future, it may be worth revisiting if we can move this inside libdatadog: if libdatadog was able to track
|
96
|
+
// entire stacks for us, then we wouldn't need to do it on the Ruby side.
|
125
97
|
st_table *heap_records;
|
126
98
|
|
127
99
|
// Map[obj_id: long, record: object_record*]
|
@@ -132,6 +104,8 @@ struct heap_recorder {
|
|
132
104
|
//
|
133
105
|
// TODO: @ivoanjo We've evolved to actually never need to look up on object_records (we only insert and iterate),
|
134
106
|
// so right now this seems to be just a really really fancy self-resizing list/set.
|
107
|
+
// If we replace this with a list, we could record the latest id and compare it when inserting to make sure our
|
108
|
+
// assumption of ids never reused + always increasing always holds. (This as an alternative to checking for duplicates)
|
135
109
|
st_table *object_records;
|
136
110
|
|
137
111
|
// Map[obj_id: long, record: object_record*]
|
@@ -156,12 +130,19 @@ struct heap_recorder {
|
|
156
130
|
// Data for a heap recording that was started but not yet ended
|
157
131
|
object_record *active_recording;
|
158
132
|
|
159
|
-
// Reusable
|
133
|
+
// Reusable arrays, implementing a flyweight pattern for things like iteration
|
134
|
+
#define REUSABLE_LOCATIONS_SIZE MAX_FRAMES_LIMIT
|
160
135
|
ddog_prof_Location *reusable_locations;
|
161
136
|
|
137
|
+
#define REUSABLE_FRAME_DETAILS_SIZE (2 * MAX_FRAMES_LIMIT) // because it'll be used for both function names AND file names)
|
138
|
+
ddog_prof_ManagedStringId *reusable_ids;
|
139
|
+
ddog_CharSlice *reusable_char_slices;
|
140
|
+
|
162
141
|
// Sampling state
|
163
142
|
uint num_recordings_skipped;
|
164
143
|
|
144
|
+
ddog_prof_ManagedStringStorage string_storage;
|
145
|
+
|
165
146
|
struct stats_last_update {
|
166
147
|
size_t objects_alive;
|
167
148
|
size_t objects_dead;
|
@@ -185,10 +166,10 @@ struct heap_recorder {
|
|
185
166
|
} stats_lifetime;
|
186
167
|
};
|
187
168
|
|
188
|
-
struct
|
189
|
-
|
169
|
+
typedef struct {
|
170
|
+
heap_recorder *heap_recorder;
|
190
171
|
ddog_prof_Slice_Location locations;
|
191
|
-
};
|
172
|
+
} end_heap_allocation_args;
|
192
173
|
|
193
174
|
static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
|
194
175
|
static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
|
@@ -203,6 +184,9 @@ static void commit_recording(heap_recorder *, heap_record *, object_record *acti
|
|
203
184
|
static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
|
204
185
|
static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
|
205
186
|
static inline double ewma_stat(double previous, double current);
|
187
|
+
static void unintern_or_raise(heap_recorder *, ddog_prof_ManagedStringId);
|
188
|
+
static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids);
|
189
|
+
static VALUE get_ruby_string_or_raise(heap_recorder*, ddog_prof_ManagedStringId);
|
206
190
|
|
207
191
|
// ==========================
|
208
192
|
// Heap Recorder External API
|
@@ -213,16 +197,19 @@ static inline double ewma_stat(double previous, double current);
|
|
213
197
|
// happens under the GVL.
|
214
198
|
//
|
215
199
|
// ==========================
|
216
|
-
heap_recorder* heap_recorder_new(
|
200
|
+
heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage) {
|
217
201
|
heap_recorder *recorder = ruby_xcalloc(1, sizeof(heap_recorder));
|
218
202
|
|
219
|
-
recorder->heap_records = st_init_table(&
|
203
|
+
recorder->heap_records = st_init_table(&st_hash_type_heap_record);
|
220
204
|
recorder->object_records = st_init_numtable();
|
221
205
|
recorder->object_records_snapshot = NULL;
|
222
|
-
recorder->reusable_locations = ruby_xcalloc(
|
206
|
+
recorder->reusable_locations = ruby_xcalloc(REUSABLE_LOCATIONS_SIZE, sizeof(ddog_prof_Location));
|
207
|
+
recorder->reusable_ids = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_prof_ManagedStringId));
|
208
|
+
recorder->reusable_char_slices = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_CharSlice));
|
223
209
|
recorder->active_recording = NULL;
|
224
210
|
recorder->size_enabled = true;
|
225
211
|
recorder->sample_rate = 1; // By default do no sampling on top of what allocation profiling already does
|
212
|
+
recorder->string_storage = string_storage;
|
226
213
|
|
227
214
|
return recorder;
|
228
215
|
}
|
@@ -239,19 +226,21 @@ void heap_recorder_free(heap_recorder *heap_recorder) {
|
|
239
226
|
}
|
240
227
|
|
241
228
|
// Clean-up all object records
|
242
|
-
st_foreach(heap_recorder->object_records, st_object_record_entry_free,
|
229
|
+
st_foreach(heap_recorder->object_records, st_object_record_entry_free, (st_data_t) heap_recorder);
|
243
230
|
st_free_table(heap_recorder->object_records);
|
244
231
|
|
245
232
|
// Clean-up all heap records (this includes those only referred to by queued_samples)
|
246
|
-
st_foreach(heap_recorder->heap_records, st_heap_record_entry_free,
|
233
|
+
st_foreach(heap_recorder->heap_records, st_heap_record_entry_free, (st_data_t) heap_recorder);
|
247
234
|
st_free_table(heap_recorder->heap_records);
|
248
235
|
|
249
236
|
if (heap_recorder->active_recording != NULL && heap_recorder->active_recording != &SKIPPED_RECORD) {
|
250
237
|
// If there's a partial object record, clean it up as well
|
251
|
-
object_record_free(heap_recorder->active_recording);
|
238
|
+
object_record_free(heap_recorder, heap_recorder->active_recording);
|
252
239
|
}
|
253
240
|
|
254
241
|
ruby_xfree(heap_recorder->reusable_locations);
|
242
|
+
ruby_xfree(heap_recorder->reusable_ids);
|
243
|
+
ruby_xfree(heap_recorder->reusable_char_slices);
|
255
244
|
|
256
245
|
ruby_xfree(heap_recorder);
|
257
246
|
}
|
@@ -321,6 +310,10 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
|
|
321
310
|
#else
|
322
311
|
false
|
323
312
|
#endif
|
313
|
+
// If we got really unlucky and an allocation showed up during an update (because it triggered an allocation
|
314
|
+
// directly OR because the GVL got released in the middle of an update), let's skip this sample as well.
|
315
|
+
// See notes on `heap_recorder_update` for details.
|
316
|
+
|| heap_recorder->updating
|
324
317
|
) {
|
325
318
|
heap_recorder->active_recording = &SKIPPED_RECORD;
|
326
319
|
return;
|
@@ -338,7 +331,7 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
|
|
338
331
|
NULL,
|
339
332
|
(live_object_data) {
|
340
333
|
.weight = weight * heap_recorder->sample_rate,
|
341
|
-
.class =
|
334
|
+
.class = intern_or_raise(heap_recorder->string_storage, alloc_class),
|
342
335
|
.alloc_gen = rb_gc_count(),
|
343
336
|
}
|
344
337
|
);
|
@@ -348,24 +341,30 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
|
|
348
341
|
// locks. To enable us to correctly unlock the profile on exception, we wrap the call to end_heap_allocation_recording
|
349
342
|
// with an rb_protect.
|
350
343
|
__attribute__((warn_unused_result))
|
351
|
-
int end_heap_allocation_recording_with_rb_protect(
|
344
|
+
int end_heap_allocation_recording_with_rb_protect(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
|
352
345
|
if (heap_recorder == NULL) {
|
353
346
|
return 0;
|
354
347
|
}
|
348
|
+
if (heap_recorder->active_recording == &SKIPPED_RECORD) {
|
349
|
+
// Short circuit, in this case there's nothing to be done
|
350
|
+
heap_recorder->active_recording = NULL;
|
351
|
+
return 0;
|
352
|
+
}
|
353
|
+
|
355
354
|
|
356
355
|
int exception_state;
|
357
|
-
|
356
|
+
end_heap_allocation_args args = {
|
358
357
|
.heap_recorder = heap_recorder,
|
359
358
|
.locations = locations,
|
360
359
|
};
|
361
|
-
rb_protect(end_heap_allocation_recording, (VALUE) &
|
360
|
+
rb_protect(end_heap_allocation_recording, (VALUE) &args, &exception_state);
|
362
361
|
return exception_state;
|
363
362
|
}
|
364
363
|
|
365
|
-
static VALUE end_heap_allocation_recording(VALUE
|
366
|
-
|
364
|
+
static VALUE end_heap_allocation_recording(VALUE protect_args) {
|
365
|
+
end_heap_allocation_args *args = (end_heap_allocation_args *) protect_args;
|
367
366
|
|
368
|
-
|
367
|
+
heap_recorder *heap_recorder = args->heap_recorder;
|
369
368
|
ddog_prof_Slice_Location locations = args->locations;
|
370
369
|
|
371
370
|
object_record *active_recording = heap_recorder->active_recording;
|
@@ -380,6 +379,7 @@ static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args) {
|
|
380
379
|
heap_recorder->active_recording = NULL;
|
381
380
|
|
382
381
|
if (active_recording == &SKIPPED_RECORD) { // special marker when we decided to skip due to sampling
|
382
|
+
// Note: Remember to update the short circuit in end_heap_allocation_recording_with_rb_protect if this logic changes
|
383
383
|
return Qnil;
|
384
384
|
}
|
385
385
|
|
@@ -399,15 +399,28 @@ void heap_recorder_update_young_objects(heap_recorder *heap_recorder) {
|
|
399
399
|
heap_recorder_update(heap_recorder, /* full_update: */ false);
|
400
400
|
}
|
401
401
|
|
402
|
+
// NOTE: This function needs and assumes it gets called with the GVL being held.
|
403
|
+
// But importantly **some of the operations inside `st_object_record_update` may cause a thread switch**,
|
404
|
+
// so we can't assume a single update happens in a single "atomic" step -- other threads may get some running time
|
405
|
+
// in the meanwhile.
|
402
406
|
static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update) {
|
403
407
|
if (heap_recorder->updating) {
|
404
|
-
if (full_update)
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
408
|
+
if (full_update) {
|
409
|
+
// There's another thread that's already doing an update :(
|
410
|
+
//
|
411
|
+
// Because there's a lock on the `StackRecorder` (see @no_concurrent_serialize_mutex) then it's not possible that
|
412
|
+
// the other update is a full update.
|
413
|
+
// Thus we expect is happening is that the GVL got released by the other thread in the middle of a non-full update
|
414
|
+
// and the scheduler thread decided now was a great time to serialize the profile.
|
415
|
+
//
|
416
|
+
// So, let's yield the time on the current thread until Ruby goes back to the other thread doing the update and
|
417
|
+
// it finishes cleanly.
|
418
|
+
while (heap_recorder->updating) { rb_thread_schedule(); }
|
419
|
+
} else {
|
420
|
+
// Non-full updates are optional, so let's walk away
|
421
|
+
heap_recorder->stats_lifetime.updates_skipped_concurrent++;
|
422
|
+
return;
|
423
|
+
}
|
411
424
|
}
|
412
425
|
|
413
426
|
if (heap_recorder->object_records_snapshot != NULL) {
|
@@ -561,26 +574,10 @@ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
|
|
561
574
|
return hash;
|
562
575
|
}
|
563
576
|
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
.heap_stack = stack,
|
569
|
-
};
|
570
|
-
heap_record_key location_based_key = (heap_record_key) {
|
571
|
-
.type = LOCATION_SLICE,
|
572
|
-
.location_slice = &locations,
|
573
|
-
};
|
574
|
-
|
575
|
-
st_index_t stack_hash = heap_record_key_hash_st((st_data_t) &stack_based_key);
|
576
|
-
st_index_t location_hash = heap_record_key_hash_st((st_data_t) &location_based_key);
|
577
|
-
|
578
|
-
heap_stack_free(stack);
|
579
|
-
|
580
|
-
if (stack_hash != location_hash) {
|
581
|
-
rb_raise(rb_eRuntimeError, "Heap record key hashes built from the same locations differ. stack_based_hash=%"PRI_VALUE_PREFIX"u location_based_hash=%"PRI_VALUE_PREFIX"u", stack_hash, location_hash);
|
582
|
-
}
|
583
|
-
}
|
577
|
+
typedef struct {
|
578
|
+
heap_recorder *recorder;
|
579
|
+
VALUE debug_str;
|
580
|
+
} debug_context;
|
584
581
|
|
585
582
|
VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
586
583
|
if (heap_recorder == NULL) {
|
@@ -588,7 +585,8 @@ VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
|
588
585
|
}
|
589
586
|
|
590
587
|
VALUE debug_str = rb_str_new2("object records:\n");
|
591
|
-
|
588
|
+
debug_context context = (debug_context) {.recorder = heap_recorder, .debug_str = debug_str};
|
589
|
+
st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) &context);
|
592
590
|
|
593
591
|
rb_str_catf(debug_str, "state snapshot: %"PRIsVALUE"\n------\n", heap_recorder_state_snapshot(heap_recorder));
|
594
592
|
|
@@ -598,18 +596,19 @@ VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
|
598
596
|
// ==========================
|
599
597
|
// Heap Recorder Internal API
|
600
598
|
// ==========================
|
601
|
-
static int st_heap_record_entry_free(st_data_t key, st_data_t value,
|
602
|
-
|
603
|
-
|
604
|
-
heap_record_free((heap_record *) value);
|
599
|
+
static int st_heap_record_entry_free(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra_arg) {
|
600
|
+
heap_recorder *recorder = (heap_recorder *) extra_arg;
|
601
|
+
heap_record_free(recorder, (heap_record *) key);
|
605
602
|
return ST_DELETE;
|
606
603
|
}
|
607
604
|
|
608
|
-
static int st_object_record_entry_free(DDTRACE_UNUSED st_data_t key, st_data_t value,
|
609
|
-
|
605
|
+
static int st_object_record_entry_free(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra_arg) {
|
606
|
+
heap_recorder *recorder = (heap_recorder *) extra_arg;
|
607
|
+
object_record_free(recorder, (object_record *) value);
|
610
608
|
return ST_DELETE;
|
611
609
|
}
|
612
610
|
|
611
|
+
// NOTE: Some operations inside this function can cause the GVL to be released! Plan accordingly.
|
613
612
|
static int st_object_record_update(st_data_t key, st_data_t value, st_data_t extra_arg) {
|
614
613
|
long obj_id = (long) key;
|
615
614
|
object_record *record = (object_record*) value;
|
@@ -635,7 +634,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
635
634
|
return ST_CONTINUE;
|
636
635
|
}
|
637
636
|
|
638
|
-
if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
|
637
|
+
if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) { // Note: This function call can cause the GVL to be released
|
639
638
|
// Id no longer associated with a valid ref. Need to delete this object record!
|
640
639
|
on_committed_object_record_cleanup(recorder, record);
|
641
640
|
recorder->stats_last_update.objects_dead++;
|
@@ -651,7 +650,8 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
651
650
|
) {
|
652
651
|
// if we were asked to update sizes and this object was not already seen as being frozen,
|
653
652
|
// update size again.
|
654
|
-
record->object_data.size = ruby_obj_memsize_of(ref);
|
653
|
+
record->object_data.size = ruby_obj_memsize_of(ref); // Note: This function call can cause the GVL to be released... maybe?
|
654
|
+
// (With T_DATA for instance, since it can be a custom method supplied by extensions)
|
655
655
|
// Check if it's now frozen so we skip a size update next time
|
656
656
|
record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
|
657
657
|
}
|
@@ -671,7 +671,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
671
671
|
// WARN: This can get called outside the GVL. NO HEAP ALLOCATIONS OR EXCEPTIONS ARE ALLOWED.
|
672
672
|
static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
|
673
673
|
object_record *record = (object_record*) value;
|
674
|
-
const
|
674
|
+
const heap_record *stack = record->heap_record;
|
675
675
|
iteration_context *context = (iteration_context*) extra;
|
676
676
|
|
677
677
|
const heap_recorder *recorder = context->heap_recorder;
|
@@ -687,8 +687,10 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
687
687
|
locations[i] = (ddog_prof_Location) {
|
688
688
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
689
689
|
.function = {
|
690
|
-
.name =
|
691
|
-
.
|
690
|
+
.name = DDOG_CHARSLICE_C(""),
|
691
|
+
.name_id = frame->name,
|
692
|
+
.filename = DDOG_CHARSLICE_C(""),
|
693
|
+
.filename_id = frame->filename,
|
692
694
|
},
|
693
695
|
.line = frame->line,
|
694
696
|
};
|
@@ -707,11 +709,12 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
707
709
|
}
|
708
710
|
|
709
711
|
static int st_object_records_debug(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
|
710
|
-
|
712
|
+
debug_context *context = (debug_context*) extra;
|
713
|
+
VALUE debug_str = context->debug_str;
|
711
714
|
|
712
715
|
object_record *record = (object_record*) value;
|
713
716
|
|
714
|
-
rb_str_catf(debug_str, "%"PRIsVALUE"\n", object_record_inspect(record));
|
717
|
+
rb_str_catf(debug_str, "%"PRIsVALUE"\n", object_record_inspect(context->recorder, record));
|
715
718
|
|
716
719
|
return ST_CONTINUE;
|
717
720
|
}
|
@@ -740,60 +743,35 @@ static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_rec
|
|
740
743
|
st_lookup(heap_recorder->object_records, active_recording->obj_id, (st_data_t *) &existing_record);
|
741
744
|
if (existing_record == NULL) rb_raise(rb_eRuntimeError, "Unexpected NULL when reading existing record");
|
742
745
|
|
743
|
-
VALUE existing_inspect = object_record_inspect(existing_record);
|
744
|
-
VALUE new_inspect = object_record_inspect(active_recording);
|
746
|
+
VALUE existing_inspect = object_record_inspect(heap_recorder, existing_record);
|
747
|
+
VALUE new_inspect = object_record_inspect(heap_recorder, active_recording);
|
745
748
|
rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
|
746
749
|
"the same id. previous={%"PRIsVALUE"} new={%"PRIsVALUE"}", existing_inspect, new_inspect);
|
747
750
|
}
|
748
751
|
}
|
749
752
|
|
750
|
-
// Struct holding data required for an update operation on heap_records
|
751
|
-
typedef struct {
|
752
|
-
// [in] The locations we did this update with
|
753
|
-
ddog_prof_Slice_Location locations;
|
754
|
-
// [out] Pointer that will be updated to the updated heap record to prevent having to do
|
755
|
-
// another lookup to access the updated heap record.
|
756
|
-
heap_record **record;
|
757
|
-
} heap_record_update_data;
|
758
|
-
|
759
|
-
// This function assumes ownership of stack_data is passed on to it so it'll either transfer ownership or clean-up.
|
760
753
|
static int update_heap_record_entry_with_new_allocation(st_data_t *key, st_data_t *value, st_data_t data, int existing) {
|
761
|
-
|
754
|
+
heap_record **new_or_existing_record = (heap_record **) data;
|
755
|
+
(*new_or_existing_record) = (heap_record *) (*key);
|
762
756
|
|
763
757
|
if (!existing) {
|
764
|
-
|
765
|
-
// we need to initialize a heap_record_key with a new stack and use that for the key storage. We can't use the
|
766
|
-
// locations-based key we used for the update call because we don't own its lifecycle. So we create a new
|
767
|
-
// heap stack and will pass ownership of it to the heap_record.
|
768
|
-
heap_stack *stack = heap_stack_new(update_data->locations);
|
769
|
-
(*key) = (st_data_t) heap_record_key_new(stack);
|
770
|
-
(*value) = (st_data_t) heap_record_new(stack);
|
758
|
+
(*value) = (st_data_t) true; // We're only using this hash as a set
|
771
759
|
}
|
772
760
|
|
773
|
-
heap_record *record = (heap_record*) (*value);
|
774
|
-
(*update_data->record) = record;
|
775
|
-
|
776
761
|
return ST_CONTINUE;
|
777
762
|
}
|
778
763
|
|
779
764
|
static heap_record* get_or_create_heap_record(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
|
780
|
-
//
|
781
|
-
|
782
|
-
// NOTE: If we end up creating a new record, we'll create a heap-allocated key we own and use that for storage
|
783
|
-
// instead of this one.
|
784
|
-
heap_record_key lookup_key = (heap_record_key) {
|
785
|
-
.type = LOCATION_SLICE,
|
786
|
-
.location_slice = &locations,
|
787
|
-
};
|
765
|
+
// See note on "heap_records" definition for why we keep this map.
|
766
|
+
heap_record *stack = heap_record_new(heap_recorder, locations);
|
788
767
|
|
789
|
-
heap_record *
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
}
|
794
|
-
st_update(heap_recorder->heap_records, (st_data_t) &lookup_key, update_heap_record_entry_with_new_allocation, (st_data_t) &update_data);
|
768
|
+
heap_record *new_or_existing_record = NULL; // Will be set inside update_heap_record_entry_with_new_allocation
|
769
|
+
bool existing = st_update(heap_recorder->heap_records, (st_data_t) stack, update_heap_record_entry_with_new_allocation, (st_data_t) &new_or_existing_record);
|
770
|
+
if (existing) {
|
771
|
+
heap_record_free(heap_recorder, stack);
|
772
|
+
}
|
795
773
|
|
796
|
-
return
|
774
|
+
return new_or_existing_record;
|
797
775
|
}
|
798
776
|
|
799
777
|
static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_record *heap_record) {
|
@@ -802,18 +780,10 @@ static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_rec
|
|
802
780
|
return;
|
803
781
|
}
|
804
782
|
|
805
|
-
|
806
|
-
.type = HEAP_STACK,
|
807
|
-
.heap_stack = heap_record->stack,
|
808
|
-
};
|
809
|
-
// We need to access the deleted key to free it since we gave ownership of the keys to the hash.
|
810
|
-
// st_delete will change this pointer to point to the removed key if one is found.
|
811
|
-
heap_record_key *deleted_key = &heap_key;
|
812
|
-
if (!st_delete(heap_recorder->heap_records, (st_data_t*) &deleted_key, NULL)) {
|
783
|
+
if (!st_delete(heap_recorder->heap_records, (st_data_t*) &heap_record, NULL)) {
|
813
784
|
rb_raise(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
|
814
785
|
};
|
815
|
-
|
816
|
-
heap_record_free(heap_record);
|
786
|
+
heap_record_free(heap_recorder, heap_record);
|
817
787
|
}
|
818
788
|
|
819
789
|
static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record) {
|
@@ -829,59 +799,44 @@ static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, obj
|
|
829
799
|
heap_record *heap_record = record->heap_record;
|
830
800
|
|
831
801
|
if (heap_record == NULL) rb_raise(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
|
832
|
-
if (heap_record->stack == NULL) rb_raise(rb_eRuntimeError, "heap_record->stack was NULL in on_committed_object_record_cleanup");
|
833
802
|
|
834
803
|
heap_record->num_tracked_objects--;
|
835
804
|
|
836
805
|
// One less object using this heap record, it may have become unused...
|
837
806
|
cleanup_heap_record_if_unused(heap_recorder, heap_record);
|
838
807
|
|
839
|
-
object_record_free(record);
|
840
|
-
}
|
841
|
-
|
842
|
-
// ===============
|
843
|
-
// Heap Record API
|
844
|
-
// ===============
|
845
|
-
heap_record* heap_record_new(heap_stack *stack) {
|
846
|
-
heap_record *record = ruby_xcalloc(1, sizeof(heap_record));
|
847
|
-
record->num_tracked_objects = 0;
|
848
|
-
record->stack = stack;
|
849
|
-
return record;
|
850
|
-
}
|
851
|
-
|
852
|
-
void heap_record_free(heap_record *record) {
|
853
|
-
heap_stack_free(record->stack);
|
854
|
-
ruby_xfree(record);
|
808
|
+
object_record_free(heap_recorder, record);
|
855
809
|
}
|
856
810
|
|
857
811
|
// =================
|
858
812
|
// Object Record API
|
859
813
|
// =================
|
860
814
|
object_record* object_record_new(long obj_id, heap_record *heap_record, live_object_data object_data) {
|
861
|
-
object_record *record =
|
815
|
+
object_record *record = calloc(1, sizeof(object_record)); // See "note on calloc vs ruby_xcalloc use" above
|
862
816
|
record->obj_id = obj_id;
|
863
817
|
record->heap_record = heap_record;
|
864
818
|
record->object_data = object_data;
|
865
819
|
return record;
|
866
820
|
}
|
867
821
|
|
868
|
-
void object_record_free(object_record *record) {
|
869
|
-
|
870
|
-
|
871
|
-
}
|
872
|
-
ruby_xfree(record);
|
822
|
+
void object_record_free(heap_recorder *recorder, object_record *record) {
|
823
|
+
unintern_or_raise(recorder, record->object_data.class);
|
824
|
+
free(record); // See "note on calloc vs ruby_xcalloc use" above
|
873
825
|
}
|
874
826
|
|
875
|
-
VALUE object_record_inspect(object_record *record) {
|
876
|
-
heap_frame top_frame = record->heap_record->
|
827
|
+
VALUE object_record_inspect(heap_recorder *recorder, object_record *record) {
|
828
|
+
heap_frame top_frame = record->heap_record->frames[0];
|
829
|
+
VALUE filename = get_ruby_string_or_raise(recorder, top_frame.filename);
|
877
830
|
live_object_data object_data = record->object_data;
|
878
|
-
|
879
|
-
|
831
|
+
|
832
|
+
VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%"PRIsVALUE":%d alloc_gen=%zu gen_age=%zu frozen=%d ",
|
833
|
+
record->obj_id, object_data.weight, object_data.size, filename,
|
880
834
|
(int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
|
881
835
|
|
882
|
-
|
883
|
-
|
884
|
-
|
836
|
+
if (record->object_data.class.value > 0) {
|
837
|
+
VALUE class = get_ruby_string_or_raise(recorder, record->object_data.class);
|
838
|
+
|
839
|
+
rb_str_catf(inspect, "class=%"PRIsVALUE" ", class);
|
885
840
|
}
|
886
841
|
VALUE ref;
|
887
842
|
|
@@ -901,202 +856,103 @@ VALUE object_record_inspect(object_record *record) {
|
|
901
856
|
}
|
902
857
|
|
903
858
|
// ==============
|
904
|
-
// Heap
|
905
|
-
// ==============
|
906
|
-
// WARN: Must be kept in-sync with ::char_slice_hash
|
907
|
-
st_index_t string_hash(char *str, st_index_t seed) {
|
908
|
-
return st_hash(str, strlen(str), seed);
|
909
|
-
}
|
910
|
-
|
911
|
-
// WARN: Must be kept in-sync with ::string_hash
|
912
|
-
st_index_t char_slice_hash(ddog_CharSlice char_slice, st_index_t seed) {
|
913
|
-
return st_hash(char_slice.ptr, char_slice.len, seed);
|
914
|
-
}
|
915
|
-
|
916
|
-
// WARN: Must be kept in-sync with ::ddog_location_hash
|
917
|
-
st_index_t heap_frame_hash(heap_frame *frame, st_index_t seed) {
|
918
|
-
st_index_t hash = string_hash(frame->name, seed);
|
919
|
-
hash = string_hash(frame->filename, hash);
|
920
|
-
hash = st_hash(&frame->line, sizeof(frame->line), hash);
|
921
|
-
return hash;
|
922
|
-
}
|
923
|
-
|
924
|
-
// WARN: Must be kept in-sync with ::heap_frame_hash
|
925
|
-
st_index_t ddog_location_hash(ddog_prof_Location location, st_index_t seed) {
|
926
|
-
st_index_t hash = char_slice_hash(location.function.name, seed);
|
927
|
-
hash = char_slice_hash(location.function.filename, hash);
|
928
|
-
// Convert ddog_prof line type to the same type we use for our heap_frames to
|
929
|
-
// ensure we have compatible hashes
|
930
|
-
int32_t line_as_int32 = (int32_t) location.line;
|
931
|
-
hash = st_hash(&line_as_int32, sizeof(line_as_int32), hash);
|
932
|
-
return hash;
|
933
|
-
}
|
934
|
-
|
935
|
-
// ==============
|
936
|
-
// Heap Stack API
|
859
|
+
// Heap Record API
|
937
860
|
// ==============
|
938
|
-
|
861
|
+
heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location locations) {
|
939
862
|
uint16_t frames_len = locations.len;
|
940
863
|
if (frames_len > MAX_FRAMES_LIMIT) {
|
941
864
|
// This is not expected as MAX_FRAMES_LIMIT is shared with the stacktrace construction mechanism
|
942
865
|
rb_raise(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
|
943
866
|
}
|
944
|
-
|
867
|
+
heap_record *stack = calloc(1, sizeof(heap_record) + frames_len * sizeof(heap_frame)); // See "note on calloc vs ruby_xcalloc use" above
|
868
|
+
stack->num_tracked_objects = 0;
|
945
869
|
stack->frames_len = frames_len;
|
870
|
+
|
871
|
+
// Intern all these strings...
|
872
|
+
ddog_CharSlice *strings = recorder->reusable_char_slices;
|
873
|
+
// Put all the char slices in the same array; we'll pull them out in the same order from the ids array
|
946
874
|
for (uint16_t i = 0; i < stack->frames_len; i++) {
|
947
875
|
const ddog_prof_Location *location = &locations.ptr[i];
|
876
|
+
strings[i] = location->function.filename;
|
877
|
+
strings[i + stack->frames_len] = location->function.name;
|
878
|
+
}
|
879
|
+
intern_all_or_raise(recorder->string_storage, (ddog_prof_Slice_CharSlice) { .ptr = strings, .len = stack->frames_len * 2 }, recorder->reusable_ids, stack->frames_len * 2);
|
880
|
+
|
881
|
+
// ...and record them for later use
|
882
|
+
for (uint16_t i = 0; i < stack->frames_len; i++) {
|
948
883
|
stack->frames[i] = (heap_frame) {
|
949
|
-
.
|
950
|
-
.
|
884
|
+
.filename = recorder->reusable_ids[i],
|
885
|
+
.name = recorder->reusable_ids[i + stack->frames_len],
|
951
886
|
// ddog_prof_Location is a int64_t. We don't expect to have to profile files with more than
|
952
887
|
// 2M lines so this cast should be fairly safe?
|
953
|
-
.line = (int32_t)
|
888
|
+
.line = (int32_t) locations.ptr[i].line,
|
954
889
|
};
|
955
890
|
}
|
956
|
-
return stack;
|
957
|
-
}
|
958
891
|
|
959
|
-
|
960
|
-
for (uint64_t i = 0; i < stack->frames_len; i++) {
|
961
|
-
heap_frame *frame = &stack->frames[i];
|
962
|
-
ruby_xfree(frame->name);
|
963
|
-
ruby_xfree(frame->filename);
|
964
|
-
}
|
965
|
-
ruby_xfree(stack);
|
892
|
+
return stack;
|
966
893
|
}
|
967
894
|
|
968
|
-
|
969
|
-
|
970
|
-
st_index_t hash = seed;
|
971
|
-
for (uint64_t i = 0; i < stack->frames_len; i++) {
|
972
|
-
hash = heap_frame_hash(&stack->frames[i], hash);
|
973
|
-
}
|
974
|
-
return hash;
|
975
|
-
}
|
895
|
+
void heap_record_free(heap_recorder *recorder, heap_record *stack) {
|
896
|
+
ddog_prof_ManagedStringId *ids = recorder->reusable_ids;
|
976
897
|
|
977
|
-
//
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
hash = ddog_location_hash(locations.ptr[i], hash);
|
898
|
+
// Put all the ids in the same array; doesn't really matter the order
|
899
|
+
for (u_int16_t i = 0; i < stack->frames_len; i++) {
|
900
|
+
ids[i] = stack->frames[i].filename;
|
901
|
+
ids[i + stack->frames_len] = stack->frames[i].name;
|
982
902
|
}
|
983
|
-
|
984
|
-
}
|
903
|
+
unintern_all_or_raise(recorder, (ddog_prof_Slice_ManagedStringId) { .ptr = ids, .len = stack->frames_len * 2 });
|
985
904
|
|
986
|
-
//
|
987
|
-
// Heap Record Key API
|
988
|
-
// ===================
|
989
|
-
heap_record_key* heap_record_key_new(heap_stack *stack) {
|
990
|
-
heap_record_key *key = ruby_xmalloc(sizeof(heap_record_key));
|
991
|
-
key->type = HEAP_STACK;
|
992
|
-
key->heap_stack = stack;
|
993
|
-
return key;
|
905
|
+
free(stack); // See "note on calloc vs ruby_xcalloc use" above
|
994
906
|
}
|
995
907
|
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
if (key->type == HEAP_STACK) {
|
1002
|
-
return key->heap_stack->frames_len;
|
1003
|
-
} else {
|
1004
|
-
return key->location_slice->len;
|
1005
|
-
}
|
1006
|
-
}
|
908
|
+
// The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
|
909
|
+
// a big string of bytes and compare it all in one go.
|
910
|
+
int heap_record_cmp_st(st_data_t key1, st_data_t key2) {
|
911
|
+
heap_record *stack1 = (heap_record*) key1;
|
912
|
+
heap_record *stack2 = (heap_record*) key2;
|
1007
913
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
return key->heap_stack->frames[entry_i].line;
|
914
|
+
if (stack1->frames_len != stack2->frames_len) {
|
915
|
+
return ((int) stack1->frames_len) - ((int) stack2->frames_len);
|
1011
916
|
} else {
|
1012
|
-
return
|
917
|
+
return memcmp(stack1->frames, stack2->frames, stack1->frames_len * sizeof(heap_frame));
|
1013
918
|
}
|
1014
919
|
}
|
1015
920
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
char *name = key->heap_stack->frames[entry_i].name;
|
1019
|
-
(*name_ptr) = name;
|
1020
|
-
return strlen(name);
|
1021
|
-
} else {
|
1022
|
-
ddog_CharSlice name = key->location_slice->ptr[entry_i].function.name;
|
1023
|
-
(*name_ptr) = name.ptr;
|
1024
|
-
return name.len;
|
1025
|
-
}
|
1026
|
-
}
|
921
|
+
// Initial seed for hash function, same as Ruby uses
|
922
|
+
#define FNV1_32A_INIT 0x811c9dc5
|
1027
923
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
} else {
|
1034
|
-
ddog_CharSlice filename = key->location_slice->ptr[entry_i].function.filename;
|
1035
|
-
(*filename_ptr) = filename.ptr;
|
1036
|
-
return filename.len;
|
1037
|
-
}
|
924
|
+
// The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
|
925
|
+
// a big string of bytes and hash it all in one go.
|
926
|
+
st_index_t heap_record_hash_st(st_data_t key) {
|
927
|
+
heap_record *stack = (heap_record*) key;
|
928
|
+
return st_hash(stack->frames, stack->frames_len * sizeof(heap_frame), FNV1_32A_INIT);
|
1038
929
|
}
|
1039
930
|
|
1040
|
-
|
1041
|
-
|
1042
|
-
heap_record_key *key_record2 = (heap_record_key*) key2;
|
931
|
+
static void unintern_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
|
932
|
+
if (id.value == 0) return; // Empty string, nothing to do
|
1043
933
|
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
if (key_record1_len != key_record2_len) {
|
1049
|
-
return ((int) key_record1_len) - ((int) key_record2_len);
|
934
|
+
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id);
|
935
|
+
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
936
|
+
rb_raise(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
1050
937
|
}
|
938
|
+
}
|
1051
939
|
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
size_t line2 = heap_record_key_entry_line(key_record2, i);
|
1057
|
-
if (line1 != line2) {
|
1058
|
-
return ((int) line1) - ((int)line2);
|
1059
|
-
}
|
1060
|
-
|
1061
|
-
// Then come names, they are usually smaller than filenames
|
1062
|
-
const char *name1, *name2;
|
1063
|
-
size_t name1_len = heap_record_key_entry_name(key_record1, i, &name1);
|
1064
|
-
size_t name2_len = heap_record_key_entry_name(key_record2, i, &name2);
|
1065
|
-
if (name1_len != name2_len) {
|
1066
|
-
return ((int) name1_len) - ((int) name2_len);
|
1067
|
-
}
|
1068
|
-
int name_cmp_result = strncmp(name1, name2, name1_len);
|
1069
|
-
if (name_cmp_result != 0) {
|
1070
|
-
return name_cmp_result;
|
1071
|
-
}
|
1072
|
-
|
1073
|
-
// Then come filenames
|
1074
|
-
const char *filename1, *filename2;
|
1075
|
-
int64_t filename1_len = heap_record_key_entry_filename(key_record1, i, &filename1);
|
1076
|
-
int64_t filename2_len = heap_record_key_entry_filename(key_record2, i, &filename2);
|
1077
|
-
if (filename1_len != filename2_len) {
|
1078
|
-
return ((int) filename1_len) - ((int) filename2_len);
|
1079
|
-
}
|
1080
|
-
int filename_cmp_result = strncmp(filename1, filename2, filename1_len);
|
1081
|
-
if (filename_cmp_result != 0) {
|
1082
|
-
return filename_cmp_result;
|
1083
|
-
}
|
940
|
+
static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids) {
|
941
|
+
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids);
|
942
|
+
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
943
|
+
rb_raise(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
1084
944
|
}
|
1085
|
-
|
1086
|
-
// If we survived the above for, then everything matched
|
1087
|
-
return 0;
|
1088
945
|
}
|
1089
946
|
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
heap_record_key *record_key = (heap_record_key*) key;
|
1095
|
-
if (record_key->type == HEAP_STACK) {
|
1096
|
-
return heap_stack_hash(record_key->heap_stack, FNV1_32A_INIT);
|
1097
|
-
} else {
|
1098
|
-
return ddog_location_slice_hash(*record_key->location_slice, FNV1_32A_INIT);
|
947
|
+
static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
|
948
|
+
ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id);
|
949
|
+
if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) {
|
950
|
+
rb_raise(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err));
|
1099
951
|
}
|
952
|
+
VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message);
|
953
|
+
ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok);
|
954
|
+
|
955
|
+
return ruby_string;
|
1100
956
|
}
|
1101
957
|
|
1102
958
|
static inline double ewma_stat(double previous, double current) {
|
@@ -1120,3 +976,23 @@ void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder) {
|
|
1120
976
|
|
1121
977
|
heap_recorder->last_update_ns = 0;
|
1122
978
|
}
|
979
|
+
|
980
|
+
void heap_recorder_testonly_benchmark_intern(heap_recorder *heap_recorder, ddog_CharSlice string, int times, bool use_all) {
|
981
|
+
if (heap_recorder == NULL) rb_raise(rb_eArgError, "heap profiling must be enabled");
|
982
|
+
if (times > REUSABLE_FRAME_DETAILS_SIZE) rb_raise(rb_eArgError, "times cannot be > than REUSABLE_FRAME_DETAILS_SIZE");
|
983
|
+
|
984
|
+
if (use_all) {
|
985
|
+
ddog_CharSlice *strings = heap_recorder->reusable_char_slices;
|
986
|
+
|
987
|
+
for (int i = 0; i < times; i++) strings[i] = string;
|
988
|
+
|
989
|
+
intern_all_or_raise(
|
990
|
+
heap_recorder->string_storage,
|
991
|
+
(ddog_prof_Slice_CharSlice) { .ptr = strings, .len = times },
|
992
|
+
heap_recorder->reusable_ids,
|
993
|
+
times
|
994
|
+
);
|
995
|
+
} else {
|
996
|
+
for (int i = 0; i < times; i++) intern_or_raise(heap_recorder->string_storage, string);
|
997
|
+
}
|
998
|
+
}
|