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.
53 __builtin__.False = not 1
58 # Pre-2.2 Python has no True keyword.
60 __builtin__.True = not 0
65 # Pre-2.4 Python has no sorted() function.
67 # The pre-2.4 Python list.sort() method does not support
68 # list.sort(key=) nor list.sort(reverse=) keyword arguments, so
69 # we must implement the functionality of those keyword arguments
70 # by hand instead of passing them to list.sort().
71 def sorted(iterable, cmp=None, key=None, reverse=False):
73 result = [(key(x), x) for x in iterable]
77 # Pre-2.3 Python does not support list.sort(None).
82 result = [t1 for t0,t1 in result]
86 __builtin__.sorted = sorted
88 def make_temp_file(**kw):
90 result = tempfile.mktemp(**kw)
92 result = os.path.realpath(result)
93 except AttributeError:
94 # Python 2.1 has no os.path.realpath() method.
98 save_template = tempfile.template
101 tempfile.template = prefix
102 result = tempfile.mktemp(**kw)
104 tempfile.template = save_template
107 def HACK_for_exec(cmd, *args):
109 For some reason, Python won't allow an exec() within a function
110 that also declares an internal function (including lambda functions).
111 This function is a hack that calls exec() in a function with no
114 if not args: exec(cmd)
115 elif len(args) == 1: exec cmd in args[0]
116 else: exec cmd in args[0], args[1]
119 def increment_size(self, largest):
121 Return the size of each horizontal increment line for a specified
122 maximum value. This returns a value that will provide somewhere
123 between 5 and 9 horizontal lines on the graph, on some set of
124 boundaries that are multiples of 10/100/1000/etc.
132 multiplier = multiplier * 10
133 return i * multiplier
135 def max_graph_value(self, largest):
136 # Round up to next integer.
137 largest = int(largest) + 1
138 increment = self.increment_size(largest)
139 return ((largest + increment - 1) / increment) * increment
142 def __init__(self, points, type, title, label, comment, fmt="%s %s"):
147 self.comment = comment
150 def print_label(self, inx, x, y):
152 print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
154 def plot_string(self):
156 title_string = 'title "%s"' % self.title
158 title_string = 'notitle'
159 return "'-' %s with lines lt %s" % (title_string, self.type)
161 def print_points(self, fmt=None):
165 print '# %s' % self.comment
166 for x, y in self.points:
167 # If y is None, it usually represents some kind of break
168 # in the line's index number. We might want to represent
169 # this some way rather than just drawing the line straight
170 # between the two points on either side.
175 def get_x_values(self):
176 return [ p[0] for p in self.points ]
178 def get_y_values(self):
179 return [ p[1] for p in self.points ]
181 class Gnuplotter(Plotter):
183 def __init__(self, title, key_location):
186 self.key_location = key_location
188 def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
190 line = Line(points, type, title, label, comment, fmt)
191 self.lines.append(line)
193 def plot_string(self, line):
194 return line.plot_string()
196 def vertical_bar(self, x, type, label, comment):
197 if self.get_min_x() <= x and x <= self.get_max_x():
198 points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
199 self.line(points, type, label, comment)
201 def get_all_x_values(self):
203 for line in self.lines:
204 result.extend(line.get_x_values())
205 return [r for r in result if not r is None]
207 def get_all_y_values(self):
209 for line in self.lines:
210 result.extend(line.get_y_values())
211 return [r for r in result if not r is None]
216 except AttributeError:
218 self.min_x = min(self.get_all_x_values())
226 except AttributeError:
228 self.max_x = max(self.get_all_x_values())
236 except AttributeError:
238 self.min_y = min(self.get_all_y_values())
246 except AttributeError:
248 self.max_y = max(self.get_all_y_values())
259 print 'set title "%s"' % self.title
260 print 'set key %s' % self.key_location
262 min_y = self.get_min_y()
263 max_y = self.max_graph_value(self.get_max_y())
264 range = max_y - min_y
266 start = min_y + (max_y / 2.0) + (2.0 * incr)
267 position = [ start - (i * incr) for i in xrange(5) ]
270 for line in self.lines:
271 line.print_label(inx, line.points[0][0]-1,
272 position[(inx-1) % len(position)])
275 plot_strings = [ self.plot_string(l) for l in self.lines ]
276 print 'plot ' + ', \\\n '.join(plot_strings)
278 for line in self.lines:
285 tar = tarfile.open(name=fname, mode='r')
292 zf = zipfile.ZipFile(fname, 'r')
293 for name in zf.namelist():
294 dir = os.path.dirname(name)
299 open(name, 'w').write(zf.read(name))
302 def read_files(arg, dirname, fnames):
304 fn = os.path.join(dirname, fn)
305 if os.path.isfile(fn):
306 open(fn, 'rb').read()
307 os.path.walk('.', read_files, None)
309 def redirect_to_file(command, log):
310 return '%s > %s 2>&1' % (command, log)
312 def tee_to_file(command, log):
313 return '%s 2>&1 | tee %s' % (command, log)
319 Usage: scons-time SUBCOMMAND [ARGUMENTS]
320 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
322 Available subcommands:
323 func Extract test-run data for a function
325 mem Extract --debug=memory data from test runs
326 obj Extract --debug=count data from test runs
327 time Extract --debug=time data from test runs
328 run Runs a test configuration
332 name_spaces = ' '*len(name)
337 default_settings = makedict(
339 aegis_project = None,
342 initial_commands = [],
343 key_location = 'bottom left',
344 orig_cwd = os.getcwd(),
347 python = '"%s"' % sys.executable,
348 redirect = redirect_to_file,
350 scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
351 scons_lib_dir = None,
352 scons_wrapper = None,
353 startup_targets = '--help',
355 subversion_url = None,
369 '.tar.gz' : (untar, '%(tar)s xzf %%s'),
370 '.tgz' : (untar, '%(tar)s xzf %%s'),
371 '.tar' : (untar, '%(tar)s xf %%s'),
372 '.zip' : (unzip, '%(unzip)s %%s'),
383 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
384 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
385 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
396 'pre-read' : 'Memory before reading SConscript files:',
397 'post-read' : 'Memory after reading SConscript files:',
398 'pre-build' : 'Memory before building targets:',
399 'post-build' : 'Memory after building targets:',
402 memory_string_all = 'Memory '
404 default_stage = stages[-1]
407 'total' : 'Total build time',
408 'SConscripts' : 'Total SConscript file execution time',
409 'SCons' : 'Total SCons execution time',
410 'commands' : 'Total command execution time',
413 time_string_all = 'Total .* time'
418 self.__dict__.update(self.default_settings)
420 # Functions for displaying and executing commands.
422 def subst(self, x, dictionary):
424 return x % dictionary
426 # x isn't a string (it's probably a Python function),
430 def subst_variables(self, command, dictionary):
432 Substitutes (via the format operator) the values in the specified
433 dictionary into the specified command.
435 The command can be an (action, string) tuple. In all cases, we
436 perform substitution on strings and don't worry if something isn't
437 a string. (It's probably a Python function to be executed.)
449 action = self.subst(action, dictionary)
450 string = self.subst(string, dictionary)
451 return (action, string, args)
453 def _do_not_display(self, msg, *args):
456 def display(self, msg, *args):
458 Displays the specified message.
460 Each message is prepended with a standard prefix of our name
470 sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
472 def _do_not_execute(self, action, *args):
475 def execute(self, action, *args):
477 Executes the specified action.
479 The action is called if it's a callable Python function, and
480 otherwise passed to os.system().
485 os.system(action % args)
487 def run_command_list(self, commands, dict):
489 Executes a list of commands, substituting values from the
490 specified dictionary.
492 commands = [ self.subst_variables(c, dict) for c in commands ]
493 for action, string, args in commands:
494 self.display(string, *args)
496 status = self.execute(action, *args)
500 def log_display(self, command, log):
501 command = self.subst(command, self.__dict__)
503 command = self.redirect(command, log)
506 def log_execute(self, command, log):
507 command = self.subst(command, self.__dict__)
508 output = os.popen(command).read()
510 sys.stdout.write(output)
511 open(log, 'wb').write(output)
515 def archive_splitext(self, path):
517 Splits an archive name into a filename base and extension.
519 This is like os.path.splitext() (which it calls) except that it
520 also looks for '.tar.gz' and treats it as an atomic extensions.
522 if path.endswith('.tar.gz'):
523 return path[:-7], path[-7:]
525 return os.path.splitext(path)
527 def args_to_files(self, args, tail=None):
529 Takes a list of arguments, expands any glob patterns, and
530 returns the last "tail" files from the list.
534 files.extend(sorted(glob.glob(a)))
537 files = files[-tail:]
541 def ascii_table(self, files, columns,
542 line_function, file_function=lambda x: x,
545 header_fmt = ' '.join(['%12s'] * len(columns))
546 line_fmt = header_fmt + ' %s'
548 print header_fmt % columns
551 t = line_function(file, *args, **kw)
554 diff = len(columns) - len(t)
557 t.append(file_function(file))
558 print line_fmt % tuple(t)
560 def collect_results(self, files, function, *args, **kw):
564 base = os.path.splitext(file)[0]
565 run, index = base.split('-')[-2:]
570 value = function(file, *args, **kw)
577 r.append((run, value))
581 def doc_to_help(self, obj):
583 Translates an object's __doc__ string into help text.
585 This strips a consistent number of spaces from each line in the
586 help text, essentially "outdenting" the text to the left-most
592 return self.outdent(doc)
594 def find_next_run_number(self, dir, prefix):
596 Returns the next run number in a directory for the specified prefix.
598 Examines the contents the specified directory for files with the
599 specified prefix, extracts the run numbers from each file name,
600 and returns the next run number after the largest it finds.
602 x = re.compile(re.escape(prefix) + '-([0-9]+).*')
603 matches = [x.match(e) for e in os.listdir(dir)]
604 matches = [_f for _f in matches if _f]
607 run_numbers = [int(m.group(1)) for m in matches]
608 return int(max(run_numbers)) + 1
610 def gnuplot_results(self, results, fmt='%s %.3f'):
612 Prints out a set of results in Gnuplot format.
614 gp = Gnuplotter(self.title, self.key_location)
616 for i in sorted(results.keys()):
618 t = self.run_titles[i]
622 gp.line(results[i], i+1, t, None, t, fmt=fmt)
624 for bar_tuple in self.vertical_bars:
626 x, type, label, comment = bar_tuple
628 x, type, label = bar_tuple
630 gp.vertical_bar(x, type, label, comment)
634 def logfile_name(self, invocation):
636 Returns the absolute path of a log file for the specificed
639 name = self.prefix_run + '-%d.log' % invocation
640 return os.path.join(self.outdir, name)
642 def outdent(self, s):
644 Strip as many spaces from each line as are found at the beginning
645 of the first line in the list.
647 lines = s.split('\n')
650 spaces = re.match(' *', lines[0]).group(0)
651 def strip_initial_spaces(l, s=spaces):
652 if l.startswith(spaces):
655 return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
657 def profile_name(self, invocation):
659 Returns the absolute path of a profile file for the specified
662 name = self.prefix_run + '-%d.prof' % invocation
663 return os.path.join(self.outdir, name)
665 def set_env(self, key, value):
666 os.environ[key] = value
670 def get_debug_times(self, file, time_string=None):
672 Fetch times from the --debug=time strings in the specified file.
674 if time_string is None:
675 search_string = self.time_string_all
677 search_string = time_string
678 contents = open(file).read()
680 sys.stderr.write('file %s has no contents!\n' % repr(file))
682 result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
683 result = [ float(r) for r in result ]
684 if not time_string is None:
688 sys.stderr.write('file %s has no results!\n' % repr(file))
692 def get_function_profile(self, file, function):
694 Returns the file, line number, function name, and cumulative time.
698 except ImportError, e:
699 sys.stderr.write('%s: func: %s\n' % (self.name, e))
700 sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
701 sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
703 statistics = pstats.Stats(file).stats
704 matches = [ e for e in statistics.items() if e[0][2] == function ]
706 return r[0][0], r[0][1], r[0][2], r[1][3]
708 def get_function_time(self, file, function):
710 Returns just the cumulative time for the specified function.
712 return self.get_function_profile(file, function)[3]
714 def get_memory(self, file, memory_string=None):
716 Returns a list of integers of the amount of memory used. The
717 default behavior is to return all the stages.
719 if memory_string is None:
720 search_string = self.memory_string_all
722 search_string = memory_string
723 lines = open(file).readlines()
724 lines = [ l for l in lines if l.startswith(search_string) ][-4:]
725 result = [ int(l.split()[-1]) for l in lines[-4:] ]
730 def get_object_counts(self, file, object_name, index=None):
732 Returns the counts of the specified object_name.
734 object_string = ' ' + object_name + '\n'
735 lines = open(file).readlines()
736 line = [ l for l in lines if l.endswith(object_string) ][0]
737 result = [ int(field) for field in line.split()[:4] ]
738 if index is not None:
739 result = result[index]
746 def execute_subcommand(self, argv):
748 Executes the do_*() function for the specified subcommand (argv[0]).
752 cmdName = self.command_alias.get(argv[0], argv[0])
754 func = getattr(self, 'do_' + cmdName)
755 except AttributeError:
756 return self.default(argv)
760 sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
762 traceback.print_exc(file=sys.stderr)
763 sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
765 def default(self, argv):
767 The default behavior for an unknown subcommand. Prints an
768 error message and exits.
770 sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
771 sys.stderr.write('Type "%s help" for usage.\n' % self.name)
776 def do_help(self, argv):
782 func = getattr(self, 'do_' + arg)
783 except AttributeError:
784 sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
787 help = getattr(self, 'help_' + arg)
788 except AttributeError:
789 sys.stdout.write(self.doc_to_help(func))
794 doc = self.doc_to_help(self.__class__)
796 sys.stdout.write(doc)
804 Usage: scons-time func [OPTIONS] FILE [...]
806 -C DIR, --chdir=DIR Change to DIR before looking for files
807 -f FILE, --file=FILE Read configuration from specified FILE
808 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
809 --func=NAME, --function=NAME Report time for function NAME
810 -h, --help Print this help and exit
811 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
812 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
813 --title=TITLE Specify the output plot TITLE
815 sys.stdout.write(self.outdent(help))
818 def do_func(self, argv):
822 function_name = '_main'
825 short_opts = '?C:f:hp:t:'
840 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
843 if o in ('-C', '--chdir'):
845 elif o in ('-f', '--file'):
847 elif o in ('--fmt', '--format'):
849 elif o in ('--func', '--function'):
851 elif o in ('-?', '-h', '--help'):
852 self.do_help(['help', 'func'])
854 elif o in ('--max',):
856 elif o in ('-p', '--prefix'):
858 elif o in ('-t', '--tail'):
860 elif o in ('--title',):
864 exec open(self.config_file, 'rU').read() in self.__dict__
871 pattern = '%s*.prof' % self.prefix
872 args = self.args_to_files([pattern], tail)
876 directory = self.chdir
878 directory = os.getcwd()
880 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
881 sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
882 sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
887 args = self.args_to_files(args, tail)
889 cwd_ = os.getcwd() + os.sep
891 if format == 'ascii':
895 f, line, func, time = \
896 self.get_function_profile(file, function_name)
897 except ValueError, e:
898 sys.stderr.write("%s: func: %s: %s\n" %
899 (self.name, file, e))
901 if f.startswith(cwd_):
903 print "%.3f %s:%d(%s)" % (time, f, line, func)
905 elif format == 'gnuplot':
907 results = self.collect_results(args, self.get_function_time,
910 self.gnuplot_results(results)
914 sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
921 Usage: scons-time mem [OPTIONS] FILE [...]
923 -C DIR, --chdir=DIR Change to DIR before looking for files
924 -f FILE, --file=FILE Read configuration from specified FILE
925 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
926 -h, --help Print this help and exit
927 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
928 --stage=STAGE Plot memory at the specified stage:
929 pre-read, post-read, pre-build,
930 post-build (default: post-build)
931 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
932 --title=TITLE Specify the output plot TITLE
934 sys.stdout.write(self.outdent(help))
937 def do_mem(self, argv):
940 logfile_path = lambda x: x
941 stage = self.default_stage
944 short_opts = '?C:f:hp:t:'
958 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
961 if o in ('-C', '--chdir'):
963 elif o in ('-f', '--file'):
965 elif o in ('--fmt', '--format'):
967 elif o in ('-?', '-h', '--help'):
968 self.do_help(['help', 'mem'])
970 elif o in ('-p', '--prefix'):
972 elif o in ('--stage',):
973 if not a in self.stages:
974 sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
977 elif o in ('-t', '--tail'):
979 elif o in ('--title',):
983 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
987 logfile_path = lambda x: os.path.join(self.chdir, x)
991 pattern = '%s*.log' % self.prefix
992 args = self.args_to_files([pattern], tail)
996 directory = self.chdir
998 directory = os.getcwd()
1000 sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
1001 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1002 sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
1007 args = self.args_to_files(args, tail)
1009 cwd_ = os.getcwd() + os.sep
1011 if format == 'ascii':
1013 self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
1015 elif format == 'gnuplot':
1017 results = self.collect_results(args, self.get_memory,
1018 self.stage_strings[stage])
1020 self.gnuplot_results(results)
1024 sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1033 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1035 -C DIR, --chdir=DIR Change to DIR before looking for files
1036 -f FILE, --file=FILE Read configuration from specified FILE
1037 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1038 -h, --help Print this help and exit
1039 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1040 --stage=STAGE Plot memory at the specified stage:
1041 pre-read, post-read, pre-build,
1042 post-build (default: post-build)
1043 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1044 --title=TITLE Specify the output plot TITLE
1046 sys.stdout.write(self.outdent(help))
1049 def do_obj(self, argv):
1052 logfile_path = lambda x: x
1053 stage = self.default_stage
1056 short_opts = '?C:f:hp:t:'
1070 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1073 if o in ('-C', '--chdir'):
1075 elif o in ('-f', '--file'):
1076 self.config_file = a
1077 elif o in ('--fmt', '--format'):
1079 elif o in ('-?', '-h', '--help'):
1080 self.do_help(['help', 'obj'])
1082 elif o in ('-p', '--prefix'):
1084 elif o in ('--stage',):
1085 if not a in self.stages:
1086 sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1087 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1090 elif o in ('-t', '--tail'):
1092 elif o in ('--title',):
1096 sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1097 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1100 object_name = args.pop(0)
1102 if self.config_file:
1103 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1106 os.chdir(self.chdir)
1107 logfile_path = lambda x: os.path.join(self.chdir, x)
1111 pattern = '%s*.log' % self.prefix
1112 args = self.args_to_files([pattern], tail)
1116 directory = self.chdir
1118 directory = os.getcwd()
1120 sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1121 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1122 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1127 args = self.args_to_files(args, tail)
1129 cwd_ = os.getcwd() + os.sep
1131 if format == 'ascii':
1133 self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1135 elif format == 'gnuplot':
1138 for s in self.stages:
1141 stage_index = stage_index + 1
1143 results = self.collect_results(args, self.get_object_counts,
1144 object_name, stage_index)
1146 self.gnuplot_results(results)
1150 sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1159 Usage: scons-time run [OPTIONS] [FILE ...]
1161 --aegis=PROJECT Use SCons from the Aegis PROJECT
1162 --chdir=DIR Name of unpacked directory for chdir
1163 -f FILE, --file=FILE Read configuration from specified FILE
1164 -h, --help Print this help and exit
1165 -n, --no-exec No execute, just print command lines
1166 --number=NUMBER Put output in files for run NUMBER
1167 --outdir=OUTDIR Put output files in OUTDIR
1168 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1169 --python=PYTHON Time using the specified PYTHON
1170 -q, --quiet Don't print command lines
1171 --scons=SCONS Time using the specified SCONS
1172 --svn=URL, --subversion=URL Use SCons from Subversion URL
1173 -v, --verbose Display output of commands
1175 sys.stdout.write(self.outdent(help))
1178 def do_run(self, argv):
1181 run_number_list = [None]
1183 short_opts = '?f:hnp:qs:v'
1202 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1205 if o in ('--aegis',):
1206 self.aegis_project = a
1207 elif o in ('-f', '--file'):
1208 self.config_file = a
1209 elif o in ('-?', '-h', '--help'):
1210 self.do_help(['help', 'run'])
1212 elif o in ('-n', '--no-exec'):
1213 self.execute = self._do_not_execute
1214 elif o in ('--number',):
1215 run_number_list = self.split_run_numbers(a)
1216 elif o in ('--outdir',):
1218 elif o in ('-p', '--prefix'):
1220 elif o in ('--python',):
1222 elif o in ('-q', '--quiet'):
1223 self.display = self._do_not_display
1224 elif o in ('-s', '--subdir'):
1226 elif o in ('--scons',):
1228 elif o in ('--svn', '--subversion'):
1229 self.subversion_url = a
1230 elif o in ('-v', '--verbose'):
1231 self.redirect = tee_to_file
1233 self.svn_co_flag = ''
1235 if not args and not self.config_file:
1236 sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1237 sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1240 if self.config_file:
1241 exec open(self.config_file, 'rU').read() in self.__dict__
1244 self.archive_list = args
1246 archive_file_name = os.path.split(self.archive_list[0])[1]
1249 self.subdir = self.archive_splitext(archive_file_name)[0]
1252 self.prefix = self.archive_splitext(archive_file_name)[0]
1255 if self.subversion_url:
1256 prepare = self.prep_subversion_run
1257 elif self.aegis_project:
1258 prepare = self.prep_aegis_run
1260 for run_number in run_number_list:
1261 self.individual_run(run_number, self.archive_list, prepare)
1263 def split_run_numbers(self, s):
1265 for n in s.split(','):
1269 result.append(int(n))
1271 result.extend(range(int(x), int(y)+1))
1274 def scons_path(self, dir):
1275 return os.path.join(dir, 'src', 'script', 'scons.py')
1277 def scons_lib_dir_path(self, dir):
1278 return os.path.join(dir, 'src', 'engine')
1280 def prep_aegis_run(self, commands, removals):
1281 self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1282 removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1284 self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1285 self.scons = self.scons_path(self.aegis_tmpdir)
1286 self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1289 'mkdir %(aegis_tmpdir)s',
1290 (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1291 '%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1292 '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1295 def prep_subversion_run(self, commands, removals):
1296 self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1297 removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1299 self.scons = self.scons_path(self.svn_tmpdir)
1300 self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1303 'mkdir %(svn_tmpdir)s',
1304 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1307 def individual_run(self, run_number, archive_list, prepare=None):
1309 Performs an individual run of the default SCons invocations.
1316 prepare(commands, removals)
1318 save_scons = self.scons
1319 save_scons_wrapper = self.scons_wrapper
1320 save_scons_lib_dir = self.scons_lib_dir
1322 if self.outdir is None:
1323 self.outdir = self.orig_cwd
1324 elif not os.path.isabs(self.outdir):
1325 self.outdir = os.path.join(self.orig_cwd, self.outdir)
1327 if self.scons is None:
1328 self.scons = self.scons_path(self.orig_cwd)
1330 if self.scons_lib_dir is None:
1331 self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1333 if self.scons_wrapper is None:
1334 self.scons_wrapper = self.scons
1337 run_number = self.find_next_run_number(self.outdir, self.prefix)
1339 self.run_number = str(run_number)
1341 self.prefix_run = self.prefix + '-%03d' % run_number
1343 if self.targets0 is None:
1344 self.targets0 = self.startup_targets
1345 if self.targets1 is None:
1346 self.targets1 = self.targets
1347 if self.targets2 is None:
1348 self.targets2 = self.targets
1350 self.tmpdir = make_temp_file(prefix = self.name + '-')
1355 (os.chdir, 'cd %%s', self.tmpdir),
1358 for archive in archive_list:
1359 if not os.path.isabs(archive):
1360 archive = os.path.join(self.orig_cwd, archive)
1361 if os.path.isdir(archive):
1362 dest = os.path.split(archive)[1]
1363 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1365 suffix = self.archive_splitext(archive)[1]
1366 unpack_command = self.unpack_map.get(suffix)
1367 if not unpack_command:
1368 dest = os.path.split(archive)[1]
1369 commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1371 commands.append(unpack_command + (archive,))
1374 (os.chdir, 'cd %%s', self.subdir),
1377 commands.extend(self.initial_commands)
1380 (lambda: read_tree('.'),
1381 'find * -type f | xargs cat > /dev/null'),
1383 (self.set_env, 'export %%s=%%s',
1384 'SCONS_LIB_DIR', self.scons_lib_dir),
1386 '%(python)s %(scons_wrapper)s --version',
1390 for run_command in self.run_commands:
1391 setattr(self, 'prof%d' % index, self.profile_name(index))
1396 self.logfile_name(index),
1402 (os.chdir, 'cd %%s', self.orig_cwd),
1405 if not os.environ.get('PRESERVE'):
1406 commands.extend(removals)
1408 commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1410 self.run_command_list(commands, self.__dict__)
1412 self.scons = save_scons
1413 self.scons_lib_dir = save_scons_lib_dir
1414 self.scons_wrapper = save_scons_wrapper
1418 def help_time(self):
1420 Usage: scons-time time [OPTIONS] FILE [...]
1422 -C DIR, --chdir=DIR Change to DIR before looking for files
1423 -f FILE, --file=FILE Read configuration from specified FILE
1424 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1425 -h, --help Print this help and exit
1426 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1427 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1428 --which=TIMER Plot timings for TIMER: total,
1429 SConscripts, SCons, commands.
1431 sys.stdout.write(self.outdent(help))
1434 def do_time(self, argv):
1437 logfile_path = lambda x: x
1441 short_opts = '?C:f:hp:t:'
1455 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1458 if o in ('-C', '--chdir'):
1460 elif o in ('-f', '--file'):
1461 self.config_file = a
1462 elif o in ('--fmt', '--format'):
1464 elif o in ('-?', '-h', '--help'):
1465 self.do_help(['help', 'time'])
1467 elif o in ('-p', '--prefix'):
1469 elif o in ('-t', '--tail'):
1471 elif o in ('--title',):
1473 elif o in ('--which',):
1474 if not a in self.time_strings.keys():
1475 sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1476 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1480 if self.config_file:
1481 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1484 os.chdir(self.chdir)
1485 logfile_path = lambda x: os.path.join(self.chdir, x)
1489 pattern = '%s*.log' % self.prefix
1490 args = self.args_to_files([pattern], tail)
1494 directory = self.chdir
1496 directory = os.getcwd()
1498 sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1499 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1500 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1505 args = self.args_to_files(args, tail)
1507 cwd_ = os.getcwd() + os.sep
1509 if format == 'ascii':
1511 columns = ("Total", "SConscripts", "SCons", "commands")
1512 self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1514 elif format == 'gnuplot':
1516 results = self.collect_results(args, self.get_debug_times,
1517 self.time_strings[which])
1519 self.gnuplot_results(results, fmt='%s %.6f')
1523 sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1526 if __name__ == '__main__':
1527 opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1532 if o in ('-?', '-h', '--help'):
1533 ST.do_help(['help'])
1535 elif o in ('-V', '--version'):
1536 sys.stdout.write('scons-time version\n')
1540 sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1543 ST.execute_subcommand(args)
1547 # indent-tabs-mode:nil
1549 # vim: set expandtab tabstop=4 shiftwidth=4: