edb51fc861
* Update LICENSE
* Update conf.py
* Update copyright year
* [fix] Update copyright year
* Update copyright year "ROCm Developer Tools"
* Add license headers to c++ files
* Add license to *.py
* Update licenses in rocdecode sources
---------
Co-authored-by: srawat <120587655+SwRaw@users.noreply.github.com>
Co-authored-by: Mythreya <mythreya.kuricheti@amd.com>
Co-authored-by: Jonathan R. Madsen <jonathanrmadsen@gmail.com>
[ROCm/rocprofiler-sdk commit: 97b7a6315d]
201 sor
7.7 KiB
Python
201 sor
7.7 KiB
Python
# MIT License
|
|
#
|
|
# Copyright (c) 2023-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import otf2
|
|
import pandas as pd
|
|
|
|
from otf2.events import Enter, Leave
|
|
|
|
|
|
class Region(object):
|
|
""" """
|
|
|
|
def __init__(self, _enter, _leave, _depth, _location=None):
|
|
if _enter.region != _leave.region:
|
|
_location_info = f"\n{_location}" if _location else ""
|
|
raise ValueError(
|
|
f"enter region != leave region :: '{_enter}' != '{_leave}'{_location_info}"
|
|
)
|
|
|
|
if _depth < 0:
|
|
_location_info = f". location: '{_location}'" if _location else ""
|
|
raise ValueError(
|
|
f"negative depth ({_depth})! enter: '{_enter}'. leave: '{_leave}'{_location_info}"
|
|
)
|
|
|
|
self.region = _enter.region
|
|
self.depth = _depth
|
|
self.name = _enter.region.name
|
|
self.attributes = [
|
|
itr for itr in [_enter.attributes, _leave.attributes] if itr is not None
|
|
]
|
|
self.enter_nsec = _enter.time
|
|
self.leave_nsec = _leave.time
|
|
self.delta_nsec = _leave.time - _enter.time
|
|
if self.delta_nsec < 0:
|
|
raise ValueError(
|
|
f"negative timestamp delta :: '{_enter.time}' > '{_leave.time}'"
|
|
)
|
|
|
|
for itr in self.attributes:
|
|
for key, val in itr.items():
|
|
_key = f"{key.name}"
|
|
if not hasattr(self, _key):
|
|
setattr(self, _key, val)
|
|
|
|
if not hasattr(self, "category"):
|
|
self.category = "unk"
|
|
|
|
def __str__(self):
|
|
return f"{self.name:<35} :: {self.delta_nsec} nsec"
|
|
|
|
|
|
class OTF2Reader:
|
|
"""Read in perfetto protobuf output"""
|
|
|
|
def __init__(self, filename):
|
|
self.filename = filename if isinstance(filename, (list, tuple)) else [filename]
|
|
|
|
def read(self):
|
|
def _read_trace(trace_name):
|
|
trace = otf2.reader.Reader(trace_name)
|
|
# print(f"Read {len(trace.definitions.strings)} string definitions")
|
|
# for string in trace.definitions.strings:
|
|
# print(f"String definition with value '{string}' in trace.")
|
|
# print("Read {} events".format(len(trace.events)))
|
|
|
|
events = [[loc, evt] for loc, evt in trace.events]
|
|
locations = [itr for itr in trace.definitions.locations]
|
|
location_groups = [itr for itr in trace.definitions.location_groups]
|
|
system_tree_nodes = [itr for itr in trace.definitions.system_tree_nodes]
|
|
|
|
call_stack = {}
|
|
partial_call_stack = {}
|
|
|
|
for itr in system_tree_nodes:
|
|
call_stack[itr] = {}
|
|
partial_call_stack[itr] = {}
|
|
|
|
for itr in location_groups:
|
|
call_stack[itr.system_tree_parent][itr] = {}
|
|
partial_call_stack[itr.system_tree_parent][itr] = {}
|
|
|
|
for itr in locations:
|
|
call_stack[itr.group.system_tree_parent][itr.group][itr] = []
|
|
partial_call_stack[itr.group.system_tree_parent][itr.group][itr] = []
|
|
|
|
for location, event in events:
|
|
_stree = location.group.system_tree_parent
|
|
_group = location.group
|
|
_partial = partial_call_stack[_stree][_group][location]
|
|
if isinstance(event, Enter):
|
|
# expected length
|
|
_elen = len(_partial) + 1
|
|
_partial.append(event)
|
|
elif isinstance(event, Leave):
|
|
# expected length
|
|
_elen = len(_partial) - 1
|
|
_depth = len(_partial)
|
|
_leave = event
|
|
|
|
# it appears that on MI300, the end of A may exceed the
|
|
# begin of B kernels very slightly (i.e. overlap in same
|
|
# stream/queue). This leads to slightly out of order
|
|
# Enter/Leave regions and thus we need to occasionally
|
|
# search further back in the callstack to find the correct
|
|
# Enter region
|
|
_enter = _partial[-1]
|
|
if _enter.region == _leave.region:
|
|
_partial.pop()
|
|
else:
|
|
for ridx, ritr in enumerate(reversed(_partial)):
|
|
if ritr.region == _leave.region:
|
|
_enter = _partial.pop(len(_partial) - ridx - 1)
|
|
break
|
|
|
|
# below is what is expected on non-MI300
|
|
# _enter = _partial.pop()
|
|
|
|
# add the region
|
|
call_stack[_stree][_group][location].append(
|
|
Region(_enter, _leave, _depth - 1, location)
|
|
)
|
|
|
|
# modified length
|
|
_mlen = len(partial_call_stack[_stree][_group][location])
|
|
# if modified length != expected length
|
|
if _mlen != _elen:
|
|
raise RuntimeError(
|
|
f"Modified length ({_mlen}) != Expected length({_elen}) for {event} at {location}"
|
|
)
|
|
|
|
data = {
|
|
"system_tree_node": [],
|
|
"location_group": [],
|
|
"location": [],
|
|
"region": [],
|
|
"attributes": [],
|
|
"depth": [],
|
|
"name": [],
|
|
"category": [],
|
|
"start_ts": [],
|
|
"end_ts": [],
|
|
}
|
|
|
|
for tree, lgitr in call_stack.items():
|
|
for group, gitr in lgitr.items():
|
|
for loc, ritr in gitr.items():
|
|
for region in ritr:
|
|
data["system_tree_node"] += [tree]
|
|
data["location_group"] += [group]
|
|
data["location"] += [loc]
|
|
data["region"] += [region.region]
|
|
data["attributes"] += [region.attributes]
|
|
data["depth"] += [region.depth]
|
|
data["category"] += [region.category]
|
|
data["name"] += [region.name]
|
|
data["start_ts"] += [region.enter_nsec]
|
|
data["end_ts"] += [region.leave_nsec]
|
|
|
|
return (trace, pd.DataFrame.from_dict(data))
|
|
|
|
readers = []
|
|
df = pd.DataFrame()
|
|
for itr in self.filename:
|
|
_reader, _df = _read_trace(itr)
|
|
readers += [_reader]
|
|
df = pd.concat([df, _df])
|
|
|
|
return (df, readers)
|
|
|
|
|
|
def read_trace(filename):
|
|
data = OTF2Reader(filename).read()[0]
|
|
|
|
print(f"\nDATA:\n{data}")
|
|
|
|
attributes = list(data["attributes"])
|
|
|
|
print(f"\nATTRIBUTES:\n{attributes}")
|