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__"
50 # Pre-2.2 Python has no False keyword.
56 # Pre-2.2 Python has no True keyword.
62 # Pre-2.4 Python has no sorted() function.
64 # The pre-2.4 Python list.sort() method does not support
65 # list.sort(key=) nor list.sort(reverse=) keyword arguments, so
66 # we must implement the functionality of those keyword arguments
67 # by hand instead of passing them to list.sort().
68 def sorted(iterable, cmp=None, key=None, reverse=False):
70 result = [(key(x), x) for x in iterable]
74 # Pre-2.3 Python does not support list.sort(None).
79 result = [t1 for t0,t1 in result]
84 if os.environ.get('SCONS_HORRIBLE_REGRESSION_TEST_HACK') is not None:
85 # We can't apply the 'callable' fixer until the floor is 2.6, but the
86 # '-3' option to Python 2.6 and 2.7 generates almost ten thousand
87 # warnings. This hack allows us to run regression tests with the '-3'
88 # option by replacing the callable() built-in function with a hack
89 # that performs the same function but doesn't generate the warning.
90 # Note that this hack is ONLY intended to be used for regression
91 # testing, and should NEVER be used for real runs.
92 from types import ClassType
94 if hasattr(obj, '__call__'): return True
95 if isinstance(obj, (ClassType, type)): return True
98 def make_temp_file(**kw):
100 result = tempfile.mktemp(**kw)
102 result = os.path.realpath(result)
103 except AttributeError:
104 # Python 2.1 has no os.path.realpath() method.
108 save_template = tempfile.template
109 prefix = kw['prefix']
111 tempfile.template = prefix
112 result = tempfile.mktemp(**kw)
114 tempfile.template = save_template
117 def HACK_for_exec(cmd, *args):
119 For some reason, Python won't allow an exec() within a function
120 that also declares an internal function (including lambda functions).
121 This function is a hack that calls exec() in a function with no
124 if not args: exec(cmd)
125 elif len(args) == 1: exec cmd in args[0]
126 else: exec cmd in args[0], args[1]
129 def increment_size(self, largest):
131 Return the size of each horizontal increment line for a specified
132 maximum value. This returns a value that will provide somewhere
133 between 5 and 9 horizontal lines on the graph, on some set of
134 boundaries that are multiples of 10/100/1000/etc.
142 multiplier = multiplier * 10
143 return i * multiplier
145 def max_graph_value(self, largest):
146 # Round up to next integer.
147 largest = int(largest) + 1
148 increment = self.increment_size(largest)
149 return ((largest + increment - 1) / increment) * increment
152 def __init__(self, points, type, title, label, comment, fmt="%s %s"):
157 self.comment = comment
160 def print_label(self, inx, x, y):
162 print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
164 def plot_string(self):
166 title_string = 'title "%s"' % self.title
168 title_string = 'notitle'
169 return "'-' %s with lines lt %s" % (title_string, self.type)
171 def print_points(self, fmt=None):
175 print '# %s' % self.comment
176 for x, y in self.points:
177 # If y is None, it usually represents some kind of break
178 # in the line's index number. We might want to represent
179 # this some way rather than just drawing the line straight
180 # between the two points on either side.
185 def get_x_values(self):
186 return [ p[0] for p in self.points ]
188 def get_y_values(self):
189 return [ p[1] for p in self.points ]
191 class Gnuplotter(Plotter):
193 def __init__(self, title, key_location):
196 self.key_location = key_location
198 def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
200 line = Line(points, type, title, label, comment, fmt)
201 self.lines.append(line)
203 def plot_string(self, line):
204 return line.plot_string()
206 def vertical_bar(self, x, type, label, comment):
207 if self.get_min_x() <= x and x <= self.get_max_x():
208 points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
209 self.line(points, type, label, comment)
211 def get_all_x_values(self):
213 for line in self.lines:
214 result.extend(line.get_x_values())
215 return [r for r in result if not r is None]
217 def get_all_y_values(self):
219 for line in self.lines:
220 result.extend(line.get_y_values())
221 return [r for r in result if not r is None]
226 except AttributeError:
228 self.min_x = min(self.get_all_x_values())
236 except AttributeError:
238 self.max_x = max(self.get_all_x_values())
246 except AttributeError:
248 self.min_y = min(self.get_all_y_values())
256 except AttributeError:
258 self.max_y = max(self.get_all_y_values())
269 print 'set title "%s"' % self.title
270 print 'set key %s' % self.key_location
272 min_y = self.get_min_y()
273 max_y = self.max_graph_value(self.get_max_y())
274 incr = (max_y - min_y) / 10.0
275 start = min_y + (max_y / 2.0) + (2.0 * incr)
276 position = [ start - (i * incr) for i in range(5) ]
279 for line in self.lines:
280 line.print_label(inx, line.points[0][0]-1,
281 position[(inx-1) % len(position)])
284 plot_strings = [ self.plot_string(l) for l in self.lines ]
285 print 'plot ' + ', \\\n '.join(plot_strings)
287 for line in self.lines:
294 tar = tarfile.open(name=fname, mode='r')
301 zf = zipfile.ZipFile(fname, 'r')
302 for name in zf.namelist():
303 dir = os.path.dirname(name)
308 open(name, 'w').write(zf.read(name))
311 def read_files(arg, dirname, fnames):
313 fn = os.path.join(dirname, fn)
314 if os.path.isfile(fn):
315 open(fn, 'rb').read()
316 os.path.walk('.', read_files, None)
318 def redirect_to_file(command, log):
319 return '%s > %s 2>&1' % (command, log)
321 def tee_to_file(command, log):
322 return '%s 2>&1 | tee %s' % (command, log)
328 Usage: scons-time SUBCOMMAND [ARGUMENTS]
329 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
331 Available subcommands:
332 func Extract test-run data for a function
334 mem Extract --debug=memory data from test runs
335 obj Extract --debug=count data from test runs
336 time Extract --debug=time data from test runs
337 run Runs a test configuration
341 name_spaces = ' '*len(name)
346 default_settings = makedict(
348 aegis_project = None,
351 initial_commands = [],
352 key_location = 'bottom left',
353 orig_cwd = os.getcwd(),
356 python = '"%s"' % sys.executable,
357 redirect = redirect_to_file,
359 scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
360 scons_lib_dir = None,
361 scons_wrapper = None,
362 startup_targets = '--help',
364 subversion_url = None,
378 '.tar.gz' : (untar, '%(tar)s xzf %%s'),
379 '.tgz' : (untar, '%(tar)s xzf %%s'),
380 '.tar' : (untar, '%(tar)s xf %%s'),
381 '.zip' : (unzip, '%(unzip)s %%s'),
392 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
393 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
394 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
405 'pre-read' : 'Memory before reading SConscript files:',
406 'post-read' : 'Memory after reading SConscript files:',
407 'pre-build' : 'Memory before building targets:',
408 'post-build' : 'Memory after building targets:',
411 memory_string_all = 'Memory '
413 default_stage = stages[-1]
416 'total' : 'Total build time',
417 'SConscripts' : 'Total SConscript file execution time',
418 'SCons' : 'Total SCons execution time',
419 'commands' : 'Total command execution time',
422 time_string_all = 'Total .* time'
427 self.__dict__.update(self.default_settings)
429 # Functions for displaying and executing commands.
431 def subst(self, x, dictionary):
433 return x % dictionary
435 # x isn't a string (it's probably a Python function),
439 def subst_variables(self, command, dictionary):
441 Substitutes (via the format operator) the values in the specified
442 dictionary into the specified command.
444 The command can be an (action, string) tuple. In all cases, we
445 perform substitution on strings and don't worry if something isn't
446 a string. (It's probably a Python function to be executed.)
458 action = self.subst(action, dictionary)
459 string = self.subst(string, dictionary)
460 return (action, string, args)
462 def _do_not_display(self, msg, *args):
465 def display(self, msg, *args):
467 Displays the specified message.
469 Each message is prepended with a standard prefix of our name
479 sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
481 def _do_not_execute(self, action, *args):
484 def execute(self, action, *args):
486 Executes the specified action.
488 The action is called if it's a callable Python function, and
489 otherwise passed to os.system().
494 os.system(action % args)
496 def run_command_list(self, commands, dict):
498 Executes a list of commands, substituting values from the
499 specified dictionary.
501 commands = [ self.subst_variables(c, dict) for c in commands ]
502 for action, string, args in commands:
503 self.display(string, *args)
505 status = self.execute(action, *args)
509 def log_display(self, command, log):
510 command = self.subst(command, self.__dict__)
512 command = self.redirect(command, log)
515 def log_execute(self, command, log):
516 command = self.subst(command, self.__dict__)
517 output = os.popen(command).read()
519 sys.stdout.write(output)
520 open(log, 'wb').write(output)
524 def archive_splitext(self, path):
526 Splits an archive name into a filename base and extension.
528 This is like os.path.splitext() (which it calls) except that it
529 also looks for '.tar.gz' and treats it as an atomic extensions.
531 if path.endswith('.tar.gz'):
532 return path[:-7], path[-7:]
534 return os.path.splitext(path)
536 def args_to_files(self, args, tail=None):
538 Takes a list of arguments, expands any glob patterns, and
539 returns the last "tail" files from the list.
543 files.extend(sorted(glob.glob(a)))
546 files = files[-tail:]
550 def ascii_table(self, files, columns,
551 line_function, file_function=lambda x: x,
554 header_fmt = ' '.join(['%12s'] * len(columns))
555 line_fmt = header_fmt + ' %s'
557 print header_fmt % columns
560 t = line_function(file, *args, **kw)
563 diff = len(columns) - len(t)
566 t.append(file_function(file))
567 print line_fmt % tuple(t)
569 def collect_results(self, files, function, *args, **kw):
573 base = os.path.splitext(file)[0]
574 run, index = base.split('-')[-2:]
579 value = function(file, *args, **kw)
586 r.append((run, value))
590 def doc_to_help(self, obj):
592 Translates an object's __doc__ string into help text.
594 This strips a consistent number of spaces from each line in the
595 help text, essentially "outdenting" the text to the left-most
601 return self.outdent(doc)
603 def find_next_run_number(self, dir, prefix):
605 Returns the next run number in a directory for the specified prefix.
607 Examines the contents the specified directory for files with the
608 specified prefix, extracts the run numbers from each file name,
609 and returns the next run number after the largest it finds.
611 x = re.compile(re.escape(prefix) + '-([0-9]+).*')
612 matches = [x.match(e) for e in os.listdir(dir)]
613 matches = [_f for _f in matches if _f]
616 run_numbers = [int(m.group(1)) for m in matches]
617 return int(max(run_numbers)) + 1
619 def gnuplot_results(self, results, fmt='%s %.3f'):
621 Prints out a set of results in Gnuplot format.
623 gp = Gnuplotter(self.title, self.key_location)
625 for i in sorted(results.keys()):
627 t = self.run_titles[i]
631 gp.line(results[i], i+1, t, None, t, fmt=fmt)
633 for bar_tuple in self.vertical_bars:
635 x, type, label, comment = bar_tuple
637 x, type, label = bar_tuple
639 gp.vertical_bar(x, type, label, comment)
643 def logfile_name(self, invocation):
645 Returns the absolute path of a log file for the specificed
648 name = self.prefix_run + '-%d.log' % invocation
649 return os.path.join(self.outdir, name)
651 def outdent(self, s):
653 Strip as many spaces from each line as are found at the beginning
654 of the first line in the list.
656 lines = s.split('\n')
659 spaces = re.match(' *', lines[0]).group(0)
660 def strip_initial_spaces(l, s=spaces):
661 if l.startswith(spaces):
664 return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
666 def profile_name(self, invocation):
668 Returns the absolute path of a profile file for the specified
671 name = self.prefix_run + '-%d.prof' % invocation
672 return os.path.join(self.outdir, name)
674 def set_env(self, key, value):
675 os.environ[key] = value
679 def get_debug_times(self, file, time_string=None):
681 Fetch times from the --debug=time strings in the specified file.
683 if time_string is None:
684 search_string = self.time_string_all
686 search_string = time_string
687 contents = open(file).read()
689 sys.stderr.write('file %s has no contents!\n' % repr(file))
691 result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
692 result = [ float(r) for r in result ]
693 if not time_string is None:
697 sys.stderr.write('file %s has no results!\n' % repr(file))
701 def get_function_profile(self, file, function):
703 Returns the file, line number, function name, and cumulative time.
707 except ImportError, e:
708 sys.stderr.write('%s: func: %s\n' % (self.name, e))
709 sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
710 sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
712 statistics = pstats.Stats(file).stats
713 matches = [ e for e in statistics.items() if e[0][2] == function ]
715 return r[0][0], r[0][1], r[0][2], r[1][3]
717 def get_function_time(self, file, function):
719 Returns just the cumulative time for the specified function.
721 return self.get_function_profile(file, function)[3]
723 def get_memory(self, file, memory_string=None):
725 Returns a list of integers of the amount of memory used. The
726 default behavior is to return all the stages.
728 if memory_string is None:
729 search_string = self.memory_string_all
731 search_string = memory_string
732 lines = open(file).readlines()
733 lines = [ l for l in lines if l.startswith(search_string) ][-4:]
734 result = [ int(l.split()[-1]) for l in lines[-4:] ]
739 def get_object_counts(self, file, object_name, index=None):
741 Returns the counts of the specified object_name.
743 object_string = ' ' + object_name + '\n'
744 lines = open(file).readlines()
745 line = [ l for l in lines if l.endswith(object_string) ][0]
746 result = [ int(field) for field in line.split()[:4] ]
747 if index is not None:
748 result = result[index]
755 def execute_subcommand(self, argv):
757 Executes the do_*() function for the specified subcommand (argv[0]).
761 cmdName = self.command_alias.get(argv[0], argv[0])
763 func = getattr(self, 'do_' + cmdName)
764 except AttributeError:
765 return self.default(argv)
769 sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
771 traceback.print_exc(file=sys.stderr)
772 sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
774 def default(self, argv):
776 The default behavior for an unknown subcommand. Prints an
777 error message and exits.
779 sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
780 sys.stderr.write('Type "%s help" for usage.\n' % self.name)
785 def do_help(self, argv):
791 func = getattr(self, 'do_' + arg)
792 except AttributeError:
793 sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
796 help = getattr(self, 'help_' + arg)
797 except AttributeError:
798 sys.stdout.write(self.doc_to_help(func))
803 doc = self.doc_to_help(self.__class__)
805 sys.stdout.write(doc)
813 Usage: scons-time func [OPTIONS] FILE [...]
815 -C DIR, --chdir=DIR Change to DIR before looking for files
816 -f FILE, --file=FILE Read configuration from specified FILE
817 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
818 --func=NAME, --function=NAME Report time for function NAME
819 -h, --help Print this help and exit
820 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
821 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
822 --title=TITLE Specify the output plot TITLE
824 sys.stdout.write(self.outdent(help))
827 def do_func(self, argv):
831 function_name = '_main'
834 short_opts = '?C:f:hp:t:'
849 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
852 if o in ('-C', '--chdir'):
854 elif o in ('-f', '--file'):
856 elif o in ('--fmt', '--format'):
858 elif o in ('--func', '--function'):
860 elif o in ('-?', '-h', '--help'):
861 self.do_help(['help', 'func'])
863 elif o in ('--max',):
865 elif o in ('-p', '--prefix'):
867 elif o in ('-t', '--tail'):
869 elif o in ('--title',):
873 exec open(self.config_file, 'rU').read() in self.__dict__
880 pattern = '%s*.prof' % self.prefix
881 args = self.args_to_files([pattern], tail)
885 directory = self.chdir
887 directory = os.getcwd()
889 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
890 sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
891 sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
896 args = self.args_to_files(args, tail)
898 cwd_ = os.getcwd() + os.sep
900 if format == 'ascii':
904 f, line, func, time = \
905 self.get_function_profile(file, function_name)
906 except ValueError, e:
907 sys.stderr.write("%s: func: %s: %s\n" %
908 (self.name, file, e))
910 if f.startswith(cwd_):
912 print "%.3f %s:%d(%s)" % (time, f, line, func)
914 elif format == 'gnuplot':
916 results = self.collect_results(args, self.get_function_time,
919 self.gnuplot_results(results)
923 sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
930 Usage: scons-time mem [OPTIONS] FILE [...]
932 -C DIR, --chdir=DIR Change to DIR before looking for files
933 -f FILE, --file=FILE Read configuration from specified FILE
934 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
935 -h, --help Print this help and exit
936 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
937 --stage=STAGE Plot memory at the specified stage:
938 pre-read, post-read, pre-build,
939 post-build (default: post-build)
940 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
941 --title=TITLE Specify the output plot TITLE
943 sys.stdout.write(self.outdent(help))
946 def do_mem(self, argv):
949 logfile_path = lambda x: x
950 stage = self.default_stage
953 short_opts = '?C:f:hp:t:'
967 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
970 if o in ('-C', '--chdir'):
972 elif o in ('-f', '--file'):
974 elif o in ('--fmt', '--format'):
976 elif o in ('-?', '-h', '--help'):
977 self.do_help(['help', 'mem'])
979 elif o in ('-p', '--prefix'):
981 elif o in ('--stage',):
982 if not a in self.stages:
983 sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
986 elif o in ('-t', '--tail'):
988 elif o in ('--title',):
992 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
996 logfile_path = lambda x: os.path.join(self.chdir, x)
1000 pattern = '%s*.log' % self.prefix
1001 args = self.args_to_files([pattern], tail)
1005 directory = self.chdir
1007 directory = os.getcwd()
1009 sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
1010 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1011 sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
1016 args = self.args_to_files(args, tail)
1018 cwd_ = os.getcwd() + os.sep
1020 if format == 'ascii':
1022 self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
1024 elif format == 'gnuplot':
1026 results = self.collect_results(args, self.get_memory,
1027 self.stage_strings[stage])
1029 self.gnuplot_results(results)
1033 sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1042 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1044 -C DIR, --chdir=DIR Change to DIR before looking for files
1045 -f FILE, --file=FILE Read configuration from specified FILE
1046 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1047 -h, --help Print this help and exit
1048 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1049 --stage=STAGE Plot memory at the specified stage:
1050 pre-read, post-read, pre-build,
1051 post-build (default: post-build)
1052 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1053 --title=TITLE Specify the output plot TITLE
1055 sys.stdout.write(self.outdent(help))
1058 def do_obj(self, argv):
1061 logfile_path = lambda x: x
1062 stage = self.default_stage
1065 short_opts = '?C:f:hp:t:'
1079 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1082 if o in ('-C', '--chdir'):
1084 elif o in ('-f', '--file'):
1085 self.config_file = a
1086 elif o in ('--fmt', '--format'):
1088 elif o in ('-?', '-h', '--help'):
1089 self.do_help(['help', 'obj'])
1091 elif o in ('-p', '--prefix'):
1093 elif o in ('--stage',):
1094 if not a in self.stages:
1095 sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1096 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1099 elif o in ('-t', '--tail'):
1101 elif o in ('--title',):
1105 sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1106 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1109 object_name = args.pop(0)
1111 if self.config_file:
1112 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1115 os.chdir(self.chdir)
1116 logfile_path = lambda x: os.path.join(self.chdir, x)
1120 pattern = '%s*.log' % self.prefix
1121 args = self.args_to_files([pattern], tail)
1125 directory = self.chdir
1127 directory = os.getcwd()
1129 sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1130 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1131 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1136 args = self.args_to_files(args, tail)
1138 cwd_ = os.getcwd() + os.sep
1140 if format == 'ascii':
1142 self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1144 elif format == 'gnuplot':
1147 for s in self.stages:
1150 stage_index = stage_index + 1
1152 results = self.collect_results(args, self.get_object_counts,
1153 object_name, stage_index)
1155 self.gnuplot_results(results)
1159 sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1168 Usage: scons-time run [OPTIONS] [FILE ...]
1170 --aegis=PROJECT Use SCons from the Aegis PROJECT
1171 --chdir=DIR Name of unpacked directory for chdir
1172 -f FILE, --file=FILE Read configuration from specified FILE
1173 -h, --help Print this help and exit
1174 -n, --no-exec No execute, just print command lines
1175 --number=NUMBER Put output in files for run NUMBER
1176 --outdir=OUTDIR Put output files in OUTDIR
1177 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1178 --python=PYTHON Time using the specified PYTHON
1179 -q, --quiet Don't print command lines
1180 --scons=SCONS Time using the specified SCONS
1181 --svn=URL, --subversion=URL Use SCons from Subversion URL
1182 -v, --verbose Display output of commands
1184 sys.stdout.write(self.outdent(help))
1187 def do_run(self, argv):
1190 run_number_list = [None]
1192 short_opts = '?f:hnp:qs:v'
1211 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1214 if o in ('--aegis',):
1215 self.aegis_project = a
1216 elif o in ('-f', '--file'):
1217 self.config_file = a
1218 elif o in ('-?', '-h', '--help'):
1219 self.do_help(['help', 'run'])
1221 elif o in ('-n', '--no-exec'):
1222 self.execute = self._do_not_execute
1223 elif o in ('--number',):
1224 run_number_list = self.split_run_numbers(a)
1225 elif o in ('--outdir',):
1227 elif o in ('-p', '--prefix'):
1229 elif o in ('--python',):
1231 elif o in ('-q', '--quiet'):
1232 self.display = self._do_not_display
1233 elif o in ('-s', '--subdir'):
1235 elif o in ('--scons',):
1237 elif o in ('--svn', '--subversion'):
1238 self.subversion_url = a
1239 elif o in ('-v', '--verbose'):
1240 self.redirect = tee_to_file
1242 self.svn_co_flag = ''
1244 if not args and not self.config_file:
1245 sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1246 sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1249 if self.config_file:
1250 exec open(self.config_file, 'rU').read() in self.__dict__
1253 self.archive_list = args
1255 archive_file_name = os.path.split(self.archive_list[0])[1]
1258 self.subdir = self.archive_splitext(archive_file_name)[0]
1261 self.prefix = self.archive_splitext(archive_file_name)[0]
1264 if self.subversion_url:
1265 prepare = self.prep_subversion_run
1266 elif self.aegis_project:
1267 prepare = self.prep_aegis_run
1269 for run_number in run_number_list:
1270 self.individual_run(run_number, self.archive_list, prepare)
1272 def split_run_numbers(self, s):
1274 for n in s.split(','):
1278 result.append(int(n))
1280 result.extend(list(range(int(x), int(y)+1)))
1283 def scons_path(self, dir):
1284 return os.path.join(dir, 'src', 'script', 'scons.py')
1286 def scons_lib_dir_path(self, dir):
1287 return os.path.join(dir, 'src', 'engine')
1289 def prep_aegis_run(self, commands, removals):
1290 self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1291 removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1293 self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1294 self.scons = self.scons_path(self.aegis_tmpdir)
1295 self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1298 'mkdir %(aegis_tmpdir)s',
1299 (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1300 '%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1301 '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1304 def prep_subversion_run(self, commands, removals):
1305 self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1306 removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1308 self.scons = self.scons_path(self.svn_tmpdir)
1309 self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1312 'mkdir %(svn_tmpdir)s',
1313 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1316 def individual_run(self, run_number, archive_list, prepare=None):
1318 Performs an individual run of the default SCons invocations.
1325 prepare(commands, removals)
1327 save_scons = self.scons
1328 save_scons_wrapper = self.scons_wrapper
1329 save_scons_lib_dir = self.scons_lib_dir
1331 if self.outdir is None:
1332 self.outdir = self.orig_cwd
1333 elif not os.path.isabs(self.outdir):
1334 self.outdir = os.path.join(self.orig_cwd, self.outdir)
1336 if self.scons is None:
1337 self.scons = self.scons_path(self.orig_cwd)
1339 if self.scons_lib_dir is None:
1340 self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1342 if self.scons_wrapper is None:
1343 self.scons_wrapper = self.scons
1346 run_number = self.find_next_run_number(self.outdir, self.prefix)
1348 self.run_number = str(run_number)
1350 self.prefix_run = self.prefix + '-%03d' % run_number
1352 if self.targets0 is None:
1353 self.targets0 = self.startup_targets
1354 if self.targets1 is None:
1355 self.targets1 = self.targets
1356 if self.targets2 is None:
1357 self.targets2 = self.targets
1359 self.tmpdir = make_temp_file(prefix = self.name + '-')
1364 (os.chdir, 'cd %%s', self.tmpdir),
1367 for archive in archive_list:
1368 if not os.path.isabs(archive):
1369 archive = os.path.join(self.orig_cwd, archive)
1370 if os.path.isdir(archive):
1371 dest = os.path.split(archive)[1]
1372 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1374 suffix = self.archive_splitext(archive)[1]
1375 unpack_command = self.unpack_map.get(suffix)
1376 if not unpack_command:
1377 dest = os.path.split(archive)[1]
1378 commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1380 commands.append(unpack_command + (archive,))
1383 (os.chdir, 'cd %%s', self.subdir),
1386 commands.extend(self.initial_commands)
1389 (lambda: read_tree('.'),
1390 'find * -type f | xargs cat > /dev/null'),
1392 (self.set_env, 'export %%s=%%s',
1393 'SCONS_LIB_DIR', self.scons_lib_dir),
1395 '%(python)s %(scons_wrapper)s --version',
1399 for run_command in self.run_commands:
1400 setattr(self, 'prof%d' % index, self.profile_name(index))
1405 self.logfile_name(index),
1411 (os.chdir, 'cd %%s', self.orig_cwd),
1414 if not os.environ.get('PRESERVE'):
1415 commands.extend(removals)
1417 commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1419 self.run_command_list(commands, self.__dict__)
1421 self.scons = save_scons
1422 self.scons_lib_dir = save_scons_lib_dir
1423 self.scons_wrapper = save_scons_wrapper
1427 def help_time(self):
1429 Usage: scons-time time [OPTIONS] FILE [...]
1431 -C DIR, --chdir=DIR Change to DIR before looking for files
1432 -f FILE, --file=FILE Read configuration from specified FILE
1433 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1434 -h, --help Print this help and exit
1435 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1436 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1437 --which=TIMER Plot timings for TIMER: total,
1438 SConscripts, SCons, commands.
1440 sys.stdout.write(self.outdent(help))
1443 def do_time(self, argv):
1446 logfile_path = lambda x: x
1450 short_opts = '?C:f:hp:t:'
1464 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1467 if o in ('-C', '--chdir'):
1469 elif o in ('-f', '--file'):
1470 self.config_file = a
1471 elif o in ('--fmt', '--format'):
1473 elif o in ('-?', '-h', '--help'):
1474 self.do_help(['help', 'time'])
1476 elif o in ('-p', '--prefix'):
1478 elif o in ('-t', '--tail'):
1480 elif o in ('--title',):
1482 elif o in ('--which',):
1483 if not a in self.time_strings.keys():
1484 sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1485 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1489 if self.config_file:
1490 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1493 os.chdir(self.chdir)
1494 logfile_path = lambda x: os.path.join(self.chdir, x)
1498 pattern = '%s*.log' % self.prefix
1499 args = self.args_to_files([pattern], tail)
1503 directory = self.chdir
1505 directory = os.getcwd()
1507 sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1508 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1509 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1514 args = self.args_to_files(args, tail)
1516 cwd_ = os.getcwd() + os.sep
1518 if format == 'ascii':
1520 columns = ("Total", "SConscripts", "SCons", "commands")
1521 self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1523 elif format == 'gnuplot':
1525 results = self.collect_results(args, self.get_debug_times,
1526 self.time_strings[which])
1528 self.gnuplot_results(results, fmt='%s %.6f')
1532 sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1535 if __name__ == '__main__':
1536 opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1541 if o in ('-?', '-h', '--help'):
1542 ST.do_help(['help'])
1544 elif o in ('-V', '--version'):
1545 sys.stdout.write('scons-time version\n')
1549 sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1552 ST.execute_subcommand(args)
1556 # indent-tabs-mode:nil
1558 # vim: set expandtab tabstop=4 shiftwidth=4: