xref: /linux/tools/perf/scripts/python/gecko.py (revision 5aacd7f08a3276f4fad729a600d51a1cc5d5191a)
1# firefox-gecko-converter.py - Convert perf record output to Firefox's gecko profile format
2# SPDX-License-Identifier: GPL-2.0
3#
4# The script converts perf.data to Gecko Profile Format,
5# which can be read by https://profiler.firefox.com/.
6#
7# Usage:
8#
9#     perf record -a -g -F 99 sleep 60
10#     perf script report gecko > output.json
11
12import os
13import sys
14from dataclasses import dataclass, field
15from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any
16
17# Add the Perf-Trace-Util library to the Python path
18sys.path.append(os.environ['PERF_EXEC_PATH'] + \
19	'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
20
21from perf_trace_context import *
22from Core import *
23
24StringID = int
25StackID = int
26FrameID = int
27CategoryID = int
28Milliseconds = float
29
30# start_time is intialiazed only once for the all event traces.
31start_time = None
32
33# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
34class Frame(NamedTuple):
35	string_id: StringID
36	relevantForJS: bool
37	innerWindowID: int
38	implementation: None
39	optimizations: None
40	line: None
41	column: None
42	category: CategoryID
43	subcategory: int
44
45# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
46class Stack(NamedTuple):
47	prefix_id: Optional[StackID]
48	frame_id: FrameID
49
50# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
51class Sample(NamedTuple):
52	stack_id: Optional[StackID]
53	time_ms: Milliseconds
54	responsiveness: int
55
56@dataclass
57class Thread:
58	"""A builder for a profile of the thread.
59
60	Attributes:
61		comm: Thread command-line (name).
62		pid: process ID of containing process.
63		tid: thread ID.
64		samples: Timeline of profile samples.
65		frameTable: interned stack frame ID -> stack frame.
66		stringTable: interned string ID -> string.
67		stringMap: interned string -> string ID.
68		stackTable: interned stack ID -> stack.
69		stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID.
70		frameMap: Stack Frame string -> interned Frame ID.
71		comm: str
72		pid: int
73		tid: int
74		samples: List[Sample] = field(default_factory=list)
75		frameTable: List[Frame] = field(default_factory=list)
76		stringTable: List[str] = field(default_factory=list)
77		stringMap: Dict[str, int] = field(default_factory=dict)
78		stackTable: List[Stack] = field(default_factory=list)
79		stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
80		frameMap: Dict[str, int] = field(default_factory=dict)
81	"""
82	comm: str
83	pid: int
84	tid: int
85	samples: List[Sample] = field(default_factory=list)
86	frameTable: List[Frame] = field(default_factory=list)
87	stringTable: List[str] = field(default_factory=list)
88	stringMap: Dict[str, int] = field(default_factory=dict)
89	stackTable: List[Stack] = field(default_factory=list)
90	stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
91	frameMap: Dict[str, int] = field(default_factory=dict)
92
93	def _to_json_dict(self) -> Dict:
94		"""Converts current Thread to GeckoThread JSON format."""
95		# Gecko profile format is row-oriented data as List[List],
96		# And a schema for interpreting each index.
97		# Schema:
98		# https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md
99		# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230
100		return {
101			"tid": self.tid,
102			"pid": self.pid,
103			"name": self.comm,
104			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51
105			"markers": {
106				"schema": {
107					"name": 0,
108					"startTime": 1,
109					"endTime": 2,
110					"phase": 3,
111					"category": 4,
112					"data": 5,
113				},
114				"data": [],
115			},
116
117			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
118			"samples": {
119				"schema": {
120					"stack": 0,
121					"time": 1,
122					"responsiveness": 2,
123				},
124				"data": self.samples
125			},
126
127			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
128			"frameTable": {
129				"schema": {
130					"location": 0,
131					"relevantForJS": 1,
132					"innerWindowID": 2,
133					"implementation": 3,
134					"optimizations": 4,
135					"line": 5,
136					"column": 6,
137					"category": 7,
138					"subcategory": 8,
139				},
140				"data": self.frameTable,
141			},
142
143			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
144			"stackTable": {
145				"schema": {
146					"prefix": 0,
147					"frame": 1,
148				},
149				"data": self.stackTable,
150			},
151			"stringTable": self.stringTable,
152			"registerTime": 0,
153			"unregisterTime": None,
154			"processType": "default",
155		}
156
157# Uses perf script python interface to parse each
158# event and store the data in the thread builder.
159def process_event(param_dict: Dict) -> None:
160	global start_time
161	global tid_to_thread
162	time_stamp = (param_dict['sample']['time'] // 1000) / 1000
163	pid = param_dict['sample']['pid']
164	tid = param_dict['sample']['tid']
165	comm = param_dict['comm']
166
167	# Start time is the time of the first sample
168	if not start_time:
169		start_time = time_stamp
170
171# Trace_end runs at the end and will be used to aggregate
172# the data into the final json object and print it out to stdout.
173def trace_end() -> None:
174	pass
175