11#!/usr/bin/env python
22#
3- # SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
3+ # SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
44# SPDX-License-Identifier: Apache-2.0
55#
66# This is python script to process various types trace data streams in SystemView format.
1414import os .path
1515import signal
1616import sys
17+ import tempfile
1718import traceback
1819
1920import espytrace .apptrace as apptrace
2021import espytrace .sysview as sysview
2122
2223
23- def main ():
24+ def is_segger_multicore_format (file_path ):
25+ """Check if the file has offsets in header"""
26+ try :
27+ with open (file_path , 'rb' ) as f :
28+ header = f .read (200 )
29+ header_str = header .decode ('utf-8' , errors = 'ignore' )
30+
31+ if (
32+ '; Version SEGGER SystemViewer' in header_str
33+ and '; Author Espressif Inc' in header_str
34+ and '; Offset Core0' in header_str
35+ and '; Offset Core1' in header_str
36+ ):
37+ logging .info ('Detected SEGGER multicore format in file:' , file_path )
38+ return True
39+ except Exception as e :
40+ logging .error ('Error checking file format:' , e )
41+ return False
42+
43+
44+ def split_segger_multicore_file (file_path ):
45+ """Split SEGGER multicore file into separate core files."""
46+ try :
47+ with open (file_path , 'rb' ) as f :
48+ # Read first few lines to get offsets for each core
49+ header = f .read (200 )
50+ header_str = header .decode ('utf-8' , errors = 'ignore' )
51+
52+ core0_offset = None
53+ core1_offset = None
54+ for line in header_str .split ('\n ' ):
55+ if '; Offset Core0' in line :
56+ core0_offset = int (line .strip ().split ()[- 1 ])
57+ elif '; Offset Core1' in line :
58+ core1_offset = int (line .strip ().split ()[- 1 ])
59+
60+ if core0_offset is None or core1_offset is None :
61+ logging .error ('Failed to parse core offsets' )
62+ return None , None
63+
64+ # Read the entire file
65+ f .seek (0 )
66+ data = f .read ()
67+
68+ # Find first 10 sync bytes start offset
69+ sync_start = data .find (b'\x00 ' * 10 )
70+ if sync_start == - 1 :
71+ logging .error ('Sync bytes not found' )
72+ return None , None
73+
74+ core0_offset = sync_start
75+ core1_offset += sync_start
76+
77+ # Parse original header and get version from there, if not found, use default version
78+ version = 'V3.60'
79+ for line in header_str .split ('\n ' ):
80+ if '; Version SEGGER SystemViewer' in line :
81+ version = line .strip ().split ()[- 1 ]
82+ break
2483
25- verbosity_levels = [
26- logging .CRITICAL ,
27- logging .ERROR ,
28- logging .WARNING ,
29- logging .INFO ,
30- logging .DEBUG
31- ]
84+ # Rebuild header without offset lines
85+ core_header = f';\n ; Version SEGGER SystemViewer { version } \n ; Author Espressif Inc\n ;\n '
86+ core_header = core_header .encode ('utf-8' )
87+ core_base = core_header + b'\x00 ' * 10
88+
89+ core0_data = core_base + data [core0_offset :core1_offset ]
90+ core1_data = core_base + data [core1_offset :]
91+
92+ core0_file = tempfile .NamedTemporaryFile (delete = False , suffix = '.svdat' )
93+ core1_file = tempfile .NamedTemporaryFile (delete = False , suffix = '.svdat' )
94+
95+ core0_file .write (core0_data )
96+ core1_file .write (core1_data )
97+
98+ core0_file .close ()
99+ core1_file .close ()
100+
101+ return core0_file .name , core1_file .name
102+
103+ except Exception as e :
104+ logging .error ('Failed to process files:' , e )
105+ return None , None
106+
107+
108+ def main ():
109+ verbosity_levels = [logging .CRITICAL , logging .ERROR , logging .WARNING , logging .INFO , logging .DEBUG ]
32110
33111 parser = argparse .ArgumentParser (description = 'ESP32 SEGGER SystemView Trace Parsing Tool' )
34112
35- parser .add_argument ('trace_sources' , help = 'Trace data sources. Format: [file://]/path/to/file.' , nargs = '+' , type = str )
113+ parser .add_argument (
114+ 'trace_sources' , help = 'Trace data sources. Format: [file://]/path/to/file.' , nargs = '+' , type = str
115+ )
36116 parser .add_argument ('--elf-file' , '-b' , help = 'Path to program ELF file.' , type = str , default = '' )
37117 parser .add_argument ('--tmo' , '-w' , help = 'Data wait timeout in sec. -1: infinite, 0: no wait' , type = int , default = 0 )
38118 parser .add_argument ('--dump-events' , '-d' , help = 'Dump all events.' , action = 'store_true' )
39- parser .add_argument ('--print-events' , '-p' , help = 'Print events of selected types. By default only reports are printed' , action = 'store_true' )
40- parser .add_argument ('--include-events' , '-i' , help = 'Events types to be included into report.' , type = str , choices = ['heap' , 'log' , 'all' ], default = 'all' )
119+ parser .add_argument (
120+ '--print-events' ,
121+ '-p' ,
122+ help = 'Print events of selected types. By default only reports are printed' ,
123+ action = 'store_true' ,
124+ )
125+ parser .add_argument (
126+ '--include-events' ,
127+ '-i' ,
128+ help = 'Events types to be included into report.' ,
129+ type = str ,
130+ choices = ['heap' , 'log' , 'all' ],
131+ default = 'all' ,
132+ )
41133 parser .add_argument ('--toolchain' , '-t' , help = 'Toolchain prefix.' , type = str , default = 'xtensa-esp32-elf-' )
42- parser .add_argument ('--events-map' , '-e' , help = 'Events map file.' , type = str , default = os .path .join (os .path .dirname (__file__ ), 'SYSVIEW_FreeRTOS.txt' ))
134+ parser .add_argument (
135+ '--events-map' ,
136+ '-e' ,
137+ help = 'Events map file.' ,
138+ type = str ,
139+ default = os .path .join (os .path .dirname (__file__ ), 'SYSVIEW_FreeRTOS.txt' ),
140+ )
43141 parser .add_argument ('--to-json' , '-j' , help = 'Print JSON.' , action = 'store_true' , default = False )
44- parser .add_argument ('--verbose' , '-v' , help = 'Verbosity level. Default 1' , choices = range (0 , len (verbosity_levels )), type = int , default = 1 )
142+ parser .add_argument (
143+ '--verbose' ,
144+ '-v' ,
145+ help = 'Verbosity level. Default 1' ,
146+ choices = range (0 , len (verbosity_levels )),
147+ type = int ,
148+ default = 1 ,
149+ )
45150 args = parser .parse_args ()
46151
47152 def sig_int_handler (signum , frame ):
@@ -60,17 +165,33 @@ def sig_int_handler(signum, frame):
60165
61166 logging .basicConfig (level = verbosity_levels [args .verbose ], format = '[%(levelname)s] %(message)s' )
62167
168+ segger_files = []
169+ # Only check for SEGGER format if there's exactly one trace source
170+ if len (args .trace_sources ) == 1 :
171+ trace_source = args .trace_sources [0 ]
172+ if is_segger_multicore_format (trace_source ):
173+ core0_file , core1_file = split_segger_multicore_file (trace_source )
174+ if core0_file and core1_file :
175+ segger_files .extend ([core0_file , core1_file ])
176+ args .trace_sources = segger_files
177+ else :
178+ sys .exit (2 )
179+
63180 # parse trace files
64181 parsers = []
65182 for i , trace_source in enumerate (args .trace_sources ):
66183 try :
67184 parser = sysview .SysViewMultiTraceDataParser (print_events = False , core_id = i )
68185 if include_events ['heap' ]:
69- parser .add_stream_parser (sysview .SysViewTraceDataParser .STREAMID_HEAP ,
70- sysview .SysViewHeapTraceDataParser (print_events = False , core_id = i ))
186+ parser .add_stream_parser (
187+ sysview .SysViewTraceDataParser .STREAMID_HEAP ,
188+ sysview .SysViewHeapTraceDataParser (print_events = False , core_id = i ),
189+ )
71190 if include_events ['log' ]:
72- parser .add_stream_parser (sysview .SysViewTraceDataParser .STREAMID_LOG ,
73- sysview .SysViewLogTraceDataParser (print_events = False , core_id = i ))
191+ parser .add_stream_parser (
192+ sysview .SysViewTraceDataParser .STREAMID_LOG ,
193+ sysview .SysViewLogTraceDataParser (print_events = False , core_id = i ),
194+ )
74195 parsers .append (parser )
75196 except Exception as e :
76197 logging .error ('Failed to create data parser (%s)!' , e )
@@ -97,13 +218,21 @@ def sig_int_handler(signum, frame):
97218
98219 # merge and process traces
99220 try :
100- proc = sysview .SysViewMultiStreamTraceDataProcessor (traces = parsers , print_events = args .dump_events , keep_all_events = True if args .to_json else False )
221+ proc = sysview .SysViewMultiStreamTraceDataProcessor (
222+ traces = parsers , print_events = args .dump_events , keep_all_events = True if args .to_json else False
223+ )
101224 if include_events ['heap' ]:
102- proc .add_stream_processor (sysview .SysViewTraceDataParser .STREAMID_HEAP ,
103- sysview .SysViewHeapTraceDataProcessor (args .toolchain , args .elf_file , root_proc = proc , print_heap_events = args .print_events ))
225+ proc .add_stream_processor (
226+ sysview .SysViewTraceDataParser .STREAMID_HEAP ,
227+ sysview .SysViewHeapTraceDataProcessor (
228+ args .toolchain , args .elf_file , root_proc = proc , print_heap_events = args .print_events
229+ ),
230+ )
104231 if include_events ['log' ]:
105- proc .add_stream_processor (sysview .SysViewTraceDataParser .STREAMID_LOG ,
106- sysview .SysViewLogTraceDataProcessor (root_proc = proc , print_log_events = args .print_events ))
232+ proc .add_stream_processor (
233+ sysview .SysViewTraceDataParser .STREAMID_LOG ,
234+ sysview .SysViewLogTraceDataProcessor (root_proc = proc , print_log_events = args .print_events ),
235+ )
107236 except Exception as e :
108237 logging .error ('Failed to create data processor (%s)!' , e )
109238 traceback .print_exc ()
@@ -119,11 +248,22 @@ def sig_int_handler(signum, frame):
119248 sys .exit (2 )
120249 finally :
121250 if args .to_json :
122- print (json .dumps (proc , cls = sysview .SysViewTraceDataJsonEncoder , indent = 4 , separators = (',' , ': ' ), sort_keys = True ))
251+ print (
252+ json .dumps (
253+ proc , cls = sysview .SysViewTraceDataJsonEncoder , indent = 4 , separators = (',' , ': ' ), sort_keys = True
254+ )
255+ )
123256 else :
124257 proc .print_report ()
125258 proc .cleanup ()
126259
260+ if segger_files :
261+ for file in segger_files :
262+ try :
263+ os .remove (file )
264+ except Exception as e :
265+ logging .warning ('Failed to remove temporary file %s: %s' , file , e )
266+
127267
128268if __name__ == '__main__' :
129269 main ()
0 commit comments