util: Make dot_writer ignore NULL simobjects.
[gem5.git] / src / python / m5 / stats / __init__.py
index 612dbdfb5e2ceeeb3d0de8aa9ab916dbe047b40b..ba91f22c7d813ee946786361668b0fc076bb9e9f 100644 (file)
@@ -1,3 +1,15 @@
+# Copyright (c) 2017 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.
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
 # Authors: Nathan Binkert
+#          Andreas Sandberg
 
-from m5 import internal
-from m5.internal.stats import schedStatEvent as schedEvent
+import m5
+
+import _m5.stats
 from m5.objects import Root
-from m5.util import attrdict
+from m5.util import attrdict, fatal
+
+# Stat exports
+from _m5.stats import schedStatEvent as schedEvent
+from _m5.stats import periodicStatDump
+
+outputList = []
+
+def _url_factory(func):
+    """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.
+
+    For example:
+        wrapped_f(urlparse.urlsplit("text://stats.txt?desc=False")) ->
+        f("stats.txt", desc=False)
+
+    """
+
+    from functools import wraps
+
+    @wraps(func)
+    def wrapper(url):
+        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")
+
+    return wrapper
+
+@_url_factory
+def _textFactory(fn, desc=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.
+
+    Example: text://stats.txt?desc=False
+
+    """
 
-def initText(filename, desc=True):
-    internal.stats.initText(filename, desc)
+    return _m5.stats.initText(fn, desc)
 
-def initMySQL(host, database, user='', passwd='', project='test', name='test',
-              sample='0'):
-    if not user:
-        import getpass
-        user = getpass.getuser()
+factories = {
+    # Default to the text factory if we're given a naked path
+    "" : _textFactory,
+    "file" : _textFactory,
+    "text" : _textFactory,
+}
 
-    internal.stats.initMySQL(host, database, user, passwd, project, name,
-                             sample)
+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.
+
+    """
+
+    from urlparse import urlsplit
+
+    parsed = urlsplit(url)
+
+    try:
+        factory = factories[parsed.scheme]
+    except KeyError:
+        fatal("Illegal stat file type specified.")
+
+    outputList.append(factory(parsed))
 
 def initSimStats():
-    internal.stats.initSimStats()
+    _m5.stats.initSimStats()
+    _m5.stats.registerPythonStatsHandlers()
 
 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)
+
+    global stats_list
+    stats_list = list(_m5.stats.statsList())
+
+    for stat in stats_list:
+        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('.')
@@ -78,24 +177,53 @@ def enable():
     stats_list.sort(less)
     for stat in stats_list:
         stats_dict[stat.name] = stat
+        stat.enable()
 
-    internal.stats.enable()
+    _m5.stats.enable();
 
+def prepare():
+    '''Prepare all stats for data access.  This must be done before
+    dumping and serialization.'''
+
+    for stat in stats_list:
+        stat.prepare()
+
+lastDump = 0
 def dump():
-    # Currently prepare happens in the dump, but we should maybe move
-    # that out.
+    '''Dump all statistics data to the registered outputs'''
+
+    curTick = m5.curTick()
+
+    global lastDump
+    assert lastDump <= curTick
+    if lastDump == curTick:
+        return
+    lastDump = curTick
 
-    #internal.stats.prepare()
-    internal.stats.dump()
+    _m5.stats.processDumpQueue()
+
+    prepare()
+
+    for output in outputList:
+        if output.valid():
+            output.begin()
+            for stat in stats_list:
+                stat.visit(output)
+            output.end()
 
 def reset():
+    '''Reset all statistics to the base state'''
+
     # call reset stats on all SimObjects
     root = Root.getInstance()
     if root:
         for obj in root.descendants(): obj.resetStats()
 
     # call any other registered stats reset callbacks
-    internal.stats.reset()
+    for stat in stats_list:
+        stat.reset()
+
+    _m5.stats.processResetQueue()
 
 flags = attrdict({
     'none'    : 0x0000,