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, 2009, 2010 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.
32 from __future__ import division
33 from __future__ import nested_scopes
35 __revision__ = "src/script/scons-time.py 5023 2010/06/14 22:05:46 scons"
49 # Pre-2.4 Python has no sorted() function.
51 # The pre-2.4 Python list.sort() method does not support
52 # list.sort(key=) nor list.sort(reverse=) keyword arguments, so
53 # we must implement the functionality of those keyword arguments
54 # by hand instead of passing them to list.sort().
55 def sorted(iterable, cmp=None, key=None, reverse=False):
57 result = [(key(x), x) for x in iterable]
61 # Pre-2.3 Python does not support list.sort(None).
66 result = [t1 for t0,t1 in result]
71 if os.environ.get('SCONS_HORRIBLE_REGRESSION_TEST_HACK') is not None:
72 # We can't apply the 'callable' fixer until the floor is 2.6, but the
73 # '-3' option to Python 2.6 and 2.7 generates almost ten thousand
74 # warnings. This hack allows us to run regression tests with the '-3'
75 # option by replacing the callable() built-in function with a hack
76 # that performs the same function but doesn't generate the warning.
77 # Note that this hack is ONLY intended to be used for regression
78 # testing, and should NEVER be used for real runs.
79 from types import ClassType
81 if hasattr(obj, '__call__'): return True
82 if isinstance(obj, (ClassType, type)): return True
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]
115 class Plotter(object):
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 for dirpath, dirnames, filenames in os.walk(dir):
300 fn = os.path.join(dirpath, fn)
301 if os.path.isfile(fn):
302 open(fn, 'rb').read()
304 def redirect_to_file(command, log):
305 return '%s > %s 2>&1' % (command, log)
307 def tee_to_file(command, log):
308 return '%s 2>&1 | tee %s' % (command, log)
312 class SConsTimer(object):
314 Usage: scons-time SUBCOMMAND [ARGUMENTS]
315 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
317 Available subcommands:
318 func Extract test-run data for a function
320 mem Extract --debug=memory data from test runs
321 obj Extract --debug=count data from test runs
322 time Extract --debug=time data from test runs
323 run Runs a test configuration
327 name_spaces = ' '*len(name)
332 default_settings = makedict(
334 aegis_project = None,
337 initial_commands = [],
338 key_location = 'bottom left',
339 orig_cwd = os.getcwd(),
342 python = '"%s"' % sys.executable,
343 redirect = redirect_to_file,
345 scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
346 scons_lib_dir = None,
347 scons_wrapper = None,
348 startup_targets = '--help',
350 subversion_url = None,
364 '.tar.gz' : (untar, '%(tar)s xzf %%s'),
365 '.tgz' : (untar, '%(tar)s xzf %%s'),
366 '.tar' : (untar, '%(tar)s xf %%s'),
367 '.zip' : (unzip, '%(unzip)s %%s'),
378 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
379 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
380 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
391 'pre-read' : 'Memory before reading SConscript files:',
392 'post-read' : 'Memory after reading SConscript files:',
393 'pre-build' : 'Memory before building targets:',
394 'post-build' : 'Memory after building targets:',
397 memory_string_all = 'Memory '
399 default_stage = stages[-1]
402 'total' : 'Total build time',
403 'SConscripts' : 'Total SConscript file execution time',
404 'SCons' : 'Total SCons execution time',
405 'commands' : 'Total command execution time',
408 time_string_all = 'Total .* time'
413 self.__dict__.update(self.default_settings)
415 # Functions for displaying and executing commands.
417 def subst(self, x, dictionary):
419 return x % dictionary
421 # x isn't a string (it's probably a Python function),
425 def subst_variables(self, command, dictionary):
427 Substitutes (via the format operator) the values in the specified
428 dictionary into the specified command.
430 The command can be an (action, string) tuple. In all cases, we
431 perform substitution on strings and don't worry if something isn't
432 a string. (It's probably a Python function to be executed.)
444 action = self.subst(action, dictionary)
445 string = self.subst(string, dictionary)
446 return (action, string, args)
448 def _do_not_display(self, msg, *args):
451 def display(self, msg, *args):
453 Displays the specified message.
455 Each message is prepended with a standard prefix of our name
465 sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
467 def _do_not_execute(self, action, *args):
470 def execute(self, action, *args):
472 Executes the specified action.
474 The action is called if it's a callable Python function, and
475 otherwise passed to os.system().
480 os.system(action % args)
482 def run_command_list(self, commands, dict):
484 Executes a list of commands, substituting values from the
485 specified dictionary.
487 commands = [ self.subst_variables(c, dict) for c in commands ]
488 for action, string, args in commands:
489 self.display(string, *args)
491 status = self.execute(action, *args)
495 def log_display(self, command, log):
496 command = self.subst(command, self.__dict__)
498 command = self.redirect(command, log)
501 def log_execute(self, command, log):
502 command = self.subst(command, self.__dict__)
503 output = os.popen(command).read()
505 sys.stdout.write(output)
506 open(log, 'wb').write(output)
510 def archive_splitext(self, path):
512 Splits an archive name into a filename base and extension.
514 This is like os.path.splitext() (which it calls) except that it
515 also looks for '.tar.gz' and treats it as an atomic extensions.
517 if path.endswith('.tar.gz'):
518 return path[:-7], path[-7:]
520 return os.path.splitext(path)
522 def args_to_files(self, args, tail=None):
524 Takes a list of arguments, expands any glob patterns, and
525 returns the last "tail" files from the list.
529 files.extend(sorted(glob.glob(a)))
532 files = files[-tail:]
536 def ascii_table(self, files, columns,
537 line_function, file_function=lambda x: x,
540 header_fmt = ' '.join(['%12s'] * len(columns))
541 line_fmt = header_fmt + ' %s'
543 print header_fmt % columns
546 t = line_function(file, *args, **kw)
549 diff = len(columns) - len(t)
552 t.append(file_function(file))
553 print line_fmt % tuple(t)
555 def collect_results(self, files, function, *args, **kw):
559 base = os.path.splitext(file)[0]
560 run, index = base.split('-')[-2:]
565 value = function(file, *args, **kw)
572 r.append((run, value))
576 def doc_to_help(self, obj):
578 Translates an object's __doc__ string into help text.
580 This strips a consistent number of spaces from each line in the
581 help text, essentially "outdenting" the text to the left-most
587 return self.outdent(doc)
589 def find_next_run_number(self, dir, prefix):
591 Returns the next run number in a directory for the specified prefix.
593 Examines the contents the specified directory for files with the
594 specified prefix, extracts the run numbers from each file name,
595 and returns the next run number after the largest it finds.
597 x = re.compile(re.escape(prefix) + '-([0-9]+).*')
598 matches = [x.match(e) for e in os.listdir(dir)]
599 matches = [_f for _f in matches if _f]
602 run_numbers = [int(m.group(1)) for m in matches]
603 return int(max(run_numbers)) + 1
605 def gnuplot_results(self, results, fmt='%s %.3f'):
607 Prints out a set of results in Gnuplot format.
609 gp = Gnuplotter(self.title, self.key_location)
611 for i in sorted(results.keys()):
613 t = self.run_titles[i]
617 gp.line(results[i], i+1, t, None, t, fmt=fmt)
619 for bar_tuple in self.vertical_bars:
621 x, type, label, comment = bar_tuple
623 x, type, label = bar_tuple
625 gp.vertical_bar(x, type, label, comment)
629 def logfile_name(self, invocation):
631 Returns the absolute path of a log file for the specificed
634 name = self.prefix_run + '-%d.log' % invocation
635 return os.path.join(self.outdir, name)
637 def outdent(self, s):
639 Strip as many spaces from each line as are found at the beginning
640 of the first line in the list.
642 lines = s.split('\n')
645 spaces = re.match(' *', lines[0]).group(0)
646 def strip_initial_spaces(l, s=spaces):
647 if l.startswith(spaces):
650 return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
652 def profile_name(self, invocation):
654 Returns the absolute path of a profile file for the specified
657 name = self.prefix_run + '-%d.prof' % invocation
658 return os.path.join(self.outdir, name)
660 def set_env(self, key, value):
661 os.environ[key] = value
665 def get_debug_times(self, file, time_string=None):
667 Fetch times from the --debug=time strings in the specified file.
669 if time_string is None:
670 search_string = self.time_string_all
672 search_string = time_string
673 contents = open(file).read()
675 sys.stderr.write('file %s has no contents!\n' % repr(file))
677 result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
678 result = [ float(r) for r in result ]
679 if not time_string is None:
683 sys.stderr.write('file %s has no results!\n' % repr(file))
687 def get_function_profile(self, file, function):
689 Returns the file, line number, function name, and cumulative time.
693 except ImportError, e:
694 sys.stderr.write('%s: func: %s\n' % (self.name, e))
695 sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
696 sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
698 statistics = pstats.Stats(file).stats
699 matches = [ e for e in statistics.items() if e[0][2] == function ]
701 return r[0][0], r[0][1], r[0][2], r[1][3]
703 def get_function_time(self, file, function):
705 Returns just the cumulative time for the specified function.
707 return self.get_function_profile(file, function)[3]
709 def get_memory(self, file, memory_string=None):
711 Returns a list of integers of the amount of memory used. The
712 default behavior is to return all the stages.
714 if memory_string is None:
715 search_string = self.memory_string_all
717 search_string = memory_string
718 lines = open(file).readlines()
719 lines = [ l for l in lines if l.startswith(search_string) ][-4:]
720 result = [ int(l.split()[-1]) for l in lines[-4:] ]
725 def get_object_counts(self, file, object_name, index=None):
727 Returns the counts of the specified object_name.
729 object_string = ' ' + object_name + '\n'
730 lines = open(file).readlines()
731 line = [ l for l in lines if l.endswith(object_string) ][0]
732 result = [ int(field) for field in line.split()[:4] ]
733 if index is not None:
734 result = result[index]
741 def execute_subcommand(self, argv):
743 Executes the do_*() function for the specified subcommand (argv[0]).
747 cmdName = self.command_alias.get(argv[0], argv[0])
749 func = getattr(self, 'do_' + cmdName)
750 except AttributeError:
751 return self.default(argv)
755 sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
757 traceback.print_exc(file=sys.stderr)
758 sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
760 def default(self, argv):
762 The default behavior for an unknown subcommand. Prints an
763 error message and exits.
765 sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
766 sys.stderr.write('Type "%s help" for usage.\n' % self.name)
771 def do_help(self, argv):
777 func = getattr(self, 'do_' + arg)
778 except AttributeError:
779 sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
782 help = getattr(self, 'help_' + arg)
783 except AttributeError:
784 sys.stdout.write(self.doc_to_help(func))
789 doc = self.doc_to_help(self.__class__)
791 sys.stdout.write(doc)
799 Usage: scons-time func [OPTIONS] FILE [...]
801 -C DIR, --chdir=DIR Change to DIR before looking for files
802 -f FILE, --file=FILE Read configuration from specified FILE
803 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
804 --func=NAME, --function=NAME Report time for function NAME
805 -h, --help Print this help and exit
806 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
807 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
808 --title=TITLE Specify the output plot TITLE
810 sys.stdout.write(self.outdent(help))
813 def do_func(self, argv):
817 function_name = '_main'
820 short_opts = '?C:f:hp:t:'
835 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
838 if o in ('-C', '--chdir'):
840 elif o in ('-f', '--file'):
842 elif o in ('--fmt', '--format'):
844 elif o in ('--func', '--function'):
846 elif o in ('-?', '-h', '--help'):
847 self.do_help(['help', 'func'])
849 elif o in ('--max',):
851 elif o in ('-p', '--prefix'):
853 elif o in ('-t', '--tail'):
855 elif o in ('--title',):
859 exec open(self.config_file, 'rU').read() in self.__dict__
866 pattern = '%s*.prof' % self.prefix
867 args = self.args_to_files([pattern], tail)
871 directory = self.chdir
873 directory = os.getcwd()
875 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
876 sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
877 sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
882 args = self.args_to_files(args, tail)
884 cwd_ = os.getcwd() + os.sep
886 if format == 'ascii':
890 f, line, func, time = \
891 self.get_function_profile(file, function_name)
892 except ValueError, e:
893 sys.stderr.write("%s: func: %s: %s\n" %
894 (self.name, file, e))
896 if f.startswith(cwd_):
898 print "%.3f %s:%d(%s)" % (time, f, line, func)
900 elif format == 'gnuplot':
902 results = self.collect_results(args, self.get_function_time,
905 self.gnuplot_results(results)
909 sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
916 Usage: scons-time mem [OPTIONS] FILE [...]
918 -C DIR, --chdir=DIR Change to DIR before looking for files
919 -f FILE, --file=FILE Read configuration from specified FILE
920 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
921 -h, --help Print this help and exit
922 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
923 --stage=STAGE Plot memory at the specified stage:
924 pre-read, post-read, pre-build,
925 post-build (default: post-build)
926 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
927 --title=TITLE Specify the output plot TITLE
929 sys.stdout.write(self.outdent(help))
932 def do_mem(self, argv):
935 logfile_path = lambda x: x
936 stage = self.default_stage
939 short_opts = '?C:f:hp:t:'
953 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
956 if o in ('-C', '--chdir'):
958 elif o in ('-f', '--file'):
960 elif o in ('--fmt', '--format'):
962 elif o in ('-?', '-h', '--help'):
963 self.do_help(['help', 'mem'])
965 elif o in ('-p', '--prefix'):
967 elif o in ('--stage',):
968 if not a in self.stages:
969 sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
972 elif o in ('-t', '--tail'):
974 elif o in ('--title',):
978 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
982 logfile_path = lambda x: os.path.join(self.chdir, x)
986 pattern = '%s*.log' % self.prefix
987 args = self.args_to_files([pattern], tail)
991 directory = self.chdir
993 directory = os.getcwd()
995 sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
996 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
997 sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
1002 args = self.args_to_files(args, tail)
1004 cwd_ = os.getcwd() + os.sep
1006 if format == 'ascii':
1008 self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
1010 elif format == 'gnuplot':
1012 results = self.collect_results(args, self.get_memory,
1013 self.stage_strings[stage])
1015 self.gnuplot_results(results)
1019 sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1028 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1030 -C DIR, --chdir=DIR Change to DIR before looking for files
1031 -f FILE, --file=FILE Read configuration from specified FILE
1032 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1033 -h, --help Print this help and exit
1034 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1035 --stage=STAGE Plot memory at the specified stage:
1036 pre-read, post-read, pre-build,
1037 post-build (default: post-build)
1038 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1039 --title=TITLE Specify the output plot TITLE
1041 sys.stdout.write(self.outdent(help))
1044 def do_obj(self, argv):
1047 logfile_path = lambda x: x
1048 stage = self.default_stage
1051 short_opts = '?C:f:hp:t:'
1065 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1068 if o in ('-C', '--chdir'):
1070 elif o in ('-f', '--file'):
1071 self.config_file = a
1072 elif o in ('--fmt', '--format'):
1074 elif o in ('-?', '-h', '--help'):
1075 self.do_help(['help', 'obj'])
1077 elif o in ('-p', '--prefix'):
1079 elif o in ('--stage',):
1080 if not a in self.stages:
1081 sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1082 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1085 elif o in ('-t', '--tail'):
1087 elif o in ('--title',):
1091 sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1092 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1095 object_name = args.pop(0)
1097 if self.config_file:
1098 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1101 os.chdir(self.chdir)
1102 logfile_path = lambda x: os.path.join(self.chdir, x)
1106 pattern = '%s*.log' % self.prefix
1107 args = self.args_to_files([pattern], tail)
1111 directory = self.chdir
1113 directory = os.getcwd()
1115 sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1116 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1117 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1122 args = self.args_to_files(args, tail)
1124 cwd_ = os.getcwd() + os.sep
1126 if format == 'ascii':
1128 self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1130 elif format == 'gnuplot':
1133 for s in self.stages:
1136 stage_index = stage_index + 1
1138 results = self.collect_results(args, self.get_object_counts,
1139 object_name, stage_index)
1141 self.gnuplot_results(results)
1145 sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1154 Usage: scons-time run [OPTIONS] [FILE ...]
1156 --aegis=PROJECT Use SCons from the Aegis PROJECT
1157 --chdir=DIR Name of unpacked directory for chdir
1158 -f FILE, --file=FILE Read configuration from specified FILE
1159 -h, --help Print this help and exit
1160 -n, --no-exec No execute, just print command lines
1161 --number=NUMBER Put output in files for run NUMBER
1162 --outdir=OUTDIR Put output files in OUTDIR
1163 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1164 --python=PYTHON Time using the specified PYTHON
1165 -q, --quiet Don't print command lines
1166 --scons=SCONS Time using the specified SCONS
1167 --svn=URL, --subversion=URL Use SCons from Subversion URL
1168 -v, --verbose Display output of commands
1170 sys.stdout.write(self.outdent(help))
1173 def do_run(self, argv):
1176 run_number_list = [None]
1178 short_opts = '?f:hnp:qs:v'
1197 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1200 if o in ('--aegis',):
1201 self.aegis_project = a
1202 elif o in ('-f', '--file'):
1203 self.config_file = a
1204 elif o in ('-?', '-h', '--help'):
1205 self.do_help(['help', 'run'])
1207 elif o in ('-n', '--no-exec'):
1208 self.execute = self._do_not_execute
1209 elif o in ('--number',):
1210 run_number_list = self.split_run_numbers(a)
1211 elif o in ('--outdir',):
1213 elif o in ('-p', '--prefix'):
1215 elif o in ('--python',):
1217 elif o in ('-q', '--quiet'):
1218 self.display = self._do_not_display
1219 elif o in ('-s', '--subdir'):
1221 elif o in ('--scons',):
1223 elif o in ('--svn', '--subversion'):
1224 self.subversion_url = a
1225 elif o in ('-v', '--verbose'):
1226 self.redirect = tee_to_file
1228 self.svn_co_flag = ''
1230 if not args and not self.config_file:
1231 sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1232 sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1235 if self.config_file:
1236 exec open(self.config_file, 'rU').read() in self.__dict__
1239 self.archive_list = args
1241 archive_file_name = os.path.split(self.archive_list[0])[1]
1244 self.subdir = self.archive_splitext(archive_file_name)[0]
1247 self.prefix = self.archive_splitext(archive_file_name)[0]
1250 if self.subversion_url:
1251 prepare = self.prep_subversion_run
1252 elif self.aegis_project:
1253 prepare = self.prep_aegis_run
1255 for run_number in run_number_list:
1256 self.individual_run(run_number, self.archive_list, prepare)
1258 def split_run_numbers(self, s):
1260 for n in s.split(','):
1264 result.append(int(n))
1266 result.extend(list(range(int(x), int(y)+1)))
1269 def scons_path(self, dir):
1270 return os.path.join(dir, 'src', 'script', 'scons.py')
1272 def scons_lib_dir_path(self, dir):
1273 return os.path.join(dir, 'src', 'engine')
1275 def prep_aegis_run(self, commands, removals):
1276 self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1277 removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1279 self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1280 self.scons = self.scons_path(self.aegis_tmpdir)
1281 self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1284 'mkdir %(aegis_tmpdir)s',
1285 (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1286 '%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1287 '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1290 def prep_subversion_run(self, commands, removals):
1291 self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1292 removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1294 self.scons = self.scons_path(self.svn_tmpdir)
1295 self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1298 'mkdir %(svn_tmpdir)s',
1299 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1302 def individual_run(self, run_number, archive_list, prepare=None):
1304 Performs an individual run of the default SCons invocations.
1311 prepare(commands, removals)
1313 save_scons = self.scons
1314 save_scons_wrapper = self.scons_wrapper
1315 save_scons_lib_dir = self.scons_lib_dir
1317 if self.outdir is None:
1318 self.outdir = self.orig_cwd
1319 elif not os.path.isabs(self.outdir):
1320 self.outdir = os.path.join(self.orig_cwd, self.outdir)
1322 if self.scons is None:
1323 self.scons = self.scons_path(self.orig_cwd)
1325 if self.scons_lib_dir is None:
1326 self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1328 if self.scons_wrapper is None:
1329 self.scons_wrapper = self.scons
1332 run_number = self.find_next_run_number(self.outdir, self.prefix)
1334 self.run_number = str(run_number)
1336 self.prefix_run = self.prefix + '-%03d' % run_number
1338 if self.targets0 is None:
1339 self.targets0 = self.startup_targets
1340 if self.targets1 is None:
1341 self.targets1 = self.targets
1342 if self.targets2 is None:
1343 self.targets2 = self.targets
1345 self.tmpdir = make_temp_file(prefix = self.name + '-')
1350 (os.chdir, 'cd %%s', self.tmpdir),
1353 for archive in archive_list:
1354 if not os.path.isabs(archive):
1355 archive = os.path.join(self.orig_cwd, archive)
1356 if os.path.isdir(archive):
1357 dest = os.path.split(archive)[1]
1358 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1360 suffix = self.archive_splitext(archive)[1]
1361 unpack_command = self.unpack_map.get(suffix)
1362 if not unpack_command:
1363 dest = os.path.split(archive)[1]
1364 commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1366 commands.append(unpack_command + (archive,))
1369 (os.chdir, 'cd %%s', self.subdir),
1372 commands.extend(self.initial_commands)
1375 (lambda: read_tree('.'),
1376 'find * -type f | xargs cat > /dev/null'),
1378 (self.set_env, 'export %%s=%%s',
1379 'SCONS_LIB_DIR', self.scons_lib_dir),
1381 '%(python)s %(scons_wrapper)s --version',
1385 for run_command in self.run_commands:
1386 setattr(self, 'prof%d' % index, self.profile_name(index))
1391 self.logfile_name(index),
1397 (os.chdir, 'cd %%s', self.orig_cwd),
1400 if not os.environ.get('PRESERVE'):
1401 commands.extend(removals)
1403 commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1405 self.run_command_list(commands, self.__dict__)
1407 self.scons = save_scons
1408 self.scons_lib_dir = save_scons_lib_dir
1409 self.scons_wrapper = save_scons_wrapper
1413 def help_time(self):
1415 Usage: scons-time time [OPTIONS] FILE [...]
1417 -C DIR, --chdir=DIR Change to DIR before looking for files
1418 -f FILE, --file=FILE Read configuration from specified FILE
1419 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1420 -h, --help Print this help and exit
1421 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1422 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1423 --which=TIMER Plot timings for TIMER: total,
1424 SConscripts, SCons, commands.
1426 sys.stdout.write(self.outdent(help))
1429 def do_time(self, argv):
1432 logfile_path = lambda x: x
1436 short_opts = '?C:f:hp:t:'
1450 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1453 if o in ('-C', '--chdir'):
1455 elif o in ('-f', '--file'):
1456 self.config_file = a
1457 elif o in ('--fmt', '--format'):
1459 elif o in ('-?', '-h', '--help'):
1460 self.do_help(['help', 'time'])
1462 elif o in ('-p', '--prefix'):
1464 elif o in ('-t', '--tail'):
1466 elif o in ('--title',):
1468 elif o in ('--which',):
1469 if not a in self.time_strings.keys():
1470 sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1471 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1475 if self.config_file:
1476 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1479 os.chdir(self.chdir)
1480 logfile_path = lambda x: os.path.join(self.chdir, x)
1484 pattern = '%s*.log' % self.prefix
1485 args = self.args_to_files([pattern], tail)
1489 directory = self.chdir
1491 directory = os.getcwd()
1493 sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1494 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1495 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1500 args = self.args_to_files(args, tail)
1502 cwd_ = os.getcwd() + os.sep
1504 if format == 'ascii':
1506 columns = ("Total", "SConscripts", "SCons", "commands")
1507 self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1509 elif format == 'gnuplot':
1511 results = self.collect_results(args, self.get_debug_times,
1512 self.time_strings[which])
1514 self.gnuplot_results(results, fmt='%s %.6f')
1518 sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1521 if __name__ == '__main__':
1522 opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1527 if o in ('-?', '-h', '--help'):
1528 ST.do_help(['help'])
1530 elif o in ('-V', '--version'):
1531 sys.stdout.write('scons-time version\n')
1535 sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1538 ST.execute_subcommand(args)
1542 # indent-tabs-mode:nil
1544 # vim: set expandtab tabstop=4 shiftwidth=4: