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
62 def make_temp_file(**kw):
64 result = tempfile.mktemp(**kw)
66 result = os.path.realpath(result)
67 except AttributeError:
68 # Python 2.1 has no os.path.realpath() method.
72 save_template = tempfile.template
75 tempfile.template = prefix
76 result = tempfile.mktemp(**kw)
78 tempfile.template = save_template
81 def HACK_for_exec(cmd, *args):
83 For some reason, Python won't allow an exec() within a function
84 that also declares an internal function (including lambda functions).
85 This function is a hack that calls exec() in a function with no
88 if not args: exec(cmd)
89 elif len(args) == 1: exec cmd in args[0]
90 else: exec cmd in args[0], args[1]
93 def increment_size(self, largest):
95 Return the size of each horizontal increment line for a specified
96 maximum value. This returns a value that will provide somewhere
97 between 5 and 9 horizontal lines on the graph, on some set of
98 boundaries that are multiples of 10/100/1000/etc.
106 multiplier = multiplier * 10
107 return i * multiplier
109 def max_graph_value(self, largest):
110 # Round up to next integer.
111 largest = int(largest) + 1
112 increment = self.increment_size(largest)
113 return ((largest + increment - 1) / increment) * increment
116 def __init__(self, points, type, title, label, comment, fmt="%s %s"):
121 self.comment = comment
124 def print_label(self, inx, x, y):
126 print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
128 def plot_string(self):
130 title_string = 'title "%s"' % self.title
132 title_string = 'notitle'
133 return "'-' %s with lines lt %s" % (title_string, self.type)
135 def print_points(self, fmt=None):
139 print '# %s' % self.comment
140 for x, y in self.points:
141 # If y is None, it usually represents some kind of break
142 # in the line's index number. We might want to represent
143 # this some way rather than just drawing the line straight
144 # between the two points on either side.
149 def get_x_values(self):
150 return [ p[0] for p in self.points ]
152 def get_y_values(self):
153 return [ p[1] for p in self.points ]
155 class Gnuplotter(Plotter):
157 def __init__(self, title, key_location):
160 self.key_location = key_location
162 def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
164 line = Line(points, type, title, label, comment, fmt)
165 self.lines.append(line)
167 def plot_string(self, line):
168 return line.plot_string()
170 def vertical_bar(self, x, type, label, comment):
171 if self.get_min_x() <= x and x <= self.get_max_x():
172 points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
173 self.line(points, type, label, comment)
175 def get_all_x_values(self):
177 for line in self.lines:
178 result.extend(line.get_x_values())
179 return [r for r in result if not r is None]
181 def get_all_y_values(self):
183 for line in self.lines:
184 result.extend(line.get_y_values())
185 return [r for r in result if not r is None]
190 except AttributeError:
192 self.min_x = min(self.get_all_x_values())
200 except AttributeError:
202 self.max_x = max(self.get_all_x_values())
210 except AttributeError:
212 self.min_y = min(self.get_all_y_values())
220 except AttributeError:
222 self.max_y = max(self.get_all_y_values())
233 print 'set title "%s"' % self.title
234 print 'set key %s' % self.key_location
236 min_y = self.get_min_y()
237 max_y = self.max_graph_value(self.get_max_y())
238 range = max_y - min_y
240 start = min_y + (max_y / 2.0) + (2.0 * incr)
241 position = [ start - (i * incr) for i in xrange(5) ]
244 for line in self.lines:
245 line.print_label(inx, line.points[0][0]-1,
246 position[(inx-1) % len(position)])
249 plot_strings = [ self.plot_string(l) for l in self.lines ]
250 print 'plot ' + ', \\\n '.join(plot_strings)
252 for line in self.lines:
259 tar = tarfile.open(name=fname, mode='r')
266 zf = zipfile.ZipFile(fname, 'r')
267 for name in zf.namelist():
268 dir = os.path.dirname(name)
273 open(name, 'w').write(zf.read(name))
276 def read_files(arg, dirname, fnames):
278 fn = os.path.join(dirname, fn)
279 if os.path.isfile(fn):
280 open(fn, 'rb').read()
281 os.path.walk('.', read_files, None)
283 def redirect_to_file(command, log):
284 return '%s > %s 2>&1' % (command, log)
286 def tee_to_file(command, log):
287 return '%s 2>&1 | tee %s' % (command, log)
293 Usage: scons-time SUBCOMMAND [ARGUMENTS]
294 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
296 Available subcommands:
297 func Extract test-run data for a function
299 mem Extract --debug=memory data from test runs
300 obj Extract --debug=count data from test runs
301 time Extract --debug=time data from test runs
302 run Runs a test configuration
306 name_spaces = ' '*len(name)
311 default_settings = makedict(
313 aegis_project = None,
316 initial_commands = [],
317 key_location = 'bottom left',
318 orig_cwd = os.getcwd(),
321 python = '"%s"' % sys.executable,
322 redirect = redirect_to_file,
324 scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
325 scons_lib_dir = None,
326 scons_wrapper = None,
327 startup_targets = '--help',
329 subversion_url = None,
343 '.tar.gz' : (untar, '%(tar)s xzf %%s'),
344 '.tgz' : (untar, '%(tar)s xzf %%s'),
345 '.tar' : (untar, '%(tar)s xf %%s'),
346 '.zip' : (unzip, '%(unzip)s %%s'),
357 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
358 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
359 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
370 'pre-read' : 'Memory before reading SConscript files:',
371 'post-read' : 'Memory after reading SConscript files:',
372 'pre-build' : 'Memory before building targets:',
373 'post-build' : 'Memory after building targets:',
376 memory_string_all = 'Memory '
378 default_stage = stages[-1]
381 'total' : 'Total build time',
382 'SConscripts' : 'Total SConscript file execution time',
383 'SCons' : 'Total SCons execution time',
384 'commands' : 'Total command execution time',
387 time_string_all = 'Total .* time'
392 self.__dict__.update(self.default_settings)
394 # Functions for displaying and executing commands.
396 def subst(self, x, dictionary):
398 return x % dictionary
400 # x isn't a string (it's probably a Python function),
404 def subst_variables(self, command, dictionary):
406 Substitutes (via the format operator) the values in the specified
407 dictionary into the specified command.
409 The command can be an (action, string) tuple. In all cases, we
410 perform substitution on strings and don't worry if something isn't
411 a string. (It's probably a Python function to be executed.)
423 action = self.subst(action, dictionary)
424 string = self.subst(string, dictionary)
425 return (action, string, args)
427 def _do_not_display(self, msg, *args):
430 def display(self, msg, *args):
432 Displays the specified message.
434 Each message is prepended with a standard prefix of our name
444 sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
446 def _do_not_execute(self, action, *args):
449 def execute(self, action, *args):
451 Executes the specified action.
453 The action is called if it's a callable Python function, and
454 otherwise passed to os.system().
459 os.system(action % args)
461 def run_command_list(self, commands, dict):
463 Executes a list of commands, substituting values from the
464 specified dictionary.
466 commands = [ self.subst_variables(c, dict) for c in commands ]
467 for action, string, args in commands:
468 self.display(string, *args)
470 status = self.execute(action, *args)
474 def log_display(self, command, log):
475 command = self.subst(command, self.__dict__)
477 command = self.redirect(command, log)
480 def log_execute(self, command, log):
481 command = self.subst(command, self.__dict__)
482 output = os.popen(command).read()
484 sys.stdout.write(output)
485 open(log, 'wb').write(output)
489 def archive_splitext(self, path):
491 Splits an archive name into a filename base and extension.
493 This is like os.path.splitext() (which it calls) except that it
494 also looks for '.tar.gz' and treats it as an atomic extensions.
496 if path.endswith('.tar.gz'):
497 return path[:-7], path[-7:]
499 return os.path.splitext(path)
501 def args_to_files(self, args, tail=None):
503 Takes a list of arguments, expands any glob patterns, and
504 returns the last "tail" files from the list.
513 files = files[-tail:]
517 def ascii_table(self, files, columns,
518 line_function, file_function=lambda x: x,
521 header_fmt = ' '.join(['%12s'] * len(columns))
522 line_fmt = header_fmt + ' %s'
524 print header_fmt % columns
527 t = line_function(file, *args, **kw)
530 diff = len(columns) - len(t)
533 t.append(file_function(file))
534 print line_fmt % tuple(t)
536 def collect_results(self, files, function, *args, **kw):
540 base = os.path.splitext(file)[0]
541 run, index = base.split('-')[-2:]
546 value = function(file, *args, **kw)
553 r.append((run, value))
557 def doc_to_help(self, obj):
559 Translates an object's __doc__ string into help text.
561 This strips a consistent number of spaces from each line in the
562 help text, essentially "outdenting" the text to the left-most
568 return self.outdent(doc)
570 def find_next_run_number(self, dir, prefix):
572 Returns the next run number in a directory for the specified prefix.
574 Examines the contents the specified directory for files with the
575 specified prefix, extracts the run numbers from each file name,
576 and returns the next run number after the largest it finds.
578 x = re.compile(re.escape(prefix) + '-([0-9]+).*')
579 matches = [x.match(e) for e in os.listdir(dir)]
580 matches = [_f for _f in matches if _f]
583 run_numbers = [int(m.group(1)) for m in matches]
584 return int(max(run_numbers)) + 1
586 def gnuplot_results(self, results, fmt='%s %.3f'):
588 Prints out a set of results in Gnuplot format.
590 gp = Gnuplotter(self.title, self.key_location)
592 indices = results.keys()
597 t = self.run_titles[i]
601 gp.line(results[i], i+1, t, None, t, fmt=fmt)
603 for bar_tuple in self.vertical_bars:
605 x, type, label, comment = bar_tuple
607 x, type, label = bar_tuple
609 gp.vertical_bar(x, type, label, comment)
613 def logfile_name(self, invocation):
615 Returns the absolute path of a log file for the specificed
618 name = self.prefix_run + '-%d.log' % invocation
619 return os.path.join(self.outdir, name)
621 def outdent(self, s):
623 Strip as many spaces from each line as are found at the beginning
624 of the first line in the list.
626 lines = s.split('\n')
629 spaces = re.match(' *', lines[0]).group(0)
630 def strip_initial_spaces(l, s=spaces):
631 if l.startswith(spaces):
634 return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
636 def profile_name(self, invocation):
638 Returns the absolute path of a profile file for the specified
641 name = self.prefix_run + '-%d.prof' % invocation
642 return os.path.join(self.outdir, name)
644 def set_env(self, key, value):
645 os.environ[key] = value
649 def get_debug_times(self, file, time_string=None):
651 Fetch times from the --debug=time strings in the specified file.
653 if time_string is None:
654 search_string = self.time_string_all
656 search_string = time_string
657 contents = open(file).read()
659 sys.stderr.write('file %s has no contents!\n' % repr(file))
661 result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
662 result = [ float(r) for r in result ]
663 if not time_string is None:
667 sys.stderr.write('file %s has no results!\n' % repr(file))
671 def get_function_profile(self, file, function):
673 Returns the file, line number, function name, and cumulative time.
677 except ImportError, e:
678 sys.stderr.write('%s: func: %s\n' % (self.name, e))
679 sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
680 sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
682 statistics = pstats.Stats(file).stats
683 matches = [ e for e in statistics.items() if e[0][2] == function ]
685 return r[0][0], r[0][1], r[0][2], r[1][3]
687 def get_function_time(self, file, function):
689 Returns just the cumulative time for the specified function.
691 return self.get_function_profile(file, function)[3]
693 def get_memory(self, file, memory_string=None):
695 Returns a list of integers of the amount of memory used. The
696 default behavior is to return all the stages.
698 if memory_string is None:
699 search_string = self.memory_string_all
701 search_string = memory_string
702 lines = open(file).readlines()
703 lines = [ l for l in lines if l.startswith(search_string) ][-4:]
704 result = [ int(l.split()[-1]) for l in lines[-4:] ]
709 def get_object_counts(self, file, object_name, index=None):
711 Returns the counts of the specified object_name.
713 object_string = ' ' + object_name + '\n'
714 lines = open(file).readlines()
715 line = [ l for l in lines if l.endswith(object_string) ][0]
716 result = [ int(field) for field in line.split()[:4] ]
717 if index is not None:
718 result = result[index]
725 def execute_subcommand(self, argv):
727 Executes the do_*() function for the specified subcommand (argv[0]).
731 cmdName = self.command_alias.get(argv[0], argv[0])
733 func = getattr(self, 'do_' + cmdName)
734 except AttributeError:
735 return self.default(argv)
739 sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
741 traceback.print_exc(file=sys.stderr)
742 sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
744 def default(self, argv):
746 The default behavior for an unknown subcommand. Prints an
747 error message and exits.
749 sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
750 sys.stderr.write('Type "%s help" for usage.\n' % self.name)
755 def do_help(self, argv):
761 func = getattr(self, 'do_' + arg)
762 except AttributeError:
763 sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
766 help = getattr(self, 'help_' + arg)
767 except AttributeError:
768 sys.stdout.write(self.doc_to_help(func))
773 doc = self.doc_to_help(self.__class__)
775 sys.stdout.write(doc)
783 Usage: scons-time func [OPTIONS] FILE [...]
785 -C DIR, --chdir=DIR Change to DIR before looking for files
786 -f FILE, --file=FILE Read configuration from specified FILE
787 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
788 --func=NAME, --function=NAME Report time for function NAME
789 -h, --help Print this help and exit
790 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
791 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
792 --title=TITLE Specify the output plot TITLE
794 sys.stdout.write(self.outdent(help))
797 def do_func(self, argv):
801 function_name = '_main'
804 short_opts = '?C:f:hp:t:'
819 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
822 if o in ('-C', '--chdir'):
824 elif o in ('-f', '--file'):
826 elif o in ('--fmt', '--format'):
828 elif o in ('--func', '--function'):
830 elif o in ('-?', '-h', '--help'):
831 self.do_help(['help', 'func'])
833 elif o in ('--max',):
835 elif o in ('-p', '--prefix'):
837 elif o in ('-t', '--tail'):
839 elif o in ('--title',):
843 exec open(self.config_file, 'rU').read() in self.__dict__
850 pattern = '%s*.prof' % self.prefix
851 args = self.args_to_files([pattern], tail)
855 directory = self.chdir
857 directory = os.getcwd()
859 sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
860 sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
861 sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
866 args = self.args_to_files(args, tail)
868 cwd_ = os.getcwd() + os.sep
870 if format == 'ascii':
874 f, line, func, time = \
875 self.get_function_profile(file, function_name)
876 except ValueError, e:
877 sys.stderr.write("%s: func: %s: %s\n" %
878 (self.name, file, e))
880 if f.startswith(cwd_):
882 print "%.3f %s:%d(%s)" % (time, f, line, func)
884 elif format == 'gnuplot':
886 results = self.collect_results(args, self.get_function_time,
889 self.gnuplot_results(results)
893 sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
900 Usage: scons-time mem [OPTIONS] FILE [...]
902 -C DIR, --chdir=DIR Change to DIR before looking for files
903 -f FILE, --file=FILE Read configuration from specified FILE
904 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
905 -h, --help Print this help and exit
906 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
907 --stage=STAGE Plot memory at the specified stage:
908 pre-read, post-read, pre-build,
909 post-build (default: post-build)
910 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
911 --title=TITLE Specify the output plot TITLE
913 sys.stdout.write(self.outdent(help))
916 def do_mem(self, argv):
919 logfile_path = lambda x: x
920 stage = self.default_stage
923 short_opts = '?C:f:hp:t:'
937 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
940 if o in ('-C', '--chdir'):
942 elif o in ('-f', '--file'):
944 elif o in ('--fmt', '--format'):
946 elif o in ('-?', '-h', '--help'):
947 self.do_help(['help', 'mem'])
949 elif o in ('-p', '--prefix'):
951 elif o in ('--stage',):
952 if not a in self.stages:
953 sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
956 elif o in ('-t', '--tail'):
958 elif o in ('--title',):
962 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
966 logfile_path = lambda x: os.path.join(self.chdir, x)
970 pattern = '%s*.log' % self.prefix
971 args = self.args_to_files([pattern], tail)
975 directory = self.chdir
977 directory = os.getcwd()
979 sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
980 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
981 sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
986 args = self.args_to_files(args, tail)
988 cwd_ = os.getcwd() + os.sep
990 if format == 'ascii':
992 self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
994 elif format == 'gnuplot':
996 results = self.collect_results(args, self.get_memory,
997 self.stage_strings[stage])
999 self.gnuplot_results(results)
1003 sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1012 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1014 -C DIR, --chdir=DIR Change to DIR before looking for files
1015 -f FILE, --file=FILE Read configuration from specified FILE
1016 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1017 -h, --help Print this help and exit
1018 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1019 --stage=STAGE Plot memory at the specified stage:
1020 pre-read, post-read, pre-build,
1021 post-build (default: post-build)
1022 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1023 --title=TITLE Specify the output plot TITLE
1025 sys.stdout.write(self.outdent(help))
1028 def do_obj(self, argv):
1031 logfile_path = lambda x: x
1032 stage = self.default_stage
1035 short_opts = '?C:f:hp:t:'
1049 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1052 if o in ('-C', '--chdir'):
1054 elif o in ('-f', '--file'):
1055 self.config_file = a
1056 elif o in ('--fmt', '--format'):
1058 elif o in ('-?', '-h', '--help'):
1059 self.do_help(['help', 'obj'])
1061 elif o in ('-p', '--prefix'):
1063 elif o in ('--stage',):
1064 if not a in self.stages:
1065 sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1066 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1069 elif o in ('-t', '--tail'):
1071 elif o in ('--title',):
1075 sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1076 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1079 object_name = args.pop(0)
1081 if self.config_file:
1082 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1085 os.chdir(self.chdir)
1086 logfile_path = lambda x: os.path.join(self.chdir, x)
1090 pattern = '%s*.log' % self.prefix
1091 args = self.args_to_files([pattern], tail)
1095 directory = self.chdir
1097 directory = os.getcwd()
1099 sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1100 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1101 sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1106 args = self.args_to_files(args, tail)
1108 cwd_ = os.getcwd() + os.sep
1110 if format == 'ascii':
1112 self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1114 elif format == 'gnuplot':
1117 for s in self.stages:
1120 stage_index = stage_index + 1
1122 results = self.collect_results(args, self.get_object_counts,
1123 object_name, stage_index)
1125 self.gnuplot_results(results)
1129 sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1138 Usage: scons-time run [OPTIONS] [FILE ...]
1140 --aegis=PROJECT Use SCons from the Aegis PROJECT
1141 --chdir=DIR Name of unpacked directory for chdir
1142 -f FILE, --file=FILE Read configuration from specified FILE
1143 -h, --help Print this help and exit
1144 -n, --no-exec No execute, just print command lines
1145 --number=NUMBER Put output in files for run NUMBER
1146 --outdir=OUTDIR Put output files in OUTDIR
1147 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1148 --python=PYTHON Time using the specified PYTHON
1149 -q, --quiet Don't print command lines
1150 --scons=SCONS Time using the specified SCONS
1151 --svn=URL, --subversion=URL Use SCons from Subversion URL
1152 -v, --verbose Display output of commands
1154 sys.stdout.write(self.outdent(help))
1157 def do_run(self, argv):
1160 run_number_list = [None]
1162 short_opts = '?f:hnp:qs:v'
1181 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1184 if o in ('--aegis',):
1185 self.aegis_project = a
1186 elif o in ('-f', '--file'):
1187 self.config_file = a
1188 elif o in ('-?', '-h', '--help'):
1189 self.do_help(['help', 'run'])
1191 elif o in ('-n', '--no-exec'):
1192 self.execute = self._do_not_execute
1193 elif o in ('--number',):
1194 run_number_list = self.split_run_numbers(a)
1195 elif o in ('--outdir',):
1197 elif o in ('-p', '--prefix'):
1199 elif o in ('--python',):
1201 elif o in ('-q', '--quiet'):
1202 self.display = self._do_not_display
1203 elif o in ('-s', '--subdir'):
1205 elif o in ('--scons',):
1207 elif o in ('--svn', '--subversion'):
1208 self.subversion_url = a
1209 elif o in ('-v', '--verbose'):
1210 self.redirect = tee_to_file
1212 self.svn_co_flag = ''
1214 if not args and not self.config_file:
1215 sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1216 sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1219 if self.config_file:
1220 exec open(self.config_file, 'rU').read() in self.__dict__
1223 self.archive_list = args
1225 archive_file_name = os.path.split(self.archive_list[0])[1]
1228 self.subdir = self.archive_splitext(archive_file_name)[0]
1231 self.prefix = self.archive_splitext(archive_file_name)[0]
1234 if self.subversion_url:
1235 prepare = self.prep_subversion_run
1236 elif self.aegis_project:
1237 prepare = self.prep_aegis_run
1239 for run_number in run_number_list:
1240 self.individual_run(run_number, self.archive_list, prepare)
1242 def split_run_numbers(self, s):
1244 for n in s.split(','):
1248 result.append(int(n))
1250 result.extend(range(int(x), int(y)+1))
1253 def scons_path(self, dir):
1254 return os.path.join(dir, 'src', 'script', 'scons.py')
1256 def scons_lib_dir_path(self, dir):
1257 return os.path.join(dir, 'src', 'engine')
1259 def prep_aegis_run(self, commands, removals):
1260 self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1261 removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1263 self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1264 self.scons = self.scons_path(self.aegis_tmpdir)
1265 self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1268 'mkdir %(aegis_tmpdir)s',
1269 (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1270 '%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1271 '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1274 def prep_subversion_run(self, commands, removals):
1275 self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1276 removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1278 self.scons = self.scons_path(self.svn_tmpdir)
1279 self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1282 'mkdir %(svn_tmpdir)s',
1283 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1286 def individual_run(self, run_number, archive_list, prepare=None):
1288 Performs an individual run of the default SCons invocations.
1295 prepare(commands, removals)
1297 save_scons = self.scons
1298 save_scons_wrapper = self.scons_wrapper
1299 save_scons_lib_dir = self.scons_lib_dir
1301 if self.outdir is None:
1302 self.outdir = self.orig_cwd
1303 elif not os.path.isabs(self.outdir):
1304 self.outdir = os.path.join(self.orig_cwd, self.outdir)
1306 if self.scons is None:
1307 self.scons = self.scons_path(self.orig_cwd)
1309 if self.scons_lib_dir is None:
1310 self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1312 if self.scons_wrapper is None:
1313 self.scons_wrapper = self.scons
1316 run_number = self.find_next_run_number(self.outdir, self.prefix)
1318 self.run_number = str(run_number)
1320 self.prefix_run = self.prefix + '-%03d' % run_number
1322 if self.targets0 is None:
1323 self.targets0 = self.startup_targets
1324 if self.targets1 is None:
1325 self.targets1 = self.targets
1326 if self.targets2 is None:
1327 self.targets2 = self.targets
1329 self.tmpdir = make_temp_file(prefix = self.name + '-')
1334 (os.chdir, 'cd %%s', self.tmpdir),
1337 for archive in archive_list:
1338 if not os.path.isabs(archive):
1339 archive = os.path.join(self.orig_cwd, archive)
1340 if os.path.isdir(archive):
1341 dest = os.path.split(archive)[1]
1342 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1344 suffix = self.archive_splitext(archive)[1]
1345 unpack_command = self.unpack_map.get(suffix)
1346 if not unpack_command:
1347 dest = os.path.split(archive)[1]
1348 commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1350 commands.append(unpack_command + (archive,))
1353 (os.chdir, 'cd %%s', self.subdir),
1356 commands.extend(self.initial_commands)
1359 (lambda: read_tree('.'),
1360 'find * -type f | xargs cat > /dev/null'),
1362 (self.set_env, 'export %%s=%%s',
1363 'SCONS_LIB_DIR', self.scons_lib_dir),
1365 '%(python)s %(scons_wrapper)s --version',
1369 for run_command in self.run_commands:
1370 setattr(self, 'prof%d' % index, self.profile_name(index))
1375 self.logfile_name(index),
1381 (os.chdir, 'cd %%s', self.orig_cwd),
1384 if not os.environ.get('PRESERVE'):
1385 commands.extend(removals)
1387 commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1389 self.run_command_list(commands, self.__dict__)
1391 self.scons = save_scons
1392 self.scons_lib_dir = save_scons_lib_dir
1393 self.scons_wrapper = save_scons_wrapper
1397 def help_time(self):
1399 Usage: scons-time time [OPTIONS] FILE [...]
1401 -C DIR, --chdir=DIR Change to DIR before looking for files
1402 -f FILE, --file=FILE Read configuration from specified FILE
1403 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1404 -h, --help Print this help and exit
1405 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1406 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1407 --which=TIMER Plot timings for TIMER: total,
1408 SConscripts, SCons, commands.
1410 sys.stdout.write(self.outdent(help))
1413 def do_time(self, argv):
1416 logfile_path = lambda x: x
1420 short_opts = '?C:f:hp:t:'
1434 opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1437 if o in ('-C', '--chdir'):
1439 elif o in ('-f', '--file'):
1440 self.config_file = a
1441 elif o in ('--fmt', '--format'):
1443 elif o in ('-?', '-h', '--help'):
1444 self.do_help(['help', 'time'])
1446 elif o in ('-p', '--prefix'):
1448 elif o in ('-t', '--tail'):
1450 elif o in ('--title',):
1452 elif o in ('--which',):
1453 if not a in self.time_strings.keys():
1454 sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1455 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1459 if self.config_file:
1460 HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1463 os.chdir(self.chdir)
1464 logfile_path = lambda x: os.path.join(self.chdir, x)
1468 pattern = '%s*.log' % self.prefix
1469 args = self.args_to_files([pattern], tail)
1473 directory = self.chdir
1475 directory = os.getcwd()
1477 sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1478 sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1479 sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1484 args = self.args_to_files(args, tail)
1486 cwd_ = os.getcwd() + os.sep
1488 if format == 'ascii':
1490 columns = ("Total", "SConscripts", "SCons", "commands")
1491 self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1493 elif format == 'gnuplot':
1495 results = self.collect_results(args, self.get_debug_times,
1496 self.time_strings[which])
1498 self.gnuplot_results(results, fmt='%s %.6f')
1502 sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1505 if __name__ == '__main__':
1506 opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1511 if o in ('-?', '-h', '--help'):
1512 ST.do_help(['help'])
1514 elif o in ('-V', '--version'):
1515 sys.stdout.write('scons-time version\n')
1519 sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1522 ST.execute_subcommand(args)
1526 # indent-tabs-mode:nil
1528 # vim: set expandtab tabstop=4 shiftwidth=4: