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 | 2 | # SPDX-License-Identifier: GPL-2.0 |
| 3 | 3 | # |
| 4 | 4 | # The script converts perf.data to Gecko Profile Format, |
| @@ -7,14 +7,26 @@ · 2 unmodified lines | ||
| 7 | 7 | # Usage: |
| 8 | 8 | # |
| 9 | 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 | 16 | import os |
| 13 | 17 | import sys |
| 18 | +import time | |
| 14 | 19 | import json |
| 20 | +import string | |
| 21 | +import random | |
| 15 | 22 | import argparse |
| 23 | +import threading | |
| 24 | +import webbrowser | |
| 25 | +import urllib.parse | |
| 26 | +from os import system | |
| 16 | 27 | from functools import reduce |
| 17 | 28 | from dataclasses import dataclass, field |
| 29 | +from http.server import HTTPServer, SimpleHTTPRequestHandler, test | |
| 18 | 30 | from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any |
| 19 | 31 | |
| 20 | 32 | # Add the Perf-Trace-Util library to the Python path |
| @@ -40,9 +52,15 @@ CATEGORIES = None · 19 unmodified lines | ||
| 40 | 52 | # The product name is used by the profiler UI to show the Operating system and Processor. |
| 41 | 53 | PRODUCT = os.popen('uname -op').read().strip() |
| 42 | 54 | |
| 55 | +# store the output file | |
| 56 | +output_file = None | |
| 57 | + | |
| 43 | 58 | # Here key = tid, value = Thread |
| 44 | 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 | 64 | # The category index is used by the profiler UI to show the color of the flame graph. |
| 47 | 65 | USER_CATEGORY_INDEX = 0 |
| 48 | 66 | KERNEL_CATEGORY_INDEX = 1 |
| @@ -278,9 +296,19 @@ def process_event(param_dict: Dict) -> None: · 229 unmodified lines | ||
| 278 | 296 | tid_to_thread[tid] = thread |
| 279 | 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 | 308 | # Trace_end runs at the end and will be used to aggregate |
| 282 | 309 | # the data into the final json object and print it out to stdout. |
| 283 | 310 | def trace_end() -> None: |
| 311 | + global output_file | |
| 284 | 312 | threads = [thread._to_json_dict() for thread in tid_to_thread.values()] |
| 285 | 313 | |
| 286 | 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 | 333 | "processes": [], |
| 306 | 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 | 361 | def main() -> None: |
| 362 | + global output_file | |
| 311 | 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 | 366 | # Add the command-line options |
| 315 | 367 | # Colors must be defined according to this: |
| 316 | 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') | |
| 318 | − parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category') | |
| 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', 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 | 374 | # Parse the command-line arguments |
| 320 | 375 | args = parser.parse_args() |
| 321 | 376 | # Access the values provided by the user |
| 322 | 377 | user_color = args.user_color |
| 323 | 378 | kernel_color = args.kernel_color |
| 379 | + output_file = args.save_only | |
| 324 | 380 | |
| 325 | 381 | CATEGORIES = [ |
| 326 | 382 | { |
| @@ -336,4 +392,4 @@ def main() -> None: · 9 unmodified lines | ||
| 336 | 392 | ] |
| 337 | 393 | |
| 338 | 394 | if __name__ == '__main__': |
| 339 | − main() | |
| 395 | + main() | |