tools/perf/scripts/python/gecko.py | 70 +++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 7 deletions(-)
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.
Signed-off-by: Anup Sharma <anupnewsmail@gmail.com>
---
tools/perf/scripts/python/gecko.py | 70 +++++++++++++++++++++++++++---
1 file changed, 63 insertions(+), 7 deletions(-)
diff --git a/tools/perf/scripts/python/gecko.py b/tools/perf/scripts/python/gecko.py
index 278c3aed282a..bc5a72f94bfa 100644
--- a/tools/perf/scripts/python/gecko.py
+++ b/tools/perf/scripts/python/gecko.py
@@ -1,4 +1,4 @@
-# firefox-gecko-converter.py - Convert perf record output to Firefox's gecko profile format
+# gecko.py - Convert perf record output to Firefox's gecko profile format
# SPDX-License-Identifier: GPL-2.0
#
# The script converts perf.data to Gecko Profile Format,
@@ -7,14 +7,26 @@
# Usage:
#
# perf record -a -g -F 99 sleep 60
-# perf script report gecko > output.json
+# perf script report gecko
+#
+# Combined:
+#
+# perf script gecko -F 99 -a sleep 60
import os
import sys
+import time
import json
+import string
+import random
import argparse
+import threading
+import webbrowser
+import urllib.parse
+from os import system
from functools import reduce
from dataclasses import dataclass, field
+from http.server import HTTPServer, SimpleHTTPRequestHandler, test
from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any
# Add the Perf-Trace-Util library to the Python path
@@ -40,9 +52,15 @@ CATEGORIES = None
# The product name is used by the profiler UI to show the Operating system and Processor.
PRODUCT = os.popen('uname -op').read().strip()
+# store the output file
+output_file = None
+
# Here key = tid, value = Thread
tid_to_thread = dict()
+# The HTTP server is used to serve the profile to the profiler UI.
+http_server_thread = None
+
# The category index is used by the profiler UI to show the color of the flame graph.
USER_CATEGORY_INDEX = 0
KERNEL_CATEGORY_INDEX = 1
@@ -278,9 +296,19 @@ def process_event(param_dict: Dict) -> None:
tid_to_thread[tid] = thread
thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp)
+def trace_begin() -> None:
+ global output_file
+ if (output_file is None):
+ print("Staring Firefox Profiler on your default browser...")
+ global http_server_thread
+ http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,))
+ http_server_thread.daemon = True
+ http_server_thread.start()
+
# Trace_end runs at the end and will be used to aggregate
# the data into the final json object and print it out to stdout.
def trace_end() -> None:
+ global output_file
threads = [thread._to_json_dict() for thread in tid_to_thread.values()]
# Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305
@@ -305,22 +333,50 @@ def trace_end() -> None:
"processes": [],
"pausedRanges": [],
}
- json.dump(gecko_profile_with_meta, sys.stdout, indent=2)
+ # launch the profiler on local host if not specified --save-only args, otherwise print to file
+ if (output_file is None):
+ output_file = 'gecko_profile.json'
+ with open(output_file, 'w') as f:
+ json.dump(gecko_profile_with_meta, f, indent=2)
+ launchFirefox(output_file)
+ time.sleep(1)
+ print(f'[ perf gecko: Captured and wrote into {output_file} ]')
+ else:
+ print(f'[ perf gecko: Captured and wrote into {output_file} ]')
+ with open(output_file, 'w') as f:
+ json.dump(gecko_profile_with_meta, f, indent=2)
+
+# Used to enable Cross-Origin Resource Sharing (CORS) for requests coming from 'https://profiler.firefox.com', allowing it to access resources from this server.
+class CORSRequestHandler(SimpleHTTPRequestHandler):
+ def end_headers (self):
+ self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com')
+ SimpleHTTPRequestHandler.end_headers(self)
+
+# start a local server to serve the gecko_profile.json file to the profiler.firefox.com
+def launchFirefox(file):
+ safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}')
+ url = 'https://profiler.firefox.com/from-url/' + safe_string
+ webbrowser.open(f'{url}')
def main() -> None:
+ global output_file
global CATEGORIES
- parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format")
+ parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization")
# Add the command-line options
# Colors must be defined according to this:
# https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css
- parser.add_argument('--user-color', default='yellow', help='Color for the User category')
- parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category')
+ parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta'])
+ parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta'])
+ # If --save-only is specified, the output will be saved to a file instead of opening Firefox's profiler directly.
+ parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler')
+
# Parse the command-line arguments
args = parser.parse_args()
# Access the values provided by the user
user_color = args.user_color
kernel_color = args.kernel_color
+ output_file = args.save_only
CATEGORIES = [
{
@@ -336,4 +392,4 @@ def main() -> None:
]
if __name__ == '__main__':
- main()
+ main()
--
2.34.1
On Wed, Aug 9, 2023 at 6:21 AM Anup Sharma <anupnewsmail@gmail.com> wrote: > > 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. > > Signed-off-by: Anup Sharma <anupnewsmail@gmail.com> Hi Anup, I'm not able to replicate the behavior of a running server. What I get is: ``` $ sudo bash -c "export PERF_EXEC_PATH=`pwd`/tools/perf/ && perf script gecko -F 99 -a sleep 1" Staring Firefox Profiler on your default browser... Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... [ perf gecko: Captured and wrote into gecko_profile.json ] $ ``` ie the process immediately terminates. The same is true for record/report. Could you take a look? Thanks, Ian > --- > tools/perf/scripts/python/gecko.py | 70 +++++++++++++++++++++++++++--- > 1 file changed, 63 insertions(+), 7 deletions(-) > > diff --git a/tools/perf/scripts/python/gecko.py b/tools/perf/scripts/python/gecko.py > index 278c3aed282a..bc5a72f94bfa 100644 > --- a/tools/perf/scripts/python/gecko.py > +++ b/tools/perf/scripts/python/gecko.py > @@ -1,4 +1,4 @@ > -# firefox-gecko-converter.py - Convert perf record output to Firefox's gecko profile format > +# gecko.py - Convert perf record output to Firefox's gecko profile format > # SPDX-License-Identifier: GPL-2.0 > # > # The script converts perf.data to Gecko Profile Format, > @@ -7,14 +7,26 @@ > # Usage: > # > # perf record -a -g -F 99 sleep 60 > -# perf script report gecko > output.json > +# perf script report gecko > +# > +# Combined: > +# > +# perf script gecko -F 99 -a sleep 60 > > import os > import sys > +import time > import json > +import string > +import random > import argparse > +import threading > +import webbrowser > +import urllib.parse > +from os import system > from functools import reduce > from dataclasses import dataclass, field > +from http.server import HTTPServer, SimpleHTTPRequestHandler, test > from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any > > # Add the Perf-Trace-Util library to the Python path > @@ -40,9 +52,15 @@ CATEGORIES = None > # The product name is used by the profiler UI to show the Operating system and Processor. > PRODUCT = os.popen('uname -op').read().strip() > > +# store the output file > +output_file = None > + > # Here key = tid, value = Thread > tid_to_thread = dict() > > +# The HTTP server is used to serve the profile to the profiler UI. > +http_server_thread = None > + > # The category index is used by the profiler UI to show the color of the flame graph. > USER_CATEGORY_INDEX = 0 > KERNEL_CATEGORY_INDEX = 1 > @@ -278,9 +296,19 @@ def process_event(param_dict: Dict) -> None: > tid_to_thread[tid] = thread > thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp) > > +def trace_begin() -> None: > + global output_file > + if (output_file is None): > + print("Staring Firefox Profiler on your default browser...") > + global http_server_thread > + http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,)) > + http_server_thread.daemon = True > + http_server_thread.start() > + > # Trace_end runs at the end and will be used to aggregate > # the data into the final json object and print it out to stdout. > def trace_end() -> None: > + global output_file > threads = [thread._to_json_dict() for thread in tid_to_thread.values()] > > # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305 > @@ -305,22 +333,50 @@ def trace_end() -> None: > "processes": [], > "pausedRanges": [], > } > - json.dump(gecko_profile_with_meta, sys.stdout, indent=2) > + # launch the profiler on local host if not specified --save-only args, otherwise print to file > + if (output_file is None): > + output_file = 'gecko_profile.json' > + with open(output_file, 'w') as f: > + json.dump(gecko_profile_with_meta, f, indent=2) > + launchFirefox(output_file) > + time.sleep(1) > + print(f'[ perf gecko: Captured and wrote into {output_file} ]') > + else: > + print(f'[ perf gecko: Captured and wrote into {output_file} ]') > + with open(output_file, 'w') as f: > + json.dump(gecko_profile_with_meta, f, indent=2) > + > +# Used to enable Cross-Origin Resource Sharing (CORS) for requests coming from 'https://profiler.firefox.com', allowing it to access resources from this server. > +class CORSRequestHandler(SimpleHTTPRequestHandler): > + def end_headers (self): > + self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com') > + SimpleHTTPRequestHandler.end_headers(self) > + > +# start a local server to serve the gecko_profile.json file to the profiler.firefox.com > +def launchFirefox(file): > + safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}') > + url = 'https://profiler.firefox.com/from-url/' + safe_string > + webbrowser.open(f'{url}') > > def main() -> None: > + global output_file > global CATEGORIES > - parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format") > + parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization") > > # Add the command-line options > # Colors must be defined according to this: > # https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css > - parser.add_argument('--user-color', default='yellow', help='Color for the User category') > - parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category') > + parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) > + parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) > + # If --save-only is specified, the output will be saved to a file instead of opening Firefox's profiler directly. > + parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler') > + > # Parse the command-line arguments > args = parser.parse_args() > # Access the values provided by the user > user_color = args.user_color > kernel_color = args.kernel_color > + output_file = args.save_only > > CATEGORIES = [ > { > @@ -336,4 +392,4 @@ def main() -> None: > ] > > if __name__ == '__main__': > - main() > + main() > -- > 2.34.1 >
On Tue, Aug 15, 2023 at 1:48 AM Ian Rogers <irogers@google.com> wrote: > > On Wed, Aug 9, 2023 at 6:21 AM Anup Sharma <anupnewsmail@gmail.com> wrote: > > > > 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. > > > > Signed-off-by: Anup Sharma <anupnewsmail@gmail.com> > > Hi Anup, > > I'm not able to replicate the behavior of a running server. What I get is: > ``` > $ sudo bash -c "export PERF_EXEC_PATH=`pwd`/tools/perf/ && perf script > gecko -F 99 -a sleep 1" > Staring Firefox Profiler on your default browser... > Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... > [ perf gecko: Captured and wrote into gecko_profile.json ] > $ > ``` > ie the process immediately terminates. The same is true for > record/report. Could you take a look? Ah, running as a user it is okay. Tested-by: Ian Rogers <irogers@google.com> Thanks, Ian > > > --- > > tools/perf/scripts/python/gecko.py | 70 +++++++++++++++++++++++++++--- > > 1 file changed, 63 insertions(+), 7 deletions(-) > > > > diff --git a/tools/perf/scripts/python/gecko.py b/tools/perf/scripts/python/gecko.py > > index 278c3aed282a..bc5a72f94bfa 100644 > > --- a/tools/perf/scripts/python/gecko.py > > +++ b/tools/perf/scripts/python/gecko.py > > @@ -1,4 +1,4 @@ > > -# firefox-gecko-converter.py - Convert perf record output to Firefox's gecko profile format > > +# gecko.py - Convert perf record output to Firefox's gecko profile format > > # SPDX-License-Identifier: GPL-2.0 > > # > > # The script converts perf.data to Gecko Profile Format, > > @@ -7,14 +7,26 @@ > > # Usage: > > # > > # perf record -a -g -F 99 sleep 60 > > -# perf script report gecko > output.json > > +# perf script report gecko > > +# > > +# Combined: > > +# > > +# perf script gecko -F 99 -a sleep 60 > > > > import os > > import sys > > +import time > > import json > > +import string > > +import random > > import argparse > > +import threading > > +import webbrowser > > +import urllib.parse > > +from os import system > > from functools import reduce > > from dataclasses import dataclass, field > > +from http.server import HTTPServer, SimpleHTTPRequestHandler, test > > from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any > > > > # Add the Perf-Trace-Util library to the Python path > > @@ -40,9 +52,15 @@ CATEGORIES = None > > # The product name is used by the profiler UI to show the Operating system and Processor. > > PRODUCT = os.popen('uname -op').read().strip() > > > > +# store the output file > > +output_file = None > > + > > # Here key = tid, value = Thread > > tid_to_thread = dict() > > > > +# The HTTP server is used to serve the profile to the profiler UI. > > +http_server_thread = None > > + > > # The category index is used by the profiler UI to show the color of the flame graph. > > USER_CATEGORY_INDEX = 0 > > KERNEL_CATEGORY_INDEX = 1 > > @@ -278,9 +296,19 @@ def process_event(param_dict: Dict) -> None: > > tid_to_thread[tid] = thread > > thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp) > > > > +def trace_begin() -> None: > > + global output_file > > + if (output_file is None): > > + print("Staring Firefox Profiler on your default browser...") > > + global http_server_thread > > + http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,)) > > + http_server_thread.daemon = True > > + http_server_thread.start() > > + > > # Trace_end runs at the end and will be used to aggregate > > # the data into the final json object and print it out to stdout. > > def trace_end() -> None: > > + global output_file > > threads = [thread._to_json_dict() for thread in tid_to_thread.values()] > > > > # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305 > > @@ -305,22 +333,50 @@ def trace_end() -> None: > > "processes": [], > > "pausedRanges": [], > > } > > - json.dump(gecko_profile_with_meta, sys.stdout, indent=2) > > + # launch the profiler on local host if not specified --save-only args, otherwise print to file > > + if (output_file is None): > > + output_file = 'gecko_profile.json' > > + with open(output_file, 'w') as f: > > + json.dump(gecko_profile_with_meta, f, indent=2) > > + launchFirefox(output_file) > > + time.sleep(1) > > + print(f'[ perf gecko: Captured and wrote into {output_file} ]') > > + else: > > + print(f'[ perf gecko: Captured and wrote into {output_file} ]') > > + with open(output_file, 'w') as f: > > + json.dump(gecko_profile_with_meta, f, indent=2) > > + > > +# Used to enable Cross-Origin Resource Sharing (CORS) for requests coming from 'https://profiler.firefox.com', allowing it to access resources from this server. > > +class CORSRequestHandler(SimpleHTTPRequestHandler): > > + def end_headers (self): > > + self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com') > > + SimpleHTTPRequestHandler.end_headers(self) > > + > > +# start a local server to serve the gecko_profile.json file to the profiler.firefox.com > > +def launchFirefox(file): > > + safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}') > > + url = 'https://profiler.firefox.com/from-url/' + safe_string > > + webbrowser.open(f'{url}') > > > > def main() -> None: > > + global output_file > > global CATEGORIES > > - parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format") > > + parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization") > > > > # Add the command-line options > > # Colors must be defined according to this: > > # https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css > > - parser.add_argument('--user-color', default='yellow', help='Color for the User category') > > - parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category') > > + parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) > > + parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta']) > > + # If --save-only is specified, the output will be saved to a file instead of opening Firefox's profiler directly. > > + parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler') > > + > > # Parse the command-line arguments > > args = parser.parse_args() > > # Access the values provided by the user > > user_color = args.user_color > > kernel_color = args.kernel_color > > + output_file = args.save_only > > > > CATEGORIES = [ > > { > > @@ -336,4 +392,4 @@ def main() -> None: > > ] > > > > if __name__ == '__main__': > > - main() > > + main() > > -- > > 2.34.1 > >
© 2016 - 2025 Red Hat, Inc.