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