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