| # Copyright (c) 2021 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 serves as the bridge between the gem5 statistics exposed via PyBind11 and | |
| the Python Stats model. | |
| """ | |
| from datetime import datetime | |
| from typing import IO, List, Union | |
| import _m5.stats | |
| from m5.objects import * | |
| from m5.ext.pystats.group import * | |
| from m5.ext.pystats.simstat import * | |
| from m5.ext.pystats.statistic import * | |
| from m5.ext.pystats.storagetype import * | |
| class JsonOutputVistor(): | |
| """ | |
| This is a helper vistor class used to include a JSON output via the stats | |
| API (`src/python/m5/stats/__init__.py`). | |
| """ | |
| file: str | |
| json_args: Dict | |
| def __init__(self, file: str, **kwargs): | |
| """ | |
| Parameters | |
| ---------- | |
| file: str | |
| The output file location in which the JSON will be dumped. | |
| kwargs: Dict[str, Any] | |
| Additional parameters to be passed to the `json.dumps` method. | |
| """ | |
| self.file = file | |
| self.json_args = kwargs | |
| def dump(self, roots: Union[List[SimObject], Root]) -> None: | |
| """ | |
| Dumps the stats of a simulation root (or list of roots) to the output | |
| JSON file specified in the JsonOutput constructor. | |
| WARNING: This dump assumes the statistics have already been prepared | |
| for the target root. | |
| Parameters | |
| ---------- | |
| roots: Union[List[Root], Root]] | |
| The Root, or List of roots, whose stats are are to be dumped JSON. | |
| """ | |
| with open(self.file, 'w') as fp: | |
| simstat = get_simstat(root=roots, prepare_stats=False) | |
| 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). | |
| Parameters | |
| ---------- | |
| group: _m5.stats.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 | |
| ------- | |
| Group | |
| 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 | |
| statistics at the Python level. | |
| Parameters | |
| ---------- | |
| statistic: Info | |
| The Info object to be translated to a Statistic object. | |
| Returns | |
| ------- | |
| Optional[Statistic] | |
| The Statistic object of the Info object. Returns None if Info object | |
| cannot be translated. | |
| """ | |
| assert(isinstance(statistic, _m5.stats.Info)) | |
| statistic.prepare() | |
| if isinstance(statistic, _m5.stats.ScalarInfo): | |
| return __get_scaler(statistic) | |
| elif isinstance(statistic, _m5.stats.DistInfo): | |
| return __get_distribution(statistic) | |
| elif isinstance(statistic, _m5.stats.FormulaInfo): | |
| # We don't do anything with Formula's right now. | |
| # We may never do so, see https://gem5.atlassian.net/browse/GEM5-868. | |
| pass | |
| elif isinstance(statistic, _m5.stats.VectorInfo): | |
| return __get_vector(statistic) | |
| return None | |
| def __get_scaler(statistic: _m5.stats.ScalarInfo) -> Scalar: | |
| value = statistic.value | |
| unit = statistic.unit | |
| description = statistic.desc | |
| # ScalarInfo uses the C++ `double`. | |
| datatype = StorageType["f64"] | |
| return Scalar( | |
| value=value, | |
| unit=unit, | |
| description=description, | |
| datatype=datatype, | |
| ) | |
| def __get_distribution(statistic: _m5.stats.DistInfo) -> Distribution: | |
| unit = statistic.unit | |
| description = statistic.desc | |
| value = statistic.values | |
| bin_size = statistic.bucket_size | |
| min = statistic.min_val | |
| max = statistic.max_val | |
| num_bins = len(value) | |
| sum_val = statistic.sum | |
| sum_squared = statistic.squares | |
| underflow = statistic.underflow | |
| overflow = statistic.overflow | |
| logs = statistic.logs | |
| # DistInfo uses the C++ `double`. | |
| datatype = StorageType["f64"] | |
| return Distribution( | |
| value=value, | |
| min=min, | |
| max=max, | |
| num_bins=num_bins, | |
| bin_size=bin_size, | |
| sum = sum_val, | |
| sum_squared = sum_squared, | |
| underflow = underflow, | |
| overflow = overflow, | |
| logs = logs, | |
| unit=unit, | |
| description=description, | |
| datatype=datatype, | |
| ) | |
| def __get_vector(statistic: _m5.stats.VectorInfo) -> Vector: | |
| to_add = dict() | |
| 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"] | |
| # 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]) | |
| else: | |
| index_string = str(index) | |
| to_add[index_string] = Scalar( | |
| value=value, | |
| unit=unit, | |
| description=description, | |
| datatype=datatype, | |
| ) | |
| return Vector(scalar_map=to_add) | |
| def _prepare_stats(group: _m5.stats.Group): | |
| """ | |
| Prepares the statistics for dumping. | |
| """ | |
| group.preDumpStats() | |
| for stat in group.getStats(): | |
| stat.prepare() | |
| for child in group.getStatGroups().values(): | |
| _prepare_stats(child) | |
| def get_simstat(root: Union[Root, List[SimObject]], | |
| prepare_stats: bool = True) -> SimStat: | |
| """ | |
| This function will return the SimStat object for a simulation. From the | |
| SimStat object all stats within the current gem5 simulation are present. | |
| Parameters | |
| ---------- | |
| root: Union[Root, List[Root]] | |
| The root, or a list of Simobjects, of the simulation for translation to | |
| a SimStat object. | |
| prepare_stats: bool | |
| Dictates whether the stats are to be prepared prior to creating the | |
| SimStat object. By default this is 'True'. | |
| Returns | |
| ------- | |
| SimStat | |
| 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 | |
| sim_ticks = Root.getInstance().resolveStat("simTicks").value | |
| 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): | |
| 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.name] = get_stats_group(r) | |
| else: | |
| raise TypeError("Object (" + str(r) + ") passed is neither Root " | |
| "nor SimObject. " + __name__ + " only processes " | |
| "Roots, SimObjects, or a list of Roots and/or " | |
| "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, | |
| ) |