| # Copyright (c) 2017-2020 ARM Limited |
| # All rights reserved. |
| # |
| # The license below extends only to copyright in the software and shall |
| # not be construed as granting a license to any other intellectual |
| # property including but not limited to intellectual property relating |
| # to a hardware implementation of the functionality of the software |
| # licensed hereunder. You may use the software subject to the license |
| # terms below provided that you ensure that this notice is replicated |
| # unmodified and in its entirety in all distributions of the software, |
| # modified or unmodified, in source code or in binary form. |
| # |
| # Copyright (c) 2007 The Regents of The University of Michigan |
| # Copyright (c) 2010 The Hewlett-Packard Development Company |
| # 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 m5 |
| |
| import _m5.stats |
| from m5.objects import Root |
| from m5.params import isNullPointer |
| from .gem5stats import JsonOutputVistor |
| from m5.util import attrdict, fatal |
| |
| # Stat exports |
| from _m5.stats import schedStatEvent as schedEvent |
| from _m5.stats import periodicStatDump |
| |
| outputList = [] |
| |
| # Dictionary of stat visitor factories populated by the _url_factory |
| # visitor. |
| factories = { } |
| |
| # List of all factories. Contains tuples of (factory, schemes, |
| # enabled). |
| all_factories = [] |
| |
| def _url_factory(schemes, enable=True): |
| """Wrap a plain Python function with URL parsing helpers |
| |
| Wrap a plain Python function f(fn, **kwargs) to expect a URL that |
| has been split using urlparse.urlsplit. First positional argument |
| is assumed to be a filename, this is created as the concatenation |
| of the netloc (~hostname) and path in the parsed URL. Keyword |
| arguments are derived from the query values in the URL. |
| |
| Arguments: |
| schemes: A list of URL schemes to use for this function. |
| |
| Keyword arguments: |
| enable: Enable/disable this factory. Typically used when the |
| presence of a function depends on some runtime property. |
| |
| For example: |
| wrapped_f(urlparse.urlsplit("text://stats.txt?desc=False")) -> |
| f("stats.txt", desc=False) |
| |
| """ |
| |
| from functools import wraps |
| |
| def decorator(func): |
| @wraps(func) |
| def wrapper(url): |
| try: |
| from urllib.parse import parse_qs |
| except ImportError: |
| # Python 2 fallback |
| from urlparse import parse_qs |
| from ast import literal_eval |
| |
| qs = parse_qs(url.query, keep_blank_values=True) |
| |
| # parse_qs returns a list of values for each parameter. Only |
| # use the last value since kwargs don't allow multiple values |
| # per parameter. Use literal_eval to transform string param |
| # values into proper Python types. |
| def parse_value(key, values): |
| if len(values) == 0 or (len(values) == 1 and not values[0]): |
| fatal("%s: '%s' doesn't have a value." % ( |
| url.geturl(), key)) |
| elif len(values) > 1: |
| fatal("%s: '%s' has multiple values." % ( |
| url.geturl(), key)) |
| else: |
| try: |
| return key, literal_eval(values[0]) |
| except ValueError: |
| fatal("%s: %s isn't a valid Python literal" \ |
| % (url.geturl(), values[0])) |
| |
| kwargs = dict([ parse_value(k, v) for k, v in qs.items() ]) |
| |
| try: |
| return func("%s%s" % (url.netloc, url.path), **kwargs) |
| except TypeError: |
| fatal("Illegal stat visitor parameter specified") |
| |
| all_factories.append((wrapper, schemes, enable)) |
| for scheme in schemes: |
| assert scheme not in factories |
| factories[scheme] = wrapper if enable else None |
| return wrapper |
| |
| return decorator |
| |
| @_url_factory([ None, "", "text", "file", ]) |
| def _textFactory(fn, desc=True, spaces=True): |
| """Output stats in text format. |
| |
| Text stat files contain one stat per line with an optional |
| description. The description is enabled by default, but can be |
| disabled by setting the desc parameter to False. |
| |
| Parameters: |
| * desc (bool): Output stat descriptions (default: True) |
| * spaces (bool): Output alignment spaces (default: True) |
| |
| Example: |
| text://stats.txt?desc=False;spaces=False |
| |
| """ |
| |
| return _m5.stats.initText(fn, desc, spaces) |
| |
| @_url_factory([ "h5", ], enable=hasattr(_m5.stats, "initHDF5")) |
| def _hdf5Factory(fn, chunking=10, desc=True, formulas=True): |
| """Output stats in HDF5 format. |
| |
| The HDF5 file format is a structured binary file format. It has |
| the multiple benefits over traditional text stat files: |
| |
| * Efficient storage of time series (multiple stat dumps) |
| * Fast lookup of stats |
| * Plenty of existing tooling (e.g., Python libraries and graphical |
| viewers) |
| * File format can be used to store frame buffers together with |
| normal stats. |
| |
| There are some drawbacks compared to the default text format: |
| * Large startup cost (single stat dump larger than text equivalent) |
| * Stat dumps are slower than text |
| |
| |
| Known limitations: |
| * Distributions and histograms currently unsupported. |
| * No support for forking. |
| |
| |
| Parameters: |
| * chunking (unsigned): Number of time steps to pre-allocate (default: 10) |
| * desc (bool): Output stat descriptions (default: True) |
| * formulas (bool): Output derived stats (default: True) |
| |
| Example: |
| h5://stats.h5?desc=False;chunking=100;formulas=False |
| |
| """ |
| |
| return _m5.stats.initHDF5(fn, chunking, desc, formulas) |
| |
| @_url_factory(["json"]) |
| def _jsonFactory(fn): |
| """Output stats in JSON format. |
| |
| Example: |
| json://stats.json |
| |
| """ |
| |
| return JsonOutputVistor(fn) |
| |
| def addStatVisitor(url): |
| """Add a stat visitor specified using a URL string |
| |
| Stat visitors are specified using URLs on the following format: |
| format://path[?param=value[;param=value]] |
| |
| The available formats are listed in the factories list. Factories |
| are called with the path as the first positional parameter and the |
| parameters are keyword arguments. Parameter values must be valid |
| Python literals. |
| |
| """ |
| |
| try: |
| from urllib.parse import urlsplit |
| except ImportError: |
| # Python 2 fallback |
| from urlparse import urlsplit |
| |
| parsed = urlsplit(url) |
| |
| try: |
| factory = factories[parsed.scheme] |
| except KeyError: |
| fatal("Illegal stat file type '%s' specified." % parsed.scheme) |
| |
| if factory is None: |
| fatal("Stat type '%s' disabled at compile time" % parsed.scheme) |
| |
| outputList.append(factory(parsed)) |
| |
| def printStatVisitorTypes(): |
| """List available stat visitors and their documentation""" |
| |
| import inspect |
| |
| def print_doc(doc): |
| for line in doc.splitlines(): |
| print("| %s" % line) |
| print() |
| |
| enabled_visitors = [ x for x in all_factories if x[2] ] |
| for factory, schemes, _ in enabled_visitors: |
| print("%s:" % ", ".join(filter(lambda x: x is not None, schemes))) |
| |
| # Try to extract the factory doc string |
| print_doc(inspect.getdoc(factory)) |
| |
| def initSimStats(): |
| _m5.stats.initSimStats() |
| _m5.stats.registerPythonStatsHandlers() |
| |
| def _visit_groups(visitor, root=None): |
| if root is None: |
| root = Root.getInstance() |
| for group in root.getStatGroups().values(): |
| visitor(group) |
| _visit_groups(visitor, root=group) |
| |
| def _visit_stats(visitor, root=None): |
| def for_each_stat(g): |
| for stat in g.getStats(): |
| visitor(g, stat) |
| _visit_groups(for_each_stat, root=root) |
| |
| def _bindStatHierarchy(root): |
| def _bind_obj(name, obj): |
| if isNullPointer(obj): |
| return |
| if m5.SimObject.isSimObjectVector(obj): |
| if len(obj) == 1: |
| _bind_obj(name, obj[0]) |
| else: |
| for idx, obj in enumerate(obj): |
| _bind_obj("{}{}".format(name, idx), obj) |
| else: |
| # We need this check because not all obj.getCCObject() is an |
| # instance of Stat::Group. For example, sc_core::sc_module, the C++ |
| # class of SystemC_ScModule, is not a subclass of Stat::Group. So |
| # it will cause a type error if obj is a SystemC_ScModule when |
| # calling addStatGroup(). |
| if isinstance(obj.getCCObject(), _m5.stats.Group): |
| parent = root |
| while parent: |
| if hasattr(parent, 'addStatGroup'): |
| parent.addStatGroup(name, obj.getCCObject()) |
| break |
| parent = parent.get_parent(); |
| |
| _bindStatHierarchy(obj) |
| |
| for name, obj in root._children.items(): |
| _bind_obj(name, obj) |
| |
| names = [] |
| stats_dict = {} |
| stats_list = [] |
| def enable(): |
| '''Enable the statistics package. Before the statistics package is |
| enabled, all statistics must be created and initialized and once |
| the package is enabled, no more statistics can be created.''' |
| |
| def check_stat(group, stat): |
| if not stat.check() or not stat.baseCheck(): |
| fatal("statistic '%s' (%d) was not properly initialized " \ |
| "by a regStats() function\n", stat.name, stat.id) |
| |
| if not (stat.flags & flags.display): |
| stat.name = "__Stat%06d" % stat.id |
| |
| |
| # Legacy stat |
| global stats_list |
| stats_list = list(_m5.stats.statsList()) |
| |
| for stat in stats_list: |
| check_stat(None, stat) |
| |
| stats_list.sort(key=lambda s: s.name.split('.')) |
| for stat in stats_list: |
| stats_dict[stat.name] = stat |
| stat.enable() |
| |
| |
| # New stats |
| _visit_stats(check_stat) |
| _visit_stats(lambda g, s: s.enable()) |
| |
| _m5.stats.enable(); |
| |
| def prepare(): |
| '''Prepare all stats for data access. This must be done before |
| dumping and serialization.''' |
| |
| # Legacy stats |
| for stat in stats_list: |
| stat.prepare() |
| |
| # New stats |
| _visit_stats(lambda g, s: s.prepare()) |
| |
| def _dump_to_visitor(visitor, roots=None): |
| # New stats |
| def dump_group(group): |
| for stat in group.getStats(): |
| stat.visit(visitor) |
| for n, g in group.getStatGroups().items(): |
| visitor.beginGroup(n) |
| dump_group(g) |
| visitor.endGroup() |
| |
| if roots: |
| # New stats from selected subroots. |
| for root in roots: |
| for p in root.path_list(): |
| visitor.beginGroup(p) |
| dump_group(root) |
| for p in reversed(root.path_list()): |
| visitor.endGroup() |
| else: |
| # New stats starting from root. |
| dump_group(Root.getInstance()) |
| |
| # Legacy stats |
| for stat in stats_list: |
| stat.visit(visitor) |
| |
| lastDump = 0 |
| # List[SimObject]. |
| global_dump_roots = [] |
| |
| def dump(roots=None): |
| '''Dump all statistics data to the registered outputs''' |
| |
| all_roots = [] |
| if roots is not None: |
| all_roots.extend(roots) |
| global global_dump_roots |
| all_roots.extend(global_dump_roots) |
| |
| now = m5.curTick() |
| global lastDump |
| assert lastDump <= now |
| new_dump = lastDump != now |
| lastDump = now |
| |
| # Don't allow multiple global stat dumps in the same tick. It's |
| # still possible to dump a multiple sub-trees. |
| if not new_dump and not all_roots: |
| return |
| |
| # Only prepare stats the first time we dump them in the same tick. |
| if new_dump: |
| _m5.stats.processDumpQueue() |
| # Notify new-style stats group that we are about to dump stats. |
| sim_root = Root.getInstance() |
| if sim_root: |
| sim_root.preDumpStats(); |
| prepare() |
| |
| for output in outputList: |
| if isinstance(output, JsonOutputVistor): |
| if not all_roots: |
| output.dump(Root.getInstance()) |
| else: |
| output.dump(all_roots) |
| else: |
| if output.valid(): |
| output.begin() |
| _dump_to_visitor(output, roots=all_roots) |
| output.end() |
| |
| def reset(): |
| '''Reset all statistics to the base state''' |
| |
| # call reset stats on all SimObjects |
| root = Root.getInstance() |
| if root: |
| root.resetStats() |
| |
| # call any other registered legacy stats reset callbacks |
| for stat in stats_list: |
| stat.reset() |
| |
| _m5.stats.processResetQueue() |
| |
| flags = attrdict({ |
| 'none' : 0x0000, |
| 'init' : 0x0001, |
| 'display' : 0x0002, |
| 'total' : 0x0010, |
| 'pdf' : 0x0020, |
| 'cdf' : 0x0040, |
| 'dist' : 0x0080, |
| 'nozero' : 0x0100, |
| 'nonan' : 0x0200, |
| }) |