@@ -42,10 +42,38 @@ from cpython.pylifecycle cimport Py_FinalizeEx
42
42
from libc.errno cimport errno
43
43
from libc.stdint cimport uintptr_t
44
44
from libc.stdlib cimport exit as _exit
45
+ from libcpp.utility cimport move
45
46
from libcpp.vector cimport vector
46
47
47
48
from ._destination import Destination
48
49
50
+ from _memray.record_writer cimport RecordWriter
51
+ from _memray.record_writer cimport createRecordWriter
52
+ from _memray.records cimport AllocationRecord
53
+ from _memray.records cimport Allocator
54
+ from _memray.records cimport FileFormat
55
+ from _memray.records cimport FramePop
56
+ from _memray.records cimport FramePush
57
+ from _memray.records cimport HeaderRecord
58
+ from _memray.records cimport ImageSegments
59
+ from _memray.records cimport MemoryRecord
60
+ from _memray.records cimport NativeAllocationRecord
61
+ from _memray.records cimport Segment
62
+ from _memray.records cimport ThreadRecord
63
+ from _memray.records cimport UnresolvedNativeFrame
64
+ from _memray.records cimport thread_id_t
65
+ from _memray.sink cimport FileSink
66
+ from _memray.sink cimport Sink
67
+ from cpython.ref cimport PyObject
68
+ from libcpp cimport bool
69
+ from libcpp.memory cimport unique_ptr
70
+ from libcpp.string cimport string
71
+
72
+ import os
73
+ from typing import List
74
+ from typing import Optional
75
+ from typing import Union
76
+
49
77
50
78
cdef extern from * :
51
79
"""
@@ -285,3 +313,123 @@ cdef class PrimeCaches:
285
313
return self
286
314
def __exit__ (self , *args ):
287
315
sys.setprofile(self .old_profile)
316
+
317
+
318
+ cdef class TestRecordWriter:
319
+ """ A Python wrapper around the C++ RecordWriter class for testing purposes."""
320
+
321
+ cdef unique_ptr[RecordWriter] _writer
322
+ cdef unique_ptr[Sink] _sink
323
+ cdef bool _native_traces
324
+ cdef bool _trace_python_allocators
325
+ cdef FileFormat _file_format
326
+
327
+ def __cinit__ (self , str file_path , bool native_traces = False ,
328
+ bool trace_python_allocators = False ,
329
+ FileFormat file_format = FileFormat.ALL_ALLOCATIONS):
330
+ """ Initialize a new TestRecordWriter.
331
+
332
+ Args:
333
+ file_path: Path to the output file
334
+ native_traces: Whether to include native traces
335
+ trace_python_allocators: Whether to trace Python allocators
336
+ file_format: The format of the output file
337
+ """
338
+ self ._native_traces = native_traces
339
+ self ._trace_python_allocators = trace_python_allocators
340
+ self ._file_format = file_format
341
+
342
+ # Create the sink
343
+ cdef string cpp_path = file_path.encode(' utf-8' )
344
+ try :
345
+ self ._sink = unique_ptr[Sink](new FileSink(cpp_path, True , False ))
346
+ except :
347
+ raise IOError (" Failed to create file sink" )
348
+
349
+ # Create the writer
350
+ cdef string command_line = b" " .join(arg.encode(' utf-8' ) for arg in sys.argv)
351
+ try :
352
+ self ._writer = createRecordWriter(
353
+ move(self ._sink),
354
+ command_line,
355
+ native_traces,
356
+ file_format,
357
+ trace_python_allocators
358
+ )
359
+ except :
360
+ raise IOError (" Failed to create record writer" )
361
+
362
+ # Write the header
363
+ if not self ._writer.get().writeHeader(True ):
364
+ raise RuntimeError (" Failed to write header" )
365
+
366
+ def write_memory_record (self , unsigned long ms_since_epoch , size_t rss ) -> bool:
367
+ """Write a memory record to the file."""
368
+ cdef MemoryRecord record
369
+ record.ms_since_epoch = ms_since_epoch
370
+ record.rss = rss
371
+ return self._writer.get().writeRecord(record )
372
+
373
+ def write_allocation_record(self , thread_id_t tid , uintptr_t address ,
374
+ size_t size , unsigned char allocator ) -> bool:
375
+ """Write an allocation record to the file."""
376
+ cdef AllocationRecord record
377
+ record.address = address
378
+ record.size = size
379
+ record.allocator = < Allocator> allocator
380
+ return self._writer.get().writeThreadSpecificRecord(tid , record )
381
+
382
+ def write_native_allocation_record(self , thread_id_t tid , uintptr_t address ,
383
+ size_t size , unsigned char allocator ,
384
+ size_t native_frame_id ) -> bool:
385
+ """Write a native allocation record to the file."""
386
+ cdef NativeAllocationRecord record
387
+ record.address = address
388
+ record.size = size
389
+ record.allocator = < Allocator> allocator
390
+ record.native_frame_id = native_frame_id
391
+ return self._writer.get().writeThreadSpecificRecord(tid , record )
392
+
393
+ def write_frame_push(self , thread_id_t tid , size_t frame_id ) -> bool:
394
+ """Write a frame push record to the file."""
395
+ cdef FramePush record
396
+ record.frame_id = frame_id
397
+ return self._writer.get().writeThreadSpecificRecord(tid , record )
398
+
399
+ def write_frame_pop(self , thread_id_t tid , size_t count ) -> bool:
400
+ """Write a frame pop record to the file."""
401
+ cdef FramePop record
402
+ record.count = count
403
+ return self._writer.get().writeThreadSpecificRecord(tid , record )
404
+
405
+ def write_thread_record(self , thread_id_t tid , str name ) -> bool:
406
+ """Write a thread record to the file."""
407
+ cdef ThreadRecord record
408
+ cdef bytes name_bytes = name.encode(' utf-8' )
409
+ record.name = name_bytes
410
+ return self._writer.get().writeThreadSpecificRecord(tid , record )
411
+
412
+ def write_mappings(self , list mappings ) -> bool:
413
+ """Write memory mappings to the file."""
414
+ cdef vector[ImageSegments] cpp_mappings
415
+ cdef ImageSegments segments
416
+ cdef Segment segment
417
+ for mapping in mappings:
418
+ segments = ImageSegments()
419
+ segments.filename = mapping[' filename' ].encode(' utf-8' )
420
+ segments.addr = mapping[' addr' ]
421
+ for seg in mapping['segments']:
422
+ segment.vaddr = seg[' vaddr' ]
423
+ segment.memsz = seg[' memsz' ]
424
+ segments.segments.push_back(segment )
425
+ cpp_mappings.push_back(segments )
426
+ return self._writer.get().writeMappings(cpp_mappings )
427
+
428
+ def write_trailer(self ) -> bool:
429
+ """Write the trailer to the file."""
430
+ return self._writer.get().writeTrailer()
431
+
432
+ def set_main_tid_and_skipped_frames(self , thread_id_t main_tid ,
433
+ size_t skipped_frames ) -> None:
434
+ """Set the main thread ID and number of skipped frames."""
435
+ self._writer.get().setMainTidAndSkippedFrames(main_tid , skipped_frames )
0 commit comments