root/branches/Carsten_PtrWork2/Tools/scons-local/scons-time.py

Revision 568, 45.2 kB (checked in by vossg, 2 years ago)

added : scons local (0.96.95)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 #!/usr/bin/env python
2 #
3 # scons-time - run SCons timings and collect statistics
4 #
5 # A script for running a configuration through SCons with a standard
6 # set of invocations to collect timing and memory statistics and to
7 # capture the results in a consistent set of output files for display
8 # and analysis.
9 #
10
11 #
12 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 The SCons Foundation
13 #
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
21 #
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
24 #
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 #
33
34 __revision__ = "/home/scons/scons/branch.0/branch.96/baseline/src/script/scons-time.py 0.96.95.D001 2007/02/12 21:41:50 knight"
35
36 import getopt
37 import glob
38 import os
39 import re
40 import shutil
41 import string
42 import sys
43 import tempfile
44 import time
45
46 class Plotter:
47     def increment_size(self, largest):
48         """
49         Return the size of each horizontal increment line for a specified
50         maximum value.  This returns a value that will provide somewhere
51         between 5 and 9 horizontal lines on the graph, on some set of
52         boundaries that are multiples of 10/100/1000/etc.
53         """
54         i = largest / 5
55         if not i:
56             return largest
57         multiplier = 1
58         while i >= 10:
59             i = i / 10
60             multiplier = multiplier * 10
61         return i * multiplier
62
63     def max_graph_value(self, largest):
64         # Round up to next integer.
65         largest = int(largest) + 1
66         increment = self.increment_size(largest)
67         return ((largest + increment - 1) / increment) * increment
68
69 class Line:
70     def __init__(self, points, type, title, label, comment, fmt="%s %s"):
71         self.points = points
72         self.type = type
73         self.title = title
74         self.label = label
75         self.comment = comment
76         self.fmt = fmt
77
78     def print_label(self, inx, x, y):
79         if self.label:
80             print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
81
82     def plot_string(self):
83         if self.title:
84             title_string = 'title "%s"' % self.title
85         else:
86             title_string = 'notitle'
87         return "'-' %s with lines lt %s" % (title_string, self.type)
88
89     def print_points(self, fmt=None):
90         if fmt is None:
91             fmt = self.fmt
92         if self.comment:
93             print '# %s' % self.comment
94         for x, y in self.points:
95             print fmt % (x, y)
96         print 'e'
97
98     def get_x_values(self):
99         return [ p[0] for p in self.points ]
100
101     def get_y_values(self):
102         return [ p[1] for p in self.points ]
103
104 class Gnuplotter(Plotter):
105
106     def __init__(self, title, key_location):
107         self.lines = []
108         self.title = title
109         self.key_location = key_location
110
111     def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
112         if points:
113             line = Line(points, type, title, label, comment, fmt)
114             self.lines.append(line)
115
116     def plot_string(self, line):
117         return line.plot_string()
118
119     def vertical_bar(self, x, type, label, comment):
120         if self.get_min_x() <= x and x <= self.get_max_x():
121             points = [(x, 0), (x, self.get_max_x())]
122             self.line(points, type, label, comment)
123
124     def get_all_x_values(self):
125         result = []
126         for line in self.lines:
127             result.extend(line.get_x_values())
128         return result
129
130     def get_all_y_values(self):
131         result = []
132         for line in self.lines:
133             result.extend(line.get_y_values())
134         return result
135
136     def get_min_x(self):
137         try:
138             return self.min_x
139         except AttributeError:
140             self.min_x = min(self.get_all_x_values())
141             return self.min_x
142
143     def get_max_x(self):
144         try:
145             return self.max_x
146         except AttributeError:
147             self.max_x = max(self.get_all_x_values())
148             return self.max_x
149
150     def get_min_y(self):
151         try:
152             return self.min_y
153         except AttributeError:
154             self.min_y = min(self.get_all_y_values())
155             return self.min_y
156
157     def get_max_y(self):
158         try:
159             return self.max_y
160         except AttributeError:
161             self.max_y = max(self.get_all_y_values())
162             return self.max_y
163
164     def draw(self):
165
166         if not self.lines:
167             return
168
169         if self.title:
170             print 'set title "%s"' % self.title
171         print 'set key %s' % self.key_location
172
173         inx = 1
174         max_y = self.max_graph_value(self.get_max_y())/2
175         for line in self.lines:
176             line.print_label(inx, line.points[0][0]-1, max_y)
177             inx += 1
178
179         plot_strings = [ self.plot_string(l) for l in self.lines ]
180         print 'plot ' + ', \\\n     '.join(plot_strings)
181
182         for line in self.lines:
183             line.print_points()
184
185
186
187 def untar(fname):
188     import tarfile
189     tar = tarfile.open(name=fname, mode='r')
190     for tarinfo in tar:
191         tar.extract(tarinfo)
192     tar.close()
193
194 def unzip(fname):
195     import zipfile
196     zf = zipfile.ZipFile(fname, 'r')
197     for name in zf.namelist():
198         dir = os.path.dirname(name)
199         try:
200             os.makedirs(dir)
201         except:
202             pass
203         open(name, 'w').write(zf.read(name))
204
205 def read_tree(dir):
206     def read_files(arg, dirname, fnames):
207         for fn in fnames:
208             fn = os.path.join(dirname, fn)
209             if os.path.isfile(fn):
210                 open(fn, 'rb').read()
211     os.path.walk('.', read_files, None)
212
213 def redirect_to_file(command, log):
214     return '%s > %s 2>&1' % (command, log)
215
216 def tee_to_file(command, log):
217     return '%s 2>&1 | tee %s' % (command, log)
218
219
220    
221 class SConsTimer:
222     """
223     Usage: scons-time SUBCOMMAND [ARGUMENTS]
224     Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
225
226     Available subcommands:
227         func            Extract test-run data for a function
228         help            Provides help
229         mem             Extract --debug=memory data from test runs
230         obj             Extract --debug=count data from test runs
231         time            Extract --debug=time data from test runs
232         run             Runs a test configuration
233     """
234
235     name = 'scons-time'
236     name_spaces = ' '*len(name)
237
238     def makedict(**kw):
239         return kw
240
241     default_settings = makedict(
242         aegis               = 'aegis',
243         aegis_project       = None,
244         chdir               = None,
245         config_file         = None,
246         initial_commands    = [],
247         key_location        = 'bottom left',
248         orig_cwd            = os.getcwd(),
249         outdir              = None,
250         prefix              = '',
251         python              = '"%s"' % sys.executable,
252         redirect            = redirect_to_file,
253         scons               = None,
254         scons_flags         = '--debug=count --debug=memory --debug=time --debug=memoizer',
255         scons_lib_dir       = None,
256         scons_wrapper       = None,
257         startup_targets     = '--help',
258         subdir              = None,
259         subversion_url      = None,
260         svn                 = 'svn',
261         svn_co_flag         = '-q',
262         tar                 = 'tar',
263         targets             = '',
264         targets0            = None,
265         targets1            = None,
266         targets2            = None,
267         title               = None,
268         unzip               = 'unzip',
269         verbose             = False,
270         vertical_bars       = [],
271
272         unpack_map = {
273             '.tar.gz'       : (untar,   '%(tar)s xzf %%s'),
274             '.tgz'          : (untar,   '%(tar)s xzf %%s'),
275             '.tar'          : (untar,   '%(tar)s xf %%s'),
276             '.zip'          : (unzip,   '%(unzip)s %%s'),
277         },
278     )
279
280     run_titles = [
281         'Startup',
282         'Full build',
283         'Up-to-date build',
284     ]
285
286     run_commands = [
287         '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
288         '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
289         '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
290     ]
291
292     stages = [
293         'pre-read',
294         'post-read',
295         'pre-build',
296         'post-build',
297     ]
298
299     stage_strings = {
300         'pre-read'      : 'Memory before reading SConscript files:',
301         'post-read'     : 'Memory after reading SConscript files:',
302         'pre-build'     : 'Memory before building targets:',
303         'post-build'    : 'Memory after building targets:',
304     }
305
306     memory_string_all = 'Memory '
307
308     default_stage = stages[-1]
309
310     time_strings = {
311         'total'         : 'Total build time',
312         'SConscripts'   : 'Total SConscript file execution time',
313         'SCons'         : 'Total SCons execution time',
314         'commands'      : 'Total command execution time',
315     }
316    
317     time_string_all = 'Total .* time'
318
319     #
320
321     def __init__(self):
322         self.__dict__.update(self.default_settings)
323
324     # Functions for displaying and executing commands.
325
326     def subst(self, x, dictionary):
327         try:
328             return x % dictionary
329         except TypeError:
330             # x isn't a string (it's probably a Python function),
331             # so just return it.
332             return x
333
334     def subst_variables(self, command, dictionary):
335         """
336         Substitutes (via the format operator) the values in the specified
337         dictionary into the specified command.
338
339         The command can be an (action, string) tuple.  In all cases, we
340         perform substitution on strings and don't worry if something isn't
341         a string.  (It's probably a Python function to be executed.)
342         """
343         try:
344             command + ''
345         except TypeError:
346             action = command[0]
347             string = command[1]
348             args = command[2:]
349         else:
350             action = command
351             string = action
352             args = (())
353         action = self.subst(action, dictionary)
354         string = self.subst(string, dictionary)
355         return (action, string, args)
356
357     def _do_not_display(self, msg, *args):
358         pass
359
360     def display(self, msg, *args):
361         """
362         Displays the specified message.
363
364         Each message is prepended with a standard prefix of our name
365         plus the time.
366         """
367         if callable(msg):
368             msg = msg(*args)
369         else:
370             msg = msg % args
371         if msg is None:
372             return
373         fmt = '%s[%s]: %s\n'
374         sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
375
376     def _do_not_execute(self, action, *args):
377         pass
378
379     def execute(self, action, *args):
380         """
381         Executes the specified action.
382
383         The action is called if it's a callable Python function, and
384         otherwise passed to os.system().
385         """
386         if callable(action):
387             action(*args)
388         else:
389             os.system(action % args)
390
391     def run_command_list(self, commands, dict):
392         """
393         Executes a list of commands, substituting values from the
394         specified dictionary.
395         """
396         commands = [ self.subst_variables(c, dict) for c in commands ]
397         for action, string, args in commands:
398             self.display(string, *args)
399             sys.stdout.flush()
400             status = self.execute(action, *args)
401             if status:
402                 sys.exit(status)
403
404     def log_display(self, command, log):
405         command = self.subst(command, self.__dict__)
406         if log:
407             command = self.redirect(command, log)
408         return command
409
410     def log_execute(self, command, log):
411         command = self.subst(command, self.__dict__)
412         output = os.popen(command).read()
413         if self.verbose:
414             sys.stdout.write(output)
415         open(log, 'wb').write(output)
416
417     #
418
419     def archive_splitext(self, path):
420         """
421         Splits an archive name into a filename base and extension.
422
423         This is like os.path.splitext() (which it calls) except that it
424         also looks for '.tar.gz' and treats it as an atomic extensions.
425         """
426         if path.endswith('.tar.gz'):
427             return path[:-7], path[-7:]
428         else:
429             return os.path.splitext(path)
430
431     def args_to_files(self, args, tail=None):
432         """
433         Takes a list of arguments, expands any glob patterns, and
434         returns the last "tail" files from the list.
435         """
436         files = []
437         for a in args:
438             g = glob.glob(a)
439             g.sort()
440             files.extend(g)
441
442         if tail:
443             files = files[-tail:]
444
445         return files
446
447     def ascii_table(self, files, columns,
448                     line_function, file_function=lambda x: x,
449                     *args, **kw):
450
451         header_fmt = ' '.join(['%12s'] * len(columns))
452         line_fmt = header_fmt + '    %s'
453
454         print header_fmt % columns
455
456         for file in files:
457             t = line_function(file, *args, **kw)
458             diff = len(columns) - len(t)
459             if diff > 0:
460                 t += [''] * diff
461             t.append(file_function(file))
462             print line_fmt % tuple(t)
463
464     def collect_results(self, files, function, *args, **kw):
465         results = {}
466
467         for file in files:
468             base = os.path.splitext(file)[0]
469             run, index = string.split(base, '-')[-2:]
470
471             run = int(run)
472             index = int(index)
473
474             value = function(file, *args, **kw)
475
476             try:
477                 r = results[index]
478             except KeyError:
479                 r = []
480                 results[index] = r
481             r.append((run, value))
482
483         return results
484
485     def doc_to_help(self, obj):
486         """
487         Translates an object's __doc__ string into help text.
488
489         This strips a consistent number of spaces from each line in the
490         help text, essentially "outdenting" the text to the left-most
491         column.
492         """
493         doc = obj.__doc__
494         if doc is None:
495             return ''
496         return self.outdent(doc)
497
498     def find_next_run_number(self, dir, prefix):
499         """
500         Returns the next run number in a directory for the specified prefix.
501
502         Examines the contents the specified directory for files with the
503         specified prefix, extracts the run numbers from each file name,
504         and returns the next run number after the largest it finds.
505         """
506         x = re.compile(re.escape(prefix) + '-([0-9]+).*')
507         matches = map(lambda e, x=x: x.match(e), os.listdir(dir))
508         matches = filter(None, matches)
509         if not matches:
510             return 0
511         run_numbers = map(lambda m: int(m.group(1)), matches)
512         return int(max(run_numbers)) + 1
513
514     def gnuplot_results(self, results, fmt='%s %.3f'):
515         """
516         Prints out a set of results in Gnuplot format.
517         """
518         gp = Gnuplotter(self.title, self.key_location)
519
520         indices = results.keys()
521         indices.sort()
522
523         for i in indices:
524             try:
525                 t = self.run_titles[i]
526             except IndexError:
527                 t = '??? %s ???' % i
528             results[i].sort()
529             gp.line(results[i], i+1, t, None, t, fmt=fmt)
530
531         for bar_tuple in self.vertical_bars:
532             try:
533                 x, type, label, comment = bar_tuple
534             except ValueError:
535                 x, type, label = bar_tuple
536                 comment = label
537             gp.vertical_bar(x, type, None, label, comment)
538
539         gp.draw()
540
541     def logfile_name(self, invocation):
542         """
543         Returns the absolute path of a log file for the specificed
544         invocation number.
545         """
546         name = self.prefix_run + '-%d.log' % invocation
547         return os.path.join(self.outdir, name)
548
549     def outdent(self, s):
550         """
551         Strip as many spaces from each line as are found at the beginning
552         of the first line in the list.
553         """
554         lines = s.split('\n')
555         if lines[0] == '':
556             lines = lines[1:]
557         spaces = re.match(' *', lines[0]).group(0)
558         def strip_initial_spaces(l, s=spaces):
559             if l.startswith(spaces):
560                 l = l[len(spaces):]
561             return l
562         return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
563
564     def profile_name(self, invocation):
565         """
566         Returns the absolute path of a profile file for the specified
567         invocation number.
568         """
569         name = self.prefix_run + '-%d.prof' % invocation
570         return os.path.join(self.outdir, name)
571
572     def set_env(self, key, value):
573         os.environ[key] = value
574
575     #
576
577     def get_debug_times(self, file, time_string=None):
578         """
579         Fetch times from the --debug=time strings in the specified file.
580         """
581         if time_string is None:
582             search_string = self.time_string_all
583         else:
584             search_string = time_string
585         contents = open(file).read()
586         result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
587         result = [ float(r) for r in result ]
588         if not time_string is None:
589             result = result[0]
590         return result
591
592     def get_function_profile(self, file, function):
593         """
594         Returns the file, line number, function name, and cumulative time.
595         """
596         try:
597             import pstats
598         except ImportError, e:
599             sys.stderr.write('%s: func: %s\n' % (self.name, e))
600             sys.stderr.write('%s  This version of Python is missing the profiler.\n' % self.name_spaces)
601             sys.stderr.write('%s  Cannot use the "func" subcommand.\n' % self.name_spaces)
602             sys.exit(1)
603         statistics = pstats.Stats(file).stats
604         matches = [ e for e in statistics.items() if e[0][2] == function ]
605         r = matches[0]
606         return r[0][0], r[0][1], r[0][2], r[1][3]
607
608     def get_function_time(self, file, function):
609         """
610         Returns just the cumulative time for the specified function.
611         """
612         return self.get_function_profile(file, function)[3]
613
614     def get_memory(self, file, memory_string=None):
615         """
616         Returns a list of integers of the amount of memory used.  The
617         default behavior is to return all the stages.
618         """
619         if memory_string is None:
620             search_string = self.memory_string_all
621         else:
622             search_string = memory_string
623         lines = open(file).readlines()
624         lines = [ l for l in lines if l.startswith(search_string) ][-4:]
625         result = [ int(l.split()[-1]) for l in lines[-4:] ]
626         if len(result) == 1:
627             result = result[0]
628         return result
629
630     def get_object_counts(self, file, object_name, index=None):
631         """
632         Returns the counts of the specified object_name.
633         """
634         object_string = ' ' + object_name + '\n'
635         lines = open(file).readlines()
636         line = [ l for l in lines if l.endswith(object_string) ][0]
637         result = [ int(field) for field in line.split()[:4] ]
638         if not index is None:
639             result = result[index]
640         return result
641
642     #
643
644     command_alias = {}
645
646     def execute_subcommand(self, argv):
647         """
648         Executes the do_*() function for the specified subcommand (argv[0]).
649         """
650         if not argv:
651             return
652         cmdName = self.command_alias.get(argv[0], argv[0])
653         try:
654             func = getattr(self, 'do_' + cmdName)
655         except AttributeError:
656             return self.default(argv)
657         try:
658             return func(argv)
659         except TypeError, e:
660             sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
661             import traceback
662             traceback.print_exc(file=sys.stderr)
663             sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
664
665     def default(self, argv):
666         """
667         The default behavior for an unknown subcommand.  Prints an
668         error message and exits.
669         """
670         sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
671         sys.stderr.write('Type "%s help" for usage.\n' % self.name)
672         sys.exit(1)
673
674     #
675
676     def do_help(self, argv):
677         """
678         """
679         if argv[1:]:
680             for arg in argv[1:]:
681                 try:
682                     func = getattr(self, 'do_' + arg)
683                 except AttributeError:
684                     sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
685                 else:
686                     try:
687                         help = getattr(self, 'help_' + arg)
688                     except AttributeError:
689                         sys.stdout.write(self.doc_to_help(func))
690                         sys.stdout.flush()
691                     else:
692                         help()
693         else:
694             doc = self.doc_to_help(self.__class__)
695             if doc:
696                 sys.stdout.write(doc)
697             sys.stdout.flush()
698             return None
699
700     #
701
702     def help_func(self):
703         help = """\
704         Usage: scons-time func [OPTIONS] FILE [...]
705
706           -C DIR, --chdir=DIR           Change to DIR before looking for files
707           -f FILE, --file=FILE          Read configuration from specified FILE
708           --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
709           --func=NAME, --function=NAME  Report time for function NAME
710           -h, --help                    Print this help and exit
711           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
712           -t NUMBER, --tail=NUMBER      Only report the last NUMBER files
713           --title=TITLE                 Specify the output plot TITLE
714         """
715         sys.stdout.write(self.outdent(help))
716         sys.stdout.flush()
717
718     def do_func(self, argv):
719         """
720         """
721         format = 'ascii'
722         function_name = '_main'
723         tail = None
724
725         short_opts = '?C:f:hp:t:'
726
727         long_opts = [
728             'chdir=',
729             'file=',
730             'fmt=',
731             'format=',
732             'func=',
733             'function=',
734             'help',
735             'prefix=',
736             'tail=',
737             'title=',
738         ]
739
740         opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
741
742         for o, a in opts:
743             if o in ('-C', '--chdir'):
744                 self.chdir = a
745             elif o in ('-f', '--file'):
746                 self.config_file = a
747             elif o in ('--fmt', '--format'):
748                 format = a
749             elif o in ('--func', '--function'):
750                 function_name = a
751             elif o in ('-?', '-h', '--help'):
752                 self.do_help(['help', 'func'])
753                 sys.exit(0)
754             elif o in ('--max'):
755                 max_time = int(a)
756             elif o in ('-p', '--prefix'):
757                 self.prefix = a
758             elif o in ('-t', '--tail'):
759                 tail = int(a)
760             elif o in ('--title'):
761                 self.title = a
762
763         if self.config_file:
764             execfile(self.config_file, self.__dict__)
765
766         if self.chdir:
767             os.chdir(self.chdir)
768
769         if not args:
770
771             pattern = '%s*.prof' % self.prefix
772             args = self.args_to_files([pattern], tail)
773
774             if not args:
775                 if self.chdir:
776                     directory = self.chdir
777                 else:
778                     directory = os.getcwd()
779
780                 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
781                 sys.stderr.write('%s  No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
782                 sys.stderr.write('%s  Type "%s help func" for help.\n' % (self.name_spaces, self.name))
783                 sys.exit(1)
784
785         else:
786
787             args = self.args_to_files(args, tail)
788
789         cwd_ = os.getcwd() + os.sep
790
791         if format == 'ascii':
792
793             def print_function_timing(file, func):
794                 try:
795                     f, line, func, time = self.get_function_profile(file, func)
796                 except ValueError, e:
797                     sys.stderr.write("%s: func: %s: %s\n" % (self.name, file, e))
798                 else:
799                     if f.startswith(cwd_):
800                         f = f[len(cwd_):]
801                     print "%.3f %s:%d(%s)" % (time, f, line, func)
802
803             for file in args:
804                 print_function_timing(file, function_name)
805
806         elif format == 'gnuplot':
807
808             results = self.collect_results(args, self.get_function_time,
809                                            function_name)
810
811             self.gnuplot_results(results)
812
813         else:
814
815             sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
816             sys.exit(1)
817
818     #
819
820     def help_mem(self):
821         help = """\
822         Usage: scons-time mem [OPTIONS] FILE [...]
823
824           -C DIR, --chdir=DIR           Change to DIR before looking for files
825           -f FILE, --file=FILE          Read configuration from specified FILE
826           --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
827           -h, --help                    Print this help and exit
828           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
829           --stage=STAGE                 Plot memory at the specified stage:
830                                &n