xref: /linux-6.15/tools/perf/scripts/python/gecko.py (revision 833daec7)
11699d3efSAnup Sharma# firefox-gecko-converter.py - Convert perf record output to Firefox's gecko profile format
21699d3efSAnup Sharma# SPDX-License-Identifier: GPL-2.0
31699d3efSAnup Sharma#
41699d3efSAnup Sharma# The script converts perf.data to Gecko Profile Format,
51699d3efSAnup Sharma# which can be read by https://profiler.firefox.com/.
61699d3efSAnup Sharma#
71699d3efSAnup Sharma# Usage:
81699d3efSAnup Sharma#
91699d3efSAnup Sharma#     perf record -a -g -F 99 sleep 60
101699d3efSAnup Sharma#     perf script report gecko > output.json
111699d3efSAnup Sharma
121699d3efSAnup Sharmaimport os
131699d3efSAnup Sharmaimport sys
14*833daec7SAnup Sharmaimport json
15*833daec7SAnup Sharmaimport argparse
165aacd7f0SAnup Sharmafrom dataclasses import dataclass, field
175aacd7f0SAnup Sharmafrom typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any
181699d3efSAnup Sharma
191699d3efSAnup Sharma# Add the Perf-Trace-Util library to the Python path
201699d3efSAnup Sharmasys.path.append(os.environ['PERF_EXEC_PATH'] + \
211699d3efSAnup Sharma	'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
221699d3efSAnup Sharma
231699d3efSAnup Sharmafrom perf_trace_context import *
241699d3efSAnup Sharmafrom Core import *
251699d3efSAnup Sharma
265aacd7f0SAnup SharmaStringID = int
275aacd7f0SAnup SharmaStackID = int
285aacd7f0SAnup SharmaFrameID = int
295aacd7f0SAnup SharmaCategoryID = int
305aacd7f0SAnup SharmaMilliseconds = float
315aacd7f0SAnup Sharma
320a02e44cSAnup Sharma# start_time is intialiazed only once for the all event traces.
330a02e44cSAnup Sharmastart_time = None
340a02e44cSAnup Sharma
35*833daec7SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425
36*833daec7SAnup Sharma# Follow Brendan Gregg's Flamegraph convention: orange for kernel and yellow for user space by default.
37*833daec7SAnup SharmaCATEGORIES = None
38*833daec7SAnup Sharma
39*833daec7SAnup Sharma# The product name is used by the profiler UI to show the Operating system and Processor.
40*833daec7SAnup SharmaPRODUCT = os.popen('uname -op').read().strip()
41*833daec7SAnup Sharma
425aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
435aacd7f0SAnup Sharmaclass Frame(NamedTuple):
445aacd7f0SAnup Sharma	string_id: StringID
455aacd7f0SAnup Sharma	relevantForJS: bool
465aacd7f0SAnup Sharma	innerWindowID: int
475aacd7f0SAnup Sharma	implementation: None
485aacd7f0SAnup Sharma	optimizations: None
495aacd7f0SAnup Sharma	line: None
505aacd7f0SAnup Sharma	column: None
515aacd7f0SAnup Sharma	category: CategoryID
525aacd7f0SAnup Sharma	subcategory: int
535aacd7f0SAnup Sharma
545aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
555aacd7f0SAnup Sharmaclass Stack(NamedTuple):
565aacd7f0SAnup Sharma	prefix_id: Optional[StackID]
575aacd7f0SAnup Sharma	frame_id: FrameID
585aacd7f0SAnup Sharma
595aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
605aacd7f0SAnup Sharmaclass Sample(NamedTuple):
615aacd7f0SAnup Sharma	stack_id: Optional[StackID]
625aacd7f0SAnup Sharma	time_ms: Milliseconds
635aacd7f0SAnup Sharma	responsiveness: int
645aacd7f0SAnup Sharma
655aacd7f0SAnup Sharma@dataclass
665aacd7f0SAnup Sharmaclass Thread:
675aacd7f0SAnup Sharma	"""A builder for a profile of the thread.
685aacd7f0SAnup Sharma
695aacd7f0SAnup Sharma	Attributes:
705aacd7f0SAnup Sharma		comm: Thread command-line (name).
715aacd7f0SAnup Sharma		pid: process ID of containing process.
725aacd7f0SAnup Sharma		tid: thread ID.
735aacd7f0SAnup Sharma		samples: Timeline of profile samples.
745aacd7f0SAnup Sharma		frameTable: interned stack frame ID -> stack frame.
755aacd7f0SAnup Sharma		stringTable: interned string ID -> string.
765aacd7f0SAnup Sharma		stringMap: interned string -> string ID.
775aacd7f0SAnup Sharma		stackTable: interned stack ID -> stack.
785aacd7f0SAnup Sharma		stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID.
795aacd7f0SAnup Sharma		frameMap: Stack Frame string -> interned Frame ID.
805aacd7f0SAnup Sharma		comm: str
815aacd7f0SAnup Sharma		pid: int
825aacd7f0SAnup Sharma		tid: int
835aacd7f0SAnup Sharma		samples: List[Sample] = field(default_factory=list)
845aacd7f0SAnup Sharma		frameTable: List[Frame] = field(default_factory=list)
855aacd7f0SAnup Sharma		stringTable: List[str] = field(default_factory=list)
865aacd7f0SAnup Sharma		stringMap: Dict[str, int] = field(default_factory=dict)
875aacd7f0SAnup Sharma		stackTable: List[Stack] = field(default_factory=list)
885aacd7f0SAnup Sharma		stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
895aacd7f0SAnup Sharma		frameMap: Dict[str, int] = field(default_factory=dict)
905aacd7f0SAnup Sharma	"""
915aacd7f0SAnup Sharma	comm: str
925aacd7f0SAnup Sharma	pid: int
935aacd7f0SAnup Sharma	tid: int
945aacd7f0SAnup Sharma	samples: List[Sample] = field(default_factory=list)
955aacd7f0SAnup Sharma	frameTable: List[Frame] = field(default_factory=list)
965aacd7f0SAnup Sharma	stringTable: List[str] = field(default_factory=list)
975aacd7f0SAnup Sharma	stringMap: Dict[str, int] = field(default_factory=dict)
985aacd7f0SAnup Sharma	stackTable: List[Stack] = field(default_factory=list)
995aacd7f0SAnup Sharma	stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
1005aacd7f0SAnup Sharma	frameMap: Dict[str, int] = field(default_factory=dict)
1015aacd7f0SAnup Sharma
1025aacd7f0SAnup Sharma	def _to_json_dict(self) -> Dict:
1035aacd7f0SAnup Sharma		"""Converts current Thread to GeckoThread JSON format."""
1045aacd7f0SAnup Sharma		# Gecko profile format is row-oriented data as List[List],
1055aacd7f0SAnup Sharma		# And a schema for interpreting each index.
1065aacd7f0SAnup Sharma		# Schema:
1075aacd7f0SAnup Sharma		# https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md
1085aacd7f0SAnup Sharma		# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230
1095aacd7f0SAnup Sharma		return {
1105aacd7f0SAnup Sharma			"tid": self.tid,
1115aacd7f0SAnup Sharma			"pid": self.pid,
1125aacd7f0SAnup Sharma			"name": self.comm,
1135aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51
1145aacd7f0SAnup Sharma			"markers": {
1155aacd7f0SAnup Sharma				"schema": {
1165aacd7f0SAnup Sharma					"name": 0,
1175aacd7f0SAnup Sharma					"startTime": 1,
1185aacd7f0SAnup Sharma					"endTime": 2,
1195aacd7f0SAnup Sharma					"phase": 3,
1205aacd7f0SAnup Sharma					"category": 4,
1215aacd7f0SAnup Sharma					"data": 5,
1225aacd7f0SAnup Sharma				},
1235aacd7f0SAnup Sharma				"data": [],
1245aacd7f0SAnup Sharma			},
1255aacd7f0SAnup Sharma
1265aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
1275aacd7f0SAnup Sharma			"samples": {
1285aacd7f0SAnup Sharma				"schema": {
1295aacd7f0SAnup Sharma					"stack": 0,
1305aacd7f0SAnup Sharma					"time": 1,
1315aacd7f0SAnup Sharma					"responsiveness": 2,
1325aacd7f0SAnup Sharma				},
1335aacd7f0SAnup Sharma				"data": self.samples
1345aacd7f0SAnup Sharma			},
1355aacd7f0SAnup Sharma
1365aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
1375aacd7f0SAnup Sharma			"frameTable": {
1385aacd7f0SAnup Sharma				"schema": {
1395aacd7f0SAnup Sharma					"location": 0,
1405aacd7f0SAnup Sharma					"relevantForJS": 1,
1415aacd7f0SAnup Sharma					"innerWindowID": 2,
1425aacd7f0SAnup Sharma					"implementation": 3,
1435aacd7f0SAnup Sharma					"optimizations": 4,
1445aacd7f0SAnup Sharma					"line": 5,
1455aacd7f0SAnup Sharma					"column": 6,
1465aacd7f0SAnup Sharma					"category": 7,
1475aacd7f0SAnup Sharma					"subcategory": 8,
1485aacd7f0SAnup Sharma				},
1495aacd7f0SAnup Sharma				"data": self.frameTable,
1505aacd7f0SAnup Sharma			},
1515aacd7f0SAnup Sharma
1525aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
1535aacd7f0SAnup Sharma			"stackTable": {
1545aacd7f0SAnup Sharma				"schema": {
1555aacd7f0SAnup Sharma					"prefix": 0,
1565aacd7f0SAnup Sharma					"frame": 1,
1575aacd7f0SAnup Sharma				},
1585aacd7f0SAnup Sharma				"data": self.stackTable,
1595aacd7f0SAnup Sharma			},
1605aacd7f0SAnup Sharma			"stringTable": self.stringTable,
1615aacd7f0SAnup Sharma			"registerTime": 0,
1625aacd7f0SAnup Sharma			"unregisterTime": None,
1635aacd7f0SAnup Sharma			"processType": "default",
1645aacd7f0SAnup Sharma		}
1655aacd7f0SAnup Sharma
1661699d3efSAnup Sharma# Uses perf script python interface to parse each
1671699d3efSAnup Sharma# event and store the data in the thread builder.
1681699d3efSAnup Sharmadef process_event(param_dict: Dict) -> None:
1690a02e44cSAnup Sharma	global start_time
1700a02e44cSAnup Sharma	global tid_to_thread
1710a02e44cSAnup Sharma	time_stamp = (param_dict['sample']['time'] // 1000) / 1000
1720a02e44cSAnup Sharma	pid = param_dict['sample']['pid']
1730a02e44cSAnup Sharma	tid = param_dict['sample']['tid']
1740a02e44cSAnup Sharma	comm = param_dict['comm']
1750a02e44cSAnup Sharma
1760a02e44cSAnup Sharma	# Start time is the time of the first sample
1770a02e44cSAnup Sharma	if not start_time:
1780a02e44cSAnup Sharma		start_time = time_stamp
1791699d3efSAnup Sharma
1801699d3efSAnup Sharma# Trace_end runs at the end and will be used to aggregate
1811699d3efSAnup Sharma# the data into the final json object and print it out to stdout.
1821699d3efSAnup Sharmadef trace_end() -> None:
183*833daec7SAnup Sharma	# Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305
184*833daec7SAnup Sharma	gecko_profile_with_meta = {
185*833daec7SAnup Sharma		"meta": {
186*833daec7SAnup Sharma			"interval": 1,
187*833daec7SAnup Sharma			"processType": 0,
188*833daec7SAnup Sharma			"product": PRODUCT,
189*833daec7SAnup Sharma			"stackwalk": 1,
190*833daec7SAnup Sharma			"debug": 0,
191*833daec7SAnup Sharma			"gcpoison": 0,
192*833daec7SAnup Sharma			"asyncstack": 1,
193*833daec7SAnup Sharma			"startTime": start_time,
194*833daec7SAnup Sharma			"shutdownTime": None,
195*833daec7SAnup Sharma			"version": 24,
196*833daec7SAnup Sharma			"presymbolicated": True,
197*833daec7SAnup Sharma			"categories": CATEGORIES,
198*833daec7SAnup Sharma			"markerSchema": [],
199*833daec7SAnup Sharma			},
200*833daec7SAnup Sharma		"libs": [],
201*833daec7SAnup Sharma		# threads will be implemented in later commits.
202*833daec7SAnup Sharma		# "threads": threads,
203*833daec7SAnup Sharma		"processes": [],
204*833daec7SAnup Sharma		"pausedRanges": [],
205*833daec7SAnup Sharma	}
206*833daec7SAnup Sharma	json.dump(gecko_profile_with_meta, sys.stdout, indent=2)
207*833daec7SAnup Sharma
208*833daec7SAnup Sharmadef main() -> None:
209*833daec7SAnup Sharma	global CATEGORIES
210*833daec7SAnup Sharma	parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format")
211*833daec7SAnup Sharma
212*833daec7SAnup Sharma	# Add the command-line options
213*833daec7SAnup Sharma	# Colors must be defined according to this:
214*833daec7SAnup Sharma	# https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css
215*833daec7SAnup Sharma	parser.add_argument('--user-color', default='yellow', help='Color for the User category')
216*833daec7SAnup Sharma	parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category')
217*833daec7SAnup Sharma	# Parse the command-line arguments
218*833daec7SAnup Sharma	args = parser.parse_args()
219*833daec7SAnup Sharma	# Access the values provided by the user
220*833daec7SAnup Sharma	user_color = args.user_color
221*833daec7SAnup Sharma	kernel_color = args.kernel_color
222*833daec7SAnup Sharma
223*833daec7SAnup Sharma	CATEGORIES = [
224*833daec7SAnup Sharma		{
225*833daec7SAnup Sharma			"name": 'User',
226*833daec7SAnup Sharma			"color": user_color,
227*833daec7SAnup Sharma			"subcategories": ['Other']
228*833daec7SAnup Sharma		},
229*833daec7SAnup Sharma		{
230*833daec7SAnup Sharma			"name": 'Kernel',
231*833daec7SAnup Sharma			"color": kernel_color,
232*833daec7SAnup Sharma			"subcategories": ['Other']
233*833daec7SAnup Sharma		},
234*833daec7SAnup Sharma	]
235*833daec7SAnup Sharma
236*833daec7SAnup Sharmaif __name__ == '__main__':
237*833daec7SAnup Sharma    main()
238