Skip to content

Commit c5254d1

Browse files
committed
merged main
2 parents ddecdc2 + d0950d3 commit c5254d1

File tree

20 files changed

+568
-1244
lines changed

20 files changed

+568
-1244
lines changed

ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ class Sample
8282
std::vector<int64_t> values = {};
8383

8484
// Additional metadata
85-
int64_t endtime_ns = 0; // end of the event
85+
int64_t endtime_ns = 0; // end of the event
86+
bool reverse_locations = false; // whether to reverse locations when exporting/flushing
8687

8788
// Backing memory for string copies
8889
internal::StringArena string_storage{};
@@ -101,6 +102,8 @@ class Sample
101102
bool push_release(int64_t lock_time, int64_t count);
102103
bool push_alloc(int64_t size, int64_t count);
103104
bool push_heap(int64_t size);
105+
void reset_alloc();
106+
void reset_heap();
104107
bool push_gpu_gputime(int64_t time, int64_t count);
105108
bool push_gpu_memory(int64_t size, int64_t count);
106109
bool push_gpu_flops(int64_t flops, int64_t count);
@@ -132,8 +135,15 @@ class Sample
132135
int64_t line // for ddog_prof_Location
133136
);
134137

138+
// Set whether to reverse locations when exporting/flushing
139+
void set_reverse_locations(bool reverse) { reverse_locations = reverse; }
140+
135141
// Flushes the current buffer, clearing it
136-
bool flush_sample(bool reverse_locations = false);
142+
bool flush_sample();
143+
144+
// Exports the sample data to the profile without clearing buffers
145+
// This is useful when the Sample object is embedded and will be destroyed later
146+
bool export_sample();
137147

138148
static ProfileBorrow profile_borrow();
139149
static void postfork_child();

ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ ddup_flush_sample(Datadog::Sample* sample) // cppcheck-suppress unusedFunction
332332
void
333333
ddup_flush_sample_v2(Datadog::Sample* sample) // cppcheck-suppress unusedFunction
334334
{
335-
sample->flush_sample(/*reverse_locations*/ true);
335+
sample->set_reverse_locations(true);
336+
sample->flush_sample();
336337
}
337338

338339
void

ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,25 @@ Datadog::Sample::clear_buffers()
132132
labels.clear();
133133
locations.clear();
134134
dropped_frames = 0;
135+
reverse_locations = false;
135136
string_storage.reset();
136137
}
137138

138139
bool
139-
Datadog::Sample::flush_sample(bool reverse_locations)
140+
Datadog::Sample::flush_sample()
140141
{
142+
// Export the sample data (export_sample handles dropped frames)
143+
auto ret = export_sample();
144+
145+
// Clear buffers after exporting
146+
clear_buffers();
147+
return ret;
148+
}
149+
150+
bool
151+
Datadog::Sample::export_sample()
152+
{
153+
// Handle dropped frames by adding them to locations if needed
141154
if (dropped_frames > 0) {
142155
const std::string name =
143156
"<" + std::to_string(dropped_frames) + " frame" + (1 == dropped_frames ? "" : "s") + " omitted>";
@@ -146,6 +159,7 @@ Datadog::Sample::flush_sample(bool reverse_locations)
146159

147160
if (reverse_locations) {
148161
std::reverse(locations.begin(), locations.end());
162+
reverse_locations = false; // Reset after reversing
149163
}
150164

151165
const ddog_prof_Sample sample = {
@@ -154,9 +168,8 @@ Datadog::Sample::flush_sample(bool reverse_locations)
154168
.labels = { labels.data(), labels.size() },
155169
};
156170

157-
const bool ret = profile_state.collect(sample, endtime_ns);
158-
clear_buffers();
159-
return ret;
171+
// Export to profile without clearing buffers
172+
return profile_state.collect(sample, endtime_ns);
160173
}
161174

162175
bool
@@ -292,6 +305,30 @@ Datadog::Sample::push_heap(int64_t size)
292305
return false;
293306
}
294307

308+
void
309+
Datadog::Sample::reset_alloc()
310+
{
311+
if (0U != (type_mask & SampleType::Allocation)) {
312+
const size_t alloc_space_idx = profile_state.val().alloc_space;
313+
const size_t alloc_count_idx = profile_state.val().alloc_count;
314+
if (alloc_space_idx < values.size() && alloc_count_idx < values.size()) {
315+
values[alloc_space_idx] = 0;
316+
values[alloc_count_idx] = 0;
317+
}
318+
}
319+
}
320+
321+
void
322+
Datadog::Sample::reset_heap()
323+
{
324+
if (0U != (type_mask & SampleType::Heap)) {
325+
const size_t heap_space_idx = profile_state.val().heap_space;
326+
if (heap_space_idx < values.size()) {
327+
values[heap_space_idx] = 0;
328+
}
329+
}
330+
}
331+
295332
bool
296333
Datadog::Sample::push_gpu_gputime(int64_t time, int64_t count)
297334
{

ddtrace/profiling/collector/CMakeLists.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ if(NOT TARGET Python3::Python)
5555
endif()
5656

5757
# Source files for the extension
58-
set(SOURCE_FILES _memalloc.cpp _memalloc_tb.cpp _memalloc_heap.cpp _memalloc_reentrant.cpp _memalloc_heap_map.cpp)
58+
set(SOURCE_FILES _memalloc.cpp _memalloc_tb.cpp _memalloc_heap.cpp _memalloc_reentrant.cpp)
5959

6060
# Get the extension name from setup.py or use default Note: EXTENSION_NAME from setup.py already includes the full
6161
# suffix
@@ -78,8 +78,10 @@ endif()
7878

7979
# Include directories
8080
target_include_directories(
81-
${FULL_EXTENSION_NAME} PRIVATE ${Python3_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}
82-
${CMAKE_CURRENT_SOURCE_DIR}/../../internal/datadog/profiling/dd_wrapper/include)
81+
${FULL_EXTENSION_NAME}
82+
PRIVATE ${Python3_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}
83+
${CMAKE_CURRENT_SOURCE_DIR}/../../internal/datadog/profiling/dd_wrapper/include
84+
${RUST_GENERATED_HEADERS_DIR})
8385

