stats: Add support for listing available formats
[gem5.git] / src / python / m5 / stats / __init__.py
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
43 from __future__ import print_function
44 from __future__ import absolute_import
45
46 import m5
47
48 import _m5.stats
49 from m5.objects import Root
50 from m5.util import attrdict, fatal
51
52 # Stat exports
53 from _m5.stats import schedStatEvent as schedEvent
54 from _m5.stats import periodicStatDump
55
56 outputList = []
57
58 # Dictionary of stat visitor factories populated by the _url_factory
59 # visitor.
60 factories = { }
61
62 # List of all factories. Contains tuples of (factory, schemes,
63 # enabled).
64 all_factories = []
65
66 def _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", ])
136 def _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"))
154 def _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
189 def 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
220 def 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
237 def initSimStats():
238 _m5.stats.initSimStats()
239 _m5.stats.registerPythonStatsHandlers()
240
241 def _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
248 def _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
254 def _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 root.addStatGroup(name, obj.getCCObject())
261 _bindStatHierarchy(obj)
262
263 for name, obj in root._children.items():
264 _bind_obj(name, obj)
265
266 names = []
267 stats_dict = {}
268 stats_list = []
269 def enable():
270 '''Enable the statistics package. Before the statistics package is
271 enabled, all statistics must be created and initialized and once
272 the package is enabled, no more statistics can be created.'''
273
274 def check_stat(group, stat):
275 if not stat.check() or not stat.baseCheck():
276 fatal("statistic '%s' (%d) was not properly initialized " \
277 "by a regStats() function\n", stat.name, stat.id)
278
279 if not (stat.flags & flags.display):
280 stat.name = "__Stat%06d" % stat.id
281
282
283 # Legacy stat
284 global stats_list
285 stats_list = list(_m5.stats.statsList())
286
287 for stat in stats_list:
288 check_stat(None, stat)
289
290 stats_list.sort(key=lambda s: s.name.split('.'))
291 for stat in stats_list:
292 stats_dict[stat.name] = stat
293 stat.enable()
294
295
296 # New stats
297 _visit_stats(check_stat)
298 _visit_stats(lambda g, s: s.enable())
299
300 _m5.stats.enable();
301
302 def prepare():
303 '''Prepare all stats for data access. This must be done before
304 dumping and serialization.'''
305
306 # Legacy stats
307 for stat in stats_list:
308 stat.prepare()
309
310 # New stats
311 _visit_stats(lambda g, s: s.prepare())
312
313 def _dump_to_visitor(visitor, root=None):
314 # Legacy stats
315 if root is None:
316 for stat in stats_list:
317 stat.visit(visitor)
318
319 # New stats
320 def dump_group(group):
321 for stat in group.getStats():
322 stat.visit(visitor)
323
324 for n, g in group.getStatGroups().items():
325 visitor.beginGroup(n)
326 dump_group(g)
327 visitor.endGroup()
328
329 if root is not None:
330 for p in root.path_list():
331 visitor.beginGroup(p)
332 dump_group(root if root is not None else Root.getInstance())
333 if root is not None:
334 for p in reversed(root.path_list()):
335 visitor.endGroup()
336
337 lastDump = 0
338
339 def dump(root=None):
340 '''Dump all statistics data to the registered outputs'''
341
342 now = m5.curTick()
343 global lastDump
344 assert lastDump <= now
345 new_dump = lastDump != now
346 lastDump = now
347
348 # Don't allow multiple global stat dumps in the same tick. It's
349 # still possible to dump a multiple sub-trees.
350 if not new_dump and root is None:
351 return
352
353 # Only prepare stats the first time we dump them in the same tick.
354 if new_dump:
355 _m5.stats.processDumpQueue()
356 prepare()
357
358 for output in outputList:
359 if output.valid():
360 output.begin()
361 _dump_to_visitor(output, root=root)
362 output.end()
363
364 def reset():
365 '''Reset all statistics to the base state'''
366
367 # call reset stats on all SimObjects
368 root = Root.getInstance()
369 if root:
370 root.resetStats()
371
372 # call any other registered legacy stats reset callbacks
373 for stat in stats_list:
374 stat.reset()
375
376 _m5.stats.processResetQueue()
377
378 flags = attrdict({
379 'none' : 0x0000,
380 'init' : 0x0001,
381 'display' : 0x0002,
382 'total' : 0x0010,
383 'pdf' : 0x0020,
384 'cdf' : 0x0040,
385 'dist' : 0x0080,
386 'nozero' : 0x0100,
387 'nonan' : 0x0200,
388 })