@@ -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
131131static 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+
355385PyObject *
356386memalloc_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
0 commit comments