Skip to content

Commit 03260bc

Browse files
committed
adding release note, removing nframe, refactoring code and resolving unused checks
1 parent 1eb2dca commit 03260bc

File tree

7 files changed

+77
-86
lines changed

7 files changed

+77
-86
lines changed

ddtrace/profiling/collector/_memalloc.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ from .. import event
66
FrameType = event.DDFrame
77
StackType = event.StackTraceType
88

9-
# (stack, nframe, thread_id)
10-
TracebackType = typing.Tuple[StackType, int, int]
9+
# (stack, thread_id)
10+
TracebackType = typing.Tuple[StackType, int]
1111

1212
def start(max_nframe: int, max_events: int, heap_sample_size: int) -> None: ...
1313
def stop() -> None: ...

ddtrace/profiling/collector/_memalloc_heap.c

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ typedef struct
8080
ptr_array_t frees;
8181
} freezer;
8282
/* List of freed samples that haven't been reported yet */
83-
traceback_array_t allocation_list;
83+
traceback_array_t unreported_samples;
8484

8585
/* Debug guard to assert that GIL-protected critical sections are maintained
8686
* while accessing the profiler's state */
@@ -111,7 +111,7 @@ heap_tracker_init(heap_tracker_t* heap_tracker)
111111
heap_tracker->allocs_m = memalloc_heap_map_new();
112112
heap_tracker->freezer.allocs_m = memalloc_heap_map_new();
113113
ptr_array_init(&heap_tracker->freezer.frees);
114-
traceback_array_init(&heap_tracker->allocation_list);
114+
traceback_array_init(&heap_tracker->unreported_samples);
115115
heap_tracker->allocated_memory = 0;
116116
heap_tracker->frozen = false;
117117
heap_tracker->sample_size = 0;
@@ -125,7 +125,7 @@ heap_tracker_wipe(heap_tracker_t* heap_tracker)
125125
memalloc_heap_map_delete(heap_tracker->allocs_m);
126126
memalloc_heap_map_delete(heap_tracker->freezer.allocs_m);
127127
ptr_array_wipe(&heap_tracker->freezer.frees);
128-
traceback_array_wipe(&heap_tracker->allocation_list);
128+
traceback_array_wipe(&heap_tracker->unreported_samples);
129129
}
130130

131131
static void
@@ -220,7 +220,7 @@ memalloc_heap_untrack_no_cpython(heap_tracker_t* heap_tracker, void* ptr)
220220
traceback_t* tb = memalloc_heap_map_remove(heap_tracker->allocs_m, ptr);
221221
if (tb && !tb->reported) {
222222
/* If the sample hasn't been reported yet, add it to the allocation list */
223-
traceback_array_append(&heap_tracker->allocation_list, tb);
223+
traceback_array_append(&heap_tracker->unreported_samples, tb);
224224
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
225225
return NULL;
226226
}
@@ -352,6 +352,36 @@ memalloc_heap_track(uint16_t max_nframe, void* ptr, size_t size, PyMemAllocatorD
352352
memalloc_yield_guard();
353353
}
354354

