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
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.
33 from __future__ import nested_scopes
34 from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
51 # Pre-2.2 Python has no False keyword.
57 # Pre-2.2 Python has no True keyword.
63 # Pre-2.4 Python has no sorted() function.
65 # The pre-2.4 Python list.sort() method does not support
66 # list.sort(key=) nor list.sort(reverse=) keyword arguments, so
67 # we must implement the functionality of those keyword arguments
68 # by hand instead of passing them to list.sort().
69 def sorted(iterable, cmp=None, key=None, reverse=False):
71 result = [(key(x), x) for x in iterable]
75 # Pre-2.3 Python does not support list.sort(None).
80 result = [t1 for t0,t1 in result]
85 def make_temp_file(**kw):
87 result = tempfile.mktemp(**kw)
89 result = os.path.realpath(result)
90 except AttributeError:
91 # Python 2.1 has no os.path.realpath() method.
95 save_template = tempfile.template
98 tempfile.template = prefix
99 result = tempfile.mktemp(**kw)
101 tempfile.template = save_template
104 def HACK_for_exec(cmd, *args):
106 For some reason, Python won't allow an exec() within a function
107 that also declares an internal function (including lambda functions).
108 This function is a hack that calls exec() in a function with no
111 if not args: exec(cmd)
112 elif len(args) == 1: exec cmd in args[0]
113 else: exec cmd in args[0], args[1]
116 def increment_size(self, largest):
118 Return the size of each horizontal increment line for a specified
119 maximum value. This returns a value that will provide somewhere
120 between 5 and 9 horizontal lines on the graph, on some set of
121 boundaries that are multiples of 10/100/1000/etc.
129 multiplier = multiplier * 10
130 return i * multiplier
132 def max_graph_value(self, largest):
133 # Round up to next integer.
134 largest = int(largest) + 1
135 increment = self.increment_size(largest)
136 return ((largest + increment - 1) / increment) * increment
139 def __init__(self, points, type, title, label, comment, fmt="%s %s"):
144 self.comment = comment
147 def print_label(self, inx, x, y):
149 print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
151 def plot_string(self):
153 title_string = 'title "%s"' % self.title
155 title_string = 'notitle'
156 return "'-' %s with lines lt %s" % (title_string, self.type)
158 def print_points(self, fmt=None):
162 print '# %s' % self.comment
163 for x, y in self.points:
164 # If y is None, it usually represents some kind of break
165 # in the line's index number. We might want to represent
166 # this some way rather than just drawing the line straight
167 # between the two points on either side.
172 def get_x_values(self):
173 return [ p[0] for p in self.points ]
175 def get_y_values(self):
176 return [ p[1] for p in self.points ]
178 class Gnuplotter(Plotter):
180 def __init__(self, title, key_location):
183 self.key_location = key_location
185 def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
187 line = Line(points, type, title, label, comment, fmt)
188 self.lines.append(line)
190 def plot_string(self, line):
191 return line.plot_string()
193 def vertical_bar(self, x, type, label, comment):
194 if self.get_min_x() <= x and x <= self.get_max_x():
195 points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
196 self.line(points, type, label, comment)
198 def get_all_x_values(self):
200 for line in self.lines:
201 result.extend(line.get_x_values())
202 return [r for r in result if not r is None]
204 def get_all_y_values(self):
206 for line in self.lines:
207 result.extend(line.get_y_values())
208 return [r for r in result if not r is None]
213 except AttributeError:
215 self.min_x = min(self.get_all_x_values())
223 except AttributeError:
225 self.max_x = max(self.get_all_x_values())
233 except AttributeError:
235 self.min_y = min(self.get_all_y_values())
243 except AttributeError:
245 self.max_y = max(self.get_all_y_values())
256 print 'set title "%s"' % self.title
257 print 'set key %s' % self.key_location
259 min_y = self.get_min_y()
260 max_y = self.max_graph_value(self.get_max_y())
261 incr = (max_y - min_y) / 10.0
262 start = min_y + (max_y / 2.0) + (2.0 * incr)
263 position = [ start - (i * incr) for i in range(5) ]
266 for line in self.lines:
267 line.print_label(inx, line.points[0][0]-1,
268 position[(inx-1) % len(position)])
271 plot_strings = [ self.plot_string(l) for l in self.lines ]
272 print 'plot ' + ', \\\n '.join(plot_strings)
274 for line in self.lines:
281 tar = tarfile.open(name=fname, mode='r')
288 zf = zipfile.ZipFile(fname, 'r')
289 for name in zf.namelist():
290 dir = os.path.dirname(name)
295 open(name, 'w').write(zf.read(name))
298 def read_files(arg, dirname, fnames):
300 fn = os.path.join(dirname, fn)
301 if os.path.isfile(fn):
302 open(fn, 'rb').read()
303 os.path.walk('.', read_files, None)
305 def redirect_to_file(command, log):
306 return '%s > %s 2>&1' % (command, log)
308 def tee_to_file(command, log):
309 return '%s 2>&1 | tee %s' % (command, log)
315 Usage: scons-time SUBCOMMAND [ARGUMENTS]
316 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
318 Available subcommands:
319 func Extract test-run data for a function
321 mem Extract --debug=memory data from test runs
322 obj Extract --debug=count data from test runs
323 time Extract --debug=time data from test runs
324 run Runs a test configuration
328 name_spaces = ' '*len(name)
333 default_settings = makedict(
335 aegis_project = None,
338 initial_commands = [],
339 key_location = 'bottom left',
340 orig_cwd = os.getcwd(),
343 python = '"%s"' % sys.executable,
344 redirect = redirect_to_file,
346 scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
347 scons_lib_dir = None,
348 scons_wrapper = None,
349 startup_targets = '--help',
351 subversion_url = None,
365 '.tar.gz' : (untar, '%(tar)s xzf %%s'),
366 '.tgz' : (untar, '%(tar)s xzf %%s'),
367 '.tar' : (untar, '%(tar)s xf %%s'),
368 '.zip' : (unzip, '%(unzip)s %%s'),
379 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
380 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
381 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
392 'pre-read' : 'Memory before reading SConscript files:',
393 'post-read' : 'Memory after reading SConscript files:',
394 'pre-build' : 'Memory before building targets:',
395 'post-build' : 'Memory after building targets:',
398 memory_string_all = 'Memory '
400 default_stage = stages[-1]
403 'total' : 'Total build time',
404 'SConscripts' : 'Total SConscript file execution time',
405 'SCons' : 'Total SCons execution time',
406 'commands' : 'Total command execution time',
409 time_string_all = 'Total .* time'
414 self.__dict__.update(self.default_settings)
416 # Functions for displaying and executing commands.
418 def subst(self, x, dictionary):
420 return x % dictionary
422 # x isn't a string (it's probably a Python function),
426 def subst_variables(self, command, dictionary):
428 Substitutes (via the format operator) the values in the specified
429 dictionary into the specified command.
431 The command can be an (action, string) tuple. In all cases, we
432 perform substitution on strings and don't worry if something isn't
433 a string. (It's probably a Python function to be executed.)
445 action = self.subst(action, dictionary)
446 string = self.subst(string, dictionary)
447 return (action, string, args)
449 def _do_not_display(self, msg, *args):
452 def display(self, msg, *args):
454 Displays the specified message.
456 Each message is prepended with a standard prefix of our name
466 sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
468 def _do_not_execute(self, action, *args):
471 def execute(self, action, *args):
473 Executes the specified action.
475 The action is called if it's a callable Python function, and
476 otherwise passed to os.system().
481 os.system(action % args)
483 def run_command_list(self, commands, dict):
485 Executes a list of commands, substituting values from the
486 specified dictionary.
488 commands = [ self.subst_variables(c, dict) for c in commands ]
489 for action, string, args in commands:
490 self.display(string, *args)
492 status = self.execute(action, *args)
496 def log_display(self, command, log):
497 command = self.subst(command, self.__dict__)
499 command = self.redirect(command, log)
502 def log_execute(self, command, log):
503 command = self.subst(command, self.__dict__)
504 output = os.popen(command).read()
506 sys.stdout.write(output)
507 open(log, 'wb').write(output)
511 def archive_splitext(self, path):
513 Splits an archive name into a filename base and extension.
515 This is like os.path.splitext() (which it calls) except that it
516 also looks for '.tar.gz' and treats it as an atomic extensions.
518 if path.endswith('.tar.gz'):
519 return path[:-7], path[-7:]
521 return os.path.splitext(path)
523 def args_to_files(self, args, tail=None):
525 Takes a list of arguments, expands any glob patterns, and
526 returns the last "tail" files from the list.
530 files.extend(sorted(glob.glob(a)))
533 files = files[-tail:]
537 def ascii_table(self, files, columns,
538 line_function, file_function=lambda x: x,
541 header_fmt = ' '.join(['%12s'] * len(columns))
542 line_fmt = header_fmt + ' %s'
544 print header_fmt % columns
547 t = line_function(file, *args, **kw)
550 diff = len(columns) - len(t)
553 t.append(file_function(file))
554 print line_fmt % tuple(t)
556 def collect_results(self, files, function, *args, **kw):
560 base = os.path.splitext(file)[0]
561 run, index = base.split('-')[-2:]
566 value = function(file, *args, **kw)
573 r.append((run, value))
577 def doc_to_help(self, obj):
579 Translates an object's __doc__ string into help text.
581 This strips a consistent number of spaces from each line in the
582 help text, essentially "outdenting" the text to the left-most
588 return self.outdent(doc)
590 def find_next_run_number(self, dir, prefix):
592 Returns the next run number in a directory for the specified prefix.
594 Examines the contents the specified directory for files with the
595 specified prefix, extracts the run numbers from each file name,
596 and returns the next run number after the largest it finds.
598 x = re.compile(re.escape(prefix) + '-([0-9]+).*')
599 matches = [x.match(e) for e in os.listdir(dir)]
600 matches = [_f for _f in matches if _f]
603 run_numbers = [int(m.group(1)) for m in matches]
604 return int(max(run_numbers)) + 1
606 def gnuplot_results(self, results, fmt='%s %.3f'):
608 Prints out a set of results in Gnuplot format.
610 gp = Gnuplotter(self.title, self.key_location)
612 for i in sorted(results.keys()):
614 t = self.run_titles[i]
618 gp.line(results[i], i+1, t, None, t, fmt=fmt)
620 for bar_tuple in self.vertical_bars:
622 x, type, label, comment = bar_tuple
624 x, type, label = bar_tuple
626 gp.vertical_bar(x, type, label, comment)
630 def logfile_name(self, invocation):
632 Returns the absolute path of a log file for the specificed
635 name = self.prefix_run + '-%d.log' % invocation
636 return os.path.join(self.outdir, name)
638 def outdent(self, s):
640 Strip as many spaces from each line as are found at the beginning
641 of the first line in the list.
643 lines = s.split('\n')
646 spaces = re.match(' *', lines[0]).group(0)
647 def strip_initial_spaces(l, s=spaces):
648 if l.startswith(spaces):
651 return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
653 def profile_name(self, invocation):
655 Returns the absolute path of a profile file for the specified
658 name = self.prefix_run + '-%d.prof' % invocation
659 return os.path.join(self.outdir, name)
661 def set_env(self, key, value):
662 os.environ[key] = value
666 def get_debug_times(self, file, time_string=None):
668 Fetch times from the --debug=time strings in the specified file.
670 if time_string is None:
671 search_string = self.time_string_all
673 search_string = time_string
674 contents = open(file).read()
676 sys.stderr.write('file %s has no contents!\n' % repr(file))
678 result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
679 result = [ float(r) for r in result ]
680 if not time_string is None:
684 sys.stderr.write('file %s has no results!\n' % repr(file))
688 def get_function_profile(self, file, function):
690 Returns the file, line number, function name, and cumulative time.
694 except ImportError, e:
695 sys.stderr.write('%s: func: %s\n' % (self.name, e))
696 sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
697 sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
699 statistics = pstats.Stats(file).stats
700 matches = [ e for e in statistics.items() if e[0][2] == function ]
702 return r[0][0], r[0][1], r[0][2], r[1][3]
704 def get_function_time(self, file, function):
706 Returns just the cumulative time for the specified function.
708 return self.get_function_profile(file, function)[3]
710 def get_memory(self, file, memory_string=None):
712 Returns a list of integers of the amount of memory used. The
713 default behavior is to return all the stages.
715 if memory_string is None:
716 search_string = self.memory_string_all
718 search_string = memory_string
719 lines = open(file).readlines()
720 lines = [ l for l in lines if l.startswith(search_string) ][-4:]
721 result = [ int(l.split()[-1]) for l in lines[-4:] ]
726 def get_object_counts(self, file, object_name, index=None):
728 Returns the counts of the specified object_name.
730 object_string = ' ' + object_name + '\n'
731 lines = open(file).readlines()
732 line = [ l for l in lines if l.endswith(object_string) ][0]
733 result = [ int(field) for field in line.split()[:4] ]
734 if index is not None:
735 result = result[index]
742 def execute_subcommand(self, argv):
744 Executes the do_*() function for the specified subcommand (argv[0]).
748 cmdName = self.command_alias.get(argv[0], argv[0])
750 func = getattr(self, 'do_' + cmdName)
751 except AttributeError:
752 return self.default(argv)
756 sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
758 traceback.print_exc(file=sys.stderr)
759 sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
761 def default(self, argv):
763 The default behavior for an unknown subcommand. Prints an
764 error message and exits.
766 sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
767 sys.stderr.write('Type "%s help" for usage.\n' % self.name)
772 def do_help(self, argv):
778 func = getattr(self, 'do_' + arg)
779 except AttributeError:
780 sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
783 help = getattr(self, 'help_' + arg)
784 except AttributeError:
785 sys.stdout.write(self.doc_to_help(func))
790 doc = self.doc_to_help(self.__class__)
792 sys.stdout.write(doc)
800 Usage: scons-time func [OPTIONS] FILE [...]
802 -C DIR, --chdir=DIR Change to DIR before looking for files
803 -f FILE, --file=FILE Read configuration from specified FILE
804 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
805 --func=NAME, --function=NAME Report time for function NAME
806 -h, --help Print this help and exit
807 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
808 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
809 --title=TITLE Specify the output plot TITLE
811 sys.stdout.write(self.outdent(help))
814 def do_func(self, argv):
818 function_name = '_main'
821 short_opts = '?C:f:hp:t:'
836 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
839 if o in ('-C', '--chdir'):
841 elif o in ('-f', '--file'):
843 elif o in ('--fmt', '--format'):
845 elif o in ('--func', '--function'):
847 elif o in ('-?', '-h', '--help'):
848 self.do_help(['help', 'func'])
850 elif o in ('--max',):
852 elif o in ('-p', '--prefix'):
854 elif o in ('-t', '--tail'):
856 elif o in ('--title',):
860 exec open(self.config_file, 'rU').read() in self.__dict__
867 pattern = '%s*.prof' % self.prefix
868 args = self.args_to_files([pattern], tail)
872 directory = self.chdir
874 directory = os.getcwd()
876 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
877 sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
878 sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
883 args = self.args_to_files(args, tail)
885 cwd_ = os.getcwd() + os.sep
887 if format == 'ascii':
891 f, line, func, time = \
892 self.get_function_profile(file, function_name)
893 except ValueError, e:
894 sys.stderr.write("%s: func: %s: %s\n" %
895 (self.name, file, e))
897 if f.startswith(cwd_):
899 print "%.3f %s:%d(%s)" % (time, f, line, func)
901 elif format == 'gnuplot':
903 results = self.collect_results(args, self.get_function_time,
906 self.gnuplot_results(results)
910 sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
917 Usage: scons-time mem [OPTIONS] FILE [...]
919 -C DIR, --chdir=DIR Change to DIR before looking for files
920 -f FILE, --file=FILE Read configuration from specified FILE
921 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
922 -h, --help Print this help and exit
923 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
924 --stage=STAGE Plot memory at the specified stage:
925 pre-read, post-read, pre-build,
926 post-build (default: post-build)
927 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
928 --title=TITLE Specify the output plot TITLE
930 sys.stdout.write(self.outdent(help))
933 def do_mem(self, argv):
936 logfile_path = lambda x: x
937 stage = self.default_stage
940 short_opts = '?C:f:hp:t:'
954 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
957 if o in ('-C', '--chdir'):
959 elif o in ('-f', '--file'):
961 elif o in ('--fmt', '--format'):
963 elif o in ('-?', '-h', '--help'):
964 self.do_help(['help', 'mem'])
966 elif o in ('-p', '--prefix'):
968 elif o in ('--stage',):
969 if not a in self.stages:
970 sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
973 elif o in ('-t', '--tail'):
975 elif o in ('--title',):
979 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
983 logfile_path = lambda x: os.path.join(self.chdir, x)
987 pattern = '%s*.log' % self.prefix
988 args = self.args_to_files([pattern], tail)
992 directory = self.chdir
994 directory = os.getcwd()
996 sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
997 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
998 sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
1003 args = self.args_to_files(args, tail)
1005 cwd_ = os.getcwd() + os.sep
1007 if format == 'ascii':
1009 self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
1011 elif format == 'gnuplot':
1013 results = self.collect_results(args, self.get_memory,
1014 self.stage_strings[stage])
1016 self.gnuplot_results(results)
1020 sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1029 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1031 -C DIR, --chdir=DIR Change to DIR before looking for files
1032 -f FILE, --file=FILE Read configuration from specified FILE
1033 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1034 -h, --help Print this help and exit
1035 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1036 --stage=STAGE Plot memory at the specified stage:
1037 pre-read, post-read, pre-build,
1038 post-build (default: post-build)
1039 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1040 --title=TITLE Specify the output plot TITLE
1042 sys.stdout.write(self.outdent(help))
1045 def do_obj(self, argv):
1048 logfile_path = lambda x: x
1049 stage = self.default_stage
1052 short_opts = '?C:f:hp:t:'
1066 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1069 if o in ('-C', '--chdir'):
1071 elif o in ('-f', '--file'):
1072 self.config_file = a
1073 elif o in ('--fmt', '--format'):
1075 elif o in ('-?', '-h', '--help'):
1076 self.do_help(['help', 'obj'])
1078 elif o in ('-p', '--prefix'):
1080 elif o in ('--stage',):
1081 if not a in self.stages:
1082 sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1083 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1086 elif o in ('-t', '--tail'):
1088 elif o in ('--title',):
1092 sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1093 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1096 object_name = args.pop(0)
1098 if self.config_file:
1099 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1102 os.chdir(self.chdir)
1103 logfile_path = lambda x: os.path.join(self.chdir, x)
1107 pattern = '%s*.log' % self.prefix
1108 args = self.args_to_files([pattern], tail)
1112 directory = self.chdir
1114 directory = os.getcwd()
1116 sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1117 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1118 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1123 args = self.args_to_files(args, tail)
1125 cwd_ = os.getcwd() + os.sep
1127 if format == 'ascii':
1129 self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1131 elif format == 'gnuplot':
1134 for s in self.stages:
1137 stage_index = stage_index + 1
1139 results = self.collect_results(args, self.get_object_counts,
1140 object_name, stage_index)
1142 self.gnuplot_results(results)
1146 sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1155 Usage: scons-time run [OPTIONS] [FILE ...]
1157 --aegis=PROJECT Use SCons from the Aegis PROJECT
1158 --chdir=DIR Name of unpacked directory for chdir
1159 -f FILE, --file=FILE Read configuration from specified FILE
1160 -h, --help Print this help and exit
1161 -n, --no-exec No execute, just print command lines
1162 --number=NUMBER Put output in files for run NUMBER
1163 --outdir=OUTDIR Put output files in OUTDIR
1164 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1165 --python=PYTHON Time using the specified PYTHON
1166 -q, --quiet Don't print command lines
1167 --scons=SCONS Time using the specified SCONS
1168 --svn=URL, --subversion=URL Use SCons from Subversion URL
1169 -v, --verbose Display output of commands
1171 sys.stdout.write(self.outdent(help))
1174 def do_run(self, argv):
1177 run_number_list = [None]
1179 short_opts = '?f:hnp:qs:v'
1198 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1201 if o in ('--aegis',):
1202 self.aegis_project = a
1203 elif o in ('-f', '--file'):
1204 self.config_file = a
1205 elif o in ('-?', '-h', '--help'):
1206 self.do_help(['help', 'run'])
1208 elif o in ('-n', '--no-exec'):
1209 self.execute = self._do_not_execute
1210 elif o in ('--number',):
1211 run_number_list = self.split_run_numbers(a)
1212 elif o in ('--outdir',):
1214 elif o in ('-p', '--prefix'):
1216 elif o in ('--python',):
1218 elif o in ('-q', '--quiet'):
1219 self.display = self._do_not_display
1220 elif o in ('-s', '--subdir'):
1222 elif o in ('--scons',):
1224 elif o in ('--svn', '--subversion'):
1225 self.subversion_url = a
1226 elif o in ('-v', '--verbose'):
1227 self.redirect = tee_to_file
1229 self.svn_co_flag = ''
1231 if not args and not self.config_file:
1232 sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1233 sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1236 if self.config_file:
1237 exec open(self.config_file, 'rU').read() in self.__dict__
1240 self.archive_list = args
1242 archive_file_name = os.path.split(self.archive_list[0])[1]
1245 self.subdir = self.archive_splitext(archive_file_name)[0]
1248 self.prefix = self.archive_splitext(archive_file_name)[0]
1251 if self.subversion_url:
1252 prepare = self.prep_subversion_run
1253 elif self.aegis_project:
1254 prepare = self.prep_aegis_run
1256 for run_number in run_number_list:
1257 self.individual_run(run_number, self.archive_list, prepare)
1259 def split_run_numbers(self, s):
1261 for n in s.split(','):
1265 result.append(int(n))
1267 result.extend(list(range(int(x), int(y)+1)))
1270 def scons_path(self, dir):
1271 return os.path.join(dir, 'src', 'script', 'scons.py')
1273 def scons_lib_dir_path(self, dir):
1274 return os.path.join(dir, 'src', 'engine')
1276 def prep_aegis_run(self, commands, removals):
1277 self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1278 removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1280 self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1281 self.scons = self.scons_path(self.aegis_tmpdir)
1282 self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1285 'mkdir %(aegis_tmpdir)s',
1286 (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1287 '%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1288 '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1291 def prep_subversion_run(self, commands, removals):
1292 self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1293 removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1295 self.scons = self.scons_path(self.svn_tmpdir)
1296 self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1299 'mkdir %(svn_tmpdir)s',
1300 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1303 def individual_run(self, run_number, archive_list, prepare=None):
1305 Performs an individual run of the default SCons invocations.
1312 prepare(commands, removals)
1314 save_scons = self.scons
1315 save_scons_wrapper = self.scons_wrapper
1316 save_scons_lib_dir = self.scons_lib_dir
1318 if self.outdir is None:
1319 self.outdir = self.orig_cwd
1320 elif not os.path.isabs(self.outdir):
1321 self.outdir = os.path.join(self.orig_cwd, self.outdir)
1323 if self.scons is None:
1324 self.scons = self.scons_path(self.orig_cwd)
1326 if self.scons_lib_dir is None:
1327 self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1329 if self.scons_wrapper is None:
1330 self.scons_wrapper = self.scons
1333 run_number = self.find_next_run_number(self.outdir, self.prefix)
1335 self.run_number = str(run_number)
1337 self.prefix_run = self.prefix + '-%03d' % run_number
1339 if self.targets0 is None:
1340 self.targets0 = self.startup_targets
1341 if self.targets1 is None:
1342 self.targets1 = self.targets
1343 if self.targets2 is None:
1344 self.targets2 = self.targets
1346 self.tmpdir = make_temp_file(prefix = self.name + '-')
1351 (os.chdir, 'cd %%s', self.tmpdir),
1354 for archive in archive_list:
1355 if not os.path.isabs(archive):
1356 archive = os.path.join(self.orig_cwd, archive)
1357 if os.path.isdir(archive):
1358 dest = os.path.split(archive)[1]
1359 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1361 suffix = self.archive_splitext(archive)[1]
1362 unpack_command = self.unpack_map.get(suffix)
1363 if not unpack_command:
1364 dest = os.path.split(archive)[1]
1365 commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1367 commands.append(unpack_command + (archive,))
1370 (os.chdir, 'cd %%s', self.subdir),
1373 commands.extend(self.initial_commands)
1376 (lambda: read_tree('.'),
1377 'find * -type f | xargs cat > /dev/null'),
1379 (self.set_env, 'export %%s=%%s',
1380 'SCONS_LIB_DIR', self.scons_lib_dir),
1382 '%(python)s %(scons_wrapper)s --version',
1386 for run_command in self.run_commands:
1387 setattr(self, 'prof%d' % index, self.profile_name(index))
1392 self.logfile_name(index),
1398 (os.chdir, 'cd %%s', self.orig_cwd),
1401 if not os.environ.get('PRESERVE'):
1402 commands.extend(removals)
1404 commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1406 self.run_command_list(commands, self.__dict__)
1408 self.scons = save_scons
1409 self.scons_lib_dir = save_scons_lib_dir
1410 self.scons_wrapper = save_scons_wrapper
1414 def help_time(self):
1416 Usage: scons-time time [OPTIONS] FILE [...]
1418 -C DIR, --chdir=DIR Change to DIR before looking for files
1419 -f FILE, --file=FILE Read configuration from specified FILE
1420 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1421 -h, --help Print this help and exit
1422 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1423 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1424 --which=TIMER Plot timings for TIMER: total,
1425 SConscripts, SCons, commands.
1427 sys.stdout.write(self.outdent(help))
1430 def do_time(self, argv):
1433 logfile_path = lambda x: x
1437 short_opts = '?C:f:hp:t:'
1451 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1454 if o in ('-C', '--chdir'):
1456 elif o in ('-f', '--file'):
1457 self.config_file = a
1458 elif o in ('--fmt', '--format'):
1460 elif o in ('-?', '-h', '--help'):
1461 self.do_help(['help', 'time'])
1463 elif o in ('-p', '--prefix'):
1465 elif o in ('-t', '--tail'):
1467 elif o in ('--title',):
1469 elif o in ('--which',):
1470 if not a in self.time_strings.keys():
1471 sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1472 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1476 if self.config_file:
1477 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1480 os.chdir(self.chdir)
1481 logfile_path = lambda x: os.path.join(self.chdir, x)
1485 pattern = '%s*.log' % self.prefix
1486 args = self.args_to_files([pattern], tail)
1490 directory = self.chdir
1492 directory = os.getcwd()
1494 sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1495 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1496 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1501 args = self.args_to_files(args, tail)
1503 cwd_ = os.getcwd() + os.sep
1505 if format == 'ascii':
1507 columns = ("Total", "SConscripts", "SCons", "commands")
1508 self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1510 elif format == 'gnuplot':
1512 results = self.collect_results(args, self.get_debug_times,
1513 self.time_strings[which])
1515 self.gnuplot_results(results, fmt='%s %.6f')
1519 sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1522 if __name__ == '__main__':
1523 opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1528 if o in ('-?', '-h', '--help'):
1529 ST.do_help(['help'])
1531 elif o in ('-V', '--version'):
1532 sys.stdout.write('scons-time version\n')
1536 sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1539 ST.execute_subcommand(args)
1543 # indent-tabs-mode:nil
1545 # vim: set expandtab tabstop=4 shiftwidth=4: