2929/* Frame buffer: depth varint (max 2 bytes for 256) + 256 frames * 5 bytes/varint + margin */
3030#define MAX_FRAME_BUFFER_SIZE ((MAX_STACK_DEPTH * MAX_VARINT_SIZE_U32) + MAX_VARINT_SIZE_U32 + 16)
3131
32+ /* RLE pending buffer (per thread): one entry is a u64 delta varint + status byte */
33+ #define MAX_RLE_BUF_SIZE (16 * 1024)
34+ #define MAX_RLE_ENTRY_SIZE (MAX_VARINT_SIZE + sizeof(uint8_t))
35+
3236/* Helper macro: convert PyLong to int32, using default_val if conversion fails */
3337#define PYLONG_TO_INT32_OR_DEFAULT (obj , var , default_val ) \
3438 do { \
@@ -511,15 +515,14 @@ writer_get_or_create_thread_entry(BinaryWriter *writer, uint64_t thread_id,
511515 entry -> interpreter_id = interpreter_id ;
512516 entry -> prev_timestamp = writer -> start_time_us ;
513517 entry -> prev_stack_capacity = MAX_STACK_DEPTH ;
514- entry -> pending_rle_capacity = INITIAL_RLE_CAPACITY ;
515518
516519 entry -> prev_stack = PyMem_Calloc (entry -> prev_stack_capacity , sizeof (uint32_t ));
517520 if (!entry -> prev_stack ) {
518521 PyErr_NoMemory ();
519522 return NULL ;
520523 }
521524
522- entry -> pending_rle = PyMem_Malloc (entry -> pending_rle_capacity * sizeof ( PendingRLESample ) );
525+ entry -> pending_rle = PyMem_Malloc (MAX_RLE_BUF_SIZE );
523526 if (!entry -> pending_rle ) {
524527 PyMem_Free (entry -> prev_stack );
525528 PyErr_NoMemory ();
@@ -626,14 +629,9 @@ write_sample_header(BinaryWriter *writer, ThreadEntry *entry, uint8_t encoding)
626629static int
627630flush_pending_rle (BinaryWriter * writer , ThreadEntry * entry )
628631{
629- if (! entry -> has_pending_rle || entry -> pending_rle_count == 0 ) {
632+ if (entry -> pending_rle_samples == 0 ) {
630633 return 0 ;
631634 }
632- if (entry -> pending_rle_count > UINT32_MAX - writer -> total_samples ) {
633- PyErr_SetString (PyExc_OverflowError ,
634- "too many samples for binary format" );
635- return -1 ;
636- }
637635
638636 /* Write RLE record:
639637 * [thread_id: 8] [interpreter_id: 4] [STACK_REPEAT: 1] [count: varint]
@@ -644,31 +642,26 @@ flush_pending_rle(BinaryWriter *writer, ThreadEntry *entry)
644642 return -1 ;
645643 }
646644
647- if (writer_write_varint_u32 (writer , (uint32_t )entry -> pending_rle_count ) < 0 ) {
645+ if (writer_write_varint_u32 (writer , (uint32_t )entry -> pending_rle_samples ) < 0 ) {
648646 return -1 ;
649647 }
650648
651- for (size_t i = 0 ; i < entry -> pending_rle_count ; i ++ ) {
652- if (writer_write_varint_u64 (writer , entry -> pending_rle [i ].timestamp_delta ) < 0 ) {
653- return -1 ;
654- }
655- if (writer_write_bytes (writer , & entry -> pending_rle [i ].status , 1 ) < 0 ) {
656- return -1 ;
657- }
658- writer -> total_samples ++ ;
649+ if (writer_write_bytes (writer , entry -> pending_rle , entry -> pending_rle_bytes ) < 0 ) {
650+ return -1 ;
659651 }
660652
661653 writer -> stats .repeat_records ++ ;
662- writer -> stats .repeat_samples += entry -> pending_rle_count ;
654+ writer -> stats .repeat_samples += entry -> pending_rle_samples ;
663655 /* Each RLE sample saves writing the entire stack */
664- writer -> stats .frames_saved += entry -> pending_rle_count * entry -> prev_stack_depth ;
656+ writer -> stats .frames_saved += entry -> pending_rle_samples * entry -> prev_stack_depth ;
665657
666- entry -> pending_rle_count = 0 ;
667- entry -> has_pending_rle = 0 ;
658+ entry -> pending_rle_bytes = 0 ;
659+ entry -> pending_rle_samples = 0 ;
668660
669661 return 0 ;
670662}
671663
664+
672665/* Write a single sample with the specified encoding.
673666 * Returns 0 on success, -1 on failure. */
674667static int
@@ -678,12 +671,6 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry,
678671 const uint32_t * frame_indices , size_t stack_depth ,
679672 size_t shared_count , size_t pop_count , size_t push_count )
680673{
681- if (writer -> total_samples == UINT32_MAX ) {
682- PyErr_SetString (PyExc_OverflowError ,
683- "too many samples for binary format" );
684- return -1 ;
685- }
686-
687674 /* Header: thread_id(8) + interpreter_id(4) + encoding(1) + delta(varint) + status(1) */
688675 uint8_t header_buf [SAMPLE_HEADER_MAX_SIZE ];
689676 memcpy (header_buf + SMP_OFF_THREAD_ID , & entry -> thread_id , SMP_SIZE_THREAD_ID );
@@ -752,7 +739,6 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry,
752739 }
753740
754741 writer -> stats .total_frames_written += frames_written ;
755- writer -> total_samples ++ ;
756742 return 0 ;
757743}
758744
@@ -955,6 +941,12 @@ static int
955941process_thread_sample (BinaryWriter * writer , PyObject * thread_info ,
956942 uint32_t interpreter_id , uint64_t timestamp_us )
957943{
944+ if (writer -> total_samples >= UINT32_MAX ) {
945+ PyErr_SetString (PyExc_OverflowError ,
946+ "too many samples for binary format" );
947+ return -1 ;
948+ }
949+
958950 PyObject * thread_id_obj = PyStructSequence_GET_ITEM (thread_info , 0 );
959951 PyObject * status_obj = PyStructSequence_GET_ITEM (thread_info , 1 );
960952 PyObject * frame_list = PyStructSequence_GET_ITEM (thread_info , 2 );
@@ -1002,20 +994,18 @@ process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
1002994 * STACK_REPEAT against an empty curr_stack (depth 0). Buffering
1003995 * it here is correct; the RLE flush path emits it as a normal
1004996 * STACK_REPEAT record. */
1005- if (GROW_ARRAY ( entry -> pending_rle , entry -> pending_rle_count ,
1006- entry -> pending_rle_capacity , PendingRLESample ) < 0 ) {
997+ if (entry -> pending_rle_bytes + MAX_RLE_ENTRY_SIZE > MAX_RLE_BUF_SIZE
998+ && flush_pending_rle ( writer , entry ) < 0 ) {
1007999 return -1 ;
10081000 }
1009- entry -> pending_rle [ entry -> pending_rle_count ]. timestamp_delta = delta ;
1010- entry -> pending_rle [ entry -> pending_rle_count ]. status = status ;
1011- entry -> pending_rle_count ++ ;
1012- entry -> has_pending_rle = 1 ;
1001+ entry -> pending_rle_bytes += encode_varint_u64 (
1002+ entry -> pending_rle + entry -> pending_rle_bytes , delta ) ;
1003+ entry -> pending_rle [ entry -> pending_rle_bytes ++ ] = status ;
1004+ entry -> pending_rle_samples ++ ;
10131005 } else {
10141006 /* Stack changed - flush any pending RLE first */
1015- if (entry -> has_pending_rle ) {
1016- if (flush_pending_rle (writer , entry ) < 0 ) {
1017- return -1 ;
1018- }
1007+ if (flush_pending_rle (writer , entry ) < 0 ) {
1008+ return -1 ;
10191009 }
10201010
10211011 if (write_sample_with_encoding (writer , entry , delta , status , encoding ,
@@ -1028,6 +1018,7 @@ process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
10281018 entry -> prev_stack_depth = curr_depth ;
10291019 }
10301020
1021+ writer -> total_samples ++ ;
10311022 return 0 ;
10321023}
10331024
@@ -1075,10 +1066,8 @@ int
10751066binary_writer_finalize (BinaryWriter * writer )
10761067{
10771068 for (size_t i = 0 ; i < writer -> thread_count ; i ++ ) {
1078- if (writer -> thread_entries [i ].has_pending_rle ) {
1079- if (flush_pending_rle (writer , & writer -> thread_entries [i ]) < 0 ) {
1080- return -1 ;
1081- }
1069+ if (flush_pending_rle (writer , & writer -> thread_entries [i ]) < 0 ) {
1070+ return -1 ;
10821071 }
10831072 }
10841073
0 commit comments