All required libraries have been imported and make sure that none of them are external dependencies. To achieve this, created a virt env and verified. Modified usage information and added combined command. Modified the main() function to read the --save-only command-line option and set the output_file variable accordingly. Modified the trace_end() function to check for the output_file variable. If it is set, the profiler data is saved to a local file in Gecko Profile format, or the profiler.firefox.com is opened on the default browser. Included trace_begin() to initialize the Firefox Profiler and launch the default browser to display the profiler.firefox.com. Added a new function launchFirefox() to start a local server and launch the profiler UI on the default browser with the appropriate URL. Created the "CORSRequestHandler" class to enable Cross-Origin Resource Sharing. Summary: This integration now includes a exiting feature to conveniently host the Gecko Profile data on a local server and open it directly in the default web browser. This means that users can now effortlessly visualize and analyze the profiler results with just a single click. The addition of the --save-only command-line option allows users to save the profiler output to a local file in Gecko Profile format, but the real highlight lies in the capability to seamlessly launch a local server, making the data accessible to Firefox Profiler via a web browser. In addition, it's important to highlight that all data are hosted locally, eliminating any concerns about data privacy rules and regulations.
Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all
Unified · Split
| @@ -1,4 +1,4 @@ | |||
| 1 | # firefox-gecko-converter.py - Convert perf record output to Firefox's gecko profile format | 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 Profile Format, | 4 | # The script converts perf.data to Gecko Profile Format, |
| @@ -7,14 +7,26 @@ · 2 unmodified lines | |||
| 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 > output.json | 10 | # perf script report gecko |
| 11 | # | ||
| 12 | # Combined: | ||
| 13 | # | ||
| 14 | # perf script gecko -F 99 -a sleep 60 | ||
| 11 | 15 | ||
| 12 | import os | 16 | import os |
| 13 | import sys | 17 | import sys |
| 18 | import time | ||
| 14 | import json | 19 | import json |
| 20 | import string | ||
| 21 | import random | ||
| 15 | import argparse | 22 | import argparse |
| 23 | import threading | ||
| 24 | import webbrowser | ||
| 25 | import urllib.parse | ||
| 26 | from os import system | ||
| 16 | from functools import reduce | 27 | from functools import reduce |
| 17 | from dataclasses import dataclass, field | 28 | from dataclasses import dataclass, field |
| 29 | from http.server import HTTPServer, SimpleHTTPRequestHandler, test | ||
| 18 | from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any | 30 | from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any |
| 19 | 31 | ||
| 20 | # Add the Perf-Trace-Util library to the Python path | 32 | # Add the Perf-Trace-Util library to the Python path |
| @@ -40,9 +52,15 @@ CATEGORIES = None · 19 unmodified lines | |||
| 40 | # The product name is used by the profiler UI to show the Operating system and Processor. | 52 | # The product name is used by the profiler UI to show the Operating system and Processor. |
| 41 | PRODUCT = os.popen('uname -op').read().strip() | 53 | PRODUCT = os.popen('uname -op').read().strip() |
| 42 | 54 | ||
| 55 | # store the output file | ||
| 56 | output_file = None | ||
| 57 | |||
| 43 | # Here key = tid, value = Thread | 58 | # Here key = tid, value = Thread |
| 44 | tid_to_thread = dict() | 59 | tid_to_thread = dict() |
| 45 | 60 | ||
| 61 | # The HTTP server is used to serve the profile to the profiler UI. | ||
| 62 | http_server_thread = None | ||
| 63 | |||
| 46 | # The category index is used by the profiler UI to show the color of the flame graph. | 64 | # The category index is used by the profiler UI to show the color of the flame graph. |
| 47 | USER_CATEGORY_INDEX = 0 | 65 | USER_CATEGORY_INDEX = 0 |
| 48 | KERNEL_CATEGORY_INDEX = 1 | 66 | KERNEL_CATEGORY_INDEX = 1 |
| @@ -278,9 +296,19 @@ def process_event(param_dict: Dict) -> None: · 229 unmodified lines | |||
| 278 | tid_to_thread[tid] = thread | 296 | tid_to_thread[tid] = thread |
| 279 | thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp) | 297 | thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp) |
| 280 | 298 | ||
| 299 | def trace_begin() -> None: | ||
| 300 | global output_file | ||
| 301 | if (output_file is None): | ||
| 302 | print("Staring Firefox Profiler on your default browser...") | ||
| 303 | global http_server_thread | ||
| 304 | http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,)) | ||
| 305 | http_server_thread.daemon = True | ||
| 306 | http_server_thread.start() | ||
| 307 | |||
| 281 | # Trace_end runs at the end and will be used to aggregate | 308 | # Trace_end runs at the end and will be used to aggregate |
| 282 | # the data into the final json object and print it out to stdout. | 309 | # the data into the final json object and print it out to stdout. |
| 283 | def trace_end() -> None: | 310 | def trace_end() -> None: |
| 311 | global output_file | ||
| 284 | threads = [thread._to_json_dict() for thread in tid_to_thread.values()] | 312 | threads = [thread._to_json_dict() for thread in tid_to_thread.values()] |
| 285 | 313 | ||
| 286 | # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305 | 314 | # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305 |
| @@ -305,22 +333,50 @@ def trace_end() -> None: · 18 unmodified lines | |||
| 305 | "processes": [], | 333 | "processes": [], |
| 306 | "pausedRanges": [], | 334 | "pausedRanges": [], |
| 307 | } | 335 | } |
| 308 | json.dump(gecko_profile_with_meta, sys.stdout, indent=2) | 336 | # launch the profiler on local host if not specified --save-only args, otherwise print to file |
| 337 | if (output_file is None): | ||
| 338 | output_file = 'gecko_profile.json' | ||
| 339 | with open(output_file, 'w') as f: | ||
| 340 | json.dump(gecko_profile_with_meta, f, indent=2) | ||
| 341 | launchFirefox(output_file) | ||
| 342 | time.sleep(1) | ||
| 343 | print(f'[ perf gecko: Captured and wrote into {output_file} ]') | ||
| 344 | else: | ||
| 345 | print(f'[ perf gecko: Captured and wrote into {output_file} ]') | ||
| 346 | with open(output_file, 'w') as f: | ||
| 347 | json.dump(gecko_profile_with_meta, f, indent=2) | ||
| 348 | |||
| 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(SimpleHTTPRequestHandler): | ||
| 351 | def end_headers (self): | ||
| 352 | self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com') | ||
| 353 | SimpleHTTPRequestHandler.end_headers(self) | ||
| 354 | |||
| 355 | # start a local server to serve the gecko_profile.json file to the profiler.firefox.com | ||
| 356 | def launchFirefox(file): | ||
| 357 | safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}') | ||
| 358 | url = 'https://profiler.firefox.com/from-url/' + safe_string | ||
| 359 | webbrowser.open(f'{url}') | ||
| 309 | 360 | ||
| 310 | def main() -> None: | 361 | def main() -> None: |
| 362 | global output_file | ||
| 311 | global CATEGORIES | 363 | global CATEGORIES |
| 312 | parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format") | 364 | parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization") |
| 313 | 365 | ||
| 314 | # Add the command-line options | 366 | # Add the command-line options |
| 315 | # Colors must be defined according to this: | 367 | # Colors must be defined according to this: |
| 316 | # https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css | 368 | # https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css |
| 317 | parser.add_argument('--user-color', default='yellow', help='Color for the User category') | 369 | parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) |
| 318 | parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category') | 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 output will be saved to a file instead of opening Firefox's profiler directly. | ||
| 372 | parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler') | ||
| 373 | |||
| 319 | # Parse the command-line arguments | 374 | # Parse the command-line arguments |
| 320 | args = parser.parse_args() | 375 | args = parser.parse_args() |
| 321 | # Access the values provided by the user | 376 | # Access the values provided by the user |
| 322 | user_color = args.user_color | 377 | user_color = args.user_color |
| 323 | kernel_color = args.kernel_color | 378 | kernel_color = args.kernel_color |
| 379 | output_file = args.save_only | ||
| 324 | 380 | ||
| 325 | CATEGORIES = [ | 381 | CATEGORIES = [ |
| 326 | { | 382 | { |
| @@ -336,4 +392,4 @@ def main() -> None: · 9 unmodified lines | |||
| 336 | ] | 392 | ] |
| 337 | 393 | ||
| 338 | if __name__ == '__main__': | 394 | if __name__ == '__main__': |
| 339 | main() | 395 | main() |