Now that global stats have been converted to new-style stats, it's desirable to output them before legacy stats. This ensures that global statistics (e.g., host_seconds) show up first in the stat file. Change-Id: Ib099d0152a6612ebbadd234c27f2f3448aef1260 Signed-off-by: Andreas Sandberg <andreas.sandberg@arm.com> Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/35617 Reviewed-by: Jason Lowe-Power <power.jg@gmail.com> Maintainer: Jason Lowe-Power <power.jg@gmail.com> Tested-by: kokoro <noreply+kokoro@google.com>
419 lines
14 KiB
Python
419 lines
14 KiB
Python
# 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.
|
|
|
|
from __future__ import print_function
|
|
from __future__ import absolute_import
|
|
|
|
import m5
|
|
|
|
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 = []
|
|
|
|
# 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():
|
|
_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 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,
|
|
})
|