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 { \
@@ -134,15 +138,6 @@ writer_write_varint_u32(BinaryWriter *writer, uint32_t value)
134138 return writer_write_bytes (writer , buf , len );
135139}
136140
137- /* Encode and write a varint u64 - returns 0 on success, -1 on error */
138- static inline int
139- writer_write_varint_u64 (BinaryWriter * writer , uint64_t value )
140- {
141- uint8_t buf [MAX_VARINT_SIZE ];
142- size_t len = encode_varint_u64 (buf , value );
143- return writer_write_bytes (writer , buf , len );
144- }
145-
146141
147142/* ============================================================================
148143 * UTILITY FUNCTIONS
@@ -511,21 +506,13 @@ writer_get_or_create_thread_entry(BinaryWriter *writer, uint64_t thread_id,
511506 entry -> interpreter_id = interpreter_id ;
512507 entry -> prev_timestamp = writer -> start_time_us ;
513508 entry -> prev_stack_capacity = MAX_STACK_DEPTH ;
514- entry -> pending_rle_capacity = INITIAL_RLE_CAPACITY ;
515509
516510 entry -> prev_stack = PyMem_Calloc (entry -> prev_stack_capacity , sizeof (uint32_t ));
517511 if (!entry -> prev_stack ) {
518512 PyErr_NoMemory ();
519513 return NULL ;
520514 }
521515
522- entry -> pending_rle = PyMem_Malloc (entry -> pending_rle_capacity * sizeof (PendingRLESample ));
523- if (!entry -> pending_rle ) {
524- PyMem_Free (entry -> prev_stack );
525- PyErr_NoMemory ();
526- return NULL ;
527- }
528-
529516 writer -> thread_count ++ ;
530517 if (is_new ) {
531518 * is_new = 1 ;
@@ -626,14 +613,9 @@ write_sample_header(BinaryWriter *writer, ThreadEntry *entry, uint8_t encoding)
626613static int
627614flush_pending_rle (BinaryWriter * writer , ThreadEntry * entry )
628615{
629- if (! entry -> has_pending_rle || entry -> pending_rle_count == 0 ) {
616+ if (entry -> pending_rle_samples == 0 ) {
630617 return 0 ;
631618 }
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- }
637619
638620 /* Write RLE record:
639621 * [thread_id: 8] [interpreter_id: 4] [STACK_REPEAT: 1] [count: varint]
@@ -644,27 +626,21 @@ flush_pending_rle(BinaryWriter *writer, ThreadEntry *entry)
644626 return -1 ;
645627 }
646628
647- if (writer_write_varint_u32 (writer , (uint32_t )entry -> pending_rle_count ) < 0 ) {
629+ if (writer_write_varint_u32 (writer , (uint32_t )entry -> pending_rle_samples ) < 0 ) {
648630 return -1 ;
649631 }
650632
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 ++ ;
633+ if (writer_write_bytes (writer , entry -> pending_rle , entry -> pending_rle_bytes ) < 0 ) {
634+ return -1 ;
659635 }
660636
661637 writer -> stats .repeat_records ++ ;
662- writer -> stats .repeat_samples += entry -> pending_rle_count ;
638+ writer -> stats .repeat_samples += entry -> pending_rle_samples ;
663639 /* Each RLE sample saves writing the entire stack */
664- writer -> stats .frames_saved += entry -> pending_rle_count * entry -> prev_stack_depth ;
640+ writer -> stats .frames_saved += entry -> pending_rle_samples * entry -> prev_stack_depth ;
665641
666- entry -> pending_rle_count = 0 ;
667- entry -> has_pending_rle = 0 ;
642+ entry -> pending_rle_bytes = 0 ;
643+ entry -> pending_rle_samples = 0 ;
668644
669645 return 0 ;
670646}
@@ -678,12 +654,6 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry,
678654 const uint32_t * frame_indices , size_t stack_depth ,
679655 size_t shared_count , size_t pop_count , size_t push_count )
680656{
681- if (writer -> total_samples == UINT32_MAX ) {
682- PyErr_SetString (PyExc_OverflowError ,
683- "too many samples for binary format" );
684- return -1 ;
685- }
686-
687657 /* Header: thread_id(8) + interpreter_id(4) + encoding(1) + delta(varint) + status(1) */
688658 uint8_t header_buf [SAMPLE_HEADER_MAX_SIZE ];
689659 memcpy (header_buf + SMP_OFF_THREAD_ID , & entry -> thread_id , SMP_SIZE_THREAD_ID );
@@ -752,7 +722,6 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry,
752722 }
753723
754724 writer -> stats .total_frames_written += frames_written ;
755- writer -> total_samples ++ ;
756725 return 0 ;
757726}
758727
@@ -955,6 +924,12 @@ static int
955924process_thread_sample (BinaryWriter * writer , PyObject * thread_info ,
956925 uint32_t interpreter_id , uint64_t timestamp_us )
957926{
927+ if (writer -> total_samples >= UINT32_MAX ) {
928+ PyErr_SetString (PyExc_OverflowError ,
929+ "too many samples for binary format" );
930+ return -1 ;
931+ }
932+
958933 PyObject * thread_id_obj = PyStructSequence_GET_ITEM (thread_info , 0 );
959934 PyObject * status_obj = PyStructSequence_GET_ITEM (thread_info , 1 );
960935 PyObject * frame_list = PyStructSequence_GET_ITEM (thread_info , 2 );
@@ -1002,20 +977,25 @@ process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
1002977 * STACK_REPEAT against an empty curr_stack (depth 0). Buffering
1003978 * it here is correct; the RLE flush path emits it as a normal
1004979 * STACK_REPEAT record. */
1005- if (GROW_ARRAY (entry -> pending_rle , entry -> pending_rle_count ,
1006- entry -> pending_rle_capacity , PendingRLESample ) < 0 ) {
980+ if (entry -> pending_rle == NULL ) {
981+ entry -> pending_rle = PyMem_Malloc (MAX_RLE_BUF_SIZE );
982+ if (!entry -> pending_rle ) {
983+ PyErr_NoMemory ();
984+ return -1 ;
985+ }
986+ }
987+ if (entry -> pending_rle_bytes + MAX_RLE_ENTRY_SIZE > MAX_RLE_BUF_SIZE
988+ && flush_pending_rle (writer , entry ) < 0 ) {
1007989 return -1 ;
1008990 }
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 ;
991+ entry -> pending_rle_bytes += encode_varint_u64 (
992+ entry -> pending_rle + entry -> pending_rle_bytes , delta ) ;
993+ entry -> pending_rle [ entry -> pending_rle_bytes ++ ] = status ;
994+ entry -> pending_rle_samples ++ ;
1013995 } else {
1014996 /* 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- }
997+ if (flush_pending_rle (writer , entry ) < 0 ) {
998+ return -1 ;
1019999 }
10201000
10211001 if (write_sample_with_encoding (writer , entry , delta , status , encoding ,
@@ -1028,6 +1008,7 @@ process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
10281008 entry -> prev_stack_depth = curr_depth ;
10291009 }
10301010
1011+ writer -> total_samples ++ ;
10311012 return 0 ;
10321013}
10331014
@@ -1075,10 +1056,8 @@ int
10751056binary_writer_finalize (BinaryWriter * writer )
10761057{
10771058 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- }
1059+ if (flush_pending_rle (writer , & writer -> thread_entries [i ]) < 0 ) {
1060+ return -1 ;
10821061 }
10831062 }
10841063
0 commit comments