-
Notifications
You must be signed in to change notification settings - Fork 377
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PROF-8667] Heap Profiling - Part 1 - Setup (#3281)
This commit paves the way for the introduction of heap profiling functionality by: * Introduction of a setting for controlling heap profiling (`DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED`, false by default). * Refactoring of settings related to allocation profiling (`DD_PROFILING_EXPERIMENTAL_ALLOCATION_ENABLED` and `DD_PROFILING_EXPERIMENTAL_ALLOCATION_SAMPLE_RATE`) and improving the warnings on broken rubies. * As a result of this refactoring, allocation counting is now tied with allocation profiling and can no longer be enabled stand-alone. * Introduction of a heap recorder component (with noop implementation for now) in the native profiling extension and plugging it in on top of the existing allocation profiling functionality. * Interaction with the heap recorder component to collect new `heap-live-samples` data at profile serialization time. * The necessary tests to have coverage for this functionality (some marked as pending until the proper implementation is added) Future commits will gradually build on the heap recorder implementation to actually enable heap data collection.
- Loading branch information
Showing
22 changed files
with
1,063 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
#include "heap_recorder.h" | ||
#include <pthread.h> | ||
#include "ruby/st.h" | ||
#include "ruby/util.h" | ||
#include "ruby_helpers.h" | ||
#include <errno.h> | ||
|
||
// Allows storing data passed to ::start_heap_allocation_recording to make it accessible to | ||
// ::end_heap_allocation_recording. | ||
// | ||
// obj != Qnil flags this struct as holding a valid partial heap recording. | ||
typedef struct { | ||
VALUE obj; | ||
live_object_data object_data; | ||
} partial_heap_recording; | ||
|
||
struct heap_recorder { | ||
// Data for a heap recording that was started but not yet ended | ||
partial_heap_recording active_recording; | ||
}; | ||
|
||
// ========================== | ||
// Heap Recorder External API | ||
// | ||
// WARN: All these APIs should support receiving a NULL heap_recorder, resulting in a noop. | ||
// | ||
// WARN: Except for ::heap_recorder_for_each_live_object, we always assume interaction with these APIs | ||
// happens under the GVL. | ||
// | ||
// ========================== | ||
heap_recorder* heap_recorder_new(void) { | ||
heap_recorder* recorder = ruby_xmalloc(sizeof(heap_recorder)); | ||
|
||
recorder->active_recording = (partial_heap_recording) { | ||
.obj = Qnil, | ||
.object_data = {0}, | ||
}; | ||
|
||
return recorder; | ||
} | ||
|
||
void heap_recorder_free(struct heap_recorder* recorder) { | ||
if (recorder == NULL) { | ||
return; | ||
} | ||
|
||
ruby_xfree(recorder); | ||
} | ||
|
||
// TODO: Remove when things get implemented | ||
#pragma GCC diagnostic push | ||
#pragma GCC diagnostic ignored "-Wunused-parameter" | ||
|
||
void heap_recorder_after_fork(heap_recorder *heap_recorder) { | ||
if (heap_recorder == NULL) { | ||
return; | ||
} | ||
|
||
// TODO: Implement | ||
} | ||
|
||
void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight) { | ||
if (heap_recorder == NULL) { | ||
return; | ||
} | ||
|
||
heap_recorder->active_recording = (partial_heap_recording) { | ||
.obj = new_obj, | ||
.object_data = (live_object_data) { | ||
.weight = weight, | ||
}, | ||
}; | ||
} | ||
|
||
void end_heap_allocation_recording(struct heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) { | ||
if (heap_recorder == NULL) { | ||
return; | ||
} | ||
|
||
partial_heap_recording *active_recording = &heap_recorder->active_recording; | ||
|
||
VALUE new_obj = active_recording->obj; | ||
if (new_obj == Qnil) { | ||
// Recording ended without having been started? | ||
rb_raise(rb_eRuntimeError, "Ended a heap recording that was not started"); | ||
} | ||
|
||
// From now on, mark active recording as invalid so we can short-circuit at any point and | ||
// not end up with a still active recording. new_obj still holds the object for this recording | ||
active_recording->obj = Qnil; | ||
|
||
// TODO: Implement | ||
} | ||
|
||
void heap_recorder_flush(heap_recorder *heap_recorder) { | ||
if (heap_recorder == NULL) { | ||
return; | ||
} | ||
|
||
// TODO: Implement | ||
} | ||
|
||
// WARN: If with_gvl = False, NO HEAP ALLOCATIONS, EXCEPTIONS or RUBY CALLS ARE ALLOWED. | ||
void heap_recorder_for_each_live_object( | ||
heap_recorder *heap_recorder, | ||
bool (*for_each_callback)(heap_recorder_iteration_data stack_data, void *extra_arg), | ||
void *for_each_callback_extra_arg, | ||
bool with_gvl) { | ||
if (heap_recorder == NULL) { | ||
return; | ||
} | ||
|
||
// TODO: Implement | ||
} | ||
|
||
// TODO: Remove when things get implemented | ||
#pragma GCC diagnostic pop |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#pragma once | ||
|
||
#include <datadog/profiling.h> | ||
#include <ruby.h> | ||
|
||
// A heap recorder keeps track of a collection of live heap objects. | ||
// | ||
// All allocations observed by this recorder for which a corresponding free was | ||
// not yet observed are deemed as alive and can be iterated on to produce a | ||
// live heap profile. | ||
// | ||
// NOTE: All public APIs of heap_recorder support receiving a NULL heap_recorder | ||
// in which case the behaviour will be a noop. | ||
// | ||
// WARN: Unless otherwise stated the heap recorder APIs assume calls are done | ||
// under the GVL. | ||
typedef struct heap_recorder heap_recorder; | ||
|
||
// Extra data associated with each live object being tracked. | ||
typedef struct live_object_data { | ||
// The weight of this object from a sampling perspective. | ||
// | ||
// A notion of weight is preserved for each tracked object to allow for an approximate | ||
// extrapolation to an unsampled view. | ||
// | ||
// Example: If we were sampling every 50 objects, then each sampled object | ||
// could be seen as being representative of 50 objects. | ||
unsigned int weight; | ||
} live_object_data; | ||
|
||
// Data that is made available to iterators of heap recorder data for each live object | ||
// tracked therein. | ||
typedef struct { | ||
ddog_prof_Slice_Location locations; | ||
live_object_data object_data; | ||
} heap_recorder_iteration_data; | ||
|
||
// Initialize a new heap recorder. | ||
heap_recorder* heap_recorder_new(void); | ||
|
||
// Free a previously initialized heap recorder. | ||
void heap_recorder_free(heap_recorder *heap_recorder); | ||
|
||
// Do any cleanup needed after forking. | ||
void heap_recorder_after_fork(heap_recorder *heap_recorder); | ||
|
||
// Start a heap allocation recording on the heap recorder for a new object. | ||
// | ||
// This heap allocation recording needs to be ended via ::end_heap_allocation_recording | ||
// before it will become fully committed and able to be iterated on. | ||
// | ||
// @param new_obj | ||
// The newly allocated Ruby object/value. | ||
// @param weight | ||
// The sampling weight of this object. | ||
// | ||
// WARN: It needs to be paired with a ::end_heap_allocation_recording call. | ||
void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight); | ||
|
||
// End a previously started heap allocation recording on the heap recorder. | ||
// | ||
// It is at this point that an allocated object will become fully tracked and able to be iterated on. | ||
// | ||
// @param locations The stacktrace representing the location of the allocation. | ||
// | ||
// WARN: It is illegal to call this without previously having called ::start_heap_allocation_recording. | ||
void end_heap_allocation_recording(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations); | ||
|
||
// Flush any intermediate state that might be queued inside the heap recorder. | ||
// | ||
// NOTE: This should usually be called before iteration to ensure data is as little stale as possible. | ||
void heap_recorder_flush(heap_recorder *heap_recorder); | ||
|
||
// Iterate over each live object being tracked by the heap recorder. | ||
// | ||
// @param for_each_callback | ||
// A callback function that shall be called for each live object being tracked | ||
// by the heap recorder. Alongside the iteration_data for each live object, | ||
// a second argument will be forwarded with the contents of the optional | ||
// for_each_callback_extra_arg. Iteration will continue until the callback | ||
// returns false or we run out of objects. | ||
// @param for_each_callback_extra_arg | ||
// Optional (NULL if empty) extra data that should be passed to the | ||
// callback function alongside the data for each live tracked object. | ||
// @param with_gvl | ||
// True if we're calling this while holding the GVL, false otherwise. | ||
void heap_recorder_for_each_live_object( | ||
heap_recorder *heap_recorder, | ||
bool (*for_each_callback)(heap_recorder_iteration_data data, void* extra_arg), | ||
void *for_each_callback_extra_arg, | ||
bool with_gvl); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.