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*5aacd7f0SAnup Sharmafrom dataclasses import dataclass, field 15*5aacd7f0SAnup Sharmafrom typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any 161699d3efSAnup Sharma 171699d3efSAnup Sharma# Add the Perf-Trace-Util library to the Python path 181699d3efSAnup Sharmasys.path.append(os.environ['PERF_EXEC_PATH'] + \ 191699d3efSAnup Sharma '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') 201699d3efSAnup Sharma 211699d3efSAnup Sharmafrom perf_trace_context import * 221699d3efSAnup Sharmafrom Core import * 231699d3efSAnup Sharma 24*5aacd7f0SAnup SharmaStringID = int 25*5aacd7f0SAnup SharmaStackID = int 26*5aacd7f0SAnup SharmaFrameID = int 27*5aacd7f0SAnup SharmaCategoryID = int 28*5aacd7f0SAnup SharmaMilliseconds = float 29*5aacd7f0SAnup Sharma 300a02e44cSAnup Sharma# start_time is intialiazed only once for the all event traces. 310a02e44cSAnup Sharmastart_time = None 320a02e44cSAnup Sharma 33*5aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156 34*5aacd7f0SAnup Sharmaclass Frame(NamedTuple): 35*5aacd7f0SAnup Sharma string_id: StringID 36*5aacd7f0SAnup Sharma relevantForJS: bool 37*5aacd7f0SAnup Sharma innerWindowID: int 38*5aacd7f0SAnup Sharma implementation: None 39*5aacd7f0SAnup Sharma optimizations: None 40*5aacd7f0SAnup Sharma line: None 41*5aacd7f0SAnup Sharma column: None 42*5aacd7f0SAnup Sharma category: CategoryID 43*5aacd7f0SAnup Sharma subcategory: int 44*5aacd7f0SAnup Sharma 45*5aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216 46*5aacd7f0SAnup Sharmaclass Stack(NamedTuple): 47*5aacd7f0SAnup Sharma prefix_id: Optional[StackID] 48*5aacd7f0SAnup Sharma frame_id: FrameID 49*5aacd7f0SAnup Sharma 50*5aacd7f0SAnup Sharma# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90 51*5aacd7f0SAnup Sharmaclass Sample(NamedTuple): 52*5aacd7f0SAnup Sharma stack_id: Optional[StackID] 53*5aacd7f0SAnup Sharma time_ms: Milliseconds 54*5aacd7f0SAnup Sharma responsiveness: int 55*5aacd7f0SAnup Sharma 56*5aacd7f0SAnup Sharma@dataclass 57*5aacd7f0SAnup Sharmaclass Thread: 58*5aacd7f0SAnup Sharma """A builder for a profile of the thread. 59*5aacd7f0SAnup Sharma 60*5aacd7f0SAnup Sharma Attributes: 61*5aacd7f0SAnup Sharma comm: Thread command-line (name). 62*5aacd7f0SAnup Sharma pid: process ID of containing process. 63*5aacd7f0SAnup Sharma tid: thread ID. 64*5aacd7f0SAnup Sharma samples: Timeline of profile samples. 65*5aacd7f0SAnup Sharma frameTable: interned stack frame ID -> stack frame. 66*5aacd7f0SAnup Sharma stringTable: interned string ID -> string. 67*5aacd7f0SAnup Sharma stringMap: interned string -> string ID. 68*5aacd7f0SAnup Sharma stackTable: interned stack ID -> stack. 69*5aacd7f0SAnup Sharma stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID. 70*5aacd7f0SAnup Sharma frameMap: Stack Frame string -> interned Frame ID. 71*5aacd7f0SAnup Sharma comm: str 72*5aacd7f0SAnup Sharma pid: int 73*5aacd7f0SAnup Sharma tid: int 74*5aacd7f0SAnup Sharma samples: List[Sample] = field(default_factory=list) 75*5aacd7f0SAnup Sharma frameTable: List[Frame] = field(default_factory=list) 76*5aacd7f0SAnup Sharma stringTable: List[str] = field(default_factory=list) 77*5aacd7f0SAnup Sharma stringMap: Dict[str, int] = field(default_factory=dict) 78*5aacd7f0SAnup Sharma stackTable: List[Stack] = field(default_factory=list) 79*5aacd7f0SAnup Sharma stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict) 80*5aacd7f0SAnup Sharma frameMap: Dict[str, int] = field(default_factory=dict) 81*5aacd7f0SAnup Sharma """ 82*5aacd7f0SAnup Sharma comm: str 83*5aacd7f0SAnup Sharma pid: int 84*5aacd7f0SAnup Sharma tid: int 85*5aacd7f0SAnup Sharma samples: List[Sample] = field(default_factory=list) 86*5aacd7f0SAnup Sharma frameTable: List[Frame] = field(default_factory=list) 87*5aacd7f0SAnup Sharma stringTable: List[str] = field(default_factory=list) 88*5aacd7f0SAnup Sharma stringMap: Dict[str, int] = field(default_factory=dict) 89*5aacd7f0SAnup Sharma stackTable: List[Stack] = field(default_factory=list) 90*5aacd7f0SAnup Sharma stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict) 91*5aacd7f0SAnup Sharma frameMap: Dict[str, int] = field(default_factory=dict) 92*5aacd7f0SAnup Sharma 93*5aacd7f0SAnup Sharma def _to_json_dict(self) -> Dict: 94*5aacd7f0SAnup Sharma """Converts current Thread to GeckoThread JSON format.""" 95*5aacd7f0SAnup Sharma # Gecko profile format is row-oriented data as List[List], 96*5aacd7f0SAnup Sharma # And a schema for interpreting each index. 97*5aacd7f0SAnup Sharma # Schema: 98*5aacd7f0SAnup Sharma # https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md 99*5aacd7f0SAnup Sharma # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230 100*5aacd7f0SAnup Sharma return { 101*5aacd7f0SAnup Sharma "tid": self.tid, 102*5aacd7f0SAnup Sharma "pid": self.pid, 103*5aacd7f0SAnup Sharma "name": self.comm, 104*5aacd7f0SAnup Sharma # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51 105*5aacd7f0SAnup Sharma "markers": { 106*5aacd7f0SAnup Sharma "schema": { 107*5aacd7f0SAnup Sharma "name": 0, 108*5aacd7f0SAnup Sharma "startTime": 1, 109*5aacd7f0SAnup Sharma "endTime": 2, 110*5aacd7f0SAnup Sharma "phase": 3, 111*5aacd7f0SAnup Sharma "category": 4, 112*5aacd7f0SAnup Sharma "data": 5, 113*5aacd7f0SAnup Sharma }, 114*5aacd7f0SAnup Sharma "data": [], 115*5aacd7f0SAnup Sharma }, 116*5aacd7f0SAnup Sharma 117*5aacd7f0SAnup Sharma # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90 118*5aacd7f0SAnup Sharma "samples": { 119*5aacd7f0SAnup Sharma "schema": { 120*5aacd7f0SAnup Sharma "stack": 0, 121*5aacd7f0SAnup Sharma "time": 1, 122*5aacd7f0SAnup Sharma "responsiveness": 2, 123*5aacd7f0SAnup Sharma }, 124*5aacd7f0SAnup Sharma "data": self.samples 125*5aacd7f0SAnup Sharma }, 126*5aacd7f0SAnup Sharma 127*5aacd7f0SAnup Sharma # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156 128*5aacd7f0SAnup Sharma "frameTable": { 129*5aacd7f0SAnup Sharma "schema": { 130*5aacd7f0SAnup Sharma "location": 0, 131*5aacd7f0SAnup Sharma "relevantForJS": 1, 132*5aacd7f0SAnup Sharma "innerWindowID": 2, 133*5aacd7f0SAnup Sharma "implementation": 3, 134*5aacd7f0SAnup Sharma "optimizations": 4, 135*5aacd7f0SAnup Sharma "line": 5, 136*5aacd7f0SAnup Sharma "column": 6, 137*5aacd7f0SAnup Sharma "category": 7, 138*5aacd7f0SAnup Sharma "subcategory": 8, 139*5aacd7f0SAnup Sharma }, 140*5aacd7f0SAnup Sharma "data": self.frameTable, 141*5aacd7f0SAnup Sharma }, 142*5aacd7f0SAnup Sharma 143*5aacd7f0SAnup Sharma # https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216 144*5aacd7f0SAnup Sharma "stackTable": { 145*5aacd7f0SAnup Sharma "schema": { 146*5aacd7f0SAnup Sharma "prefix": 0, 147*5aacd7f0SAnup Sharma "frame": 1, 148*5aacd7f0SAnup Sharma }, 149*5aacd7f0SAnup Sharma "data": self.stackTable, 150*5aacd7f0SAnup Sharma }, 151*5aacd7f0SAnup Sharma "stringTable": self.stringTable, 152*5aacd7f0SAnup Sharma "registerTime": 0, 153*5aacd7f0SAnup Sharma "unregisterTime": None, 154*5aacd7f0SAnup Sharma "processType": "default", 155*5aacd7f0SAnup Sharma } 156*5aacd7f0SAnup Sharma 1571699d3efSAnup Sharma# Uses perf script python interface to parse each 1581699d3efSAnup Sharma# event and store the data in the thread builder. 1591699d3efSAnup Sharmadef process_event(param_dict: Dict) -> None: 1600a02e44cSAnup Sharma global start_time 1610a02e44cSAnup Sharma global tid_to_thread 1620a02e44cSAnup Sharma time_stamp = (param_dict['sample']['time'] // 1000) / 1000 1630a02e44cSAnup Sharma pid = param_dict['sample']['pid'] 1640a02e44cSAnup Sharma tid = param_dict['sample']['tid'] 1650a02e44cSAnup Sharma comm = param_dict['comm'] 1660a02e44cSAnup Sharma 1670a02e44cSAnup Sharma # Start time is the time of the first sample 1680a02e44cSAnup Sharma if not start_time: 1690a02e44cSAnup Sharma start_time = time_stamp 1701699d3efSAnup Sharma 1711699d3efSAnup Sharma# Trace_end runs at the end and will be used to aggregate 1721699d3efSAnup Sharma# the data into the final json object and print it out to stdout. 1731699d3efSAnup Sharmadef trace_end() -> None: 1741699d3efSAnup Sharma pass 175