stdlib: Improve PyStat support for SimObject Vectors
Change-Id: Iba0c93ffa5c4b18acf75af82965c63a8881df189
This commit is contained in:
@@ -99,3 +99,6 @@ class AbstractStat(SerializableStat):
|
||||
return self.children(
|
||||
lambda _name: re.match(pattern, _name), recursive=True
|
||||
)
|
||||
|
||||
def __getitem__(self, item: str):
|
||||
return getattr(self, item)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
@@ -62,3 +63,62 @@ class Group(AbstractStat):
|
||||
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class SimObjectGroup(Group):
|
||||
"""A group of statistics encapulated within a SimObject."""
|
||||
|
||||
def __init__(self, **kwargs: Dict[str, Union[Group, Statistic]]):
|
||||
super().__init__(type="SimObject", **kwargs)
|
||||
|
||||
|
||||
class SimObjectVectorGroup(Group):
|
||||
"""A Vector of SimObject objects. I.e., that which would be constructed
|
||||
from something like `system.cpu = [DerivO3CPU(), TimingSimpleCPU()]`.
|
||||
"""
|
||||
|
||||
def __init__(self, value: List[AbstractStat], **kwargs: Dict[str, Any]):
|
||||
assert isinstance(value, list), "Value must be a list"
|
||||
super().__init__(type="SimObjectVector", value=value, **kwargs)
|
||||
|
||||
def __getitem__(self, index: Union[int, str, float]) -> AbstractStat:
|
||||
if not isinstance(index, int):
|
||||
raise KeyError(
|
||||
f"Index {index} not found in int. Cannot index Array with "
|
||||
"non-int"
|
||||
)
|
||||
return self.value[index]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.value)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.value)
|
||||
|
||||
def get_all_stats_of_name(self, name: str) -> List[AbstractStat]:
|
||||
"""
|
||||
Get all the stats in the vector of that name. Useful for performing
|
||||
operations on all the stats of the same name in a vector.
|
||||
"""
|
||||
to_return = []
|
||||
for stat in self.value:
|
||||
if hasattr(stat, name):
|
||||
to_return.append(getattr(stat, name))
|
||||
|
||||
# If the name is in the format "sim.bla.whatever", we are looking for
|
||||
# the "bla.whatever" stats in the "sim" group.
|
||||
# This is messy, but it works.
|
||||
name_split = name.split(".")
|
||||
if len(name_split) == 1:
|
||||
return to_return
|
||||
|
||||
if name_split[0] not in self:
|
||||
return to_return
|
||||
|
||||
to_return.extend(
|
||||
self[name_split[0]].get_all_stats_of_name(".".join(name_split[1:]))
|
||||
)
|
||||
return to_return
|
||||
|
||||
def __getitem__(self, item: int):
|
||||
return self.value[item]
|
||||
|
||||
@@ -41,6 +41,7 @@ from m5.ext.pystats.simstat import *
|
||||
from m5.ext.pystats.statistic import *
|
||||
from m5.ext.pystats.storagetype import *
|
||||
from m5.objects import *
|
||||
from m5.params import SimObjectVector
|
||||
|
||||
import _m5.stats
|
||||
|
||||
@@ -83,33 +84,6 @@ class JsonOutputVistor:
|
||||
simstat.dump(fp=fp, **self.json_args)
|
||||
|
||||
|
||||
def get_stats_group(group: _m5.stats.Group) -> Group:
|
||||
"""
|
||||
Translates a gem5 Group object into a Python stats Group object. A Python
|
||||
statistic Group object is a dictionary of labeled Statistic objects. Any
|
||||
gem5 object passed to this will have its ``getStats()`` and ``getStatGroups``
|
||||
function called, and all the stats translated (inclusive of the stats
|
||||
further down the hierarchy).
|
||||
|
||||
:param group: The gem5 _m5.stats.Group object to be translated to be a Python
|
||||
stats Group object. Typically this will be a gem5 SimObject.
|
||||
|
||||
:returns: The stats group object translated from the input gem5 object.
|
||||
"""
|
||||
|
||||
stats_dict = {}
|
||||
|
||||
for stat in group.getStats():
|
||||
statistic = __get_statistic(stat)
|
||||
if statistic is not None:
|
||||
stats_dict[stat.name] = statistic
|
||||
|
||||
for key in group.getStatGroups():
|
||||
stats_dict[key] = get_stats_group(group.getStatGroups()[key])
|
||||
|
||||
return Group(**stats_dict)
|
||||
|
||||
|
||||
def __get_statistic(statistic: _m5.stats.Info) -> Optional[Statistic]:
|
||||
"""
|
||||
Translates a _m5.stats.Info object into a Statistic object, to process
|
||||
@@ -302,8 +276,84 @@ def _prepare_stats(group: _m5.stats.Group):
|
||||
_prepare_stats(child)
|
||||
|
||||
|
||||
def _process_simobject_object(simobject: SimObject) -> SimObjectGroup:
|
||||
"""
|
||||
Processes the stats of a SimObject, and returns a dictionary of the stats
|
||||
for the SimObject with PyStats objects when appropriate.
|
||||
|
||||
:param simobject: The SimObject to process the stats for.
|
||||
|
||||
:returns: A dictionary of the PyStats stats for the SimObject.
|
||||
"""
|
||||
|
||||
assert isinstance(
|
||||
simobject, SimObject
|
||||
), "simobject param must be a SimObject."
|
||||
|
||||
stats = (
|
||||
{
|
||||
"name": simobject.get_name(),
|
||||
}
|
||||
if simobject.get_name()
|
||||
else {}
|
||||
)
|
||||
|
||||
for stat in simobject.getStats():
|
||||
val = __get_statistic(stat)
|
||||
if val:
|
||||
stats[stat.name] = val
|
||||
|
||||
for name, child in simobject._children.items():
|
||||
to_add = _process_simobject_stats(child)
|
||||
if to_add:
|
||||
stats[name] = to_add
|
||||
|
||||
for name, child in sorted(simobject.getStatGroups().items()):
|
||||
# Note: We are using the name of the group to determine if we have
|
||||
# already processed the group as a child simobject or a statistic.
|
||||
# This is to avoid SimObjectVector's being processed twice. It is far
|
||||
# from an ideal solution, but it works for now.
|
||||
if not any(
|
||||
re.compile(f"{to_match}" + r"\d*").search(name)
|
||||
for to_match in stats.keys()
|
||||
):
|
||||
stats[name] = Group(**_process_simobject_stats(child))
|
||||
|
||||
return SimObjectGroup(**stats)
|
||||
|
||||
|
||||
def _process_simobject_stats(
|
||||
simobject: Union[
|
||||
SimObject, SimObjectVector, List[Union[SimObject, SimObjectVector]]
|
||||
]
|
||||
) -> Union[List[Dict], Dict]:
|
||||
"""
|
||||
Processes the stats of a SimObject, SimObjectVector, or List of either, and
|
||||
returns a dictionary of the PySqtats for the SimObject.
|
||||
|
||||
:param simobject: The SimObject to process the stats for.
|
||||
|
||||
:returns: A dictionary of the stats for the SimObject.
|
||||
"""
|
||||
|
||||
if isinstance(simobject, SimObject):
|
||||
return _process_simobject_object(simobject)
|
||||
|
||||
if isinstance(simobject, Union[List, SimObjectVector]):
|
||||
stats_list = []
|
||||
for obj in simobject:
|
||||
stats_list.append(_process_simobject_stats(obj))
|
||||
return SimObjectVectorGroup(value=stats_list)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def get_simstat(
|
||||
root: Union[SimObject, List[SimObject]], prepare_stats: bool = True
|
||||
root: Union[
|
||||
Union[SimObject, SimObjectVector],
|
||||
List[Union[SimObject, SimObjectVector]],
|
||||
],
|
||||
prepare_stats: bool = True,
|
||||
) -> SimStat:
|
||||
"""
|
||||
This function will return the SimStat object for a simulation given a
|
||||
@@ -323,7 +373,7 @@ def get_simstat(
|
||||
:Returns: The SimStat Object of the current simulation.
|
||||
|
||||
"""
|
||||
stats_map = {}
|
||||
|
||||
creation_time = datetime.now()
|
||||
time_converstion = None # TODO https://gem5.atlassian.net/browse/GEM5-846
|
||||
final_tick = Root.getInstance().resolveStat("finalTick").value
|
||||
@@ -334,29 +384,19 @@ def get_simstat(
|
||||
if prepare_stats:
|
||||
_m5.stats.processDumpQueue()
|
||||
|
||||
for r in root:
|
||||
if isinstance(r, Root):
|
||||
# The Root is a special case, we jump directly into adding its
|
||||
# constituent Groups.
|
||||
if prepare_stats:
|
||||
_prepare_stats(r)
|
||||
for key in r.getStatGroups():
|
||||
stats_map[key] = get_stats_group(r.getStatGroups()[key])
|
||||
elif isinstance(r, SimObject):
|
||||
if prepare_stats:
|
||||
_prepare_stats(r)
|
||||
stats_map[r.get_name()] = get_stats_group(r)
|
||||
if prepare_stats:
|
||||
if isinstance(root, list):
|
||||
for obj in root:
|
||||
_prepare_stats(obj)
|
||||
else:
|
||||
raise TypeError(
|
||||
"Object (" + str(r) + ") passed is not a "
|
||||
"SimObject. " + __name__ + " only processes "
|
||||
"SimObjects, or a list of SimObjects."
|
||||
)
|
||||
_prepare_stats(root)
|
||||
|
||||
stats = _process_simobject_stats(root).__dict__
|
||||
stats["name"] = root.get_name() if root.get_name() else "root"
|
||||
|
||||
return SimStat(
|
||||
creation_time=creation_time,
|
||||
time_conversion=time_converstion,
|
||||
simulated_begin_time=simulated_begin_time,
|
||||
simulated_end_time=simulated_end_time,
|
||||
**stats_map,
|
||||
**stats,
|
||||
)
|
||||
|
||||
@@ -70,8 +70,9 @@ root = Root(full_system=False, system=stat_tester)
|
||||
m5.instantiate()
|
||||
m5.simulate()
|
||||
|
||||
simstats = get_simstat(root)
|
||||
output = simstats.to_json()["system"]
|
||||
simstats = get_simstat(stat_tester)
|
||||
output = simstats.to_json()
|
||||
|
||||
|
||||
value_dict = {}
|
||||
for sample in args.samples:
|
||||
@@ -90,7 +91,8 @@ for key in value_dict:
|
||||
}
|
||||
|
||||
expected_output = {
|
||||
"type": "Group",
|
||||
"type": "SimObject",
|
||||
"name": "system",
|
||||
"time_conversion": None,
|
||||
args.name: {
|
||||
"value": scaler_dict,
|
||||
@@ -99,6 +101,14 @@ expected_output = {
|
||||
},
|
||||
}
|
||||
|
||||
# Remove the time related fields from the outputs if they exist.
|
||||
# `creation_time` is not deterministic, and `simulated_begin_time` and
|
||||
# simulated_end_time are not under test here.
|
||||
for field in ["creation_time", "simulated_begin_time", "simulated_end_time"]:
|
||||
for map in [output, expected_output]:
|
||||
if field in map:
|
||||
del map[field]
|
||||
|
||||
if output != expected_output:
|
||||
print("Output statistics do not match expected:", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
|
||||
@@ -144,7 +144,8 @@ for x in range(args.num_vectors):
|
||||
}
|
||||
|
||||
expected_output = {
|
||||
"type": "Group",
|
||||
"type": "SimObject",
|
||||
"name": "system",
|
||||
"time_conversion": None,
|
||||
args.name: {
|
||||
"type": "Vector2d",
|
||||
@@ -159,7 +160,15 @@ m5.instantiate()
|
||||
m5.simulate()
|
||||
|
||||
simstats = get_simstat(stat_tester)
|
||||
output = simstats.to_json()["system"]
|
||||
output = simstats.to_json()
|
||||
|
||||
# Remove the time related fields from the outputs if they exist.
|
||||
# `creation_time` is not deterministic, and `simulated_begin_time` and
|
||||
# simulated_end_time are not under test here.
|
||||
for field in ["creation_time", "simulated_begin_time", "simulated_end_time"]:
|
||||
for map in [output, expected_output]:
|
||||
if field in map:
|
||||
del map[field]
|
||||
|
||||
if output != expected_output:
|
||||
print("Output statistics do not match expected:", file=sys.stderr)
|
||||
|
||||
@@ -109,7 +109,8 @@ for i in range(len(args.value)):
|
||||
}
|
||||
|
||||
expected_output = {
|
||||
"type": "Group",
|
||||
"type": "SimObject",
|
||||
"name": "system",
|
||||
"time_conversion": None,
|
||||
args.name: {
|
||||
"value": value_dict,
|
||||
@@ -124,7 +125,15 @@ m5.instantiate()
|
||||
m5.simulate()
|
||||
|
||||
simstats = get_simstat(stat_tester)
|
||||
output = simstats.to_json()["system"]
|
||||
output = simstats.to_json()
|
||||
|
||||
# Remove the time related fields from the outputs if they exist.
|
||||
# `creation_time` is not deterministic, and `simulated_begin_time` and
|
||||
# simulated_end_time are not under test here.
|
||||
for field in ["creation_time", "simulated_begin_time", "simulated_end_time"]:
|
||||
for map in [output, expected_output]:
|
||||
if field in map:
|
||||
del map[field]
|
||||
|
||||
if output != expected_output:
|
||||
print("Output statistics do not match expected:", file=sys.stderr)
|
||||
|
||||
78
tests/gem5/stats/configs/pystats_simobjectvector_check.py
Normal file
78
tests/gem5/stats/configs/pystats_simobjectvector_check.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2024 The Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""This script is used for checking that SimObject Vectorsare
|
||||
correctly parsed through to the gem5 PyStats."""
|
||||
|
||||
import m5
|
||||
from m5.objects import (
|
||||
Root,
|
||||
ScalarStatTester,
|
||||
VectorStatTester,
|
||||
)
|
||||
from m5.stats.gem5stats import get_simstat
|
||||
|
||||
root = Root(full_system=False)
|
||||
root.stat_testers = [
|
||||
ScalarStatTester(name="placeholder", value=11),
|
||||
ScalarStatTester(
|
||||
name="placeholder", value=22, description="Index 2 desc."
|
||||
),
|
||||
ScalarStatTester(name="placeholder", value=33),
|
||||
VectorStatTester(
|
||||
name="index_4",
|
||||
values=[44, 55, 66],
|
||||
description="A SimStat Vector within a SimObject Vector.",
|
||||
),
|
||||
]
|
||||
|
||||
m5.instantiate()
|
||||
m5.simulate()
|
||||
|
||||
simstat = get_simstat(root)
|
||||
|
||||
# 'stat_testers' is a list of SimObjects
|
||||
assert hasattr(simstat, "stat_testers"), "No stat_testers attribute found."
|
||||
assert len(simstat.stat_testers) == 4, "stat_testers list is not of length 3."
|
||||
|
||||
# Accessable by index.
|
||||
simobject = simstat.stat_testers[0]
|
||||
|
||||
# We can directly access the statistic we're interested in and its "str"
|
||||
# representation should be the same as the value we set. In this case "11.0".
|
||||
assert (
|
||||
str(simobject.placeholder) == "11.0"
|
||||
), "placeholder value is not 11.0 ()."
|
||||
|
||||
# They can also be accessed like so:
|
||||
# "other_stat" is a SimObject with a single stat called "stat".
|
||||
str(
|
||||
simstat["stat_testers"][3]["index_4"][0]
|
||||
) == "44.0", 'simstat[3]["index_4"][0] value is not 44.'
|
||||
|
||||
# We can also access other stats like type and description.
|
||||
assert simstat.stat_testers[1].placeholder.description == "Index 2 desc."
|
||||
assert simstat.stat_testers[1].placeholder.type == "Scalar"
|
||||
@@ -67,7 +67,8 @@ stat_tester.name = args.name
|
||||
stat_tester.description = args.description
|
||||
stat_tester.value = args.value
|
||||
expected_output = {
|
||||
"type": "Group",
|
||||
"type": "SimObject",
|
||||
"name": "system",
|
||||
"time_conversion": None,
|
||||
args.name: {
|
||||
"value": args.value,
|
||||
@@ -84,7 +85,15 @@ m5.instantiate()
|
||||
m5.simulate()
|
||||
|
||||
simstats = get_simstat(stat_tester)
|
||||
output = simstats.to_json()["system"]
|
||||
output = simstats.to_json()
|
||||
|
||||
# Remove the time related fields from the outputs if they exist.
|
||||
# `creation_time` is not deterministic, and `simulated_begin_time` and
|
||||
# simulated_end_time are not under test here.
|
||||
for field in ["creation_time", "simulated_begin_time", "simulated_end_time"]:
|
||||
for map in [output, expected_output]:
|
||||
if field in map:
|
||||
del map[field]
|
||||
|
||||
if output != expected_output:
|
||||
print("Output statistics do not match expected:", file=sys.stderr)
|
||||
|
||||
@@ -235,3 +235,20 @@ gem5_verify_config(
|
||||
valid_isas=(constants.all_compiled_tag,),
|
||||
length=constants.quick_tag,
|
||||
)
|
||||
|
||||
gem5_verify_config(
|
||||
name="simstat-simobjectvector-test",
|
||||
fixtures=(),
|
||||
verifiers=[],
|
||||
config=joinpath(
|
||||
config.base_dir,
|
||||
"tests",
|
||||
"gem5",
|
||||
"stats",
|
||||
"configs",
|
||||
"pystats_simobjectvector_check.py",
|
||||
),
|
||||
config_args=[],
|
||||
valid_isas=(constants.all_compiled_tag,),
|
||||
length=constants.quick_tag,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user