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