1# Copyright (c) 2017-2019 ARM Limited
2# All rights reserved.
3#
4# The license below extends only to copyright in the software and shall
5# not be construed as granting a license to any other intellectual
6# property including but not limited to intellectual property relating
7# to a hardware implementation of the functionality of the software
8# licensed hereunder.  You may use the software subject to the license
9# terms below provided that you ensure that this notice is replicated
10# unmodified and in its entirety in all distributions of the software,
11# modified or unmodified, in source code or in binary form.
12#
13# Copyright (c) 2007 The Regents of The University of Michigan
14# Copyright (c) 2010 The Hewlett-Packard Development Company
15# All rights reserved.
16#
17# Redistribution and use in source and binary forms, with or without
18# modification, are permitted provided that the following conditions are
19# met: redistributions of source code must retain the above copyright
20# notice, this list of conditions and the following disclaimer;
21# redistributions in binary form must reproduce the above copyright
22# notice, this list of conditions and the following disclaimer in the
23# documentation and/or other materials provided with the distribution;
24# neither the name of the copyright holders nor the names of its
25# contributors may be used to endorse or promote products derived from
26# this software without specific prior written permission.
27#
28# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39#
40# Authors: Nathan Binkert
41#          Andreas Sandberg
42
43from __future__ import print_function
44from __future__ import absolute_import
45
46import m5
47
48import _m5.stats
49from m5.objects import Root
50from m5.params import isNullPointer
51from m5.util import attrdict, fatal
52
53# Stat exports
54from _m5.stats import schedStatEvent as schedEvent
55from _m5.stats import periodicStatDump
56
57outputList = []
58
59# Dictionary of stat visitor factories populated by the _url_factory
60# visitor.
61factories = { }
62
63# List of all factories. Contains tuples of (factory, schemes,
64# enabled).
65all_factories = []
66
67def _url_factory(schemes, enable=True):
68    """Wrap a plain Python function with URL parsing helpers
69
70    Wrap a plain Python function f(fn, **kwargs) to expect a URL that
71    has been split using urlparse.urlsplit. First positional argument
72    is assumed to be a filename, this is created as the concatenation
73    of the netloc (~hostname) and path in the parsed URL. Keyword
74    arguments are derived from the query values in the URL.
75
76    Arguments:
77        schemes: A list of URL schemes to use for this function.
78
79    Keyword arguments:
80        enable: Enable/disable this factory. Typically used when the
81                presence of a function depends on some runtime property.
82
83    For example:
84        wrapped_f(urlparse.urlsplit("text://stats.txt?desc=False")) ->
85        f("stats.txt", desc=False)
86
87    """
88
89    from functools import wraps
90
91    def decorator(func):
92        @wraps(func)
93        def wrapper(url):
94            try:
95                from urllib.parse import parse_qs
96            except ImportError:
97                # Python 2 fallback
98                from urlparse import parse_qs
99            from ast import literal_eval
100
101            qs = parse_qs(url.query, keep_blank_values=True)
102
103            # parse_qs returns a list of values for each parameter. Only
104            # use the last value since kwargs don't allow multiple values
105            # per parameter. Use literal_eval to transform string param
106            # values into proper Python types.
107            def parse_value(key, values):
108                if len(values) == 0 or (len(values) == 1 and not values[0]):
109                    fatal("%s: '%s' doesn't have a value." % (
110                        url.geturl(), key))
111                elif len(values) > 1:
112                    fatal("%s: '%s' has multiple values." % (
113                        url.geturl(), key))
114                else:
115                    try:
116                        return key, literal_eval(values[0])
117                    except ValueError:
118                        fatal("%s: %s isn't a valid Python literal" \
119                              % (url.geturl(), values[0]))
120
121            kwargs = dict([ parse_value(k, v) for k, v in qs.items() ])
122
123            try:
124                return func("%s%s" % (url.netloc, url.path), **kwargs)
125            except TypeError:
126                fatal("Illegal stat visitor parameter specified")
127
128        all_factories.append((wrapper, schemes, enable))
129        for scheme in schemes:
130            assert scheme not in factories
131            factories[scheme] = wrapper if enable else None
132        return wrapper
133
134    return decorator
135
136@_url_factory([ None, "", "text", "file", ])
137def _textFactory(fn, desc=True):
138    """Output stats in text format.
139
140    Text stat files contain one stat per line with an optional
141    description. The description is enabled by default, but can be
142    disabled by setting the desc parameter to False.
143
144    Parameters:
145      * desc (bool): Output stat descriptions (default: True)
146
147    Example:
148      text://stats.txt?desc=False
149
150    """
151
152    return _m5.stats.initText(fn, desc)
153
154@_url_factory([ "h5", ], enable=hasattr(_m5.stats, "initHDF5"))
155def _hdf5Factory(fn, chunking=10, desc=True, formulas=True):
156    """Output stats in HDF5 format.
157
158    The HDF5 file format is a structured binary file format. It has
159    the multiple benefits over traditional text stat files:
160
161      * Efficient storage of time series (multiple stat dumps)
162      * Fast lookup of stats
163      * Plenty of existing tooling (e.g., Python libraries and graphical
164        viewers)
165      * File format can be used to store frame buffers together with
166        normal stats.
167
168    There are some drawbacks compared to the default text format:
169      * Large startup cost (single stat dump larger than text equivalent)
170      * Stat dumps are slower than text
171
172
173    Known limitations:
174      * Distributions and histograms currently unsupported.
175      * No support for forking.
176
177
178    Parameters:
179      * chunking (unsigned): Number of time steps to pre-allocate (default: 10)
180      * desc (bool): Output stat descriptions (default: True)
181      * formulas (bool): Output derived stats (default: True)
182
183    Example:
184      h5://stats.h5?desc=False;chunking=100;formulas=False
185
186    """
187
188    return _m5.stats.initHDF5(fn, chunking, desc, formulas)
189
190def addStatVisitor(url):
191    """Add a stat visitor specified using a URL string
192
193    Stat visitors are specified using URLs on the following format:
194    format://path[?param=value[;param=value]]
195
196    The available formats are listed in the factories list. Factories
197    are called with the path as the first positional parameter and the
198    parameters are keyword arguments. Parameter values must be valid
199    Python literals.
200
201    """
202
203    try:
204        from urllib.parse import urlsplit
205    except ImportError:
206        # Python 2 fallback
207        from urlparse import urlsplit
208
209    parsed = urlsplit(url)
210
211    try:
212        factory = factories[parsed.scheme]
213    except KeyError:
214        fatal("Illegal stat file type '%s' specified." % parsed.scheme)
215
216    if factory is None:
217        fatal("Stat type '%s' disabled at compile time" % parsed.scheme)
218
219    outputList.append(factory(parsed))
220
221def printStatVisitorTypes():
222    """List available stat visitors and their documentation"""
223
224    import inspect
225
226    def print_doc(doc):
227        for line in doc.splitlines():
228            print("| %s" % line)
229        print()
230
231    enabled_visitors = [ x for x in all_factories if x[2] ]
232    for factory, schemes, _ in enabled_visitors:
233        print("%s:" % ", ".join(filter(lambda x: x is not None, schemes)))
234
235        # Try to extract the factory doc string
236        print_doc(inspect.getdoc(factory))
237
238def initSimStats():
239    _m5.stats.initSimStats()
240    _m5.stats.registerPythonStatsHandlers()
241
242def _visit_groups(visitor, root=None):
243    if root is None:
244        root = Root.getInstance()
245    for group in root.getStatGroups().values():
246        visitor(group)
247        _visit_groups(visitor, root=group)
248
249def _visit_stats(visitor, root=None):
250    def for_each_stat(g):
251        for stat in g.getStats():
252            visitor(g, stat)
253    _visit_groups(for_each_stat, root=root)
254
255def _bindStatHierarchy(root):
256    def _bind_obj(name, obj):
257        if isNullPointer(obj):
258            return
259        if m5.SimObject.isSimObjectVector(obj):
260            for idx, obj in enumerate(obj):
261                _bind_obj("{}{}".format(name, idx), obj)
262        else:
263            # We need this check because not all obj.getCCObject() is an
264            # instance of Stat::Group. For example, sc_core::sc_module, the C++
265            # class of SystemC_ScModule, is not a subclass of Stat::Group. So
266            # it will cause a type error if obj is a SystemC_ScModule when
267            # calling addStatGroup().
268            if isinstance(obj.getCCObject(), _m5.stats.Group):
269                parent = root
270                while parent:
271                    if hasattr(parent, 'addStatGroup'):
272                        parent.addStatGroup(name, obj.getCCObject())
273                        break
274                    parent = parent.get_parent();
275
276            _bindStatHierarchy(obj)
277
278    for name, obj in root._children.items():
279        _bind_obj(name, obj)
280
281names = []
282stats_dict = {}
283stats_list = []
284def enable():
285    '''Enable the statistics package.  Before the statistics package is
286    enabled, all statistics must be created and initialized and once
287    the package is enabled, no more statistics can be created.'''
288
289    def check_stat(group, stat):
290        if not stat.check() or not stat.baseCheck():
291            fatal("statistic '%s' (%d) was not properly initialized " \
292                  "by a regStats() function\n", stat.name, stat.id)
293
294        if not (stat.flags & flags.display):
295            stat.name = "__Stat%06d" % stat.id
296
297
298    # Legacy stat
299    global stats_list
300    stats_list = list(_m5.stats.statsList())
301
302    for stat in stats_list:
303        check_stat(None, stat)
304
305    stats_list.sort(key=lambda s: s.name.split('.'))
306    for stat in stats_list:
307        stats_dict[stat.name] = stat
308        stat.enable()
309
310
311    # New stats
312    _visit_stats(check_stat)
313    _visit_stats(lambda g, s: s.enable())
314
315    _m5.stats.enable();
316
317def prepare():
318    '''Prepare all stats for data access.  This must be done before
319    dumping and serialization.'''
320
321    # Legacy stats
322    for stat in stats_list:
323        stat.prepare()
324
325    # New stats
326    _visit_stats(lambda g, s: s.prepare())
327
328def _dump_to_visitor(visitor, root=None):
329    # Legacy stats
330    if root is None:
331        for stat in stats_list:
332            stat.visit(visitor)
333
334    # New stats
335    def dump_group(group):
336        for stat in group.getStats():
337            stat.visit(visitor)
338
339        for n, g in group.getStatGroups().items():
340            visitor.beginGroup(n)
341            dump_group(g)
342            visitor.endGroup()
343
344    if root is not None:
345        for p in root.path_list():
346            visitor.beginGroup(p)
347    dump_group(root if root is not None else Root.getInstance())
348    if root is not None:
349        for p in reversed(root.path_list()):
350            visitor.endGroup()
351
352lastDump = 0
353
354def dump(root=None):
355    '''Dump all statistics data to the registered outputs'''
356
357    now = m5.curTick()
358    global lastDump
359    assert lastDump <= now
360    new_dump = lastDump != now
361    lastDump = now
362
363    # Don't allow multiple global stat dumps in the same tick. It's
364    # still possible to dump a multiple sub-trees.
365    if not new_dump and root is None:
366        return
367
368    # Only prepare stats the first time we dump them in the same tick.
369    if new_dump:
370        _m5.stats.processDumpQueue()
371        prepare()
372
373    for output in outputList:
374        if output.valid():
375            output.begin()
376            _dump_to_visitor(output, root=root)
377            output.end()
378
379def reset():
380    '''Reset all statistics to the base state'''
381
382    # call reset stats on all SimObjects
383    root = Root.getInstance()
384    if root:
385        root.resetStats()
386
387    # call any other registered legacy stats reset callbacks
388    for stat in stats_list:
389        stat.reset()
390
391    _m5.stats.processResetQueue()
392
393flags = attrdict({
394    'none'    : 0x0000,
395    'init'    : 0x0001,
396    'display' : 0x0002,
397    'total'   : 0x0010,
398    'pdf'     : 0x0020,
399    'cdf'     : 0x0040,
400    'dist'    : 0x0080,
401    'nozero'  : 0x0100,
402    'nonan'   : 0x0200,
403})
404