xref: /linux/tools/perf/scripts/python/gecko.py (revision 03c11eb3b16dc0058589751dfd91f254be2be613)
1*f208b2c6SAnup Sharma# gecko.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
10*f208b2c6SAnup Sharma#     perf script report gecko
11*f208b2c6SAnup Sharma#
12*f208b2c6SAnup Sharma# Combined:
13*f208b2c6SAnup Sharma#
14*f208b2c6SAnup Sharma#     perf script gecko -F 99 -a sleep 60
151699d3efSAnup Sharma
161699d3efSAnup Sharmaimport os
171699d3efSAnup Sharmaimport sys
18*f208b2c6SAnup Sharmaimport time
19833daec7SAnup Sharmaimport json
20*f208b2c6SAnup Sharmaimport string
21*f208b2c6SAnup Sharmaimport random
22833daec7SAnup Sharmaimport argparse
23*f208b2c6SAnup Sharmaimport threading
24*f208b2c6SAnup Sharmaimport webbrowser
25*f208b2c6SAnup Sharmaimport urllib.parse
26*f208b2c6SAnup Sharmafrom os import system
27258dfd41SAnup Sharmafrom functools import reduce
285aacd7f0SAnup Sharmafrom dataclasses import dataclass, field
29*f208b2c6SAnup Sharmafrom http.server import HTTPServer, SimpleHTTPRequestHandler, test
305aacd7f0SAnup Sharmafrom typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any
311699d3efSAnup Sharma
321699d3efSAnup Sharma# Add the Perf-Trace-Util library to the Python path
331699d3efSAnup Sharmasys.path.append(os.environ['PERF_EXEC_PATH'] + \
341699d3efSAnup Sharma	'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
351699d3efSAnup Sharma
361699d3efSAnup Sharmafrom perf_trace_context import *
371699d3efSAnup Sharmafrom Core import *
381699d3efSAnup Sharma
395aacd7f0SAnup SharmaStringID = int
405aacd7f0SAnup SharmaStackID = int
415aacd7f0SAnup SharmaFrameID = int
425aacd7f0SAnup SharmaCategoryID = int
435aacd7f0SAnup SharmaMilliseconds = float
445aacd7f0SAnup Sharma
450a02e44cSAnup Sharma# start_time is intialiazed only once for the all event traces.
460a02e44cSAnup Sharmastart_time = None
470a02e44cSAnup Sharma
48833daec7SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425
49833daec7SAnup Sharma# Follow Brendan Gregg's Flamegraph convention: orange for kernel and yellow for user space by default.
50833daec7SAnup SharmaCATEGORIES = None
51833daec7SAnup Sharma
52833daec7SAnup Sharma# The product name is used by the profiler UI to show the Operating system and Processor.
53833daec7SAnup SharmaPRODUCT = os.popen('uname -op').read().strip()
54833daec7SAnup Sharma
55*f208b2c6SAnup Sharma# store the output file
56*f208b2c6SAnup Sharmaoutput_file = None
57*f208b2c6SAnup Sharma
582d889c6aSAnup Sharma# Here key = tid, value = Thread
592d889c6aSAnup Sharmatid_to_thread = dict()
602d889c6aSAnup Sharma
61*f208b2c6SAnup Sharma# The HTTP server is used to serve the profile to the profiler UI.
62*f208b2c6SAnup Sharmahttp_server_thread = None
63*f208b2c6SAnup Sharma
64258dfd41SAnup Sharma# The category index is used by the profiler UI to show the color of the flame graph.
65258dfd41SAnup SharmaUSER_CATEGORY_INDEX = 0
66258dfd41SAnup SharmaKERNEL_CATEGORY_INDEX = 1
67258dfd41SAnup Sharma
685aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
695aacd7f0SAnup Sharmaclass Frame(NamedTuple):
705aacd7f0SAnup Sharma	string_id: StringID
715aacd7f0SAnup Sharma	relevantForJS: bool
725aacd7f0SAnup Sharma	innerWindowID: int
735aacd7f0SAnup Sharma	implementation: None
745aacd7f0SAnup Sharma	optimizations: None
755aacd7f0SAnup Sharma	line: None
765aacd7f0SAnup Sharma	column: None
775aacd7f0SAnup Sharma	category: CategoryID
785aacd7f0SAnup Sharma	subcategory: int
795aacd7f0SAnup Sharma
805aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
815aacd7f0SAnup Sharmaclass Stack(NamedTuple):
825aacd7f0SAnup Sharma	prefix_id: Optional[StackID]
835aacd7f0SAnup Sharma	frame_id: FrameID
845aacd7f0SAnup Sharma
855aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
865aacd7f0SAnup Sharmaclass Sample(NamedTuple):
875aacd7f0SAnup Sharma	stack_id: Optional[StackID]
885aacd7f0SAnup Sharma	time_ms: Milliseconds
895aacd7f0SAnup Sharma	responsiveness: int
905aacd7f0SAnup Sharma
915aacd7f0SAnup Sharma@dataclass
925aacd7f0SAnup Sharmaclass Thread:
935aacd7f0SAnup Sharma	"""A builder for a profile of the thread.
945aacd7f0SAnup Sharma
955aacd7f0SAnup Sharma	Attributes:
965aacd7f0SAnup Sharma		comm: Thread command-line (name).
975aacd7f0SAnup Sharma		pid: process ID of containing process.
985aacd7f0SAnup Sharma		tid: thread ID.
995aacd7f0SAnup Sharma		samples: Timeline of profile samples.
1005aacd7f0SAnup Sharma		frameTable: interned stack frame ID -> stack frame.
1015aacd7f0SAnup Sharma		stringTable: interned string ID -> string.
1025aacd7f0SAnup Sharma		stringMap: interned string -> string ID.
1035aacd7f0SAnup Sharma		stackTable: interned stack ID -> stack.
1045aacd7f0SAnup Sharma		stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID.
1055aacd7f0SAnup Sharma		frameMap: Stack Frame string -> interned Frame ID.
1065aacd7f0SAnup Sharma		comm: str
1075aacd7f0SAnup Sharma		pid: int
1085aacd7f0SAnup Sharma		tid: int
1095aacd7f0SAnup Sharma		samples: List[Sample] = field(default_factory=list)
1105aacd7f0SAnup Sharma		frameTable: List[Frame] = field(default_factory=list)
1115aacd7f0SAnup Sharma		stringTable: List[str] = field(default_factory=list)
1125aacd7f0SAnup Sharma		stringMap: Dict[str, int] = field(default_factory=dict)
1135aacd7f0SAnup Sharma		stackTable: List[Stack] = field(default_factory=list)
1145aacd7f0SAnup Sharma		stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
1155aacd7f0SAnup Sharma		frameMap: Dict[str, int] = field(default_factory=dict)
1165aacd7f0SAnup Sharma	"""
1175aacd7f0SAnup Sharma	comm: str
1185aacd7f0SAnup Sharma	pid: int
1195aacd7f0SAnup Sharma	tid: int
1205aacd7f0SAnup Sharma	samples: List[Sample] = field(default_factory=list)
1215aacd7f0SAnup Sharma	frameTable: List[Frame] = field(default_factory=list)
1225aacd7f0SAnup Sharma	stringTable: List[str] = field(default_factory=list)
1235aacd7f0SAnup Sharma	stringMap: Dict[str, int] = field(default_factory=dict)
1245aacd7f0SAnup Sharma	stackTable: List[Stack] = field(default_factory=list)
1255aacd7f0SAnup Sharma	stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
1265aacd7f0SAnup Sharma	frameMap: Dict[str, int] = field(default_factory=dict)
1275aacd7f0SAnup Sharma
128258dfd41SAnup Sharma	def _intern_stack(self, frame_id: int, prefix_id: Optional[int]) -> int:
129258dfd41SAnup Sharma		"""Gets a matching stack, or saves the new stack. Returns a Stack ID."""
130258dfd41SAnup Sharma		key = f"{frame_id}" if prefix_id is None else f"{frame_id},{prefix_id}"
131258dfd41SAnup Sharma		# key = (prefix_id, frame_id)
132258dfd41SAnup Sharma		stack_id = self.stackMap.get(key)
133258dfd41SAnup Sharma		if stack_id is None:
134258dfd41SAnup Sharma			# return stack_id
135258dfd41SAnup Sharma			stack_id = len(self.stackTable)
136258dfd41SAnup Sharma			self.stackTable.append(Stack(prefix_id=prefix_id, frame_id=frame_id))
137258dfd41SAnup Sharma			self.stackMap[key] = stack_id
138258dfd41SAnup Sharma		return stack_id
139258dfd41SAnup Sharma
140258dfd41SAnup Sharma	def _intern_string(self, string: str) -> int:
141258dfd41SAnup Sharma		"""Gets a matching string, or saves the new string. Returns a String ID."""
142258dfd41SAnup Sharma		string_id = self.stringMap.get(string)
143258dfd41SAnup Sharma		if string_id is not None:
144258dfd41SAnup Sharma			return string_id
145258dfd41SAnup Sharma		string_id = len(self.stringTable)
146258dfd41SAnup Sharma		self.stringTable.append(string)
147258dfd41SAnup Sharma		self.stringMap[string] = string_id
148258dfd41SAnup Sharma		return string_id
149258dfd41SAnup Sharma
150258dfd41SAnup Sharma	def _intern_frame(self, frame_str: str) -> int:
151258dfd41SAnup Sharma		"""Gets a matching stack frame, or saves the new frame. Returns a Frame ID."""
152258dfd41SAnup Sharma		frame_id = self.frameMap.get(frame_str)
153258dfd41SAnup Sharma		if frame_id is not None:
154258dfd41SAnup Sharma			return frame_id
155258dfd41SAnup Sharma		frame_id = len(self.frameTable)
156258dfd41SAnup Sharma		self.frameMap[frame_str] = frame_id
157258dfd41SAnup Sharma		string_id = self._intern_string(frame_str)
158258dfd41SAnup Sharma
159258dfd41SAnup Sharma		symbol_name_to_category = KERNEL_CATEGORY_INDEX if frame_str.find('kallsyms') != -1 \
160258dfd41SAnup Sharma		or frame_str.find('/vmlinux') != -1 \
161258dfd41SAnup Sharma		or frame_str.endswith('.ko)') \
162258dfd41SAnup Sharma		else USER_CATEGORY_INDEX
163258dfd41SAnup Sharma
164258dfd41SAnup Sharma		self.frameTable.append(Frame(
165258dfd41SAnup Sharma			string_id=string_id,
166258dfd41SAnup Sharma			relevantForJS=False,
167258dfd41SAnup Sharma			innerWindowID=0,
168258dfd41SAnup Sharma			implementation=None,
169258dfd41SAnup Sharma			optimizations=None,
170258dfd41SAnup Sharma			line=None,
171258dfd41SAnup Sharma			column=None,
172258dfd41SAnup Sharma			category=symbol_name_to_category,
173258dfd41SAnup Sharma			subcategory=None,
174258dfd41SAnup Sharma		))
175258dfd41SAnup Sharma		return frame_id
176258dfd41SAnup Sharma
1772d889c6aSAnup Sharma	def _add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None:
1782d889c6aSAnup Sharma		"""Add a timestamped stack trace sample to the thread builder.
1792d889c6aSAnup Sharma		Args:
1802d889c6aSAnup Sharma			comm: command-line (name) of the thread at this sample
1812d889c6aSAnup Sharma			stack: sampled stack frames. Root first, leaf last.
1822d889c6aSAnup Sharma			time_ms: timestamp of sample in milliseconds.
1832d889c6aSAnup Sharma		"""
1842d889c6aSAnup Sharma		# Ihreads may not set their names right after they are created.
1852d889c6aSAnup Sharma		# Instead, they might do it later. In such situations, to use the latest name they have set.
1862d889c6aSAnup Sharma		if self.comm != comm:
1872d889c6aSAnup Sharma			self.comm = comm
1882d889c6aSAnup Sharma
1892d889c6aSAnup Sharma		prefix_stack_id = reduce(lambda prefix_id, frame: self._intern_stack
1902d889c6aSAnup Sharma						(self._intern_frame(frame), prefix_id), stack, None)
1912d889c6aSAnup Sharma		if prefix_stack_id is not None:
1922d889c6aSAnup Sharma			self.samples.append(Sample(stack_id=prefix_stack_id,
1932d889c6aSAnup Sharma									time_ms=time_ms,
1942d889c6aSAnup Sharma									responsiveness=0))
1952d889c6aSAnup Sharma
1965aacd7f0SAnup Sharma	def _to_json_dict(self) -> Dict:
1975aacd7f0SAnup Sharma		"""Converts current Thread to GeckoThread JSON format."""
1985aacd7f0SAnup Sharma		# Gecko profile format is row-oriented data as List[List],
1995aacd7f0SAnup Sharma		# And a schema for interpreting each index.
2005aacd7f0SAnup Sharma		# Schema:
2015aacd7f0SAnup Sharma		# https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md
2025aacd7f0SAnup Sharma		# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230
2035aacd7f0SAnup Sharma		return {
2045aacd7f0SAnup Sharma			"tid": self.tid,
2055aacd7f0SAnup Sharma			"pid": self.pid,
2065aacd7f0SAnup Sharma			"name": self.comm,
2075aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51
2085aacd7f0SAnup Sharma			"markers": {
2095aacd7f0SAnup Sharma				"schema": {
2105aacd7f0SAnup Sharma					"name": 0,
2115aacd7f0SAnup Sharma					"startTime": 1,
2125aacd7f0SAnup Sharma					"endTime": 2,
2135aacd7f0SAnup Sharma					"phase": 3,
2145aacd7f0SAnup Sharma					"category": 4,
2155aacd7f0SAnup Sharma					"data": 5,
2165aacd7f0SAnup Sharma				},
2175aacd7f0SAnup Sharma				"data": [],
2185aacd7f0SAnup Sharma			},
2195aacd7f0SAnup Sharma
2205aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
2215aacd7f0SAnup Sharma			"samples": {
2225aacd7f0SAnup Sharma				"schema": {
2235aacd7f0SAnup Sharma					"stack": 0,
2245aacd7f0SAnup Sharma					"time": 1,
2255aacd7f0SAnup Sharma					"responsiveness": 2,
2265aacd7f0SAnup Sharma				},
2275aacd7f0SAnup Sharma				"data": self.samples
2285aacd7f0SAnup Sharma			},
2295aacd7f0SAnup Sharma
2305aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
2315aacd7f0SAnup Sharma			"frameTable": {
2325aacd7f0SAnup Sharma				"schema": {
2335aacd7f0SAnup Sharma					"location": 0,
2345aacd7f0SAnup Sharma					"relevantForJS": 1,
2355aacd7f0SAnup Sharma					"innerWindowID": 2,
2365aacd7f0SAnup Sharma					"implementation": 3,
2375aacd7f0SAnup Sharma					"optimizations": 4,
2385aacd7f0SAnup Sharma					"line": 5,
2395aacd7f0SAnup Sharma					"column": 6,
2405aacd7f0SAnup Sharma					"category": 7,
2415aacd7f0SAnup Sharma					"subcategory": 8,
2425aacd7f0SAnup Sharma				},
2435aacd7f0SAnup Sharma				"data": self.frameTable,
2445aacd7f0SAnup Sharma			},
2455aacd7f0SAnup Sharma
2465aacd7f0SAnup Sharma			# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
2475aacd7f0SAnup Sharma			"stackTable": {
2485aacd7f0SAnup Sharma				"schema": {
2495aacd7f0SAnup Sharma					"prefix": 0,
2505aacd7f0SAnup Sharma					"frame": 1,
2515aacd7f0SAnup Sharma				},
2525aacd7f0SAnup Sharma				"data": self.stackTable,
2535aacd7f0SAnup Sharma			},
2545aacd7f0SAnup Sharma			"stringTable": self.stringTable,
2555aacd7f0SAnup Sharma			"registerTime": 0,
2565aacd7f0SAnup Sharma			"unregisterTime": None,
2575aacd7f0SAnup Sharma			"processType": "default",
2585aacd7f0SAnup Sharma		}
2595aacd7f0SAnup Sharma
2601699d3efSAnup Sharma# Uses perf script python interface to parse each
2611699d3efSAnup Sharma# event and store the data in the thread builder.
2621699d3efSAnup Sharmadef process_event(param_dict: Dict) -> None:
2630a02e44cSAnup Sharma	global start_time
2640a02e44cSAnup Sharma	global tid_to_thread
2650a02e44cSAnup Sharma	time_stamp = (param_dict['sample']['time'] // 1000) / 1000
2660a02e44cSAnup Sharma	pid = param_dict['sample']['pid']
2670a02e44cSAnup Sharma	tid = param_dict['sample']['tid']
2680a02e44cSAnup Sharma	comm = param_dict['comm']
2690a02e44cSAnup Sharma
2700a02e44cSAnup Sharma	# Start time is the time of the first sample
2710a02e44cSAnup Sharma	if not start_time:
2720a02e44cSAnup Sharma		start_time = time_stamp
2731699d3efSAnup Sharma
2742d889c6aSAnup Sharma	# Parse and append the callchain of the current sample into a stack.
2752d889c6aSAnup Sharma	stack = []
2762d889c6aSAnup Sharma	if param_dict['callchain']:
2772d889c6aSAnup Sharma		for call in param_dict['callchain']:
2782d889c6aSAnup Sharma			if 'sym' not in call:
2792d889c6aSAnup Sharma				continue
2802d889c6aSAnup Sharma			stack.append(f'{call["sym"]["name"]} (in {call["dso"]})')
2812d889c6aSAnup Sharma		if len(stack) != 0:
2822d889c6aSAnup Sharma			# Reverse the stack, as root come first and the leaf at the end.
2832d889c6aSAnup Sharma			stack = stack[::-1]
2842d889c6aSAnup Sharma
2852d889c6aSAnup Sharma	# During perf record if -g is not used, the callchain is not available.
2862d889c6aSAnup Sharma	# In that case, the symbol and dso are available in the event parameters.
2872d889c6aSAnup Sharma	else:
2882d889c6aSAnup Sharma		func = param_dict['symbol'] if 'symbol' in param_dict else '[unknown]'
2892d889c6aSAnup Sharma		dso = param_dict['dso'] if 'dso' in param_dict else '[unknown]'
2902d889c6aSAnup Sharma		stack.append(f'{func} (in {dso})')
2912d889c6aSAnup Sharma
2922d889c6aSAnup Sharma	# Add sample to the specific thread.
2932d889c6aSAnup Sharma	thread = tid_to_thread.get(tid)
2942d889c6aSAnup Sharma	if thread is None:
2952d889c6aSAnup Sharma		thread = Thread(comm=comm, pid=pid, tid=tid)
2962d889c6aSAnup Sharma		tid_to_thread[tid] = thread
2972d889c6aSAnup Sharma	thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp)
2982d889c6aSAnup Sharma
299*f208b2c6SAnup Sharmadef trace_begin() -> None:
300*f208b2c6SAnup Sharma	global output_file
301*f208b2c6SAnup Sharma	if (output_file is None):
302*f208b2c6SAnup Sharma		print("Staring Firefox Profiler on your default browser...")
303*f208b2c6SAnup Sharma		global http_server_thread
304*f208b2c6SAnup Sharma		http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,))
305*f208b2c6SAnup Sharma		http_server_thread.daemon = True
306*f208b2c6SAnup Sharma		http_server_thread.start()
307*f208b2c6SAnup Sharma
3081699d3efSAnup Sharma# Trace_end runs at the end and will be used to aggregate
3091699d3efSAnup Sharma# the data into the final json object and print it out to stdout.
3101699d3efSAnup Sharmadef trace_end() -> None:
311*f208b2c6SAnup Sharma	global output_file
3122d889c6aSAnup Sharma	threads = [thread._to_json_dict() for thread in tid_to_thread.values()]
3132d889c6aSAnup Sharma
314833daec7SAnup Sharma	# Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305
315833daec7SAnup Sharma	gecko_profile_with_meta = {
316833daec7SAnup Sharma		"meta": {
317833daec7SAnup Sharma			"interval": 1,
318833daec7SAnup Sharma			"processType": 0,
319833daec7SAnup Sharma			"product": PRODUCT,
320833daec7SAnup Sharma			"stackwalk": 1,
321833daec7SAnup Sharma			"debug": 0,
322833daec7SAnup Sharma			"gcpoison": 0,
323833daec7SAnup Sharma			"asyncstack": 1,
324833daec7SAnup Sharma			"startTime": start_time,
325833daec7SAnup Sharma			"shutdownTime": None,
326833daec7SAnup Sharma			"version": 24,
327833daec7SAnup Sharma			"presymbolicated": True,
328833daec7SAnup Sharma			"categories": CATEGORIES,
329833daec7SAnup Sharma			"markerSchema": [],
330833daec7SAnup Sharma			},
331833daec7SAnup Sharma		"libs": [],
3322d889c6aSAnup Sharma		"threads": threads,
333833daec7SAnup Sharma		"processes": [],
334833daec7SAnup Sharma		"pausedRanges": [],
335833daec7SAnup Sharma	}
336*f208b2c6SAnup Sharma	# launch the profiler on local host if not specified --save-only args, otherwise print to file
337*f208b2c6SAnup Sharma	if (output_file is None):
338*f208b2c6SAnup Sharma		output_file = 'gecko_profile.json'
339*f208b2c6SAnup Sharma		with open(output_file, 'w') as f:
340*f208b2c6SAnup Sharma			json.dump(gecko_profile_with_meta, f, indent=2)
341*f208b2c6SAnup Sharma		launchFirefox(output_file)
342*f208b2c6SAnup Sharma		time.sleep(1)
343*f208b2c6SAnup Sharma		print(f'[ perf gecko: Captured and wrote into {output_file} ]')
344*f208b2c6SAnup Sharma	else:
345*f208b2c6SAnup Sharma		print(f'[ perf gecko: Captured and wrote into {output_file} ]')
346*f208b2c6SAnup Sharma		with open(output_file, 'w') as f:
347*f208b2c6SAnup Sharma			json.dump(gecko_profile_with_meta, f, indent=2)
348*f208b2c6SAnup Sharma
349*f208b2c6SAnup Sharma# Used to enable Cross-Origin Resource Sharing (CORS) for requests coming from 'https://profiler.firefox.com', allowing it to access resources from this server.
350*f208b2c6SAnup Sharmaclass CORSRequestHandler(SimpleHTTPRequestHandler):
351*f208b2c6SAnup Sharma	def end_headers (self):
352*f208b2c6SAnup Sharma		self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com')
353*f208b2c6SAnup Sharma		SimpleHTTPRequestHandler.end_headers(self)
354*f208b2c6SAnup Sharma
355*f208b2c6SAnup Sharma# start a local server to serve the gecko_profile.json file to the profiler.firefox.com
356*f208b2c6SAnup Sharmadef launchFirefox(file):
357*f208b2c6SAnup Sharma	safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}')
358*f208b2c6SAnup Sharma	url = 'https://profiler.firefox.com/from-url/' + safe_string
359*f208b2c6SAnup Sharma	webbrowser.open(f'{url}')
360833daec7SAnup Sharma
361833daec7SAnup Sharmadef main() -> None:
362*f208b2c6SAnup Sharma	global output_file
363833daec7SAnup Sharma	global CATEGORIES
364*f208b2c6SAnup Sharma	parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization")
365833daec7SAnup Sharma
366833daec7SAnup Sharma	# Add the command-line options
367833daec7SAnup Sharma	# Colors must be defined according to this:
368833daec7SAnup Sharma	# https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css
369*f208b2c6SAnup Sharma	parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta'])
370*f208b2c6SAnup Sharma	parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta'])
371*f208b2c6SAnup Sharma	# If --save-only is specified, the output will be saved to a file instead of opening Firefox's profiler directly.
372*f208b2c6SAnup Sharma	parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler')
373*f208b2c6SAnup Sharma
374833daec7SAnup Sharma	# Parse the command-line arguments
375833daec7SAnup Sharma	args = parser.parse_args()
376833daec7SAnup Sharma	# Access the values provided by the user
377833daec7SAnup Sharma	user_color = args.user_color
378833daec7SAnup Sharma	kernel_color = args.kernel_color
379*f208b2c6SAnup Sharma	output_file = args.save_only
380833daec7SAnup Sharma
381833daec7SAnup Sharma	CATEGORIES = [
382833daec7SAnup Sharma		{
383833daec7SAnup Sharma			"name": 'User',
384833daec7SAnup Sharma			"color": user_color,
385833daec7SAnup Sharma			"subcategories": ['Other']
386833daec7SAnup Sharma		},
387833daec7SAnup Sharma		{
388833daec7SAnup Sharma			"name": 'Kernel',
389833daec7SAnup Sharma			"color": kernel_color,
390833daec7SAnup Sharma			"subcategories": ['Other']
391833daec7SAnup Sharma		},
392833daec7SAnup Sharma	]
393833daec7SAnup Sharma
394833daec7SAnup Sharmaif __name__ == '__main__':
395833daec7SAnup Sharma	main()
396