The final output will now be presented in JSON format following the Gecko profile structure. Additionally, the inclusion of PRODUCT allows easy retrieval of header information for UI. Furthermore, CATEGORIES have been introduced to enable customization of kernel and user colors using input arguments. To facilitate this functionality, an argparse-based parser has been implemented. Note: The implementation of threads will be addressed in subsequent commits for now I have commented it out. Signed-off-by: Anup Sharma <anupnewsmail@gmail.com> Cc: Adrian Hunter <adrian.hunter@intel.com> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Ian Rogers <irogers@google.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://lore.kernel.org/r/fa6d027e4134c48e8a2ea45dd8f6b21e6a3418e4.1689961706.git.anupnewsmail@gmail.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
237 lines
7.2 KiB
Python
237 lines
7.2 KiB
Python
# firefox-gecko-converter.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,
|
|
# which can be read by https://profiler.firefox.com/.
|
|
#
|
|
# Usage:
|
|
#
|
|
# perf record -a -g -F 99 sleep 60
|
|
# perf script report gecko > output.json
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any
|
|
|
|
# Add the Perf-Trace-Util library to the Python path
|
|
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
|
|
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
|
|
|
|
from perf_trace_context import *
|
|
from Core import *
|
|
|
|
StringID = int
|
|
StackID = int
|
|
FrameID = int
|
|
CategoryID = int
|
|
Milliseconds = float
|
|
|
|
# start_time is intialiazed only once for the all event traces.
|
|
start_time = None
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425
|
|
# Follow Brendan Gregg's Flamegraph convention: orange for kernel and yellow for user space by default.
|
|
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()
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
|
|
class Frame(NamedTuple):
|
|
string_id: StringID
|
|
relevantForJS: bool
|
|
innerWindowID: int
|
|
implementation: None
|
|
optimizations: None
|
|
line: None
|
|
column: None
|
|
category: CategoryID
|
|
subcategory: int
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
|
|
class Stack(NamedTuple):
|
|
prefix_id: Optional[StackID]
|
|
frame_id: FrameID
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
|
|
class Sample(NamedTuple):
|
|
stack_id: Optional[StackID]
|
|
time_ms: Milliseconds
|
|
responsiveness: int
|
|
|
|
@dataclass
|
|
class Thread:
|
|
"""A builder for a profile of the thread.
|
|
|
|
Attributes:
|
|
comm: Thread command-line (name).
|
|
pid: process ID of containing process.
|
|
tid: thread ID.
|
|
samples: Timeline of profile samples.
|
|
frameTable: interned stack frame ID -> stack frame.
|
|
stringTable: interned string ID -> string.
|
|
stringMap: interned string -> string ID.
|
|
stackTable: interned stack ID -> stack.
|
|
stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID.
|
|
frameMap: Stack Frame string -> interned Frame ID.
|
|
comm: str
|
|
pid: int
|
|
tid: int
|
|
samples: List[Sample] = field(default_factory=list)
|
|
frameTable: List[Frame] = field(default_factory=list)
|
|
stringTable: List[str] = field(default_factory=list)
|
|
stringMap: Dict[str, int] = field(default_factory=dict)
|
|
stackTable: List[Stack] = field(default_factory=list)
|
|
stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
|
|
frameMap: Dict[str, int] = field(default_factory=dict)
|
|
"""
|
|
comm: str
|
|
pid: int
|
|
tid: int
|
|
samples: List[Sample] = field(default_factory=list)
|
|
frameTable: List[Frame] = field(default_factory=list)
|
|
stringTable: List[str] = field(default_factory=list)
|
|
stringMap: Dict[str, int] = field(default_factory=dict)
|
|
stackTable: List[Stack] = field(default_factory=list)
|
|
stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
|
|
frameMap: Dict[str, int] = field(default_factory=dict)
|
|
|
|
def _to_json_dict(self) -> Dict:
|
|
"""Converts current Thread to GeckoThread JSON format."""
|
|
# Gecko profile format is row-oriented data as List[List],
|
|
# And a schema for interpreting each index.
|
|
# Schema:
|
|
# https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230
|
|
return {
|
|
"tid": self.tid,
|
|
"pid": self.pid,
|
|
"name": self.comm,
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51
|
|
"markers": {
|
|
"schema": {
|
|
"name": 0,
|
|
"startTime": 1,
|
|
"endTime": 2,
|
|
"phase": 3,
|
|
"category": 4,
|
|
"data": 5,
|
|
},
|
|
"data": [],
|
|
},
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
|
|
"samples": {
|
|
"schema": {
|
|
"stack": 0,
|
|
"time": 1,
|
|
"responsiveness": 2,
|
|
},
|
|
"data": self.samples
|
|
},
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
|
|
"frameTable": {
|
|
"schema": {
|
|
"location": 0,
|
|
"relevantForJS": 1,
|
|
"innerWindowID": 2,
|
|
"implementation": 3,
|
|
"optimizations": 4,
|
|
"line": 5,
|
|
"column": 6,
|
|
"category": 7,
|
|
"subcategory": 8,
|
|
},
|
|
"data": self.frameTable,
|
|
},
|
|
|
|
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
|
|
"stackTable": {
|
|
"schema": {
|
|
"prefix": 0,
|
|
"frame": 1,
|
|
},
|
|
"data": self.stackTable,
|
|
},
|
|
"stringTable": self.stringTable,
|
|
"registerTime": 0,
|
|
"unregisterTime": None,
|
|
"processType": "default",
|
|
}
|
|
|
|
# Uses perf script python interface to parse each
|
|
# event and store the data in the thread builder.
|
|
def process_event(param_dict: Dict) -> None:
|
|
global start_time
|
|
global tid_to_thread
|
|
time_stamp = (param_dict['sample']['time'] // 1000) / 1000
|
|
pid = param_dict['sample']['pid']
|
|
tid = param_dict['sample']['tid']
|
|
comm = param_dict['comm']
|
|
|
|
# Start time is the time of the first sample
|
|
if not start_time:
|
|
start_time = time_stamp
|
|
|
|
# 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:
|
|
# Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305
|
|
gecko_profile_with_meta = {
|
|
"meta": {
|
|
"interval": 1,
|
|
"processType": 0,
|
|
"product": PRODUCT,
|
|
"stackwalk": 1,
|
|
"debug": 0,
|
|
"gcpoison": 0,
|
|
"asyncstack": 1,
|
|
"startTime": start_time,
|
|
"shutdownTime": None,
|
|
"version": 24,
|
|
"presymbolicated": True,
|
|
"categories": CATEGORIES,
|
|
"markerSchema": [],
|
|
},
|
|
"libs": [],
|
|
# threads will be implemented in later commits.
|
|
# "threads": threads,
|
|
"processes": [],
|
|
"pausedRanges": [],
|
|
}
|
|
json.dump(gecko_profile_with_meta, sys.stdout, indent=2)
|
|
|
|
def main() -> None:
|
|
global CATEGORIES
|
|
parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format")
|
|
|
|
# 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')
|
|
# 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
|
|
|
|
CATEGORIES = [
|
|
{
|
|
"name": 'User',
|
|
"color": user_color,
|
|
"subcategories": ['Other']
|
|
},
|
|
{
|
|
"name": 'Kernel',
|
|
"color": kernel_color,
|
|
"subcategories": ['Other']
|
|
},
|
|
]
|
|
|
|
if __name__ == '__main__':
|
|
main()
|