8486
# Link libraries Python3::Python target might not exist in all build environments (e.g., manylinux) Python modules
8587
# should use -undefined dynamic_lookup on macOS and not link to libpython on Linux

ddtrace/profiling/collector/_memalloc.cpp

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#include "_memalloc_tb.h"
1212
#include "_pymacro.h"
1313

14+
// Ensure profile_state is initialized before creating Sample objects
15+
#include "../../internal/datadog/profiling/dd_wrapper/include/ddup_interface.hpp"
16+
1417
typedef struct
1518
{
1619
PyMemAllocatorEx pymem_allocator_obj;
@@ -40,7 +43,7 @@ memalloc_free(void* ctx, void* ptr)
4043
if (ptr == NULL)
4144
return;
4245

43-
memalloc_heap_untrack(ptr);
46+
memalloc_heap_untrack_no_cpython(ptr);
4447

4548
alloc->free(alloc->ctx, ptr);
4649
}
@@ -80,9 +83,9 @@ memalloc_realloc(void* ctx, void* ptr, size_t new_size)
8083
{
8184
memalloc_context_t* memalloc_ctx = (memalloc_context_t*)ctx;
8285
void* ptr2 = memalloc_ctx->pymem_allocator_obj.realloc(memalloc_ctx->pymem_allocator_obj.ctx, ptr, new_size);
83-
86+
// TODO(dsn) is the GIL held here? Can there be a race between the realloc and the untrack of the old value?
8487
if (ptr2) {
85-
memalloc_heap_untrack(ptr);
88+
memalloc_heap_untrack_no_cpython(ptr);
8689
memalloc_heap_track(memalloc_ctx->max_nframe, ptr2, new_size, memalloc_ctx->domain);
8790
}
8891

@@ -108,6 +111,11 @@ memalloc_start(PyObject* Py_UNUSED(module), PyObject* args)
108111
return NULL;
109112
}
110113

114+
// Ensure profile_state is initialized before creating Sample objects
115+
// This initializes the Sample::profile_state which is required for Sample objects to work correctly
116+
// ddup_start() uses std::call_once, so it's safe to call multiple times
117+
ddup_start();
118+
111119
char* val = getenv("_DD_MEMALLOC_DEBUG_RNG_SEED");
112120
if (val) {
113121
/* NB: we don't bother checking whether val is actually a valid integer.
@@ -144,7 +152,7 @@ memalloc_start(PyObject* Py_UNUSED(module), PyObject* args)
144152
PyUnicode_InternInPlace(&object_string);
145153
}
146154

147-
if (!memalloc_heap_tracker_init((uint32_t)heap_sample_size))
155+
if (!memalloc_heap_tracker_init_no_cpython((uint32_t)heap_sample_size))
148156
return NULL;
149157

150158
PyMemAllocatorEx alloc;
@@ -187,7 +195,7 @@ memalloc_stop(PyObject* Py_UNUSED(module), PyObject* Py_UNUSED(args))
187195
* or memalloc_heap. The higher-level collector deals with this. */
188196
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &global_memalloc_ctx.pymem_allocator_obj);
189197

190-
memalloc_heap_tracker_deinit();
198+
memalloc_heap_tracker_deinit_no_cpython();
191199

192200
/* Finally, we know in-progress sampling won't use the buffer pool, so clear it out */
193201
traceback_t::deinit();
@@ -201,7 +209,7 @@ PyDoc_STRVAR(memalloc_heap_py__doc__,
201209
"heap($module, /)\n"
202210
"--\n"
203211
"\n"
204-
"Get the sampled heap representation.\n");
212+
"Export sampled heap allocations to the pprof profile.\n");
205213
static PyObject*
206214
memalloc_heap_py(PyObject* Py_UNUSED(module), PyObject* Py_UNUSED(args))
207215
{
@@ -210,7 +218,8 @@ memalloc_heap_py(PyObject* Py_UNUSED(module), PyObject* Py_UNUSED(args))
210218
return NULL;
211219
}
212220

213-
return memalloc_heap();
221+
memalloc_heap_no_cpython();
222+
Py_RETURN_NONE;
214223
}
215224

216225
static PyMethodDef module_methods[] = { { "start", (PyCFunction)memalloc_start, METH_VARARGS, memalloc_start__doc__ },
@@ -234,11 +243,5 @@ PyInit__memalloc(void)
234243
if (m == NULL)
235244
return NULL;
236245

237-
// Initialize the DDFrame namedtuple class
238-
// Do this early so we don't have complicated cleanup
239-
if (!memalloc_ddframe_class_init()) {
240-
return NULL;
241-
}
242-
243246
return m;
244247
}

0 commit comments

Comments
 (0)