3 # scons-time - run SCons timings and collect statistics
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
12 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
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:
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
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.
34 from __future__ import nested_scopes
36 __revision__ = "src/script/scons-time.py 3842 2008/12/20 22:59:52 scons"
52 # Pre-2.2 Python has no False keyword.
54 __builtin__.False = not 1
59 # Pre-2.2 Python has no True keyword.
61 __builtin__.True = not 0
63 def make_temp_file(**kw):
65 result = tempfile.mktemp(**kw)
67 result = os.path.realpath(result)
68 except AttributeError:
69 # Python 2.1 has no os.path.realpath() method.
73 save_template = tempfile.template
76 tempfile.template = prefix
77 result = tempfile.mktemp(**kw)
79 tempfile.template = save_template
83 def increment_size(self, largest):
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.
96 multiplier = multiplier * 10
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
106 def __init__(self, points, type, title, label, comment, fmt="%s %s"):
111 self.comment = comment
114 def print_label(self, inx, x, y):
116 print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
118 def plot_string(self):
120 title_string = 'title "%s"' % self.title
122 title_string = 'notitle'
123 return "'-' %s with lines lt %s" % (title_string, self.type)
125 def print_points(self, fmt=None):
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.
139 def get_x_values(self):
140 return [ p[0] for p in self.points ]
142 def get_y_values(self):
143 return [ p[1] for p in self.points ]
145 class Gnuplotter(Plotter):
147 def __init__(self, title, key_location):
150 self.key_location = key_location
152 def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
154 line = Line(points, type, title, label, comment, fmt)
155 self.lines.append(line)
157 def plot_string(self, line):
158 return line.plot_string()
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)
165 def get_all_x_values(self):
167 for line in self.lines:
168 result.extend(line.get_x_values())
169 return filter(lambda r: not r is None, result)
171 def get_all_y_values(self):
173 for line in self.lines:
174 result.extend(line.get_y_values())
175 return filter(lambda r: not r is None, result)
180 except AttributeError:
182 self.min_x = min(self.get_all_x_values())
190 except AttributeError:
192 self.max_x = max(self.get_all_x_values())
200 except AttributeError:
202 self.min_y = min(self.get_all_y_values())
210 except AttributeError:
212 self.max_y = max(self.get_all_y_values())
223 print 'set title "%s"' % self.title
224 print 'set key %s' % self.key_location
226 min_y = self.get_min_y()
227 max_y = self.max_graph_value(self.get_max_y())
228 range = max_y - min_y
230 start = min_y + (max_y / 2.0) + (2.0 * incr)
231 position = [ start - (i * incr) for i in xrange(5) ]
234 for line in self.lines:
235 line.print_label(inx, line.points[0][0]-1,
236 position[(inx-1) % len(position)])
239 plot_strings = [ self.plot_string(l) for l in self.lines ]
240 print 'plot ' + ', \\\n '.join(plot_strings)
242 for line in self.lines:
249 tar = tarfile.open(name=fname, mode='r')
256 zf = zipfile.ZipFile(fname, 'r')
257 for name in zf.namelist():
258 dir = os.path.dirname(name)
263 open(name, 'w').write(zf.read(name))
266 def read_files(arg, dirname, 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)
273 def redirect_to_file(command, log):
274 return '%s > %s 2>&1' % (command, log)
276 def tee_to_file(command, log):
277 return '%s 2>&1 | tee %s' % (command, log)
283 Usage: scons-time SUBCOMMAND [ARGUMENTS]
284 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
286 Available subcommands:
287 func Extract test-run data for a function
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
296 name_spaces = ' '*len(name)
301 default_settings = makedict(
303 aegis_project = None,
306 initial_commands = [],
307 key_location = 'bottom left',
308 orig_cwd = os.getcwd(),
311 python = '"%s"' % sys.executable,
312 redirect = redirect_to_file,
314 scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
315 scons_lib_dir = None,
316 scons_wrapper = None,
317 startup_targets = '--help',
319 subversion_url = None,
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'),
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',
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:',
366 memory_string_all = 'Memory '
368 default_stage = stages[-1]
371 'total' : 'Total build time',
372 'SConscripts' : 'Total SConscript file execution time',
373 'SCons' : 'Total SCons execution time',
374 'commands' : 'Total command execution time',
377 time_string_all = 'Total .* time'
382 self.__dict__.update(self.default_settings)
384 # Functions for displaying and executing commands.
386 def subst(self, x, dictionary):
388 return x % dictionary
390 # x isn't a string (it's probably a Python function),
394 def subst_variables(self, command, dictionary):
396 Substitutes (via the format operator) the values in the specified
397 dictionary into the specified command.
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.)
413 action = self.subst(action, dictionary)
414 string = self.subst(string, dictionary)
415 return (action, string, args)
417 def _do_not_display(self, msg, *args):
420 def display(self, msg, *args):
422 Displays the specified message.
424 Each message is prepended with a standard prefix of our name
434 sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
436 def _do_not_execute(self, action, *args):
439 def execute(self, action, *args):
441 Executes the specified action.
443 The action is called if it's a callable Python function, and
444 otherwise passed to os.system().
449 os.system(action % args)
451 def run_command_list(self, commands, dict):
453 Executes a list of commands, substituting values from the
454 specified dictionary.
456 commands = [ self.subst_variables(c, dict) for c in commands ]
457 for action, string, args in commands:
458 self.display(string, *args)
460 status = self.execute(action, *args)
464 def log_display(self, command, log):
465 command = self.subst(command, self.__dict__)
467 command = self.redirect(command, log)
470 def log_execute(self, command, log):
471 command = self.subst(command, self.__dict__)
472 output = os.popen(command).read()
474 sys.stdout.write(output)
475 open(log, 'wb').write(output)
479 def archive_splitext(self, path):
481 Splits an archive name into a filename base and extension.
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.
486 if path.endswith('.tar.gz'):
487 return path[:-7], path[-7:]
489 return os.path.splitext(path)
491 def args_to_files(self, args, tail=None):
493 Takes a list of arguments, expands any glob patterns, and
494 returns the last "tail" files from the list.
503 files = files[-tail:]
507 def ascii_table(self, files, columns,
508 line_function, file_function=lambda x: x,
511 header_fmt = ' '.join(['%12s'] * len(columns))
512 line_fmt = header_fmt + ' %s'
514 print header_fmt % columns
517 t = line_function(file, *args, **kw)
520 diff = len(columns) - len(t)
523 t.append(file_function(file))
524 print line_fmt % tuple(t)
526 def collect_results(self, files, function, *args, **kw):
530 base = os.path.splitext(file)[0]
531 run, index = string.split(base, '-')[-2:]
536 value = function(file, *args, **kw)
543 r.append((run, value))
547 def doc_to_help(self, obj):
549 Translates an object's __doc__ string into help text.
551 This strips a consistent number of spaces from each line in the
552 help text, essentially "outdenting" the text to the left-most
558 return self.outdent(doc)
560 def find_next_run_number(self, dir, prefix):
562 Returns the next run number in a directory for the specified prefix.
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.
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)
573 run_numbers = map(lambda m: int(m.group(1)), matches)
574 return int(max(run_numbers)) + 1
576 def gnuplot_results(self, results, fmt='%s %.3f'):
578 Prints out a set of results in Gnuplot format.
580 gp = Gnuplotter(self.title, self.key_location)
582 indices = results.keys()
587 t = self.run_titles[i]
591 gp.line(results[i], i+1, t, None, t, fmt=fmt)
593 for bar_tuple in self.vertical_bars:
595 x, type, label, comment = bar_tuple
597 x, type, label = bar_tuple
599 gp.vertical_bar(x, type, label, comment)
603 def logfile_name(self, invocation):
605 Returns the absolute path of a log file for the specificed
608 name = self.prefix_run + '-%d.log' % invocation
609 return os.path.join(self.outdir, name)
611 def outdent(self, s):
613 Strip as many spaces from each line as are found at the beginning
614 of the first line in the list.
616 lines = s.split('\n')
619 spaces = re.match(' *', lines[0]).group(0)
620 def strip_initial_spaces(l, s=spaces):
621 if l.startswith(spaces):
624 return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
626 def profile_name(self, invocation):
628 Returns the absolute path of a profile file for the specified
631 name = self.prefix_run + '-%d.prof' % invocation
632 return os.path.join(self.outdir, name)
634 def set_env(self, key, value):
635 os.environ[key] = value
639 def get_debug_times(self, file, time_string=None):
641 Fetch times from the --debug=time strings in the specified file.
643 if time_string is None:
644 search_string = self.time_string_all
646 search_string = time_string
647 contents = open(file).read()
649 sys.stderr.write('file %s has no contents!\n' % repr(file))
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:
657 sys.stderr.write('file %s has no results!\n' % repr(file))
661 def get_function_profile(self, file, function):
663 Returns the file, line number, function name, and cumulative time.
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)
672 statistics = pstats.Stats(file).stats
673 matches = [ e for e in statistics.items() if e[0][2] == function ]
675 return r[0][0], r[0][1], r[0][2], r[1][3]
677 def get_function_time(self, file, function):
679 Returns just the cumulative time for the specified function.
681 return self.get_function_profile(file, function)[3]
683 def get_memory(self, file, memory_string=None):
685 Returns a list of integers of the amount of memory used. The
686 default behavior is to return all the stages.
688 if memory_string is None:
689 search_string = self.memory_string_all
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:] ]
699 def get_object_counts(self, file, object_name, index=None):
701 Returns the counts of the specified object_name.
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]
715 def execute_subcommand(self, argv):
717 Executes the do_*() function for the specified subcommand (argv[0]).
721 cmdName = self.command_alias.get(argv[0], argv[0])
723 func = getattr(self, 'do_' + cmdName)
724 except AttributeError:
725 return self.default(argv)
729 sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
731 traceback.print_exc(file=sys.stderr)
732 sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
734 def default(self, argv):
736 The default behavior for an unknown subcommand. Prints an
737 error message and exits.
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)
745 def do_help(self, argv):
751 func = getattr(self, 'do_' + arg)
752 except AttributeError:
753 sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
756 help = getattr(self, 'help_' + arg)
757 except AttributeError:
758 sys.stdout.write(self.doc_to_help(func))
763 doc = self.doc_to_help(self.__class__)
765 sys.stdout.write(doc)
773 Usage: scons-time func [OPTIONS] FILE [...]
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
784 sys.stdout.write(self.outdent(help))
787 def do_func(self, argv):
791 function_name = '_main'
794 short_opts = '?C:f:hp:t:'
809 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
812 if o in ('-C', '--chdir'):
814 elif o in ('-f', '--file'):
816 elif o in ('--fmt', '--format'):
818 elif o in ('--func', '--function'):
820 elif o in ('-?', '-h', '--help'):
821 self.do_help(['help', 'func'])
823 elif o in ('--max',):
825 elif o in ('-p', '--prefix'):
827 elif o in ('-t', '--tail'):
829 elif o in ('--title',):
833 execfile(self.config_file, self.__dict__)
840 pattern = '%s*.prof' % self.prefix
841 args = self.args_to_files([pattern], tail)
845 directory = self.chdir
847 directory = os.getcwd()
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))
856 args = self.args_to_files(args, tail)
858 cwd_ = os.getcwd() + os.sep
860 if format == 'ascii':
862 def print_function_timing(file, func):
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))
868 if f.startswith(cwd_):
870 print "%.3f %s:%d(%s)" % (time, f, line, func)
873 print_function_timing(file, function_name)
875 elif format == 'gnuplot':
877 results = self.collect_results(args, self.get_function_time,
880 self.gnuplot_results(results)
884 sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
891 Usage: scons-time mem [OPTIONS] FILE [...]
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
904 sys.stdout.write(self.outdent(help))
907 def do_mem(self, argv):
910 logfile_path = lambda x: x
911 stage = self.default_stage
914 short_opts = '?C:f:hp:t:'
928 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
931 if o in ('-C', '--chdir'):
933 elif o in ('-f', '--file'):
935 elif o in ('--fmt', '--format'):
937 elif o in ('-?', '-h', '--help'):
938 self.do_help(['help', 'mem'])
940 elif o in ('-p', '--prefix'):
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))
947 elif o in ('-t', '--tail'):
949 elif o in ('--title',):
953 execfile(self.config_file, self.__dict__)
957 logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
961 pattern = '%s*.log' % self.prefix
962 args = self.args_to_files([pattern], tail)
966 directory = self.chdir
968 directory = os.getcwd()
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))
977 args = self.args_to_files(args, tail)
979 cwd_ = os.getcwd() + os.sep
981 if format == 'ascii':
983 self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
985 elif format == 'gnuplot':
987 results = self.collect_results(args, self.get_memory,
988 self.stage_strings[stage])
990 self.gnuplot_results(results)
994 sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1003 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
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
1016 sys.stdout.write(self.outdent(help))
1019 def do_obj(self, argv):
1022 logfile_path = lambda x: x
1023 stage = self.default_stage
1026 short_opts = '?C:f:hp:t:'
1040 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1043 if o in ('-C', '--chdir'):
1045 elif o in ('-f', '--file'):
1046 self.config_file = a
1047 elif o in ('--fmt', '--format'):
1049 elif o in ('-?', '-h', '--help'):
1050 self.do_help(['help', 'obj'])
1052 elif o in ('-p', '--prefix'):
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))
1060 elif o in ('-t', '--tail'):
1062 elif o in ('--title',):
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))
1070 object_name = args.pop(0)
1072 if self.config_file:
1073 execfile(self.config_file, self.__dict__)
1076 os.chdir(self.chdir)
1077 logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
1081 pattern = '%s*.log' % self.prefix
1082 args = self.args_to_files([pattern], tail)
1086 directory = self.chdir
1088 directory = os.getcwd()
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))
1097 args = self.args_to_files(args, tail)
1099 cwd_ = os.getcwd() + os.sep
1101 if format == 'ascii':
1103 self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1105 elif format == 'gnuplot':
1108 for s in self.stages:
1111 stage_index = stage_index + 1
1113 results = self.collect_results(args, self.get_object_counts,
1114 object_name, stage_index)
1116 self.gnuplot_results(results)
1120 sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1129 Usage: scons-time run [OPTIONS] [FILE ...]
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
1145 sys.stdout.write(self.outdent(help))
1148 def do_run(self, argv):
1151 run_number_list = [None]
1153 short_opts = '?f:hnp:qs:v'
1172 opts, args = getopt.getopt(argv[1:], short_opts, long_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'])
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',):
1188 elif o in ('-p', '--prefix'):
1190 elif o in ('--python',):
1192 elif o in ('-q', '--quiet'):
1193 self.display = self._do_not_display
1194 elif o in ('-s', '--subdir'):
1196 elif o in ('--scons',):
1198 elif o in ('--svn', '--subversion'):
1199 self.subversion_url = a
1200 elif o in ('-v', '--verbose'):
1201 self.redirect = tee_to_file
1203 self.svn_co_flag = ''
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))
1210 if self.config_file:
1211 execfile(self.config_file, self.__dict__)
1214 self.archive_list = args
1216 archive_file_name = os.path.split(self.archive_list[0])[1]
1219 self.subdir = self.archive_splitext(archive_file_name)[0]
1222 self.prefix = self.archive_splitext(archive_file_name)[0]
1225 if self.subversion_url:
1226 prepare = self.prep_subversion_run
1227 elif self.aegis_project:
1228 prepare = self.prep_aegis_run
1230 for run_number in run_number_list:
1231 self.individual_run(run_number, self.archive_list, prepare)
1233 def split_run_numbers(self, s):
1235 for n in s.split(','):
1239 result.append(int(n))
1241 result.extend(range(int(x), int(y)+1))
1244 def scons_path(self, dir):
1245 return os.path.join(dir, 'src', 'script', 'scons.py')
1247 def scons_lib_dir_path(self, dir):
1248 return os.path.join(dir, 'src', 'engine')
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))
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)
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 .',
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))
1269 self.scons = self.scons_path(self.svn_tmpdir)
1270 self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1273 'mkdir %(svn_tmpdir)s',
1274 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1277 def individual_run(self, run_number, archive_list, prepare=None):
1279 Performs an individual run of the default SCons invocations.
1286 prepare(commands, removals)
1288 save_scons = self.scons
1289 save_scons_wrapper = self.scons_wrapper
1290 save_scons_lib_dir = self.scons_lib_dir
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)
1297 if self.scons is None:
1298 self.scons = self.scons_path(self.orig_cwd)
1300 if self.scons_lib_dir is None:
1301 self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1303 if self.scons_wrapper is None:
1304 self.scons_wrapper = self.scons
1307 run_number = self.find_next_run_number(self.outdir, self.prefix)
1309 self.run_number = str(run_number)
1311 self.prefix_run = self.prefix + '-%03d' % run_number
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
1320 self.tmpdir = make_temp_file(prefix = self.name + '-')
1325 (os.chdir, 'cd %%s', self.tmpdir),
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))
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))
1341 commands.append(unpack_command + (archive,))
1344 (os.chdir, 'cd %%s', self.subdir),
1347 commands.extend(self.initial_commands)
1350 (lambda: read_tree('.'),
1351 'find * -type f | xargs cat > /dev/null'),
1353 (self.set_env, 'export %%s=%%s',
1354 'SCONS_LIB_DIR', self.scons_lib_dir),
1356 '%(python)s %(scons_wrapper)s --version',
1360 for run_command in self.run_commands:
1361 setattr(self, 'prof%d' % index, self.profile_name(index))
1366 self.logfile_name(index),
1372 (os.chdir, 'cd %%s', self.orig_cwd),
1375 if not os.environ.get('PRESERVE'):
1376 commands.extend(removals)
1378 commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1380 self.run_command_list(commands, self.__dict__)
1382 self.scons = save_scons
1383 self.scons_lib_dir = save_scons_lib_dir
1384 self.scons_wrapper = save_scons_wrapper
1388 def help_time(self):
1390 Usage: scons-time time [OPTIONS] FILE [...]
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.
1401 sys.stdout.write(self.outdent(help))
1404 def do_time(self, argv):
1407 logfile_path = lambda x: x
1411 short_opts = '?C:f:hp:t:'
1425 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1428 if o in ('-C', '--chdir'):
1430 elif o in ('-f', '--file'):
1431 self.config_file = a
1432 elif o in ('--fmt', '--format'):
1434 elif o in ('-?', '-h', '--help'):
1435 self.do_help(['help', 'time'])
1437 elif o in ('-p', '--prefix'):
1439 elif o in ('-t', '--tail'):
1441 elif o in ('--title',):
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))
1450 if self.config_file:
1451 execfile(self.config_file, self.__dict__)
1454 os.chdir(self.chdir)
1455 logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
1459 pattern = '%s*.log' % self.prefix
1460 args = self.args_to_files([pattern], tail)
1464 directory = self.chdir
1466 directory = os.getcwd()
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))
1475 args = self.args_to_files(args, tail)
1477 cwd_ = os.getcwd() + os.sep
1479 if format == 'ascii':
1481 columns = ("Total", "SConscripts", "SCons", "commands")
1482 self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1484 elif format == 'gnuplot':
1486 results = self.collect_results(args, self.get_debug_times,
1487 self.time_strings[which])
1489 self.gnuplot_results(results, fmt='%s %.6f')
1493 sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1496 if __name__ == '__main__':
1497 opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1502 if o in ('-?', '-h', '--help'):
1503 ST.do_help(['help'])
1505 elif o in ('-V', '--version'):
1506 sys.stdout.write('scons-time version\n')
1510 sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1513 ST.execute_subcommand(args)