355+
PyObject*
356+
memalloc_sample_to_tuple(traceback_t* tb, bool is_live)
357+
{
358+
PyObject* tb_and_info = PyTuple_New(4);
359+
if (tb_and_info == NULL) {
360+
return NULL;
361+
}
362+
363+
size_t in_use_size;
364+
size_t alloc_size;
365+
366+
if (is_live) {
367+
/* alloc_size tracks new allocations since the last heap snapshot. Once
368+
* we report it (tb->reported == true), we set the value to 0 to avoid
369+
* double-counting allocations across multiple snapshots. */
370+
in_use_size = tb->size;
371+
alloc_size = tb->reported ? 0 : tb->size;
372+
} else {
373+
in_use_size = 0;
374+
alloc_size = tb->size;
375+
}
376+
377+
PyTuple_SET_ITEM(tb_and_info, 0, traceback_to_tuple(tb));
378+
PyTuple_SET_ITEM(tb_and_info, 1, PyLong_FromSize_t(in_use_size));
379+
PyTuple_SET_ITEM(tb_and_info, 2, PyLong_FromSize_t(alloc_size));
380+
PyTuple_SET_ITEM(tb_and_info, 3, PyLong_FromSize_t(tb->count));
381+
382+
return tb_and_info;
383+
}
384+
355385
PyObject*
356386
memalloc_heap(void)
357387
{
@@ -364,7 +394,7 @@ memalloc_heap(void)
364394

365395
/* Calculate total number of samples: live + freed */
366396
size_t live_count = memalloc_heap_map_size(global_heap_tracker.allocs_m);
367-
size_t freed_count = global_heap_tracker.allocation_list.count;
397+
size_t freed_count = global_heap_tracker.unreported_samples.count;
368398
size_t total_count = live_count + freed_count;
369399

370400
PyObject* heap_list = PyList_New(total_count);
@@ -377,77 +407,43 @@ memalloc_heap(void)
377407

378408
/* First, iterate over live samples using the new iterator API */
379409
memalloc_heap_map_iter_t* it = memalloc_heap_map_iter_new(global_heap_tracker.allocs_m);
380-
if (it) {
381-
void* key;
382-
traceback_t* tb;
383-
384-
while (memalloc_heap_map_iter_next(it, &key, &tb)) {
385-
if (list_index >= total_count) {
386-
break;
387-
}
388-
389-
PyObject* tb_and_info = PyTuple_New(4);
390-
if (tb_and_info == NULL) {
391-
continue;
392-
}
393-
394-
size_t in_use_size = tb->size;
395-
size_t alloc_size = tb->reported ? 0 : tb->size;
410+
if (it == NULL) {
411+
// TODO: return
412+
}
396413

397-
PyTuple_SET_ITEM(tb_and_info, 0, traceback_to_tuple(tb));
398-
PyTuple_SET_ITEM(tb_and_info, 1, PyLong_FromSize_t(in_use_size));
399-
PyTuple_SET_ITEM(tb_and_info, 2, PyLong_FromSize_t(alloc_size));
400-
PyTuple_SET_ITEM(tb_and_info, 3, PyLong_FromSize_t(tb->count));
414+
void* key;
415+
traceback_t* tb;
401416

402-
PyList_SET_ITEM(heap_list, list_index, tb_and_info);
403-
list_index++;
417+
while (memalloc_heap_map_iter_next(it, &key, &tb)) {
418+
PyObject* tb_and_info = memalloc_sample_to_tuple(tb, true);
404419

405-
/* Mark as reported */
406-
tb->reported = true;
407-
}
420+
PyList_SET_ITEM(heap_list, list_index, tb_and_info);
421+
list_index++;
408422

409-
memalloc_heap_map_iter_delete(it);
423+
/* Mark as reported */
424+
tb->reported = true;
410425
}
411426

412-
/* Second, iterate over freed samples from allocation_list */
413-
for (size_t i = 0; i < global_heap_tracker.allocation_list.count; i++) {
414-
if (list_index >= total_count) {
415-
break;
416-
}
427+
memalloc_heap_map_iter_delete(it);
417428

418-
traceback_t* tb = global_heap_tracker.allocation_list.tab[i];
429+
/* Second, iterate over freed samples from unreported_samples */
430+
for (size_t i = 0; i < global_heap_tracker.unreported_samples.count; i++) {
431+
traceback_t* tb = global_heap_tracker.unreported_samples.tab[i];
419432

420-
PyObject* tb_and_info = PyTuple_New(4);
421-
if (tb_and_info == NULL) {
422-
continue;
423-
}
424-
425-
size_t in_use_size = 0;
426-
size_t alloc_size = tb->size;
427-
428-
PyTuple_SET_ITEM(tb_and_info, 0, traceback_to_tuple(tb));
429-
PyTuple_SET_ITEM(tb_and_info, 1, PyLong_FromSize_t(in_use_size));
430-
PyTuple_SET_ITEM(tb_and_info, 2, PyLong_FromSize_t(alloc_size));
431-
PyTuple_SET_ITEM(tb_and_info, 3, PyLong_FromSize_t(tb->count));
433+
PyObject* tb_and_info = memalloc_sample_to_tuple(tb, false);
432434

433435
PyList_SET_ITEM(heap_list, list_index, tb_and_info);
434436
list_index++;
435437
}
436438

437-
if (list_index < total_count) {
438-
if (PyList_SetSlice(heap_list, list_index, total_count, NULL) < 0) {
439-
PyErr_Clear();
440-
}
441-
}
442-
443-
/* Free all tracebacks in allocation_list after reporting them */
444-
for (size_t i = 0; i < global_heap_tracker.allocation_list.count; i++) {
445-
if (global_heap_tracker.allocation_list.tab[i] != NULL) {
446-
traceback_free(global_heap_tracker.allocation_list.tab[i]);
439+
/* Free all tracebacks in unreported_samples after reporting them */
440+
for (size_t i = 0; i < global_heap_tracker.unreported_samples.count; i++) {
441+
if (global_heap_tracker.unreported_samples.tab[i] != NULL) {
442+
traceback_free(global_heap_tracker.unreported_samples.tab[i]);
447443
}
448444
}
449445
/* Reset the count to 0 so we can reuse the memory */
450-
global_heap_tracker.allocation_list.count = 0;
446+
global_heap_tracker.unreported_samples.count = 0;
451447

452448
heap_tracker_thaw(&global_heap_tracker);
453449

ddtrace/profiling/collector/_memalloc_tb.c

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ memalloc_get_traceback(uint16_t max_nframe,
253253
void* ptr,
254254
size_t size,
255255
PyMemAllocatorDomain domain,
256-
size_t allocated_memory)
256+
size_t weighted_size)
257257
{
258258
PyThreadState* tstate = PyThreadState_Get();
259259

@@ -274,7 +274,7 @@ memalloc_get_traceback(uint16_t max_nframe,
274274
if (traceback == NULL)
275275
return NULL;
276276

277-
traceback->size = allocated_memory;
277+
traceback->size = weighted_size;
278278
traceback->ptr = ptr;
279279

280280
traceback->thread_id = PyThread_get_thread_ident();
@@ -283,15 +283,8 @@ memalloc_get_traceback(uint16_t max_nframe,
283283

284284
traceback->reported = false;
285285

286-
if (size > 0) {
287-
double scaled_count = ((double)allocated_memory) / ((double)size);
288-
traceback->count = (size_t)scaled_count;
289-
if (traceback->count == 0) {
290-
traceback->count = 1;
291-
}
292-
} else {
293-
traceback->count = 1;
294-
}
286+
double scaled_count = ((double)weighted_size) / ((double)size);
287+
traceback->count = (size_t)scaled_count;
295288

296289
return traceback;
297290
}
@@ -331,9 +324,8 @@ traceback_to_tuple(traceback_t* tb)
331324
PyTuple_SET_ITEM(stack, nframe, frame_tuple);
332325
}
333326

334-
PyObject* tuple = PyTuple_New(3);
327+
PyObject* tuple = PyTuple_New(2);
335328
PyTuple_SET_ITEM(tuple, 0, stack);
336-
PyTuple_SET_ITEM(tuple, 1, PyLong_FromUnsignedLong(tb->total_nframe));
337-
PyTuple_SET_ITEM(tuple, 2, PyLong_FromUnsignedLong(tb->thread_id));
329+
PyTuple_SET_ITEM(tuple, 1, PyLong_FromUnsignedLong(tb->thread_id));
338330
return tuple;
339331
}

ddtrace/profiling/collector/memalloc.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
MemorySample = namedtuple(
2525
"MemorySample",
26-
("frames", "size", "count", "in_use_size", "alloc_size", "nframe", "thread_id"),
26+
("frames", "size", "count", "in_use_size", "alloc_size", "thread_id"),
2727
)
2828

2929

@@ -73,7 +73,7 @@ def on_shutdown():
7373
try:
7474
_memalloc.stop()
7575
except RuntimeError:
76-
LOG.debug("Failed to stop memalloc on shutdown", exc_info=True)
76+
LOG.debug("Failed to stop memalloc profiling on shutdown", exc_info=True)
7777

7878
def _get_thread_id_ignore_set(self):
7979
# type: () -> typing.Set[int]
@@ -96,7 +96,7 @@ def snapshot(self):
9696
return tuple()
9797

9898
for event in events:
99-
(frames, _, thread_id), in_use_size, alloc_size, count = event
99+
(frames, thread_id), in_use_size, alloc_size, count = event
100100

101101
if not self.ignore_profiler or thread_id not in thread_id_ignore_set:
102102
handle = ddup.SampleHandle()
@@ -133,12 +133,12 @@ def test_snapshot(self):
133133

134134
samples = []
135135
for event in events:
136-
(frames, nframe, thread_id), in_use_size, alloc_size, count = event
136+
(frames, thread_id), in_use_size, alloc_size, count = event
137137

138138
if not self.ignore_profiler or thread_id not in thread_id_ignore_set:
139139
size = in_use_size if in_use_size > 0 else alloc_size
140140

141-
samples.append(MemorySample(frames, size, count, in_use_size, alloc_size, nframe, thread_id))
141+
samples.append(MemorySample(frames, size, count, in_use_size, alloc_size, thread_id))
142142

143143
return tuple(samples)
144144

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
other:
3+
- |
4+
profiling: removed redundant sampling code from memory profile, improving overhead and accuracy.
5+
Sizes and counts of objects allocated since the last profile are now reported more accurately.
6+
ENV: DD_PROFILING_MAX_EVENTS is deprecated and does nothing. Use DD_PROFILING_HEAP_SAMPLE_SIZE
7+
to control sampling frequency of the memory profiler.

tests/profiling/collector/test_memalloc.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,10 @@ def test_iter_events():
8484
object_count = 0
8585
for sample in alloc_samples:
8686
stack = sample.frames
87-
nframe = sample.nframe
8887
thread_id = sample.thread_id
8988
size = sample.alloc_size
9089

9190
assert 0 < len(stack) <= max_nframe
92-
assert nframe >= len(stack)
9391
assert size >= 1 # size depends on the object size
9492
last_call = stack[0]
9593
if last_call == DDFrame(
@@ -158,12 +156,10 @@ def test_iter_events_multi_thread():
158156
count_thread = 0
159157
for sample in alloc_samples:
160158
stack = sample.frames
161-
nframe = sample.nframe
162159
thread_id = sample.thread_id
163160
size = sample.alloc_size
164161

165162
assert 0 < len(stack) <= max_nframe
166-
assert nframe >= len(stack)
167163
assert size >= 1 # size depends on the object size
168164
last_call = stack[0]
169165
if last_call == DDFrame(

tests/profiling_v2/collector/test_memalloc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def __init__(self, count, size):
197197
def get_heap_info(heap, funcs):
198198
got = {}
199199
for event in heap:
200-
(frames, _, _), in_use_size, alloc_size, count = event
200+
(frames, _), in_use_size, alloc_size, count = event
201201

202202
in_use = in_use_size > 0
203203
size = in_use_size if in_use_size > 0 else alloc_size

0 commit comments

Comments
 (0)