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