stdlib: Improve gem5 PyStats (#996)

This PR incorporates numerous improvements and fixes to the gem5
PyStats. This includes:

* PyStats now support SimObject Vectors. The PyStats representing them
are subscribable and therefore acceptable by accessing an index: e.g.,:
`simobjectvec[0]`. (This replaces the `Vector` group PyStat)
* Adds the `SparseHist` PyStats.
* Adds the `Vector2d` to PyStats.
* The `Distribution` PyStats is fixed to be a vector of Scalars.
* Tests added for the PyStat's Vector and bugs fixed.
This commit is contained in:
Bobby R. Bruce
2024-06-12 00:19:08 -07:00
committed by GitHub
22 changed files with 1796 additions and 321 deletions

View File

@@ -25,21 +25,21 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from .abstract_stat import AbstractStat
from .group import Group
from .group import (
Group,
SimObjectGroup,
SimObjectVectorGroup,
)
from .jsonloader import JsonLoader
from .serializable_stat import SerializableStat
from .simstat import SimStat
from .statistic import Statistic
from .statistic import (
Distribution,
Scalar,
SparseHist,
Statistic,
Vector,
Vector2d,
)
from .storagetype import StorageType
from .timeconversion import TimeConversion
__all__ = [
"AbstractStat",
"Group",
"SimStat",
"Statistic",
"TimeConversion",
"StorageType",
"SerializableStat",
"JsonLoader",
]

View File

@@ -26,10 +26,12 @@
import re
from typing import (
Any,
Callable,
List,
Optional,
Pattern,
Tuple,
Union,
)
@@ -60,20 +62,11 @@ class AbstractStat(SerializableStat):
If it returns ``True``, then the child is yielded.
Otherwise, the child is skipped. If not provided then
all children are returned.
Note: This is method must be implemented in AbstractStat subclasses
which have children, otherwise it will return an empty list.
"""
to_return = []
for attr in self.__dict__:
obj = getattr(self, attr)
if isinstance(obj, AbstractStat):
if (predicate and predicate(attr)) or not predicate:
to_return.append(obj)
if recursive:
to_return = to_return + obj.children(
predicate=predicate, recursive=True
)
return to_return
return []
def find(self, regex: Union[str, Pattern]) -> List["AbstractStat"]:
"""Find all stats that match the name, recursively through all the
@@ -99,3 +92,52 @@ class AbstractStat(SerializableStat):
return self.children(
lambda _name: re.match(pattern, _name), recursive=True
)
def _get_vector_item(self, item: str) -> Optional[Tuple[str, int, Any]]:
"""It has been the case in gem5 that SimObject vectors are stored as
strings such as "cpu0" or "cpu1". This function splits the string into
the SimObject name and index, (e.g.: ["cpu", 0] and ["cpu", 1]) and
returns the item for that name and it's index. If the string cannot be
split into a SimObject name and index, or if the SimObject does not
exit at `Simobject[index]`, the function returns None.
"""
regex = re.compile("[0-9]+$")
match = regex.search(item)
if not match:
return None
match_str = match.group()
assert match_str.isdigit(), f"Regex match must be a digit: {match_str}"
vector_index = int(match_str)
vector_name = item[: (-1 * len(match_str))]
if hasattr(self, vector_name):
vector = getattr(self, vector_name)
try:
vector_value = vector[vector_index]
return vector_name, vector_index, vector_value
except KeyError:
pass
return None
def __iter__(self):
return iter(self.__dict__)
def __getattr__(self, item: str) -> Any:
vector_item = self._get_vector_item(item)
if not vector_item:
return None
assert (
len(vector_item) == 3
), f"Vector item must have 3 elements: {vector_item}"
return vector_item[2]
def __getitem__(self, item: str):
return getattr(self, item)
def __contains__(self, item: Any) -> bool:
return (
isinstance(item, str) and self._get_vector_item(item)
) or hasattr(self, item)

View File

@@ -25,18 +25,16 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from typing import (
Any,
Callable,
Dict,
List,
Mapping,
Optional,
Union,
)
from .abstract_stat import AbstractStat
from .statistic import (
Scalar,
Statistic,
)
from .statistic import Statistic
from .timeconversion import TimeConversion
@@ -57,9 +55,7 @@ class Group(AbstractStat):
str, Union["Group", Statistic, List["Group"], List["Statistic"]]
],
):
if type is None:
self.type = "Group"
else:
if type:
self.type = type
self.time_conversion = time_conversion
@@ -67,18 +63,70 @@ class Group(AbstractStat):
for key, value in kwargs.items():
setattr(self, key, value)
def children(
self,
predicate: Optional[Callable[[str], bool]] = None,
recursive: bool = False,
) -> List["AbstractStat"]:
to_return = []
for attr in self.__dict__:
obj = getattr(self, attr)
if isinstance(obj, AbstractStat):
if (predicate and predicate(attr)) or not predicate:
to_return.append(obj)
if recursive:
to_return = to_return + obj.children(
predicate=predicate, recursive=True
)
return to_return
class Vector(Group):
"""
The Vector class is used to store vector information. However, in gem5
Vectors, in practise, hold information that is more like a dictionary of
Scalar Values. This class may change, and may be merged into Group in
accordance to decisions made in relation to
https://gem5.atlassian.net/browse/GEM5-867.
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, scalar_map: Mapping[str, Scalar]):
super().__init__(type="Vector", time_conversion=None, **scalar_map)
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 _repr_name(self) -> str:
return "Vector"
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 __getitem__(self, item: int):
return self.value[item]
def __contains__(self, item):
if isinstance(item, int):
return item >= 0 and item < len(self)
def children(
self,
predicate: Optional[Callable[[str], bool]] = None,
recursive: bool = False,
) -> List["AbstractStat"]:
to_return = []
for child in self.value:
to_return = to_return + child.children(
predicate=predicate, recursive=recursive
)
return to_return

View File

@@ -31,13 +31,9 @@ from typing import (
Union,
)
from .group import (
Group,
Vector,
)
from .group import Group
from .simstat import SimStat
from .statistic import (
Accumulator,
Distribution,
Scalar,
Statistic,
@@ -74,10 +70,6 @@ class JsonLoader(json.JSONDecoder):
d.pop("type", None)
return Distribution(**d)
elif d["type"] == "Accumulator":
d.pop("type", None)
return Accumulator(**d)
elif d["type"] == "Group":
return Group(**d)

View File

@@ -85,6 +85,11 @@ class SerializableStat:
return value
elif isinstance(value, datetime):
return value.replace(microsecond=0).isoformat()
elif isinstance(value, Dict):
d = {}
for k, v in value.items():
d[self.__process_json_value(k)] = self.__process_json_value(v)
return d
elif isinstance(value, list):
return [self.__process_json_value(v) for v in value]
elif isinstance(value, StorageType):

View File

@@ -32,22 +32,16 @@ from typing import (
Union,
)
from .abstract_stat import AbstractStat
from .group import Group
from .statistic import Statistic
from .timeconversion import TimeConversion
class SimStat(AbstractStat):
class SimStat(Group):
"""
Contains all the statistics for a given simulation.
"""
creation_time: Optional[datetime]
time_conversion: Optional[TimeConversion]
simulated_begin_time: Optional[Union[int, float]]
simulated_end_time: Optional[Union[int, float]]
def __init__(
self,
creation_time: Optional[datetime] = None,
@@ -56,10 +50,10 @@ class SimStat(AbstractStat):
simulated_end_time: Optional[Union[int, float]] = None,
**kwargs: Dict[str, Union[Group, Statistic, List[Group]]]
):
self.creation_time = creation_time
self.time_conversion = time_conversion
self.simulated_begin_time = simulated_begin_time
self.simulated_end_time = simulated_end_time
for key, value in kwargs.items():
setattr(self, key, value)
super().__init__(
creation_time=creation_time,
time_conversion=time_conversion,
simulated_begin_time=simulated_begin_time,
simulated_end_time=simulated_end_time,
**kwargs
)

View File

@@ -27,6 +27,8 @@
from abc import ABC
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Optional,
@@ -85,49 +87,192 @@ class Scalar(Statistic):
self.datatype = datatype
class BaseScalarVector(Statistic):
class Vector(Statistic):
"""
An abstract base class for classes containing a vector of Scalar values.
An Python statistics which representing a vector of Scalar values.
"""
value: List[Union[int, float]]
def __init__(
self,
value: Iterable[Union[int, float]],
value: Dict[Union[str, int, float], Scalar],
type: Optional[str] = None,
description: Optional[str] = None,
):
super().__init__(
value=list(value),
value=value,
type=type,
description=description,
)
def __getitem__(self, item: Union[int, str, float]) -> Scalar:
assert self.value != None
# In the case of string, we cast strings to integers of floats if they
# are numeric. This avoids users having to cast strings to integers.
if isinstance(item, str):
if item.isdigit():
item = int(item)
elif item.isnumeric():
item = float(item)
return self.value[item]
def __contains__(self, item) -> bool:
assert self.value != None
if isinstance(item, str):
if item.isdigit():
item = int(item)
elif item.isnumeric():
item = float(item)
return item in self.value
def __iner__(self) -> None:
return iter(self.value)
def __len__(self) -> int:
assert self.value != None
return len(self.value.values())
def size(self) -> int:
"""
Returns the size of the vector.
:returns: The size of the vector.
"""
assert self.value != None
return len(self.value)
def mean(self) -> float:
"""
Returns the mean of the value vector.
:returns: The mean value across all bins.
:returns: The mean value across all values in the vector.
"""
assert self.value != None
assert isinstance(self.value, List)
from statistics import mean as statistics_mean
return statistics_mean(self.value)
return self.count() / self.size()
def count(self) -> float:
"""
Returns the count across all the bins.
Returns the count (sum) of all values in the vector.
:returns: The sum of all bin values.
:returns: The sum of all vector values.
"""
assert self.value != None
return sum(self.value)
return sum(float(self.value[key]) for key in self.values)
def children(
self,
predicate: Optional[Callable[[str], bool]] = None,
recursive: bool = False,
) -> List["AbstractStat"]:
to_return = []
for attr in self.value.keys():
obj = self.value[attr]
if isinstance(obj, AbstractStat):
if (
isinstance(attr, str)
and (predicate and predicate(attr))
or not predicate
):
to_return.append(obj)
to_return = to_return + obj.children(
predicate=predicate, recursive=True
)
return to_return
class Distribution(BaseScalarVector):
class Vector2d(Statistic):
"""
A 2D vector of scalar values.
"""
value: Dict[Union[str, int, float], Vector]
def __init__(
self,
value: Dict[Union[str, int, float], Vector],
type: Optional[str] = None,
description: Optional[str] = None,
):
assert (
len({vector.size() for vector in value.values()}) == 1
), "All the Vectors in the 2d Vector are not of equal length."
super().__init__(
value=value,
type=type,
description=description,
)
def x_size(self) -> int:
"""Returns the number of elements in the x dimension."""
assert self.value is not None
return len(self.value)
def y_size(self) -> int:
"""Returns the number of elements in the y dimension."""
assert self.value is not None
return len(self.value[0])
def size(self) -> int:
"""Returns the total number of elements."""
return self.x_size() * self.y_size()
def __len__(self) -> int:
return self.x_size()
def __iter__(self):
return iter(self.keys())
def total(self) -> int:
"""The total (sum) of all the entries in the 2d vector/"""
assert self.value is not None
total = 0
for vector in self.value.values():
for scalar in vector.values():
total += scalar.value
return total
def __getitem__(self, index: Union[str, int, float]) -> Vector:
assert self.value is not None
# In the case of string, we cast strings to integers of floats if they
# are numeric. This avoids users having to cast strings to integers.
if isinstance(index, str):
if index.isindex():
index = int(index)
elif index.isnumeric():
index = float(index)
return self.value[index]
def children(
self,
predicate: Optional[Callable[[str], bool]] = None,
recursive: bool = False,
) -> List["AbstractStat"]:
to_return = []
for attr in self.value.keys():
obj = self.value[attr]
if (
isinstance(attr, str)
and (predicate and predicate(attr))
or not predicate
):
to_return.append(obj)
to_return = to_return + obj.children(
predicate=predicate, recursive=True
)
return to_return
def __contains__(self, item) -> bool:
assert self.value is not None
if isinstance(item, str):
if item.isdigit():
item = int(item)
elif item.isnumeric():
item = float(item)
return item in self.value
class Distribution(Vector):
"""
A statistic type that stores information relating to distributions. Each
distribution has a number of bins (>=1)
@@ -149,7 +294,7 @@ class Distribution(BaseScalarVector):
def __init__(
self,
value: Iterable[int],
value: Dict[Union[int, float], Scalar],
min: Union[float, int],
max: Union[float, int],
num_bins: int,
@@ -182,35 +327,29 @@ class Distribution(BaseScalarVector):
assert self.num_bins >= 1
class Accumulator(BaseScalarVector):
"""
A statistical type representing an accumulator.
"""
_count: int
min: Union[int, float]
max: Union[int, float]
sum_squared: Optional[int]
class SparseHist(Vector):
"""A Sparse Histogram of values. A sparse histogram simply counts the "
frequency of each value in a sample. Ergo, it is, ineffect an disctionary
of values mapped to their count"""
def __init__(
self,
value: Iterable[Union[int, float]],
count: int,
min: Union[int, float],
max: Union[int, float],
sum_squared: Optional[int] = None,
value: Dict[float, Scalar],
description: Optional[str] = None,
):
super().__init__(
value=value,
type="Accumulator",
type="SparseHist",
description=description,
)
self._count = count
self.min = min
self.max = max
self.sum_squared = sum_squared
def size(self) -> int:
"""The number of unique sampled values."""
return len(self.value)
def count(self) -> int:
return self._count
"""
Returns the total number of samples.
"""
assert self.value != None
return sum(self.value.values())

View File

@@ -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
@@ -138,6 +112,10 @@ def __get_statistic(statistic: _m5.stats.Info) -> Optional[Statistic]:
pass
elif isinstance(statistic, _m5.stats.VectorInfo):
return __get_vector(statistic)
elif isinstance(statistic, _m5.stats.Vector2dInfo):
return __get_vector2d(statistic)
elif isinstance(statistic, _m5.stats.SparseHistInfo):
return __get_sparse_hist(statistic)
return None
@@ -167,8 +145,16 @@ def __get_distribution(statistic: _m5.stats.DistInfo) -> Distribution:
overflow = statistic.overflow
logs = statistic.logs
parsed_values = {}
for index in range(len(value)):
parsed_values[index] = Scalar(
value=value[index],
unit=statistic.unit,
datatype=StorageType["f64"],
)
return Distribution(
value=value,
value=parsed_values,
min=min,
max=max,
num_bins=num_bins,
@@ -183,29 +169,97 @@ def __get_distribution(statistic: _m5.stats.DistInfo) -> Distribution:
def __get_vector(statistic: _m5.stats.VectorInfo) -> Vector:
to_add = dict()
vec: Dict[Union[str, int, float], Scalar] = {}
for index in range(statistic.size):
# All the values in a Vector are Scalar values
value = statistic.value[index]
unit = statistic.unit
description = statistic.subdescs[index]
# ScalarInfo uses the C++ `double`.
datatype = StorageType["f64"]
assert isinstance(value, float) or isinstance(value, int)
# Sometimes elements within a vector are defined by their name. Other
# times they have no name. When a name is not available, we name the
# stat the index value.
if str(statistic.subnames[index]):
index_string = str(statistic.subnames[index])
if len(statistic.subnames) > index and statistic.subnames[index]:
index_subname = str(statistic.subnames[index])
if index_subname.isdigit():
index_subname = int(index_subname)
elif index_subname.isnumeric():
index_subname = float(index_subname)
else:
index_string = str(index)
index_subname = index
to_add[index_string] = Scalar(
value=value, unit=unit, description=description, datatype=datatype
index_subdesc = None
if len(statistic.subdescs) > index and statistic.subdescs[index]:
index_subdesc = str(statistic.subdescs[index])
else:
index_subdesc = statistic.desc
vec[index_subname] = Scalar(
value=value,
unit=statistic.unit,
description=index_subdesc,
datatype=StorageType["f64"],
)
return Vector(scalar_map=to_add)
return Vector(
vec,
type="Vector",
description=statistic.desc,
)
def __get_vector2d(statistic: _m5.stats.Vector2dInfo) -> Vector2d:
# All the values in a 2D Vector are Scalar values
description = statistic.desc
x_size = statistic.x_size
y_size = statistic.y_size
vector_rep: Dict[Union[str, int, float], Vector] = {}
for x_index in range(x_size):
x_index_string = x_index
if x_index in statistic.subnames:
x_index_string = str(statistic.subnames[x_index])
x_desc = description
if x_index in statistic.subdescs:
x_desc = str(statistic.subdescs[x_index])
x_vec: Dict[str, Scalar] = {}
for y_index in range(y_size):
y_index_val = y_index
if y_index in statistic.ysubnames:
y_index_val = str(statistic.subnames[y_index])
x_vec[y_index_val] = Scalar(
value=statistic.value[x_index * y_size + y_index],
unit=statistic.unit,
datatype=StorageType["f64"],
)
vector_rep[x_index_string] = Vector(
x_vec,
type="Vector",
description=x_desc,
)
return Vector2d(value=vector_rep, type="Vector2d", description=description)
def __get_sparse_hist(statistic: _m5.stats.SparseHistInfo) -> SparseHist:
description = statistic.desc
value = statistic.values
parsed_values = {}
for val in value:
parsed_values[val] = Scalar(
value=value[val],
unit=statistic.unit,
datatype=StorageType["f64"],
)
return SparseHist(
value=parsed_values,
description=description,
)
def _prepare_stats(group: _m5.stats.Group):
@@ -222,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
@@ -243,7 +373,26 @@ def get_simstat(
:Returns: The SimStat Object of the current simulation.
"""
if prepare_stats:
_m5.stats.processDumpQueue()
stats_map = {}
for r in root:
if prepare_stats:
if isinstance(r, list):
for obj in r:
_prepare_stats(obj)
else:
_prepare_stats(r)
stats = _process_simobject_stats(r).__dict__
stats["name"] = r.get_name() if r.get_name() else "root"
stats_map[stats["name"]] = stats
if len(stats_map) == 1:
stats_map = stats_map[next(iter(stats_map))]
creation_time = datetime.now()
time_converstion = None # TODO https://gem5.atlassian.net/browse/GEM5-846
final_tick = Root.getInstance().resolveStat("finalTick").value
@@ -251,31 +400,8 @@ def get_simstat(
simulated_begin_time = int(final_tick - sim_ticks)
simulated_end_time = int(final_tick)
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)
else:
raise TypeError(
"Object (" + str(r) + ") passed is not a "
"SimObject. " + __name__ + " only processes "
"SimObjects, or a list of SimObjects."
)
return SimStat(
creation_time=creation_time,
time_conversion=time_converstion,
simulated_begin_time=simulated_begin_time,
simulated_end_time=simulated_end_time,
**stats_map,

View File

@@ -76,7 +76,9 @@ cast_stat_info(const statistics::Info *info)
*/
TRY_CAST(statistics::FormulaInfo);
TRY_CAST(statistics::VectorInfo);
TRY_CAST(statistics::Vector2dInfo);
TRY_CAST(statistics::DistInfo);
TRY_CAST(statistics::SparseHistInfo);
return py::cast(info);
@@ -183,6 +185,26 @@ pybind_init_stats(py::module_ &m_native)
[](const statistics::VectorInfo &info) { return info.total(); })
;
py::class_<statistics::Vector2dInfo, statistics::Info,
std::unique_ptr<statistics::Vector2dInfo, py::nodelete>>(
m, "Vector2dInfo")
.def_readonly("x_size", &statistics::Vector2dInfo::x)
.def_readonly("y_size", &statistics::Vector2dInfo::y)
.def_readonly("subnames", &statistics::Vector2dInfo::subnames)
.def_readonly("subdescs", &statistics::Vector2dInfo::subdescs)
.def_readonly("ysubnames", &statistics::Vector2dInfo::y_subnames)
.def_readonly("value", &statistics::Vector2dInfo::cvec)
;
py::class_<statistics::SparseHistInfo, statistics::Info,
std::unique_ptr<statistics::SparseHistInfo, py::nodelete>>(
m, "SparseHistInfo")
.def_property_readonly("values", //A Dict[float, int] of sample & count
[](const statistics::SparseHistInfo &info) {
return info.data.cmap;
})
;
py::class_<statistics::FormulaInfo, statistics::VectorInfo,
std::unique_ptr<statistics::FormulaInfo, py::nodelete>>(
m, "FormulaInfo")

View File

@@ -27,5 +27,11 @@
Import ("*")
if env['CONF']['USE_TEST_OBJECTS']:
SimObject('StatTester.py', sim_objects=['StatTester', 'ScalarStatTester'])
SimObject('StatTester.py', sim_objects=[
'StatTester',
'ScalarStatTester',
'VectorStatTester',
'Vector2dStatTester',
'SparseHistStatTester',
])
Source('stat_tester.cc')

View File

@@ -44,3 +44,57 @@ class ScalarStatTester(StatTester):
cxx_class = "gem5::ScalarStatTester"
value = Param.Float("The scalar stat's value.")
class VectorStatTester(StatTester):
type = "VectorStatTester"
cxx_header = "test_objects/stat_tester.hh"
cxx_class = "gem5::VectorStatTester"
values = VectorParam.Float("The vector stat's values.")
subnames = VectorParam.String(
[],
"The vector stat's subnames. If position is empty, index int is "
"used instead.",
)
subdescs = VectorParam.String(
[],
"The vector stat's subdescriptions. If empty, the subdescriptions "
"are not used.",
)
class Vector2dStatTester(StatTester):
type = "Vector2dStatTester"
cxx_header = "test_objects/stat_tester.hh"
cxx_class = "gem5::Vector2dStatTester"
x_size = Param.Int("The number of elements in the x dimension.")
y_size = Param.Int("The number of elements in the y dimension.")
values = VectorParam.Float("The vector stat's values, flattened.")
subnames = VectorParam.String(
[],
"The vector stat's subnames. If position is empty, index int is "
"used instead.",
)
subdescs = VectorParam.String(
[],
"The vector stat's subdescriptions. If empty, the subdescriptions "
"are not used.",
)
ysubnames = VectorParam.String(
[],
"The vector stat's y subdescriptions. If empty, the subdescriptions ",
)
class SparseHistStatTester(StatTester):
type = "SparseHistStatTester"
cxx_header = "test_objects/stat_tester.hh"
cxx_class = "gem5::SparseHistStatTester"
samples = VectorParam.Float(
"The sparse histogram's sampled values, to be inserted into the "
"histogram."
)

View File

@@ -28,6 +28,8 @@
#include "test_objects/stat_tester.hh"
#include <set>
#include "base/stats/group.hh"
namespace gem5
@@ -51,4 +53,110 @@ ScalarStatTester::ScalarStatTesterStats::ScalarStatTesterStats(
{
}
void
VectorStatTester::setStats()
{
for (int i = 0; i < params.values.size(); i++)
{
stats.vector[i] = (params.values[i]);
}
}
VectorStatTester::VectorStatTesterStats::VectorStatTesterStats(
statistics::Group *parent,
const VectorStatTesterParams &params
) : statistics::Group(parent),
vector(this,
params.name.c_str(),
statistics::units::Count::get(),
params.description.c_str()
)
{
vector.init(params.values.size());
for (int i = 0; i < params.values.size(); i++)
{
if (params.subnames.size() > i) {
vector.subname(i, params.subnames[i]);
} else {
vector.subname(i, std::to_string(i));
}
if (params.subdescs.size() > i) {
vector.subdesc(i, params.subdescs[i]);
}
}
}
void
Vector2dStatTester::setStats()
{
for (int i = 0; i < params.x_size; i++)
{
for (int j = 0; j < params.y_size; j++)
{
stats.vector2d[i][j] = (params.values[j + i * params.y_size]);
}
}
}
Vector2dStatTester::Vector2dStatTesterStats::Vector2dStatTesterStats(
statistics::Group *parent,
const Vector2dStatTesterParams &params
) : statistics::Group(parent),
vector2d(this,
params.name.c_str(),
statistics::units::Count::get(),
params.description.c_str()
)
{
vector2d.init(params.x_size, params.y_size);
assert(params.x_size * params.y_size == params.values.size());
for (int i = 0; i < params.x_size; i++)
{
if (params.subnames.size() > i) {
vector2d.subname(i, params.subnames[i]);
} else {
vector2d.subname(i, std::to_string(i));
}
if (params.subdescs.size() > i) {
vector2d.subdesc(i, params.subdescs[i]);
}
}
for (int j = 0; j < params.y_size; j++)
{
if (params.ysubnames.size() > j) {
vector2d.ysubname(j, params.ysubnames[j]);
} else {
vector2d.ysubname(j, std::to_string(j));
}
}
}
void
SparseHistStatTester::setStats()
{
for (auto sample : params.samples) {
stats.sparse_histogram.sample(sample);
}
}
SparseHistStatTester::SparseHistStatTesterStats::SparseHistStatTesterStats(
statistics::Group *parent,
const SparseHistStatTesterParams &params
) : statistics::Group(parent),
sparse_histogram(
this,
params.name.c_str(),
statistics::units::Count::get(),
params.description.c_str()
)
{
sparse_histogram.init(
(std::set(params.samples.begin(), params.samples.end())).size()
);
}
} // namespace gem5

View File

@@ -31,7 +31,10 @@
#include "base/statistics.hh"
#include "params/ScalarStatTester.hh"
#include "params/SparseHistStatTester.hh"
#include "params/StatTester.hh"
#include "params/Vector2dStatTester.hh"
#include "params/VectorStatTester.hh"
#include "sim/sim_object.hh"
namespace gem5
@@ -114,6 +117,70 @@ class ScalarStatTester : public StatTester
} stats;
};
class VectorStatTester : public StatTester
{
private:
VectorStatTesterParams params;
public:
VectorStatTester(const VectorStatTesterParams &p) :
StatTester(p), params(p), stats(this, p) {}
protected:
void setStats() override;
struct VectorStatTesterStats : public statistics::Group
{
VectorStatTesterStats(
statistics::Group *parent,
const VectorStatTesterParams &params
);
statistics::Vector vector;
} stats;
};
class Vector2dStatTester : public StatTester
{
private:
Vector2dStatTesterParams params;
public:
Vector2dStatTester(const Vector2dStatTesterParams &p) :
StatTester(p), params(p), stats(this, p) {}
protected:
void setStats() override;
struct Vector2dStatTesterStats : public statistics::Group
{
Vector2dStatTesterStats(
statistics::Group *parent,
const Vector2dStatTesterParams &params
);
statistics::Vector2d vector2d;
} stats;
};
class SparseHistStatTester : public StatTester
{
private:
SparseHistStatTesterParams params;
public:
SparseHistStatTester(const SparseHistStatTesterParams &p) :
StatTester(p), params(p), stats(this, p) {}
protected:
void setStats() override;
struct SparseHistStatTesterStats : public statistics::Group
{
SparseHistStatTesterStats(
statistics::Group *parent,
const SparseHistStatTesterParams &params
);
statistics::SparseHistogram sparse_histogram;
} stats;
};
} // namespace gem5
#endif // __STAT_TESTER_HH__

View File

@@ -25,8 +25,8 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import json
import sys
from typing import Union
import m5
from m5.objects import (
@@ -35,31 +35,25 @@ from m5.objects import (
)
from m5.stats.gem5stats import get_simstat
"""This script is used for checking that statistics set in the simulation are
correctly parsed through to the python SimStats. This script sets the
statistics using the "StatTester" simobjects and ensures verifies correctness
against the expected JSON output and that produced by the SimStats module.
"""This script is used for checking that Scaler statistics set in the simulation are
correctly parsed through to the python Pystats.
"""
parser = argparse.ArgumentParser(description="Tests the output of a SimStat.")
subparsers = parser.add_subparsers(
dest="statistic", help="SimStats statistic to test", required=True
parser = argparse.ArgumentParser(
description="Tests the output of a Scaler Pystat."
)
scalar_parser = subparsers.add_parser(
"scalar", help="Test a scalar statistic."
)
scalar_parser.add_argument(
parser.add_argument(
"value", type=float, help="The value of the scalar statistic."
)
scalar_parser.add_argument(
parser.add_argument(
"--name",
type=str,
default="scalar",
required=False,
help="The name of the scalar statistic.",
)
scalar_parser.add_argument(
parser.add_argument(
"--description",
type=str,
default="",
@@ -69,27 +63,22 @@ scalar_parser.add_argument(
args = parser.parse_args()
expected_output = None
stat_tester = None
if args.statistic == "scalar":
stat_tester = ScalarStatTester()
stat_tester.name = args.name
stat_tester.description = args.description
stat_tester.value = args.value
expected_output = {
"type": "Group",
"time_conversion": None,
args.name: {
"value": args.value,
"type": "Scalar",
"unit": "Count",
"description": args.description,
"datatype": "f64",
},
}
assert stat_tester is not None
assert expected_output is not None
stat_tester = ScalarStatTester()
stat_tester.name = args.name
stat_tester.description = args.description
stat_tester.value = args.value
expected_output = {
"type": "SimObject",
"name": "system",
"time_conversion": None,
args.name: {
"value": args.value,
"type": "Scalar",
"unit": "Count",
"description": args.description,
"datatype": "f64",
},
}
root = Root(full_system=False, system=stat_tester)
@@ -97,14 +86,22 @@ 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)
print("", file=sys.stderr)
print("Expected:", file=sys.stderr)
print(expected_output, file=sys.stderr)
print(json.dumps(expected_output, indent=4), file=sys.stderr)
print("", file=sys.stderr)
print("Actual:", file=sys.stderr)
print(output, file=sys.stderr)
print(json.dumps(output, indent=4), file=sys.stderr)
sys.exit(1)

View File

@@ -0,0 +1,121 @@
# 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.
import argparse
import json
import sys
import m5
from m5.objects import (
Root,
SparseHistStatTester,
)
from m5.stats.gem5stats import get_simstat
parser = argparse.ArgumentParser(
description="Tests the output of a SparseHist Pystat."
)
parser.add_argument(
"samples",
help="delimited list representing the samples for the distributed "
"histogram.",
type=lambda s: [float(item) for item in s.split(",")],
)
parser.add_argument(
"--name",
type=str,
default="sparse_hist",
required=False,
help="The name of the Sparse Histogram statistic.",
)
parser.add_argument(
"--description",
type=str,
default=None,
required=False,
help="The description of the Sparse Histogram statistic.",
)
args = parser.parse_args()
stat_tester = SparseHistStatTester(
name=args.name, description=args.description, samples=args.samples
)
root = Root(full_system=False, system=stat_tester)
m5.instantiate()
m5.simulate()
simstats = get_simstat(stat_tester)
output = simstats.to_json()
value_dict = {}
for sample in args.samples:
value_dict[sample] = (
1 if sample not in value_dict else value_dict[sample] + 1
)
scaler_dict = {}
for key in value_dict:
scaler_dict[key] = {
"unit": "Count",
"type": "Scalar",
"description": None,
"value": value_dict[key],
"datatype": "f64",
}
expected_output = {
"type": "SimObject",
"name": "system",
"time_conversion": None,
args.name: {
"value": scaler_dict,
"type": "SparseHist",
"description": str(args.description),
},
}
# 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)
print("Expected:", file=sys.stderr)
print(json.dumps(expected_output, indent=4), file=sys.stderr)
print("", file=sys.stderr)
print("Actual:", file=sys.stderr)
print(json.dumps(output, indent=4), file=sys.stderr)
sys.exit(1)

View File

@@ -0,0 +1,182 @@
# 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.
import argparse
import json
import sys
import m5
from m5.objects import (
Root,
Vector2dStatTester,
)
from m5.stats.gem5stats import get_simstat
"""This script is used for checking that Vector2d statistics set in the
simulation are correctly parsed through to the python Pystats.
"""
parser = argparse.ArgumentParser(
description="Tests the output of a Vector2D Pystat."
)
parser.add_argument(
"value",
help="Comma delimited list representing the 2d vector in a flattened "
"state.",
type=lambda s: [float(item) for item in s.split(",")],
)
parser.add_argument(
"num_vectors",
help="The number of vectors in the vector of vectors",
type=int,
)
parser.add_argument(
"--name",
type=str,
default="vector2d",
required=False,
help="Name of the vector statistic.",
)
parser.add_argument(
"--description",
type=str,
default="",
required=False,
help="Description of the vector statistic.",
)
parser.add_argument(
"--subnames",
help="delimited list representing the vector subnames",
type=str,
)
parser.add_argument(
"--subdescs",
help="delimited list representing the vector subdescs",
type=str,
)
parser.add_argument(
"--ysubnames",
help="delimited list representing the vector ysubnames",
type=str,
)
args = parser.parse_args()
expected_output = None
stat_tester = None
stat_tester = Vector2dStatTester()
stat_tester.name = args.name
stat_tester.description = args.description
stat_tester.subnames = []
if args.subnames:
stat_tester.subnames = [str(item) for item in args.subnames.split(",")]
stat_tester.subdescs = []
if args.subdescs:
stat_tester.subdescs = [str(item) for item in args.subdescs.split(",")]
stat_tester.ysubnames = []
if args.ysubnames:
stat_tester.ysubnames = [str(item) for item in args.ysubnames.split(",")]
assert (
len(args.value) % args.num_vectors == 0
), "The number of values is not divisable by the number of vectors."
stat_tester.x_size = args.num_vectors
stat_tester.y_size = len(args.value) / args.num_vectors
stat_tester.values = args.value
vectors = {} # The representation we expect output.
for x in range(args.num_vectors):
x_index = x if x not in stat_tester.subnames else stat_tester.subnames[x]
vector = {}
for y in range(stat_tester.y_size):
to_add = args.value[
int(y + (x * (len(args.value) / args.num_vectors)))
]
vector[y] = {
"value": to_add,
"type": "Scalar",
"unit": "Count",
"description": None,
"datatype": "f64",
}
vectors[x_index] = {
"type": "Vector",
"description": stat_tester.subdescs[x]
if x in stat_tester.subdescs
else stat_tester.description,
"value": vector,
}
expected_output = {
"type": "SimObject",
"name": "system",
"time_conversion": None,
args.name: {
"type": "Vector2d",
"value": vectors,
"description": args.description,
},
}
root = Root(full_system=False, system=stat_tester)
m5.instantiate()
m5.simulate()
simstats = get_simstat(stat_tester)
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)
print("", file=sys.stderr)
print("Expected:", file=sys.stderr)
print(json.dumps(expected_output, indent=4), file=sys.stderr)
print("", file=sys.stderr)
print("Actual:", file=sys.stderr)
print(json.dumps(output, indent=4), file=sys.stderr)
sys.exit(1)

View File

@@ -0,0 +1,147 @@
# 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.
import argparse
import json
import sys
import m5
from m5.objects import (
Root,
VectorStatTester,
)
from m5.stats.gem5stats import get_simstat
"""This script is used for checking that the Vector statistics set in "
the simulation are correctly parsed through to the python Pystats.
"""
parser = argparse.ArgumentParser(
description="Tests the output of a Vector PyStat."
)
parser.add_argument(
"value",
help="Comma delimited list representing the vector.",
type=lambda s: [float(item) for item in s.split(",")],
)
parser.add_argument(
"--name",
type=str,
default="vector",
required=False,
help="Name of the vector statistic.",
)
parser.add_argument(
"--description",
type=str,
default="",
required=False,
help="Description of the vector statistic.",
)
parser.add_argument(
"--subnames",
help="Comma delimited list representing the vector subnames.",
type=str,
)
parser.add_argument(
"--subdescs",
help="Comma delimited list representing the vector subdescs",
type=str,
)
args = parser.parse_args()
stat_tester = VectorStatTester()
stat_tester.name = args.name
stat_tester.description = args.description
stat_tester.values = args.value
stat_tester.subnames = []
if args.subnames:
stat_tester.subnames = [str(item) for item in args.subnames.split(",")]
stat_tester.subdescs = []
if args.subdescs:
stat_tester.subdescs = [str(item) for item in args.subdescs.split(",")]
value_dict = {}
for i in range(len(args.value)):
i_name = i
description = args.description
if stat_tester.subnames and i < len(stat_tester.subnames):
i_name = stat_tester.subnames[i]
if stat_tester.subdescs and i < len(stat_tester.subdescs):
description = stat_tester.subdescs[i]
value_dict[i_name] = {
"value": args.value[i],
"type": "Scalar",
"unit": "Count",
"description": description,
"datatype": "f64",
}
expected_output = {
"type": "SimObject",
"name": "system",
"time_conversion": None,
args.name: {
"value": value_dict,
"type": "Vector",
"description": args.description,
},
}
root = Root(full_system=False, system=stat_tester)
m5.instantiate()
m5.simulate()
simstats = get_simstat(stat_tester)
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)
print("", file=sys.stderr)
print("Expected:", file=sys.stderr)
print(json.dumps(expected_output, indent=4), file=sys.stderr)
print("", file=sys.stderr)
print("Actual:", file=sys.stderr)
print(json.dumps(output, indent=4), file=sys.stderr)
sys.exit(1)

View 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"

View File

@@ -0,0 +1,254 @@
# 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.
from testlib import *
gem5_verify_config(
name="pystat-scaler-int-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_scalar_check.py",
),
config_args=[
"42",
"--name",
"scalar_test",
"--description",
"A scalar statistic with a int value",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat-scaler-int-zero-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_scalar_check.py",
),
config_args=[
"0",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat-scaler-int-negative-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_scalar_check.py",
),
config_args=[
"-245",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat-scaler-float-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_scalar_check.py",
),
config_args=[
"42.869",
"--name",
"float_test",
"--description",
"A scalar statistic with a float value",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat_vector_test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_vector_check.py",
),
config_args=[
"2.0,4,5.9,2.3,-8,0,0.0,-8.9",
"--name",
"vector_stat",
"--description",
"A vector statistic with a float value",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat_vector_with_subnames_test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_vector_check.py",
),
config_args=[
"2.0,4,3",
"--name",
"vector_stat",
"--description",
"A vector statistic with a float value",
"--subnames",
"first,second,third",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat_vector_with_subdescs_test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_vector_check.py",
),
config_args=[
"2.0,4,3",
"--name",
"vector_stat",
"--description",
"A vector statistic with a float value",
"--subdescs",
"first,second",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat_vector2d_test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_vector2d_check.py",
),
config_args=[
"2.4,4.3,3.7,-1.4,-2,4,0,0",
2,
"--name",
"vector2d_stat",
"--description",
"A 2d vector statistic with",
"--subnames",
"decimals,integers",
"--subdescs",
"A random collection of decimals,A random collection of integers",
"--ysubnames",
"first,second,third,fourth",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat-sparsehist-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_sparse_dist_check.py",
),
config_args=[
"1.0,1,1.00,23,23,0.2,0.2,0.2,0.2,-1,-1.0,264",
"--name",
"sparsehist_stat",
"--description",
"A sparse histogram statistic.",
],
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,
)

View File

@@ -1,115 +0,0 @@
# 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.
from testlib import *
gem5_verify_config(
name="simstat-scaler-int-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"simstat_output_check.py",
),
config_args=[
"scalar",
"42",
"--name",
"scalar_test",
"--description",
"A scalar statistic with a int value",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="simstat-scaler-int-zero-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"simstat_output_check.py",
),
config_args=[
"scalar",
"0",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="simstat-scaler-int-negative-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"simstat_output_check.py",
),
config_args=[
"scalar",
"-245",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="simstat-scaler-float-test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"simstat_output_check.py",
),
config_args=[
"scalar",
"42.869",
"--name",
"float_test",
"--description",
"A scalar statistic with a float value",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)

View File

@@ -0,0 +1,208 @@
# 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.
import unittest
from datetime import datetime
from m5.ext.pystats import (
Distribution,
Scalar,
SimObjectGroup,
SimObjectVectorGroup,
SimStat,
SparseHist,
Vector,
Vector2d,
)
def _get_mock_simstat() -> SimStat:
"""Used to create a mock SimStat for testing.
This SimStat is contains all simstat Statistic types and attempts to use
most of the different types of values that can be stored in a Statistic.
"""
simobject_vector_group = SimObjectVectorGroup(
value=[
SimObjectGroup(
**{
"vector2d": Vector2d(
value={
0: Vector(
value={
"a": Scalar(value=1, description="one"),
"b": Scalar(value=2.0, description="two"),
"c": Scalar(value=-3, description="three"),
}
),
1: Vector(
value={
1: Scalar(value=4),
0.2: Scalar(value=5.0),
0.3: Scalar(value=6),
},
description="vector 1",
),
},
description="vector 2d",
),
}
),
SimObjectGroup(
**{
"distribution": Distribution(
value={
0: Scalar(1),
1: Scalar(2),
2: Scalar(3),
3: Scalar(4),
4: Scalar(5),
},
min=0,
max=4,
num_bins=5,
bin_size=1,
),
"sparse_hist": SparseHist(
value={
0.5: Scalar(4),
0.51: Scalar(1),
0.511: Scalar(4),
5: Scalar(2),
},
description="sparse hist",
),
},
),
],
)
return SimStat(
creation_time=datetime.fromisoformat("2021-01-01T00:00:00"),
time_conversion=None,
simulated_begin_time=123,
simulated_end_time=558644,
simobject_vector=simobject_vector_group,
)
class NavigatingPyStatsTestCase(unittest.TestCase):
"""A test case for navigating the PyStats data structure, primarily
on how to access children of a SimStat object, and the "find" methods to
search for a specific statistic.
"""
def setUp(self) -> None:
"""Overrides the setUp method to create a mock SimStat for testing.
Runs before each test method.
"""
self.failFast = True
self.simstat = _get_mock_simstat()
super().setUp()
def test_simstat_index(self):
self.assertTrue("simobject_vector" in self.simstat)
self.assertIsInstance(
self.simstat["simobject_vector"], SimObjectVectorGroup
)
def test_simstat_attribute(self):
self.assertTrue(hasattr(self.simstat, "simobject_vector"))
self.assertIsInstance(
self.simstat.simobject_vector, SimObjectVectorGroup
)
def test_simobject_vector_attribute(self):
# To maintan compatibility with the old way of accessing the vector,
# the simobject vectors values can be accessed by attributes of that
# simoobject vector name and the index appended to it.
# E.g., `simstat.simobject_vector0 is the same
# is simstat.simobject_vector[0]`. In cases where there is already
# an attribute with the same name as the vector+index, the attribute
# will be returned.
self.assertEqual(
self.simstat.simobject_vector0, self.simstat.simobject_vector[0]
)
def test_simobject_vector_index(self):
self.assertTrue(self.simstat.simobject_vector[0], SimObjectGroup)
def test_simobject_group_index(self):
self.assertTrue("vector2d" in self.simstat.simobject_vector[0])
self.assertIsInstance(
self.simstat.simobject_vector[0]["vector2d"], Vector2d
)
def test_simobject_group_attribute(self):
self.assertTrue(hasattr(self.simstat.simobject_vector[0], "vector2d"))
self.assertIsInstance(
self.simstat.simobject_vector[0].vector2d, Vector2d
)
def test_vector2d_index(self):
self.assertEqual(2, len(self.simstat.simobject_vector[0]["vector2d"]))
self.assertTrue(0 in self.simstat.simobject_vector[0].vector2d)
self.assertIsInstance(
self.simstat.simobject_vector[0].vector2d[0], Vector
)
def test_vector_index_int(self):
self.assertEqual(3, len(self.simstat.simobject_vector[0].vector2d[1]))
self.assertTrue(1 in self.simstat.simobject_vector[0].vector2d[1])
self.assertIsInstance(
self.simstat.simobject_vector[0].vector2d[1][1], Scalar
)
def test_vector_index_str(self):
self.assertEqual(3, len(self.simstat.simobject_vector[0].vector2d[0]))
self.assertTrue("a" in self.simstat.simobject_vector[0].vector2d[0])
self.assertIsInstance(
self.simstat.simobject_vector[0].vector2d[0]["a"], Scalar
)
def test_vector_index_float(self):
self.assertEqual(3, len(self.simstat.simobject_vector[0].vector2d[1]))
self.assertTrue(0.2 in self.simstat.simobject_vector[0].vector2d[1])
self.assertIsInstance(
self.simstat.simobject_vector[0].vector2d[1][0.2], Scalar
)
def test_distriibution_index(self):
self.assertTrue(0 in self.simstat.simobject_vector[1]["distribution"])
self.assertIsInstance(
self.simstat.simobject_vector[1]["distribution"][0], Scalar
)
def test_sparse_hist_index(self):
self.assertTrue(0.5 in self.simstat.simobject_vector[1]["sparse_hist"])
self.assertIsInstance(
self.simstat.simobject_vector[1]["sparse_hist"][0.5], Scalar
)
def test_pystat_find(self):
self.assertEqual(
self.simstat.find("sparse_hist"),
[self.simstat.simobject_vector[1]["sparse_hist"]],
)