__init__.py revision 14265:464d514d0f6d
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.util import attrdict, fatal
51
52# Stat exports
53from _m5.stats import schedStatEvent as schedEvent
54from _m5.stats import periodicStatDump
55
56outputList = []
57
58# Dictionary of stat visitor factories populated by the _url_factory
59# visitor.
60factories = { }
61
62# List of all factories. Contains tuples of (factory, schemes,
63# enabled).
64all_factories = []
65
66def _url_factory(schemes, enable=True):
67    """Wrap a plain Python function with URL parsing helpers
68
69    Wrap a plain Python function f(fn, **kwargs) to expect a URL that
70    has been split using urlparse.urlsplit. First positional argument
71    is assumed to be a filename, this is created as the concatenation
72    of the netloc (~hostname) and path in the parsed URL. Keyword
73    arguments are derived from the query values in the URL.
74
75    Arguments:
76        schemes: A list of URL schemes to use for this function.
77
78    Keyword arguments:
79        enable: Enable/disable this factory. Typically used when the
80                presence of a function depends on some runtime property.
81
82    For example:
83        wrapped_f(urlparse.urlsplit("text://stats.txt?desc=False")) ->
84        f("stats.txt", desc=False)
85
86    """
87
88    from functools import wraps
89
90    def decorator(func):
91        @wraps(func)
92        def wrapper(url):
93            try:
94                from urllib.parse import parse_qs
95            except ImportError:
96                # Python 2 fallback
97                from urlparse import parse_qs
98            from ast import literal_eval
99
100            qs = parse_qs(url.query, keep_blank_values=True)
101
102            # parse_qs returns a list of values for each parameter. Only
103            # use the last value since kwargs don't allow multiple values
104            # per parameter. Use literal_eval to transform string param
105            # values into proper Python types.
106            def parse_value(key, values):
107                if len(values) == 0 or (len(values) == 1 and not values[0]):
108                    fatal("%s: '%s' doesn't have a value." % (
109                        url.geturl(), key))
110                elif len(values) > 1:
111                    fatal("%s: '%s' has multiple values." % (
112                        url.geturl(), key))
113                else:
114                    try:
115                        return key, literal_eval(values[0])
116                    except ValueError:
117                        fatal("%s: %s isn't a valid Python literal" \
118                              % (url.geturl(), values[0]))
119
120            kwargs = dict([ parse_value(k, v) for k, v in qs.items() ])
121
122            try:
123                return func("%s%s" % (url.netloc, url.path), **kwargs)
124            except TypeError:
125                fatal("Illegal stat visitor parameter specified")
126
127        all_factories.append((wrapper, schemes, enable))
128        for scheme in schemes:
129            assert scheme not in factories
130            factories[scheme] = wrapper if enable else None
131        return wrapper
132
133    return decorator
134
135@_url_factory([ None, "", "text", "file", ])
136def _textFactory(fn, desc=True):
137    """Output stats in text format.
138
139    Text stat files contain one stat per line with an optional
140    description. The description is enabled by default, but can be
141    disabled by setting the desc parameter to False.
142
143    Parameters:
144      * desc (bool): Output stat descriptions (default: True)
145
146    Example:
147      text://stats.txt?desc=False
148
149    """
150
151    return _m5.stats.initText(fn, desc)
152
153@_url_factory([ "h5", ], enable=hasattr(_m5.stats, "initHDF5"))
154def _hdf5Factory(fn, chunking=10, desc=True, formulas=True):
155    """Output stats in HDF5 format.
156
157    The HDF5 file format is a structured binary file format. It has
158    the multiple benefits over traditional text stat files:
159
160      * Efficient storage of time series (multiple stat dumps)
161      * Fast lookup of stats
162      * Plenty of existing tooling (e.g., Python libraries and graphical
163        viewers)
164      * File format can be used to store frame buffers together with
165        normal stats.
166
167    There are some drawbacks compared to the default text format:
168      * Large startup cost (single stat dump larger than text equivalent)
169      * Stat dumps are slower than text
170
171
172    Known limitations:
173      * Distributions and histograms currently unsupported.
174      * No support for forking.
175
176
177    Parameters:
178      * chunking (unsigned): Number of time steps to pre-allocate (default: 10)
179      * desc (bool): Output stat descriptions (default: True)
180      * formulas (bool): Output derived stats (default: True)
181
182    Example:
183      h5://stats.h5?desc=False;chunking=100;formulas=False
184
185    """
186
187    return _m5.stats.initHDF5(fn, chunking, desc, formulas)
188
189def addStatVisitor(url):
190    """Add a stat visitor specified using a URL string
191
192    Stat visitors are specified using URLs on the following format:
193    format://path[?param=value[;param=value]]
194
195    The available formats are listed in the factories list. Factories
196    are called with the path as the first positional parameter and the
197    parameters are keyword arguments. Parameter values must be valid
198    Python literals.
199
200    """
201
202    try:
203        from urllib.parse import urlsplit
204    except ImportError:
205        # Python 2 fallback
206        from urlparse import urlsplit
207
208    parsed = urlsplit(url)
209
210    try:
211        factory = factories[parsed.scheme]
212    except KeyError:
213        fatal("Illegal stat file type '%s' specified." % parsed.scheme)
214
215    if factory is None:
216        fatal("Stat type '%s' disabled at compile time" % parsed.scheme)
217
218    outputList.append(factory(parsed))
219
220def printStatVisitorTypes():
221    """List available stat visitors and their documentation"""
222
223    import inspect
224
225    def print_doc(doc):
226        for line in doc.splitlines():
227            print("| %s" % line)
228        print()
229
230    enabled_visitors = [ x for x in all_factories if x[2] ]
231    for factory, schemes, _ in enabled_visitors:
232        print("%s:" % ", ".join(filter(lambda x: x is not None, schemes)))
233
234        # Try to extract the factory doc string
235        print_doc(inspect.getdoc(factory))
236
237def initSimStats():
238    _m5.stats.initSimStats()
239    _m5.stats.registerPythonStatsHandlers()
240
241def _visit_groups(visitor, root=None):
242    if root is None:
243        root = Root.getInstance()
244    for group in root.getStatGroups().values():
245        visitor(group)
246        _visit_groups(visitor, root=group)
247
248def _visit_stats(visitor, root=None):
249    def for_each_stat(g):
250        for stat in g.getStats():
251            visitor(g, stat)
252    _visit_groups(for_each_stat, root=root)
253
254def _bindStatHierarchy(root):
255    def _bind_obj(name, obj):
256        if m5.SimObject.isSimObjectVector(obj):
257            for idx, obj in enumerate(obj):
258                _bind_obj("{}{}".format(name, idx), obj)
259        else:
260            # We need this check because not all obj.getCCObject() is an
261            # instance of Stat::Group. For example, sc_core::sc_module, the C++
262            # class of SystemC_ScModule, is not a subclass of Stat::Group. So
263            # it will cause a type error if obj is a SystemC_ScModule when
264            # calling addStatGroup().
265            if isinstance(obj.getCCObject(), _m5.stats.Group):
266                parent = root
267                while parent:
268                    if hasattr(parent, 'addStatGroup'):
269                        parent.addStatGroup(name, obj.getCCObject())
270                        break
271                    parent = parent.get_parent();
272
273            _bindStatHierarchy(obj)
274
275    for name, obj in root._children.items():
276        _bind_obj(name, obj)
277
278names = []
279stats_dict = {}
280stats_list = []
281def enable():
282    '''Enable the statistics package.  Before the statistics package is
283    enabled, all statistics must be created and initialized and once
284    the package is enabled, no more statistics can be created.'''
285
286    def check_stat(group, stat):
287        if not stat.check() or not stat.baseCheck():
288            fatal("statistic '%s' (%d) was not properly initialized " \
289                  "by a regStats() function\n", stat.name, stat.id)
290
291        if not (stat.flags & flags.display):
292            stat.name = "__Stat%06d" % stat.id
293
294
295    # Legacy stat
296    global stats_list
297    stats_list = list(_m5.stats.statsList())
298
299    for stat in stats_list:
300        check_stat(None, stat)
301
302    stats_list.sort(key=lambda s: s.name.split('.'))
303    for stat in stats_list:
304        stats_dict[stat.name] = stat
305        stat.enable()
306
307
308    # New stats
309    _visit_stats(check_stat)
310    _visit_stats(lambda g, s: s.enable())
311
312    _m5.stats.enable();
313
314def prepare():
315    '''Prepare all stats for data access.  This must be done before
316    dumping and serialization.'''
317
318    # Legacy stats
319    for stat in stats_list:
320        stat.prepare()
321
322    # New stats
323    _visit_stats(lambda g, s: s.prepare())
324
325def _dump_to_visitor(visitor, root=None):
326    # Legacy stats
327    if root is None:
328        for stat in stats_list:
329            stat.visit(visitor)
330
331    # New stats
332    def dump_group(group):
333        for stat in group.getStats():
334            stat.visit(visitor)
335
336        for n, g in group.getStatGroups().items():
337            visitor.beginGroup(n)
338            dump_group(g)
339            visitor.endGroup()
340
341    if root is not None:
342        for p in root.path_list():
343            visitor.beginGroup(p)
344    dump_group(root if root is not None else Root.getInstance())
345    if root is not None:
346        for p in reversed(root.path_list()):
347            visitor.endGroup()
348
349lastDump = 0
350
351def dump(root=None):
352    '''Dump all statistics data to the registered outputs'''
353
354    now = m5.curTick()
355    global lastDump
356    assert lastDump <= now
357    new_dump = lastDump != now
358    lastDump = now
359
360    # Don't allow multiple global stat dumps in the same tick. It's
361    # still possible to dump a multiple sub-trees.
362    if not new_dump and root is None:
363        return
364
365    # Only prepare stats the first time we dump them in the same tick.
366    if new_dump:
367        _m5.stats.processDumpQueue()
368        prepare()
369
370    for output in outputList:
371        if output.valid():
372            output.begin()
373            _dump_to_visitor(output, root=root)
374            output.end()
375
376def reset():
377    '''Reset all statistics to the base state'''
378
379    # call reset stats on all SimObjects
380    root = Root.getInstance()
381    if root:
382        root.resetStats()
383
384    # call any other registered legacy stats reset callbacks
385    for stat in stats_list:
386        stat.reset()
387
388    _m5.stats.processResetQueue()
389
390flags = attrdict({
391    'none'    : 0x0000,
392    'init'    : 0x0001,
393    'display' : 0x0002,
394    'total'   : 0x0010,
395    'pdf'     : 0x0020,
396    'cdf'     : 0x0040,
397    'dist'    : 0x0080,
398    'nozero'  : 0x0100,
399    'nonan'   : 0x0200,
400})
401