1 # gecko.py - Convert perf record output to Fir 2 # SPDX-License-Identifier: GPL-2.0 3 # 4 # The script converts perf.data to Gecko Profi 5 # which can be read by https://profiler.firefo 6 # 7 # Usage: 8 # 9 # perf record -a -g -F 99 sleep 60 10 # perf script report gecko 11 # 12 # Combined: 13 # 14 # perf script gecko -F 99 -a sleep 60 15 16 import os 17 import sys 18 import time 19 import json 20 import string 21 import random 22 import argparse 23 import threading 24 import webbrowser 25 import urllib.parse 26 from os import system 27 from functools import reduce 28 from dataclasses import dataclass, field 29 from http.server import HTTPServer, SimpleHTTP 30 from typing import List, Dict, Optional, Named 31 32 # Add the Perf-Trace-Util library to the Pytho 33 sys.path.append(os.environ['PERF_EXEC_PATH'] + 34 '/scripts/python/Perf-Trace-Util/lib/P 35 36 from perf_trace_context import * 37 from Core import * 38 39 StringID = int 40 StackID = int 41 FrameID = int 42 CategoryID = int 43 Milliseconds = float 44 45 # start_time is intialiazed only once for the 46 start_time = None 47 48 # https://github.com/firefox-devtools/profiler 49 # Follow Brendan Gregg's Flamegraph convention 50 CATEGORIES = None 51 52 # The product name is used by the profiler UI 53 PRODUCT = os.popen('uname -op').read().strip() 54 55 # store the output file 56 output_file = None 57 58 # Here key = tid, value = Thread 59 tid_to_thread = dict() 60 61 # The HTTP server is used to serve the profile 62 http_server_thread = None 63 64 # The category index is used by the profiler U 65 USER_CATEGORY_INDEX = 0 66 KERNEL_CATEGORY_INDEX = 1 67 68 # https://github.com/firefox-devtools/profiler 69 class Frame(NamedTuple): 70 string_id: StringID 71 relevantForJS: bool 72 innerWindowID: int 73 implementation: None 74 optimizations: None 75 line: None 76 column: None 77 category: CategoryID 78 subcategory: int 79 80 # https://github.com/firefox-devtools/profiler 81 class Stack(NamedTuple): 82 prefix_id: Optional[StackID] 83 frame_id: FrameID 84 85 # https://github.com/firefox-devtools/profiler 86 class Sample(NamedTuple): 87 stack_id: Optional[StackID] 88 time_ms: Milliseconds 89 responsiveness: int 90 91 @dataclass 92 class Thread: 93 """A builder for a profile of the thre 94 95 Attributes: 96 comm: Thread command-line (nam 97 pid: process ID of containing 98 tid: thread ID. 99 samples: Timeline of profile s 100 frameTable: interned stack fra 101 stringTable: interned string I 102 stringMap: interned string -> 103 stackTable: interned stack ID 104 stackMap: (stack prefix ID, le 105 frameMap: Stack Frame string - 106 comm: str 107 pid: int 108 tid: int 109 samples: List[Sample] = field( 110 frameTable: List[Frame] = fiel 111 stringTable: List[str] = field 112 stringMap: Dict[str, int] = fi 113 stackTable: List[Stack] = fiel 114 stackMap: Dict[Tuple[Optional[ 115 frameMap: Dict[str, int] = fie 116 """ 117 comm: str 118 pid: int 119 tid: int 120 samples: List[Sample] = field(default_ 121 frameTable: List[Frame] = field(defaul 122 stringTable: List[str] = field(default 123 stringMap: Dict[str, int] = field(defa 124 stackTable: List[Stack] = field(defaul 125 stackMap: Dict[Tuple[Optional[int], in 126 frameMap: Dict[str, int] = field(defau 127 128 def _intern_stack(self, frame_id: int, 129 """Gets a matching stack, or s 130 key = f"{frame_id}" if prefix_ 131 # key = (prefix_id, frame_id) 132 stack_id = self.stackMap.get(k 133 if stack_id is None: 134 # return stack_id 135 stack_id = len(self.st 136 self.stackTable.append 137 self.stackMap[key] = s 138 return stack_id 139 140 def _intern_string(self, string: str) 141 """Gets a matching string, or 142 string_id = self.stringMap.get 143 if string_id is not None: 144 return string_id 145 string_id = len(self.stringTab 146 self.stringTable.append(string 147 self.stringMap[string] = strin 148 return string_id 149 150 def _intern_frame(self, frame_str: str 151 """Gets a matching stack frame 152 frame_id = self.frameMap.get(f 153 if frame_id is not None: 154 return frame_id 155 frame_id = len(self.frameTable 156 self.frameMap[frame_str] = fra 157 string_id = self._intern_strin 158 159 symbol_name_to_category = KERN 160 or frame_str.find('/vmlinux') 161 or frame_str.endswith('.ko)') 162 else USER_CATEGORY_INDEX 163 164 self.frameTable.append(Frame( 165 string_id=string_id, 166 relevantForJS=False, 167 innerWindowID=0, 168 implementation=None, 169 optimizations=None, 170 line=None, 171 column=None, 172 category=symbol_name_t 173 subcategory=None, 174 )) 175 return frame_id 176 177 def _add_sample(self, comm: str, stack 178 """Add a timestamped stack tra 179 Args: 180 comm: command-line (na 181 stack: sampled stack f 182 time_ms: timestamp of 183 """ 184 # Ihreads may not set their na 185 # Instead, they might do it la 186 if self.comm != comm: 187 self.comm = comm 188 189 prefix_stack_id = reduce(lambd 190 191 if prefix_stack_id is not None 192 self.samples.append(Sa 193 194 195 196 def _to_json_dict(self) -> Dict: 197 """Converts current Thread to 198 # Gecko profile format is row- 199 # And a schema for interpretin 200 # Schema: 201 # https://github.com/firefox-d 202 # https://github.com/firefox-d 203 return { 204 "tid": self.tid, 205 "pid": self.pid, 206 "name": self.comm, 207 # https://github.com/f 208 "markers": { 209 "schema": { 210 "name" 211 "start 212 "endTi 213 "phase 214 "categ 215 "data" 216 }, 217 "data": [], 218 }, 219 220 # https://github.com/f 221 "samples": { 222 "schema": { 223 "stack 224 "time" 225 "respo 226 }, 227 "data": self.s 228 }, 229 230 # https://github.com/f 231 "frameTable": { 232 "schema": { 233 "locat 234 "relev 235 "inner 236 "imple 237 "optim 238 "line" 239 "colum 240 "categ 241 "subca 242 }, 243 "data": self.f 244 }, 245 246 # https://github.com/f 247 "stackTable": { 248 "schema": { 249 "prefi 250 "frame 251 }, 252 "data": self.s 253 }, 254 "stringTable": self.st 255 "registerTime": 0, 256 "unregisterTime": None 257 "processType": "defaul 258 } 259 260 # Uses perf script python interface to parse e 261 # event and store the data in the thread build 262 def process_event(param_dict: Dict) -> None: 263 global start_time 264 global tid_to_thread 265 time_stamp = (param_dict['sample']['ti 266 pid = param_dict['sample']['pid'] 267 tid = param_dict['sample']['tid'] 268 comm = param_dict['comm'] 269 270 # Start time is the time of the first 271 if not start_time: 272 start_time = time_stamp 273 274 # Parse and append the callchain of th 275 stack = [] 276 if param_dict['callchain']: 277 for call in param_dict['callch 278 if 'sym' not in call: 279 continue 280 stack.append(f'{call[" 281 if len(stack) != 0: 282 # Reverse the stack, a 283 stack = stack[::-1] 284 285 # During perf record if -g is not used 286 # In that case, the symbol and dso are 287 else: 288 func = param_dict['symbol'] if 289 dso = param_dict['dso'] if 'ds 290 stack.append(f'{func} (in {dso 291 292 # Add sample to the specific thread. 293 thread = tid_to_thread.get(tid) 294 if thread is None: 295 thread = Thread(comm=comm, pid 296 tid_to_thread[tid] = thread 297 thread._add_sample(comm=comm, stack=st 298 299 def trace_begin() -> None: 300 global output_file 301 if (output_file is None): 302 print("Staring Firefox Profile 303 global http_server_thread 304 http_server_thread = threading 305 http_server_thread.daemon = Tr 306 http_server_thread.start() 307 308 # Trace_end runs at the end and will be used t 309 # the data into the final json object and prin 310 def trace_end() -> None: 311 global output_file 312 threads = [thread._to_json_dict() for 313 314 # Schema: https://github.com/firefox-d 315 gecko_profile_with_meta = { 316 "meta": { 317 "interval": 1, 318 "processType": 0, 319 "product": PRODUCT, 320 "stackwalk": 1, 321 "debug": 0, 322 "gcpoison": 0, 323 "asyncstack": 1, 324 "startTime": start_tim 325 "shutdownTime": None, 326 "version": 24, 327 "presymbolicated": Tru 328 "categories": CATEGORI 329 "markerSchema": [], 330 }, 331 "libs": [], 332 "threads": threads, 333 "processes": [], 334 "pausedRanges": [], 335 } 336 # launch the profiler on local host if 337 if (output_file is None): 338 output_file = 'gecko_profile.j 339 with open(output_file, 'w') as 340 json.dump(gecko_profil 341 launchFirefox(output_file) 342 time.sleep(1) 343 print(f'[ perf gecko: Captured 344 else: 345 print(f'[ perf gecko: Captured 346 with open(output_file, 'w') as 347 json.dump(gecko_profil 348 349 # Used to enable Cross-Origin Resource Sharing 350 class CORSRequestHandler(SimpleHTTPRequestHand 351 def end_headers (self): 352 self.send_header('Access-Contr 353 SimpleHTTPRequestHandler.end_h 354 355 # start a local server to serve the gecko_prof 356 def launchFirefox(file): 357 safe_string = urllib.parse.quote_plus( 358 url = 'https://profiler.firefox.com/fr 359 webbrowser.open(f'{url}') 360 361 def main() -> None: 362 global output_file 363 global CATEGORIES 364 parser = argparse.ArgumentParser(descr 365 366 # Add the command-line options 367 # Colors must be defined according to 368 # https://github.com/firefox-devtools/ 369 parser.add_argument('--user-color', de 370 parser.add_argument('--kernel-color', 371 # If --save-only is specified, the out 372 parser.add_argument('--save-only', hel 373 374 # Parse the command-line arguments 375 args = parser.parse_args() 376 # Access the values provided by the us 377 user_color = args.user_color 378 kernel_color = args.kernel_color 379 output_file = args.save_only 380 381 CATEGORIES = [ 382 { 383 "name": 'User', 384 "color": user_color, 385 "subcategories": ['Oth 386 }, 387 { 388 "name": 'Kernel', 389 "color": kernel_color, 390 "subcategories": ['Oth 391 }, 392 ] 393 394 if __name__ == '__main__': 395 main()
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.