@@ -34,7 +34,7 @@ by 10-50x compared to text formats while also enabling faster I/O.
3434
3535## File Layout
3636
37- The file consists of five sections:
37+ The file consists of five required sections and one optional extension :
3838
3939```
4040+------------------+ Offset 0
@@ -47,6 +47,8 @@ The file consists of five sections:
4747| String Table | Variable size
4848+------------------+ frame_table_offset
4949| Frame Table | Variable size
50+ +------------------+ file_size - 64 (when stats are present)
51+ | Profile Stats | 32 bytes (optional)
5052+------------------+ file_size - 32
5153| Footer | 32 bytes (fixed)
5254+------------------+ file_size
@@ -354,6 +356,38 @@ location. Zigzag encoding ensures these small negative values encode
354356efficiently (−1 becomes 1, which is one byte) rather than requiring the
355357maximum varint length.
356358
359+ ## Profile Statistics
360+
361+ New files can store measured duration, sampling rate, error rate, and missed
362+ sample percentage in an optional 56-byte extension immediately before the
363+ footer. Older readers ignore these
364+ bytes after parsing the declared number of frame-table entries, and newer
365+ readers treat a missing extension as unavailable statistics.
366+
367+ ```
368+ Offset Size Type Description
369+ +--------+------+---------+----------------------------------------+
370+ | 0 | 8 | double | Measured duration (seconds) |
371+ | 8 | 8 | double | Measured sample rate (samples/second) |
372+ | 16 | 8 | double | Failed sample percentage |
373+ | 24 | 8 | double | Missed sample percentage |
374+ | 32 | 4 | uint32 | Optional field presence flags |
375+ | 36 | 4 | uint32 | Reserved |
376+ | 40 | 8 | bytes | Signature ("TACHSTAT") |
377+ | 48 | 4 | uint32 | Extension version (1) |
378+ | 52 | 4 | uint32 | Extension size (56) |
379+ +--------+------+---------+----------------------------------------+
380+ ```
381+
382+ Putting the signature, version, and size at the end lets readers discover
383+ the extension from its fixed position relative to the footer while allowing
384+ future versions to add fields before that trailer. Multi-byte values use the
385+ same native byte order as the rest of the file and are byte-swapped by
386+ cross-endian readers.
387+
388+ Readers also accept the original 32-byte extension, which only contains the
389+ duration and sampling rate.
390+
357391## Footer
358392
359393```
@@ -448,8 +482,9 @@ compress less; higher levels (6+) compress more but slow down writing. Level
4484824 . Flush remaining buffered data and finalize compression
4494835 . Write the string table (length-prefixed strings in index order)
4504846 . Write the frame table (varint-encoded entries in index order)
451- 7 . Write the footer with final counts
452- 8 . Seek to offset 0 and write the header with actual values
485+ 7 . Write measured profile statistics, when available
486+ 8 . Write the footer with final counts
487+ 9 . Seek to offset 0 and write the header with actual values
453488
454489The writer maintains two dictionaries: one mapping strings to indices, one
455490mapping (filename_idx, funcname_idx, lineno) tuples to frame indices. These
@@ -461,12 +496,13 @@ enable O(1) lookup during interning.
461496 if the magic appears byte-swapped)
4624972 . Validate version and read remaining header fields (byte-swapping if needed)
4634983 . Seek to end − 32 and read the footer (byte-swapping counts if needed)
464- 4 . Allocate string array of ` string_count ` elements
465- 5 . Parse the string table, populating the array
466- 6 . Allocate frame array of ` frame_count * 3 ` uint32 elements
467- 7 . Parse the frame table, populating the array
468- 8 . If compressed, decompress the sample data region
469- 9 . Iterate through samples, resolving indices to strings/frames
499+ 4 . Read measured profile statistics when the optional extension is present
500+ 5 . Allocate string array of ` string_count ` elements
501+ 6 . Parse the string table, populating the array
502+ 7 . Allocate the frame array
503+ 8 . Parse the frame table, populating the array
504+ 9 . If compressed, decompress the sample data region
505+ 10 . Iterate through samples, resolving indices to strings/frames
470506 (byte-swapping thread_id and interpreter_id if needed)
471507
472508The reader builds lookup arrays rather than dictionaries since it only needs
@@ -530,11 +566,10 @@ one write() call (or feeds through the compression stream).
530566
531567## Future Considerations
532568
533- The format reserves space for future extensions. The 12 reserved bytes in
534- the header could hold additional metadata. The 16-byte checksum field in
535- the footer is currently unused. The version field allows incompatible
536- changes with graceful rejection. New compression types could be added
537- (compression_type > 1).
569+ The optional profile-statistics block provides an extensible metadata area.
570+ The 16-byte checksum field in the footer is currently unused. The version
571+ field allows incompatible changes with graceful rejection. New compression
572+ types could be added (compression_type > 1).
538573
539574Any changes that alter the meaning of existing fields or the parsing logic
540575should increment the version number to prevent older readers from
0 commit comments