Add some documentation to the SCons-version-switching hack
[senf.git] / tools / scons-1.2.0 / script / scons-time
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, 2008 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 from __future__ import nested_scopes
35
36 __revision__ = "src/script/scons-time.py 3842 2008/12/20 22:59:52 scons"
37
38 import getopt
39 import glob
40 import os
41 import os.path
42 import re
43 import shutil
44 import string
45 import sys
46 import tempfile
47 import time
48
49 try:
50     False
51 except NameError:
52     # Pre-2.2 Python has no False keyword.
53     import __builtin__
54     __builtin__.False = not 1
55
56 try:
57     True
58 except NameError:
59     # Pre-2.2 Python has no True keyword.
60     import __builtin__
61     __builtin__.True = not 0
62
63 def make_temp_file(**kw):
64     try:
65         result = tempfile.mktemp(**kw)
66         try:
67             result = os.path.realpath(result)
68         except AttributeError:
69             # Python 2.1 has no os.path.realpath() method.
70             pass
71     except TypeError:
72         try:
73             save_template = tempfile.template
74             prefix = kw['prefix']
75             del kw['prefix']
76             tempfile.template = prefix
77             result = tempfile.mktemp(**kw)
78         finally:
79             tempfile.template = save_template
80     return result
81
82 class Plotter:
83     def increment_size(self, largest):
84         """
85         Return the size of each horizontal increment line for a specified
86         maximum value.  This returns a value that will provide somewhere
87         between 5 and 9 horizontal lines on the graph, on some set of
88         boundaries that are multiples of 10/100/1000/etc.
89         """
90         i = largest / 5
91         if not i:
92             return largest
93         multiplier = 1
94         while i >= 10:
95             i = i / 10
96             multiplier = multiplier * 10
97         return i * multiplier
98
99     def max_graph_value(self, largest):
100         # Round up to next integer.
101         largest = int(largest) + 1
102         increment = self.increment_size(largest)
103         return ((largest + increment - 1) / increment) * increment
104
105 class Line:
106     def __init__(self, points, type, title, label, comment, fmt="%s %s"):
107         self.points = points
108         self.type = type
109         self.title = title
110         self.label = label
111         self.comment = comment
112         self.fmt = fmt
113
114     def print_label(self, inx, x, y):
115         if self.label:
116             print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
117
118     def plot_string(self):
119         if self.title:
120             title_string = 'title "%s"' % self.title
121         else:
122             title_string = 'notitle'
123         return "'-' %s with lines lt %s" % (title_string, self.type)
124
125     def print_points(self, fmt=None):
126         if fmt is None:
127             fmt = self.fmt
128         if self.comment:
129             print '# %s' % self.comment
130         for x, y in self.points:
131             # If y is None, it usually represents some kind of break
132             # in the line's index number.  We might want to represent
133             # this some way rather than just drawing the line straight
134             # between the two points on either side.
135             if not y is None:
136                 print fmt % (x, y)
137         print 'e'
138
139     def get_x_values(self):
140         return [ p[0] for p in self.points ]
141
142     def get_y_values(self):
143         return [ p[1] for p in self.points ]
144
145 class Gnuplotter(Plotter):
146
147     def __init__(self, title, key_location):
148         self.lines = []
149         self.title = title
150         self.key_location = key_location
151
152     def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
153         if points:
154             line = Line(points, type, title, label, comment, fmt)
155             self.lines.append(line)
156
157     def plot_string(self, line):
158         return line.plot_string()
159
160     def vertical_bar(self, x, type, label, comment):
161         if self.get_min_x() <= x and x <= self.get_max_x():
162             points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
163             self.line(points, type, label, comment)
164
165     def get_all_x_values(self):
166         result = []
167         for line in self.lines:
168             result.extend(line.get_x_values())
169         return filter(lambda r: not r is None, result)
170
171     def get_all_y_values(self):
172         result = []
173         for line in self.lines:
174             result.extend(line.get_y_values())
175         return filter(lambda r: not r is None, result)
176
177     def get_min_x(self):
178         try:
179             return self.min_x
180         except AttributeError:
181             try:
182                 self.min_x = min(self.get_all_x_values())
183             except ValueError:
184                 self.min_x = 0
185             return self.min_x
186
187     def get_max_x(self):
188         try:
189             return self.max_x
190         except AttributeError:
191             try:
192                 self.max_x = max(self.get_all_x_values())
193             except ValueError:
194                 self.max_x = 0
195             return self.max_x
196
197     def get_min_y(self):
198         try:
199             return self.min_y
200         except AttributeError:
201             try:
202                 self.min_y = min(self.get_all_y_values())
203             except ValueError:
204                 self.min_y = 0
205             return self.min_y
206
207     def get_max_y(self):
208         try:
209             return self.max_y
210         except AttributeError:
211             try:
212                 self.max_y = max(self.get_all_y_values())
213             except ValueError:
214                 self.max_y = 0
215             return self.max_y
216
217     def draw(self):
218
219         if not self.lines:
220             return
221
222         if self.title:
223             print 'set title "%s"' % self.title
224         print 'set key %s' % self.key_location
225
226         min_y = self.get_min_y()
227         max_y = self.max_graph_value(self.get_max_y())
228         range = max_y - min_y
229         incr = range / 10.0
230         start = min_y + (max_y / 2.0) + (2.0 * incr)
231         position = [ start - (i * incr) for i in xrange(5) ]
232
233         inx = 1
234         for line in self.lines:
235             line.print_label(inx, line.points[0][0]-1,
236                              position[(inx-1) % len(position)])
237             inx += 1
238
239         plot_strings = [ self.plot_string(l) for l in self.lines ]
240         print 'plot ' + ', \\\n     '.join(plot_strings)
241
242         for line in self.lines:
243             line.print_points()
244
245
246
247 def untar(fname):
248     import tarfile
249     tar = tarfile.open(name=fname, mode='r')
250     for tarinfo in tar:
251         tar.extract(tarinfo)
252     tar.close()
253
254 def unzip(fname):
255     import zipfile
256     zf = zipfile.ZipFile(fname, 'r')
257     for name in zf.namelist():
258         dir = os.path.dirname(name)
259         try:
260             os.makedirs(dir)
261         except:
262             pass
263         open(name, 'w').write(zf.read(name))
264
265 def read_tree(dir):
266     def read_files(arg, dirname, fnames):
267         for fn in fnames:
268             fn = os.path.join(dirname, fn)
269             if os.path.isfile(fn):
270                 open(fn, 'rb').read()
271     os.path.walk('.', read_files, None)
272
273 def redirect_to_file(command, log):
274     return '%s > %s 2>&1' % (command, log)
275
276 def tee_to_file(command, log):
277     return '%s 2>&1 | tee %s' % (command, log)
278
279
280     
281 class SConsTimer:
282     """
283     Usage: scons-time SUBCOMMAND [ARGUMENTS]
284     Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
285
286     Available subcommands:
287         func            Extract test-run data for a function
288         help            Provides help
289         mem             Extract --debug=memory data from test runs
290         obj             Extract --debug=count data from test runs
291         time            Extract --debug=time data from test runs
292         run             Runs a test configuration
293     """
294
295     name = 'scons-time'
296     name_spaces = ' '*len(name)
297
298     def makedict(**kw):
299         return kw
300
301     default_settings = makedict(
302         aegis               = 'aegis',
303         aegis_project       = None,
304         chdir               = None,
305         config_file         = None,
306         initial_commands    = [],
307         key_location        = 'bottom left',
308         orig_cwd            = os.getcwd(),
309         outdir              = None,
310         prefix              = '',
311         python              = '"%s"' % sys.executable,
312         redirect            = redirect_to_file,
313         scons               = None,
314         scons_flags         = '--debug=count --debug=memory --debug=time --debug=memoizer',
315         scons_lib_dir       = None,
316         scons_wrapper       = None,
317         startup_targets     = '--help',
318         subdir              = None,
319         subversion_url      = None,
320         svn                 = 'svn',
321         svn_co_flag         = '-q',
322         tar                 = 'tar',
323         targets             = '',
324         targets0            = None,
325         targets1            = None,
326         targets2            = None,
327         title               = None,
328         unzip               = 'unzip',
329         verbose             = False,
330         vertical_bars       = [],
331
332         unpack_map = {
333             '.tar.gz'       : (untar,   '%(tar)s xzf %%s'),
334             '.tgz'          : (untar,   '%(tar)s xzf %%s'),
335             '.tar'          : (untar,   '%(tar)s xf %%s'),
336             '.zip'          : (unzip,   '%(unzip)s %%s'),
337         },
338     )
339
340     run_titles = [
341         'Startup',
342         'Full build',
343         'Up-to-date build',
344     ]
345
346     run_commands = [
347         '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
348         '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
349         '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
350     ]
351
352     stages = [
353         'pre-read',
354         'post-read',
355         'pre-build',
356         'post-build',
357     ]
358
359     stage_strings = {
360         'pre-read'      : 'Memory before reading SConscript files:',
361         'post-read'     : 'Memory after reading SConscript files:',
362         'pre-build'     : 'Memory before building targets:',
363         'post-build'    : 'Memory after building targets:',
364     }
365
366     memory_string_all = 'Memory '
367
368     default_stage = stages[-1]
369
370     time_strings = {
371         'total'         : 'Total build time',
372         'SConscripts'   : 'Total SConscript file execution time',
373         'SCons'         : 'Total SCons execution time',
374         'commands'      : 'Total command execution time',
375     }
376     
377     time_string_all = 'Total .* time'
378
379     #
380
381     def __init__(self):
382         self.__dict__.update(self.default_settings)
383
384     # Functions for displaying and executing commands.
385
386     def subst(self, x, dictionary):
387         try:
388             return x % dictionary
389         except TypeError:
390             # x isn't a string (it's probably a Python function),
391             # so just return it.
392             return x
393
394     def subst_variables(self, command, dictionary):
395         """
396         Substitutes (via the format operator) the values in the specified
397         dictionary into the specified command.
398
399         The command can be an (action, string) tuple.  In all cases, we
400         perform substitution on strings and don't worry if something isn't
401         a string.  (It's probably a Python function to be executed.)
402         """
403         try:
404             command + ''
405         except TypeError:
406             action = command[0]
407             string = command[1]
408             args = command[2:]
409         else:
410             action = command
411             string = action
412             args = (())
413         action = self.subst(action, dictionary)
414         string = self.subst(string, dictionary)
415         return (action, string, args)
416
417     def _do_not_display(self, msg, *args):
418         pass
419
420     def display(self, msg, *args):
421         """
422         Displays the specified message.
423
424         Each message is prepended with a standard prefix of our name
425         plus the time.
426         """
427         if callable(msg):
428             msg = msg(*args)
429         else:
430             msg = msg % args
431         if msg is None:
432             return
433         fmt = '%s[%s]: %s\n'
434         sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
435
436     def _do_not_execute(self, action, *args):
437         pass
438
439     def execute(self, action, *args):
440         """
441         Executes the specified action.
442
443         The action is called if it's a callable Python function, and
444         otherwise passed to os.system().
445         """
446         if callable(action):
447             action(*args)
448         else:
449             os.system(action % args)
450
451     def run_command_list(self, commands, dict):
452         """
453         Executes a list of commands, substituting values from the
454         specified dictionary.
455         """
456         commands = [ self.subst_variables(c, dict) for c in commands ]
457         for action, string, args in commands:
458             self.display(string, *args)
459             sys.stdout.flush()
460             status = self.execute(action, *args)
461             if status:
462                 sys.exit(status)
463
464     def log_display(self, command, log):
465         command = self.subst(command, self.__dict__)
466         if log:
467             command = self.redirect(command, log)
468         return command
469
470     def log_execute(self, command, log):
471         command = self.subst(command, self.__dict__)
472         output = os.popen(command).read()
473         if self.verbose:
474             sys.stdout.write(output)
475         open(log, 'wb').write(output)
476
477     #
478
479     def archive_splitext(self, path):
480         """
481         Splits an archive name into a filename base and extension.
482
483         This is like os.path.splitext() (which it calls) except that it
484         also looks for '.tar.gz' and treats it as an atomic extensions.
485         """
486         if path.endswith('.tar.gz'):
487             return path[:-7], path[-7:]
488         else:
489             return os.path.splitext(path)
490
491     def args_to_files(self, args, tail=None):
492         """
493         Takes a list of arguments, expands any glob patterns, and
494         returns the last "tail" files from the list.
495         """
496         files = []
497         for a in args:
498             g = glob.glob(a)
499             g.sort()
500             files.extend(g)
501
502         if tail:
503             files = files[-tail:]
504
505         return files
506
507     def ascii_table(self, files, columns,
508                     line_function, file_function=lambda x: x,
509                     *args, **kw):
510
511         header_fmt = ' '.join(['%12s'] * len(columns))
512         line_fmt = header_fmt + '    %s'
513
514         print header_fmt % columns
515
516         for file in files:
517             t = line_function(file, *args, **kw)
518             if t is None:
519                 t = []
520             diff = len(columns) - len(t)
521             if diff > 0:
522                 t += [''] * diff
523             t.append(file_function(file))
524             print line_fmt % tuple(t)
525
526     def collect_results(self, files, function, *args, **kw):
527         results = {}
528
529         for file in files:
530             base = os.path.splitext(file)[0]
531             run, index = string.split(base, '-')[-2:]
532
533             run = int(run)
534             index = int(index)
535
536             value = function(file, *args, **kw)
537
538             try:
539                 r = results[index]
540             except KeyError:
541                 r = []
542                 results[index] = r
543             r.append((run, value))
544
545         return results
546
547     def doc_to_help(self, obj):
548         """
549         Translates an object's __doc__ string into help text.
550
551         This strips a consistent number of spaces from each line in the
552         help text, essentially "outdenting" the text to the left-most
553         column.
554         """
555         doc = obj.__doc__
556         if doc is None:
557             return ''
558         return self.outdent(doc)
559
560     def find_next_run_number(self, dir, prefix):
561         """
562         Returns the next run number in a directory for the specified prefix.
563
564         Examines the contents the specified directory for files with the
565         specified prefix, extracts the run numbers from each file name,
566         and returns the next run number after the largest it finds.
567         """
568         x = re.compile(re.escape(prefix) + '-([0-9]+).*')
569         matches = map(lambda e, x=x: x.match(e), os.listdir(dir))
570         matches = filter(None, matches)
571         if not matches:
572             return 0
573         run_numbers = map(lambda m: int(m.group(1)), matches)
574         return int(max(run_numbers)) + 1
575
576     def gnuplot_results(self, results, fmt='%s %.3f'):
577         """
578         Prints out a set of results in Gnuplot format.
579         """
580         gp = Gnuplotter(self.title, self.key_location)
581
582         indices = results.keys()
583         indices.sort()
584
585         for i in indices:
586             try:
587                 t = self.run_titles[i]
588             except IndexError:
589                 t = '??? %s ???' % i
590             results[i].sort()
591             gp.line(results[i], i+1, t, None, t, fmt=fmt)
592
593         for bar_tuple in self.vertical_bars:
594             try:
595                 x, type, label, comment = bar_tuple
596             except ValueError:
597                 x, type, label = bar_tuple
598                 comment = label
599             gp.vertical_bar(x, type, label, comment)
600
601         gp.draw()
602
603     def logfile_name(self, invocation):
604         """
605         Returns the absolute path of a log file for the specificed
606         invocation number.
607         """
608         name = self.prefix_run + '-%d.log' % invocation
609         return os.path.join(self.outdir, name)
610
611     def outdent(self, s):
612         """
613         Strip as many spaces from each line as are found at the beginning
614         of the first line in the list.
615         """
616         lines = s.split('\n')
617         if lines[0] == '':
618             lines = lines[1:]
619         spaces = re.match(' *', lines[0]).group(0)
620         def strip_initial_spaces(l, s=spaces):
621             if l.startswith(spaces):
622                 l = l[len(spaces):]
623             return l
624         return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
625
626     def profile_name(self, invocation):
627         """
628         Returns the absolute path of a profile file for the specified
629         invocation number.
630         """
631         name = self.prefix_run + '-%d.prof' % invocation
632         return os.path.join(self.outdir, name)
633
634     def set_env(self, key, value):
635         os.environ[key] = value
636
637     #
638
639     def get_debug_times(self, file, time_string=None):
640         """
641         Fetch times from the --debug=time strings in the specified file.
642         """
643         if time_string is None:
644             search_string = self.time_string_all
645         else:
646             search_string = time_string
647         contents = open(file).read()
648         if not contents:
649             sys.stderr.write('file %s has no contents!\n' % repr(file))
650             return None
651         result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
652         result = [ float(r) for r in result ]
653         if not time_string is None:
654             try:
655                 result = result[0]
656             except IndexError:
657                 sys.stderr.write('file %s has no results!\n' % repr(file))
658                 return None
659         return result
660
661     def get_function_profile(self, file, function):
662         """
663         Returns the file, line number, function name, and cumulative time.
664         """
665         try:
666             import pstats
667         except ImportError, e:
668             sys.stderr.write('%s: func: %s\n' % (self.name, e))
669             sys.stderr.write('%s  This version of Python is missing the profiler.\n' % self.name_spaces)
670             sys.stderr.write('%s  Cannot use the "func" subcommand.\n' % self.name_spaces)
671             sys.exit(1)
672         statistics = pstats.Stats(file).stats
673         matches = [ e for e in statistics.items() if e[0][2] == function ]
674         r = matches[0]
675         return r[0][0], r[0][1], r[0][2], r[1][3]
676
677     def get_function_time(self, file, function):
678         """
679         Returns just the cumulative time for the specified function.
680         """
681         return self.get_function_profile(file, function)[3]
682
683     def get_memory(self, file, memory_string=None):
684         """
685         Returns a list of integers of the amount of memory used.  The
686         default behavior is to return all the stages.
687         """
688         if memory_string is None:
689             search_string = self.memory_string_all
690         else:
691             search_string = memory_string
692         lines = open(file).readlines()
693         lines = [ l for l in lines if l.startswith(search_string) ][-4:]
694         result = [ int(l.split()[-1]) for l in lines[-4:] ]
695         if len(result) == 1:
696             result = result[0]
697         return result
698
699     def get_object_counts(self, file, object_name, index=None):
700         """
701         Returns the counts of the specified object_name.
702         """
703         object_string = ' ' + object_name + '\n'
704         lines = open(file).readlines()
705         line = [ l for l in lines if l.endswith(object_string) ][0]
706         result = [ int(field) for field in line.split()[:4] ]
707         if not index is None:
708             result = result[index]
709         return result
710
711     #
712
713     command_alias = {}
714
715     def execute_subcommand(self, argv):
716         """
717         Executes the do_*() function for the specified subcommand (argv[0]).
718         """
719         if not argv:
720             return
721         cmdName = self.command_alias.get(argv[0], argv[0])
722         try:
723             func = getattr(self, 'do_' + cmdName)
724         except AttributeError:
725             return self.default(argv)
726         try:
727             return func(argv)
728         except TypeError, e:
729             sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
730             import traceback
731             traceback.print_exc(file=sys.stderr)
732             sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
733
734     def default(self, argv):
735         """
736         The default behavior for an unknown subcommand.  Prints an
737         error message and exits.
738         """
739         sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
740         sys.stderr.write('Type "%s help" for usage.\n' % self.name)
741         sys.exit(1)
742
743     #
744
745     def do_help(self, argv):
746         """
747         """
748         if argv[1:]:
749             for arg in argv[1:]:
750                 try:
751                     func = getattr(self, 'do_' + arg)
752                 except AttributeError:
753                     sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
754                 else:
755                     try:
756                         help = getattr(self, 'help_' + arg)
757                     except AttributeError:
758                         sys.stdout.write(self.doc_to_help(func))
759                         sys.stdout.flush()
760                     else:
761                         help()
762         else:
763             doc = self.doc_to_help(self.__class__)
764             if doc:
765                 sys.stdout.write(doc)
766             sys.stdout.flush()
767             return None
768
769     #
770
771     def help_func(self):
772         help = """\
773         Usage: scons-time func [OPTIONS] FILE [...]
774
775           -C DIR, --chdir=DIR           Change to DIR before looking for files
776           -f FILE, --file=FILE          Read configuration from specified FILE
777           --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
778           --func=NAME, --function=NAME  Report time for function NAME
779           -h, --help                    Print this help and exit
780           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
781           -t NUMBER, --tail=NUMBER      Only report the last NUMBER files
782           --title=TITLE                 Specify the output plot TITLE
783         """
784         sys.stdout.write(self.outdent(help))
785         sys.stdout.flush()
786
787     def do_func(self, argv):
788         """
789         """
790         format = 'ascii'
791         function_name = '_main'
792         tail = None
793
794         short_opts = '?C:f:hp:t:'
795
796         long_opts = [
797             'chdir=',
798             'file=',
799             'fmt=',
800             'format=',
801             'func=',
802             'function=',
803             'help',
804             'prefix=',
805             'tail=',
806             'title=',
807         ]
808
809         opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
810
811         for o, a in opts:
812             if o in ('-C', '--chdir'):
813                 self.chdir = a
814             elif o in ('-f', '--file'):
815                 self.config_file = a
816             elif o in ('--fmt', '--format'):
817                 format = a
818             elif o in ('--func', '--function'):
819                 function_name = a
820             elif o in ('-?', '-h', '--help'):
821                 self.do_help(['help', 'func'])
822                 sys.exit(0)
823             elif o in ('--max',):
824                 max_time = int(a)
825             elif o in ('-p', '--prefix'):
826                 self.prefix = a
827             elif o in ('-t', '--tail'):
828                 tail = int(a)
829             elif o in ('--title',):
830                 self.title = a
831
832         if self.config_file:
833             execfile(self.config_file, self.__dict__)
834
835         if self.chdir:
836             os.chdir(self.chdir)
837
838         if not args:
839
840             pattern = '%s*.prof' % self.prefix
841             args = self.args_to_files([pattern], tail)
842
843             if not args:
844                 if self.chdir:
845                     directory = self.chdir
846                 else:
847                     directory = os.getcwd()
848
849                 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
850                 sys.stderr.write('%s  No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
851                 sys.stderr.write('%s  Type "%s help func" for help.\n' % (self.name_spaces, self.name))
852                 sys.exit(1)
853
854         else:
855
856             args = self.args_to_files(args, tail)
857
858         cwd_ = os.getcwd() + os.sep
859
860         if format == 'ascii':
861
862             def print_function_timing(file, func):
863                 try:
864                     f, line, func, time = self.get_function_profile(file, func)
865                 except ValueError, e:
866                     sys.stderr.write("%s: func: %s: %s\n" % (self.name, file, e))
867                 else:
868                     if f.startswith(cwd_):
869                         f = f[len(cwd_):]
870                     print "%.3f %s:%d(%s)" % (time, f, line, func)
871
872             for file in args:
873                 print_function_timing(file, function_name)
874
875         elif format == 'gnuplot':
876
877             results = self.collect_results(args, self.get_function_time,
878                                            function_name)
879
880             self.gnuplot_results(results)
881
882         else:
883
884             sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
885             sys.exit(1)
886
887     #
888
889     def help_mem(self):
890         help = """\
891         Usage: scons-time mem [OPTIONS] FILE [...]
892
893           -C DIR, --chdir=DIR           Change to DIR before looking for files
894           -f FILE, --file=FILE          Read configuration from specified FILE
895           --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
896           -h, --help                    Print this help and exit
897           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
898           --stage=STAGE                 Plot memory at the specified stage:
899                                           pre-read, post-read, pre-build,
900                                           post-build (default: post-build)
901           -t NUMBER, --tail=NUMBER      Only report the last NUMBER files
902           --title=TITLE                 Specify the output plot TITLE
903         """
904         sys.stdout.write(self.outdent(help))
905         sys.stdout.flush()
906
907     def do_mem(self, argv):
908
909         format = 'ascii'
910         logfile_path = lambda x: x
911         stage = self.default_stage
912         tail = None
913
914         short_opts = '?C:f:hp:t:'
915
916         long_opts = [
917             'chdir=',
918             'file=',
919             'fmt=',
920             'format=',
921             'help',
922             'prefix=',
923             'stage=',
924             'tail=',
925             'title=',
926         ]
927
928         opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
929
930         for o, a in opts:
931             if o in ('-C', '--chdir'):
932                 self.chdir = a
933             elif o in ('-f', '--file'):
934                 self.config_file = a
935             elif o in ('--fmt', '--format'):
936                 format = a
937             elif o in ('-?', '-h', '--help'):
938                 self.do_help(['help', 'mem'])
939                 sys.exit(0)
940             elif o in ('-p', '--prefix'):
941                 self.prefix = a
942             elif o in ('--stage',):
943                 if not a in self.stages:
944                     sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
945                     sys.exit(1)
946                 stage = a
947             elif o in ('-t', '--tail'):
948                 tail = int(a)
949             elif o in ('--title',):
950                 self.title = a
951
952         if self.config_file:
953             execfile(self.config_file, self.__dict__)
954
955         if self.chdir:
956             os.chdir(self.chdir)
957             logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
958
959         if not args:
960
961             pattern = '%s*.log' % self.prefix
962             args = self.args_to_files([pattern], tail)
963
964             if not args:
965                 if self.chdir:
966                     directory = self.chdir
967                 else:
968                     directory = os.getcwd()
969
970                 sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
971                 sys.stderr.write('%s  No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
972                 sys.stderr.write('%s  Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
973                 sys.exit(1)
974
975         else:
976
977             args = self.args_to_files(args, tail)
978
979         cwd_ = os.getcwd() + os.sep
980
981         if format == 'ascii':
982
983             self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
984
985         elif format == 'gnuplot':
986
987             results = self.collect_results(args, self.get_memory,
988                                            self.stage_strings[stage])
989
990             self.gnuplot_results(results)
991
992         else:
993
994             sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
995             sys.exit(1)
996
997         return 0
998
999     #
1000
1001     def help_obj(self):
1002         help = """\
1003         Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1004
1005           -C DIR, --chdir=DIR           Change to DIR before looking for files
1006           -f FILE, --file=FILE          Read configuration from specified FILE
1007           --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1008           -h, --help                    Print this help and exit
1009           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
1010           --stage=STAGE                 Plot memory at the specified stage:
1011                                           pre-read, post-read, pre-build,
1012                                           post-build (default: post-build)
1013           -t NUMBER, --tail=NUMBER      Only report the last NUMBER files
1014           --title=TITLE                 Specify the output plot TITLE
1015         """
1016         sys.stdout.write(self.outdent(help))
1017         sys.stdout.flush()
1018
1019     def do_obj(self, argv):
1020
1021         format = 'ascii'
1022         logfile_path = lambda x: x
1023         stage = self.default_stage
1024         tail = None
1025
1026         short_opts = '?C:f:hp:t:'
1027
1028         long_opts = [
1029             'chdir=',
1030             'file=',
1031             'fmt=',
1032             'format=',
1033             'help',
1034             'prefix=',
1035             'stage=',
1036             'tail=',
1037             'title=',
1038         ]
1039
1040         opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1041
1042         for o, a in opts:
1043             if o in ('-C', '--chdir'):
1044                 self.chdir = a
1045             elif o in ('-f', '--file'):
1046                 self.config_file = a
1047             elif o in ('--fmt', '--format'):
1048                 format = a
1049             elif o in ('-?', '-h', '--help'):
1050                 self.do_help(['help', 'obj'])
1051                 sys.exit(0)
1052             elif o in ('-p', '--prefix'):
1053                 self.prefix = a
1054             elif o in ('--stage',):
1055                 if not a in self.stages:
1056                     sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1057                     sys.stderr.write('%s       Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1058                     sys.exit(1)
1059                 stage = a
1060             elif o in ('-t', '--tail'):
1061                 tail = int(a)
1062             elif o in ('--title',):
1063                 self.title = a
1064
1065         if not args:
1066             sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1067             sys.stderr.write('%s       Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1068             sys.exit(1)
1069
1070         object_name = args.pop(0)
1071
1072         if self.config_file:
1073             execfile(self.config_file, self.__dict__)
1074
1075         if self.chdir:
1076             os.chdir(self.chdir)
1077             logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
1078
1079         if not args:
1080
1081             pattern = '%s*.log' % self.prefix
1082             args = self.args_to_files([pattern], tail)
1083
1084             if not args:
1085                 if self.chdir:
1086                     directory = self.chdir
1087                 else:
1088                     directory = os.getcwd()
1089
1090                 sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1091                 sys.stderr.write('%s  No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1092                 sys.stderr.write('%s  Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1093                 sys.exit(1)
1094
1095         else:
1096
1097             args = self.args_to_files(args, tail)
1098
1099         cwd_ = os.getcwd() + os.sep
1100
1101         if format == 'ascii':
1102
1103             self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1104
1105         elif format == 'gnuplot':
1106
1107             stage_index = 0
1108             for s in self.stages:
1109                 if stage == s:
1110                     break
1111                 stage_index = stage_index + 1
1112
1113             results = self.collect_results(args, self.get_object_counts,
1114                                            object_name, stage_index)
1115
1116             self.gnuplot_results(results)
1117
1118         else:
1119
1120             sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1121             sys.exit(1)
1122
1123         return 0
1124
1125     #
1126
1127     def help_run(self):
1128         help = """\
1129         Usage: scons-time run [OPTIONS] [FILE ...]
1130
1131           --aegis=PROJECT               Use SCons from the Aegis PROJECT
1132           --chdir=DIR                   Name of unpacked directory for chdir
1133           -f FILE, --file=FILE          Read configuration from specified FILE
1134           -h, --help                    Print this help and exit
1135           -n, --no-exec                 No execute, just print command lines
1136           --number=NUMBER               Put output in files for run NUMBER
1137           --outdir=OUTDIR               Put output files in OUTDIR
1138           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
1139           --python=PYTHON               Time using the specified PYTHON
1140           -q, --quiet                   Don't print command lines
1141           --scons=SCONS                 Time using the specified SCONS
1142           --svn=URL, --subversion=URL   Use SCons from Subversion URL
1143           -v, --verbose                 Display output of commands
1144         """
1145         sys.stdout.write(self.outdent(help))
1146         sys.stdout.flush()
1147
1148     def do_run(self, argv):
1149         """
1150         """
1151         run_number_list = [None]
1152
1153         short_opts = '?f:hnp:qs:v'
1154
1155         long_opts = [
1156             'aegis=',
1157             'file=',
1158             'help',
1159             'no-exec',
1160             'number=',
1161             'outdir=',
1162             'prefix=',
1163             'python=',
1164             'quiet',
1165             'scons=',
1166             'svn=',
1167             'subdir=',
1168             'subversion=',
1169             'verbose',
1170         ]
1171
1172         opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1173
1174         for o, a in opts:
1175             if o in ('--aegis',):
1176                 self.aegis_project = a
1177             elif o in ('-f', '--file'):
1178                 self.config_file = a
1179             elif o in ('-?', '-h', '--help'):
1180                 self.do_help(['help', 'run'])
1181                 sys.exit(0)
1182             elif o in ('-n', '--no-exec'):
1183                 self.execute = self._do_not_execute
1184             elif o in ('--number',):
1185                 run_number_list = self.split_run_numbers(a)
1186             elif o in ('--outdir',):
1187                 self.outdir = a
1188             elif o in ('-p', '--prefix'):
1189                 self.prefix = a
1190             elif o in ('--python',):
1191                 self.python = a
1192             elif o in ('-q', '--quiet'):
1193                 self.display = self._do_not_display
1194             elif o in ('-s', '--subdir'):
1195                 self.subdir = a
1196             elif o in ('--scons',):
1197                 self.scons = a
1198             elif o in ('--svn', '--subversion'):
1199                 self.subversion_url = a
1200             elif o in ('-v', '--verbose'):
1201                 self.redirect = tee_to_file
1202                 self.verbose = True
1203                 self.svn_co_flag = ''
1204
1205         if not args and not self.config_file:
1206             sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1207             sys.stderr.write('%s  Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1208             sys.exit(1)
1209
1210         if self.config_file:
1211             execfile(self.config_file, self.__dict__)
1212
1213         if args:
1214             self.archive_list = args
1215
1216         archive_file_name = os.path.split(self.archive_list[0])[1]
1217
1218         if not self.subdir:
1219             self.subdir = self.archive_splitext(archive_file_name)[0]
1220
1221         if not self.prefix:
1222             self.prefix = self.archive_splitext(archive_file_name)[0]
1223
1224         prepare = None
1225         if self.subversion_url:
1226             prepare = self.prep_subversion_run
1227         elif self.aegis_project:
1228             prepare = self.prep_aegis_run
1229
1230         for run_number in run_number_list:
1231             self.individual_run(run_number, self.archive_list, prepare)
1232
1233     def split_run_numbers(self, s):
1234         result = []
1235         for n in s.split(','):
1236             try:
1237                 x, y = n.split('-')
1238             except ValueError:
1239                 result.append(int(n))
1240             else:
1241                 result.extend(range(int(x), int(y)+1))
1242         return result
1243
1244     def scons_path(self, dir):
1245         return os.path.join(dir, 'src', 'script', 'scons.py')
1246
1247     def scons_lib_dir_path(self, dir):
1248         return os.path.join(dir, 'src', 'engine')
1249
1250     def prep_aegis_run(self, commands, removals):
1251         self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1252         removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1253
1254         self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1255         self.scons = self.scons_path(self.aegis_tmpdir)
1256         self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1257
1258         commands.extend([
1259             'mkdir %(aegis_tmpdir)s',
1260             (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1261             '%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1262             '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1263         ])
1264
1265     def prep_subversion_run(self, commands, removals):
1266         self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1267         removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1268
1269         self.scons = self.scons_path(self.svn_tmpdir)
1270         self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1271
1272         commands.extend([
1273             'mkdir %(svn_tmpdir)s',
1274             '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1275         ])
1276
1277     def individual_run(self, run_number, archive_list, prepare=None):
1278         """
1279         Performs an individual run of the default SCons invocations.
1280         """
1281
1282         commands = []
1283         removals = []
1284
1285         if prepare:
1286             prepare(commands, removals)
1287
1288         save_scons              = self.scons
1289         save_scons_wrapper      = self.scons_wrapper
1290         save_scons_lib_dir      = self.scons_lib_dir
1291
1292         if self.outdir is None:
1293             self.outdir = self.orig_cwd
1294         elif not os.path.isabs(self.outdir):
1295             self.outdir = os.path.join(self.orig_cwd, self.outdir)
1296
1297         if self.scons is None:
1298             self.scons = self.scons_path(self.orig_cwd)
1299
1300         if self.scons_lib_dir is None:
1301             self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1302
1303         if self.scons_wrapper is None:
1304             self.scons_wrapper = self.scons
1305
1306         if not run_number:
1307             run_number = self.find_next_run_number(self.outdir, self.prefix)
1308
1309         self.run_number = str(run_number)
1310
1311         self.prefix_run = self.prefix + '-%03d' % run_number
1312
1313         if self.targets0 is None:
1314             self.targets0 = self.startup_targets
1315         if self.targets1 is None:
1316             self.targets1 = self.targets
1317         if self.targets2 is None:
1318             self.targets2 = self.targets
1319
1320         self.tmpdir = make_temp_file(prefix = self.name + '-')
1321
1322         commands.extend([
1323             'mkdir %(tmpdir)s',
1324
1325             (os.chdir, 'cd %%s', self.tmpdir),
1326         ])
1327
1328         for archive in archive_list:
1329             if not os.path.isabs(archive):
1330                 archive = os.path.join(self.orig_cwd, archive)
1331             if os.path.isdir(archive):
1332                 dest = os.path.split(archive)[1]
1333                 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1334             else:
1335                 suffix = self.archive_splitext(archive)[1]
1336                 unpack_command = self.unpack_map.get(suffix)
1337                 if not unpack_command:
1338                     dest = os.path.split(archive)[1]
1339                     commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1340                 else:
1341                     commands.append(unpack_command + (archive,))
1342
1343         commands.extend([
1344             (os.chdir, 'cd %%s', self.subdir),
1345         ])
1346
1347         commands.extend(self.initial_commands)
1348
1349         commands.extend([
1350             (lambda: read_tree('.'),
1351             'find * -type f | xargs cat > /dev/null'),
1352
1353             (self.set_env, 'export %%s=%%s',
1354              'SCONS_LIB_DIR', self.scons_lib_dir),
1355
1356             '%(python)s %(scons_wrapper)s --version',
1357         ])
1358
1359         index = 0
1360         for run_command in self.run_commands:
1361             setattr(self, 'prof%d' % index, self.profile_name(index))
1362             c = (
1363                 self.log_execute,
1364                 self.log_display,
1365                 run_command,
1366                 self.logfile_name(index),
1367             )
1368             commands.append(c)
1369             index = index + 1
1370
1371         commands.extend([
1372             (os.chdir, 'cd %%s', self.orig_cwd),
1373         ])
1374
1375         if not os.environ.get('PRESERVE'):
1376             commands.extend(removals)
1377
1378             commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1379
1380         self.run_command_list(commands, self.__dict__)
1381
1382         self.scons              = save_scons
1383         self.scons_lib_dir      = save_scons_lib_dir
1384         self.scons_wrapper      = save_scons_wrapper
1385
1386     #
1387
1388     def help_time(self):
1389         help = """\
1390         Usage: scons-time time [OPTIONS] FILE [...]
1391
1392           -C DIR, --chdir=DIR           Change to DIR before looking for files
1393           -f FILE, --file=FILE          Read configuration from specified FILE
1394           --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1395           -h, --help                    Print this help and exit
1396           -p STRING, --prefix=STRING    Use STRING as log file/profile prefix
1397           -t NUMBER, --tail=NUMBER      Only report the last NUMBER files
1398           --which=TIMER                 Plot timings for TIMER:  total,
1399                                           SConscripts, SCons, commands.
1400         """
1401         sys.stdout.write(self.outdent(help))
1402         sys.stdout.flush()
1403
1404     def do_time(self, argv):
1405
1406         format = 'ascii'
1407         logfile_path = lambda x: x
1408         tail = None
1409         which = 'total'
1410
1411         short_opts = '?C:f:hp:t:'
1412
1413         long_opts = [
1414             'chdir=',
1415             'file=',
1416             'fmt=',
1417             'format=',
1418             'help',
1419             'prefix=',
1420             'tail=',
1421             'title=',
1422             'which=',
1423         ]
1424
1425         opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1426
1427         for o, a in opts:
1428             if o in ('-C', '--chdir'):
1429                 self.chdir = a
1430             elif o in ('-f', '--file'):
1431                 self.config_file = a
1432             elif o in ('--fmt', '--format'):
1433                 format = a
1434             elif o in ('-?', '-h', '--help'):
1435                 self.do_help(['help', 'time'])
1436                 sys.exit(0)
1437             elif o in ('-p', '--prefix'):
1438                 self.prefix = a
1439             elif o in ('-t', '--tail'):
1440                 tail = int(a)
1441             elif o in ('--title',):
1442                 self.title = a
1443             elif o in ('--which',):
1444                 if not a in self.time_strings.keys():
1445                     sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1446                     sys.stderr.write('%s  Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1447                     sys.exit(1)
1448                 which = a
1449
1450         if self.config_file:
1451             execfile(self.config_file, self.__dict__)
1452
1453         if self.chdir:
1454             os.chdir(self.chdir)
1455             logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
1456
1457         if not args:
1458
1459             pattern = '%s*.log' % self.prefix
1460             args = self.args_to_files([pattern], tail)
1461
1462             if not args:
1463                 if self.chdir:
1464                     directory = self.chdir
1465                 else:
1466                     directory = os.getcwd()
1467
1468                 sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1469                 sys.stderr.write('%s  No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1470                 sys.stderr.write('%s  Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1471                 sys.exit(1)
1472
1473         else:
1474
1475             args = self.args_to_files(args, tail)
1476
1477         cwd_ = os.getcwd() + os.sep
1478
1479         if format == 'ascii':
1480
1481             columns = ("Total", "SConscripts", "SCons", "commands")
1482             self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1483
1484         elif format == 'gnuplot':
1485
1486             results = self.collect_results(args, self.get_debug_times,
1487                                            self.time_strings[which])
1488
1489             self.gnuplot_results(results, fmt='%s %.6f')
1490
1491         else:
1492
1493             sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1494             sys.exit(1)
1495
1496 if __name__ == '__main__':
1497     opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1498
1499     ST = SConsTimer()
1500
1501     for o, a in opts:
1502         if o in ('-?', '-h', '--help'):
1503             ST.do_help(['help'])
1504             sys.exit(0)
1505         elif o in ('-V', '--version'):
1506             sys.stdout.write('scons-time version\n')
1507             sys.exit(0)
1508
1509     if not args:
1510         sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1511         sys.exit(1)
1512
1513     ST.execute_subcommand(args)