+# 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.
# 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.
-#
-# Authors: Nathan Binkert
+
+from __future__ import print_function
+from __future__ import absolute_import
import m5
-from m5 import internal
-from m5.internal.stats import schedStatEvent as schedEvent
+import _m5.stats
from m5.objects import Root
+from m5.params import isNullPointer
from m5.util import attrdict, fatal
+# Stat exports
+from _m5.stats import schedStatEvent as schedEvent
+from _m5.stats import periodicStatDump
+
outputList = []
-def initText(filename, desc=True):
- output = internal.stats.initText(filename, desc)
- outputList.append(output)
+
+# 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)
+
+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():
- internal.stats.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 = []
-raw_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.'''
- __dynamic_cast = []
- for k, v in internal.stats.__dict__.iteritems():
- if k.startswith('dynamic_'):
- __dynamic_cast.append(v)
-
- for stat in internal.stats.statsList():
- for cast in __dynamic_cast:
- val = cast(stat)
- if val is not None:
- stats_list.append(val)
- raw_stats_list.append(val)
- break
- else:
- fatal("unknown stat type %s", stat)
- for stat in stats_list:
+ 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
- def less(stat1, stat2):
- v1 = stat1.name.split('.')
- v2 = stat2.name.split('.')
- return v1 < v2
- stats_list.sort(less)
+ # 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()
- internal.stats.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:
+ # Legacy stats
+ for stat in stats_list:
+ stat.visit(visitor)
+
+ # New stats starting from root.
+ dump_group(Root.getInstance())
+
lastDump = 0
-def dump():
+# List[SimObject].
+global_dump_roots = []
+
+def dump(roots=None):
'''Dump all statistics data to the registered outputs'''
- curTick = m5.curTick()
+ 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 <= curTick
- if lastDump == curTick:
- return
- lastDump = curTick
+ assert lastDump <= now
+ new_dump = lastDump != now
+ lastDump = now
- internal.stats.processDumpQueue()
+ # 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
- prepare()
+ # 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 output.valid():
output.begin()
- for stat in stats_list:
- output.visit(stat)
+ _dump_to_visitor(output, roots=all_roots)
output.end()
def reset():
# call reset stats on all SimObjects
root = Root.getInstance()
if root:
- for obj in root.descendants(): obj.resetStats()
+ root.resetStats()
- # call any other registered stats reset callbacks
+ # call any other registered legacy stats reset callbacks
for stat in stats_list:
stat.reset()
- internal.stats.processResetQueue()
+ _m5.stats.processResetQueue()
flags = attrdict({
'none' : 0x0000,