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 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.
34
from __future__ import nested_scopes
36
__revision__ = "src/script/scons-time.py 3603 2008/10/10 05:46:45 scons"
52
# Pre-2.2 Python has no False keyword.
54
__builtin__.False = not 1
59
# Pre-2.2 Python has no True keyword.
61
__builtin__.True = not 0
63
def make_temp_file(**kw):
65
result = tempfile.mktemp(**kw)
67
result = os.path.realpath(result)
68
except AttributeError:
69
# Python 2.1 has no os.path.realpath() method.
73
save_template = tempfile.template
76
tempfile.template = prefix
77
result = tempfile.mktemp(**kw)
79
tempfile.template = save_template
83
def increment_size(self, largest):
85
Return the size of each horizontal increment line for a specified
86
maximum value. This returns a value that will provide somewhere
87
between 5 and 9 horizontal lines on the graph, on some set of
88
boundaries that are multiples of 10/100/1000/etc.
96
multiplier = multiplier * 10
99
def max_graph_value(self, largest):
100
# Round up to next integer.
101
largest = int(largest) + 1
102
increment = self.increment_size(largest)
103
return ((largest + increment - 1) / increment) * increment
106
def __init__(self, points, type, title, label, comment, fmt="%s %s"):
111
self.comment = comment
114
def print_label(self, inx, x, y):
116
print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
118
def plot_string(self):
120
title_string = 'title "%s"' % self.title
122
title_string = 'notitle'
123
return "'-' %s with lines lt %s" % (title_string, self.type)
125
def print_points(self, fmt=None):
129
print '# %s' % self.comment
130
for x, y in self.points:
131
# If y is None, it usually represents some kind of break
132
# in the line's index number. We might want to represent
133
# this some way rather than just drawing the line straight
134
# between the two points on either side.
139
def get_x_values(self):
140
return [ p[0] for p in self.points ]
142
def get_y_values(self):
143
return [ p[1] for p in self.points ]
145
class Gnuplotter(Plotter):
147
def __init__(self, title, key_location):
150
self.key_location = key_location
152
def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
154
line = Line(points, type, title, label, comment, fmt)
155
self.lines.append(line)
157
def plot_string(self, line):
158
return line.plot_string()
160
def vertical_bar(self, x, type, label, comment):
161
if self.get_min_x() <= x and x <= self.get_max_x():
162
points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
163
self.line(points, type, label, comment)
165
def get_all_x_values(self):
167
for line in self.lines:
168
result.extend(line.get_x_values())
169
return filter(None, result)
171
def get_all_y_values(self):
173
for line in self.lines:
174
result.extend(line.get_y_values())
175
return filter(None, result)
180
except AttributeError:
182
self.min_x = min(self.get_all_x_values())
190
except AttributeError:
192
self.max_x = max(self.get_all_x_values())
200
except AttributeError:
202
self.min_y = min(self.get_all_y_values())
210
except AttributeError:
212
self.max_y = max(self.get_all_y_values())
223
print 'set title "%s"' % self.title
224
print 'set key %s' % self.key_location
226
min_y = self.get_min_y()
227
max_y = self.max_graph_value(self.get_max_y())
228
range = max_y - min_y
230
start = min_y + (max_y / 2.0) + (2.0 * incr)
231
position = [ start - (i * incr) for i in xrange(5) ]
234
for line in self.lines:
235
line.print_label(inx, line.points[0][0]-1,
236
position[(inx-1) % len(position)])
239
plot_strings = [ self.plot_string(l) for l in self.lines ]
240
print 'plot ' + ', \\\n '.join(plot_strings)
242
for line in self.lines:
249
tar = tarfile.open(name=fname, mode='r')
256
zf = zipfile.ZipFile(fname, 'r')
257
for name in zf.namelist():
258
dir = os.path.dirname(name)
263
open(name, 'w').write(zf.read(name))
266
def read_files(arg, dirname, fnames):
268
fn = os.path.join(dirname, fn)
269
if os.path.isfile(fn):
270
open(fn, 'rb').read()
271
os.path.walk('.', read_files, None)
273
def redirect_to_file(command, log):
274
return '%s > %s 2>&1' % (command, log)
276
def tee_to_file(command, log):
277
return '%s 2>&1 | tee %s' % (command, log)
283
Usage: scons-time SUBCOMMAND [ARGUMENTS]
284
Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
286
Available subcommands:
287
func Extract test-run data for a function
289
mem Extract --debug=memory data from test runs
290
obj Extract --debug=count data from test runs
291
time Extract --debug=time data from test runs
292
run Runs a test configuration
296
name_spaces = ' '*len(name)
301
default_settings = makedict(
303
aegis_project = None,
306
initial_commands = [],
307
key_location = 'bottom left',
308
orig_cwd = os.getcwd(),
311
python = '"%s"' % sys.executable,
312
redirect = redirect_to_file,
314
scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
315
scons_lib_dir = None,
316
scons_wrapper = None,
317
startup_targets = '--help',
319
subversion_url = None,
333
'.tar.gz' : (untar, '%(tar)s xzf %%s'),
334
'.tgz' : (untar, '%(tar)s xzf %%s'),
335
'.tar' : (untar, '%(tar)s xf %%s'),
336
'.zip' : (unzip, '%(unzip)s %%s'),
347
'%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
348
'%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
349
'%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
360
'pre-read' : 'Memory before reading SConscript files:',
361
'post-read' : 'Memory after reading SConscript files:',
362
'pre-build' : 'Memory before building targets:',
363
'post-build' : 'Memory after building targets:',
366
memory_string_all = 'Memory '
368
default_stage = stages[-1]
371
'total' : 'Total build time',
372
'SConscripts' : 'Total SConscript file execution time',
373
'SCons' : 'Total SCons execution time',
374
'commands' : 'Total command execution time',
377
time_string_all = 'Total .* time'
382
self.__dict__.update(self.default_settings)
384
# Functions for displaying and executing commands.
386
def subst(self, x, dictionary):
388
return x % dictionary
390
# x isn't a string (it's probably a Python function),
394
def subst_variables(self, command, dictionary):
396
Substitutes (via the format operator) the values in the specified
397
dictionary into the specified command.
399
The command can be an (action, string) tuple. In all cases, we
400
perform substitution on strings and don't worry if something isn't
401
a string. (It's probably a Python function to be executed.)
413
action = self.subst(action, dictionary)
414
string = self.subst(string, dictionary)
415
return (action, string, args)
417
def _do_not_display(self, msg, *args):
420
def display(self, msg, *args):
422
Displays the specified message.
424
Each message is prepended with a standard prefix of our name
434
sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
436
def _do_not_execute(self, action, *args):
439
def execute(self, action, *args):
441
Executes the specified action.
443
The action is called if it's a callable Python function, and
444
otherwise passed to os.system().
449
os.system(action % args)
451
def run_command_list(self, commands, dict):
453
Executes a list of commands, substituting values from the
454
specified dictionary.
456
commands = [ self.subst_variables(c, dict) for c in commands ]
457
for action, string, args in commands:
458
self.display(string, *args)
460
status = self.execute(action, *args)
464
def log_display(self, command, log):
465
command = self.subst(command, self.__dict__)
467
command = self.redirect(command, log)
470
def log_execute(self, command, log):
471
command = self.subst(command, self.__dict__)
472
output = os.popen(command).read()
474
sys.stdout.write(output)
475
open(log, 'wb').write(output)
479
def archive_splitext(self, path):
481
Splits an archive name into a filename base and extension.
483
This is like os.path.splitext() (which it calls) except that it
484
also looks for '.tar.gz' and treats it as an atomic extensions.
486
if path.endswith('.tar.gz'):
487
return path[:-7], path[-7:]
489
return os.path.splitext(path)
491
def args_to_files(self, args, tail=None):
493
Takes a list of arguments, expands any glob patterns, and
494
returns the last "tail" files from the list.
503
files = files[-tail:]
507
def ascii_table(self, files, columns,
508
line_function, file_function=lambda x: x,
511
header_fmt = ' '.join(['%12s'] * len(columns))
512
line_fmt = header_fmt + ' %s'
514
print header_fmt % columns
517
t = line_function(file, *args, **kw)
520
diff = len(columns) - len(t)
523
t.append(file_function(file))
524
print line_fmt % tuple(t)
526
def collect_results(self, files, function, *args, **kw):
530
base = os.path.splitext(file)[0]
531
run, index = string.split(base, '-')[-2:]
536
value = function(file, *args, **kw)
543
r.append((run, value))
547
def doc_to_help(self, obj):
549
Translates an object's __doc__ string into help text.
551
This strips a consistent number of spaces from each line in the
552
help text, essentially "outdenting" the text to the left-most
558
return self.outdent(doc)
560
def find_next_run_number(self, dir, prefix):
562
Returns the next run number in a directory for the specified prefix.
564
Examines the contents the specified directory for files with the
565
specified prefix, extracts the run numbers from each file name,
566
and returns the next run number after the largest it finds.
568
x = re.compile(re.escape(prefix) + '-([0-9]+).*')
569
matches = map(lambda e, x=x: x.match(e), os.listdir(dir))
570
matches = filter(None, matches)
573
run_numbers = map(lambda m: int(m.group(1)), matches)
574
return int(max(run_numbers)) + 1
576
def gnuplot_results(self, results, fmt='%s %.3f'):
578
Prints out a set of results in Gnuplot format.
580
gp = Gnuplotter(self.title, self.key_location)
582
indices = results.keys()
587
t = self.run_titles[i]
591
gp.line(results[i], i+1, t, None, t, fmt=fmt)
593
for bar_tuple in self.vertical_bars:
595
x, type, label, comment = bar_tuple
597
x, type, label = bar_tuple
599
gp.vertical_bar(x, type, label, comment)
603
def logfile_name(self, invocation):
605
Returns the absolute path of a log file for the specificed
608
name = self.prefix_run + '-%d.log' % invocation
609
return os.path.join(self.outdir, name)
611
def outdent(self, s):
613
Strip as many spaces from each line as are found at the beginning
614
of the first line in the list.
616
lines = s.split('\n')
619
spaces = re.match(' *', lines[0]).group(0)
620
def strip_initial_spaces(l, s=spaces):
621
if l.startswith(spaces):
624
return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
626
def profile_name(self, invocation):
628
Returns the absolute path of a profile file for the specified
631
name = self.prefix_run + '-%d.prof' % invocation
632
return os.path.join(self.outdir, name)
634
def set_env(self, key, value):
635
os.environ[key] = value
639
def get_debug_times(self, file, time_string=None):
641
Fetch times from the --debug=time strings in the specified file.
643
if time_string is None:
644
search_string = self.time_string_all
646
search_string = time_string
647
contents = open(file).read()
649
sys.stderr.write('file %s has no contents!\n' % repr(file))
651
result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
652
result = [ float(r) for r in result ]
653
if not time_string is None:
657
sys.stderr.write('file %s has no results!\n' % repr(file))
661
def get_function_profile(self, file, function):
663
Returns the file, line number, function name, and cumulative time.
667
except ImportError, e:
668
sys.stderr.write('%s: func: %s\n' % (self.name, e))
669
sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
670
sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
672
statistics = pstats.Stats(file).stats
673
matches = [ e for e in statistics.items() if e[0][2] == function ]
675
return r[0][0], r[0][1], r[0][2], r[1][3]
677
def get_function_time(self, file, function):
679
Returns just the cumulative time for the specified function.
681
return self.get_function_profile(file, function)[3]
683
def get_memory(self, file, memory_string=None):
685
Returns a list of integers of the amount of memory used. The
686
default behavior is to return all the stages.
688
if memory_string is None:
689
search_string = self.memory_string_all
691
search_string = memory_string
692
lines = open(file).readlines()
693
lines = [ l for l in lines if l.startswith(search_string) ][-4:]
694
result = [ int(l.split()[-1]) for l in lines[-4:] ]
699
def get_object_counts(self, file, object_name, index=None):
701
Returns the counts of the specified object_name.
703
object_string = ' ' + object_name + '\n'
704
lines = open(file).readlines()
705
line = [ l for l in lines if l.endswith(object_string) ][0]
706
result = [ int(field) for field in line.split()[:4] ]
707
if not index is None:
708
result = result[index]
715
def execute_subcommand(self, argv):
717
Executes the do_*() function for the specified subcommand (argv[0]).
721
cmdName = self.command_alias.get(argv[0], argv[0])
723
func = getattr(self, 'do_' + cmdName)
724
except AttributeError:
725
return self.default(argv)
729
sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
731
traceback.print_exc(file=sys.stderr)
732
sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
734
def default(self, argv):
736
The default behavior for an unknown subcommand. Prints an
737
error message and exits.
739
sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
740
sys.stderr.write('Type "%s help" for usage.\n' % self.name)
745
def do_help(self, argv):
751
func = getattr(self, 'do_' + arg)
752
except AttributeError:
753
sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
756
help = getattr(self, 'help_' + arg)
757
except AttributeError:
758
sys.stdout.write(self.doc_to_help(func))
763
doc = self.doc_to_help(self.__class__)
765
sys.stdout.write(doc)
773
Usage: scons-time func [OPTIONS] FILE [...]
775
-C DIR, --chdir=DIR Change to DIR before looking for files
776
-f FILE, --file=FILE Read configuration from specified FILE
777
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
778
--func=NAME, --function=NAME Report time for function NAME
779
-h, --help Print this help and exit
780
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
781
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
782
--title=TITLE Specify the output plot TITLE
784
sys.stdout.write(self.outdent(help))
787
def do_func(self, argv):
791
function_name = '_main'
794
short_opts = '?C:f:hp:t:'
809
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
812
if o in ('-C', '--chdir'):
814
elif o in ('-f', '--file'):
816
elif o in ('--fmt', '--format'):
818
elif o in ('--func', '--function'):
820
elif o in ('-?', '-h', '--help'):
821
self.do_help(['help', 'func'])
823
elif o in ('--max',):
825
elif o in ('-p', '--prefix'):
827
elif o in ('-t', '--tail'):
829
elif o in ('--title',):
833
execfile(self.config_file, self.__dict__)
840
pattern = '%s*.prof' % self.prefix
841
args = self.args_to_files([pattern], tail)
845
directory = self.chdir
847
directory = os.getcwd()
849
sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
850
sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
851
sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
856
args = self.args_to_files(args, tail)
858
cwd_ = os.getcwd() + os.sep
860
if format == 'ascii':
862
def print_function_timing(file, func):
864
f, line, func, time = self.get_function_profile(file, func)
865
except ValueError, e:
866
sys.stderr.write("%s: func: %s: %s\n" % (self.name, file, e))
868
if f.startswith(cwd_):
870
print "%.3f %s:%d(%s)" % (time, f, line, func)
873
print_function_timing(file, function_name)
875
elif format == 'gnuplot':
877
results = self.collect_results(args, self.get_function_time,
880
self.gnuplot_results(results)
884
sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
891
Usage: scons-time mem [OPTIONS] FILE [...]
893
-C DIR, --chdir=DIR Change to DIR before looking for files
894
-f FILE, --file=FILE Read configuration from specified FILE
895
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
896
-h, --help Print this help and exit
897
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
898
--stage=STAGE Plot memory at the specified stage:
899
pre-read, post-read, pre-build,
900
post-build (default: post-build)
901
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
902
--title=TITLE Specify the output plot TITLE
904
sys.stdout.write(self.outdent(help))
907
def do_mem(self, argv):
910
logfile_path = lambda x: x
911
stage = self.default_stage
914
short_opts = '?C:f:hp:t:'
928
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
931
if o in ('-C', '--chdir'):
933
elif o in ('-f', '--file'):
935
elif o in ('--fmt', '--format'):
937
elif o in ('-?', '-h', '--help'):
938
self.do_help(['help', 'mem'])
940
elif o in ('-p', '--prefix'):
942
elif o in ('--stage',):
943
if not a in self.stages:
944
sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
947
elif o in ('-t', '--tail'):
949
elif o in ('--title',):
953
execfile(self.config_file, self.__dict__)
957
logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
961
pattern = '%s*.log' % self.prefix
962
args = self.args_to_files([pattern], tail)
966
directory = self.chdir
968
directory = os.getcwd()
970
sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
971
sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
972
sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
977
args = self.args_to_files(args, tail)
979
cwd_ = os.getcwd() + os.sep
981
if format == 'ascii':
983
self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
985
elif format == 'gnuplot':
987
results = self.collect_results(args, self.get_memory,
988
self.stage_strings[stage])
990
self.gnuplot_results(results)
994
sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1003
Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1005
-C DIR, --chdir=DIR Change to DIR before looking for files
1006
-f FILE, --file=FILE Read configuration from specified FILE
1007
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1008
-h, --help Print this help and exit
1009
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
1010
--stage=STAGE Plot memory at the specified stage:
1011
pre-read, post-read, pre-build,
1012
post-build (default: post-build)
1013
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
1014
--title=TITLE Specify the output plot TITLE
1016
sys.stdout.write(self.outdent(help))
1019
def do_obj(self, argv):
1022
logfile_path = lambda x: x
1023
stage = self.default_stage
1026
short_opts = '?C:f:hp:t:'
1040
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1043
if o in ('-C', '--chdir'):
1045
elif o in ('-f', '--file'):
1046
self.config_file = a
1047
elif o in ('--fmt', '--format'):
1049
elif o in ('-?', '-h', '--help'):
1050
self.do_help(['help', 'obj'])
1052
elif o in ('-p', '--prefix'):
1054
elif o in ('--stage',):
1055
if not a in self.stages:
1056
sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1057
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1060
elif o in ('-t', '--tail'):
1062
elif o in ('--title',):
1066
sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1067
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1070
object_name = args.pop(0)
1072
if self.config_file:
1073
execfile(self.config_file, self.__dict__)
1076
os.chdir(self.chdir)
1077
logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
1081
pattern = '%s*.log' % self.prefix
1082
args = self.args_to_files([pattern], tail)
1086
directory = self.chdir
1088
directory = os.getcwd()
1090
sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1091
sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1092
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1097
args = self.args_to_files(args, tail)
1099
cwd_ = os.getcwd() + os.sep
1101
if format == 'ascii':
1103
self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1105
elif format == 'gnuplot':
1108
for s in self.stages:
1111
stage_index = stage_index + 1
1113
results = self.collect_results(args, self.get_object_counts,
1114
object_name, stage_index)
1116
self.gnuplot_results(results)
1120
sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1129
Usage: scons-time run [OPTIONS] [FILE ...]
1131
--aegis=PROJECT Use SCons from the Aegis PROJECT
1132
--chdir=DIR Name of unpacked directory for chdir
1133
-f FILE, --file=FILE Read configuration from specified FILE
1134
-h, --help Print this help and exit
1135
-n, --no-exec No execute, just print command lines
1136
--number=NUMBER Put output in files for run NUMBER
1137
--outdir=OUTDIR Put output files in OUTDIR
1138
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
1139
--python=PYTHON Time using the specified PYTHON
1140
-q, --quiet Don't print command lines
1141
--scons=SCONS Time using the specified SCONS
1142
--svn=URL, --subversion=URL Use SCons from Subversion URL
1143
-v, --verbose Display output of commands
1145
sys.stdout.write(self.outdent(help))
1148
def do_run(self, argv):
1151
run_number_list = [None]
1153
short_opts = '?f:hnp:qs:v'
1172
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1175
if o in ('--aegis',):
1176
self.aegis_project = a
1177
elif o in ('-f', '--file'):
1178
self.config_file = a
1179
elif o in ('-?', '-h', '--help'):
1180
self.do_help(['help', 'run'])
1182
elif o in ('-n', '--no-exec'):
1183
self.execute = self._do_not_execute
1184
elif o in ('--number',):
1185
run_number_list = self.split_run_numbers(a)
1186
elif o in ('--outdir',):
1188
elif o in ('-p', '--prefix'):
1190
elif o in ('--python',):
1192
elif o in ('-q', '--quiet'):
1193
self.display = self._do_not_display
1194
elif o in ('-s', '--subdir'):
1196
elif o in ('--scons',):
1198
elif o in ('--svn', '--subversion'):
1199
self.subversion_url = a
1200
elif o in ('-v', '--verbose'):
1201
self.redirect = tee_to_file
1203
self.svn_co_flag = ''
1205
if not args and not self.config_file:
1206
sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1207
sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1210
if self.config_file:
1211
execfile(self.config_file, self.__dict__)
1214
self.archive_list = args
1216
archive_file_name = os.path.split(self.archive_list[0])[1]
1219
self.subdir = self.archive_splitext(archive_file_name)[0]
1222
self.prefix = self.archive_splitext(archive_file_name)[0]
1225
if self.subversion_url:
1226
prepare = self.prep_subversion_run
1227
elif self.aegis_project:
1228
prepare = self.prep_aegis_run
1230
for run_number in run_number_list:
1231
self.individual_run(run_number, self.archive_list, prepare)
1233
def split_run_numbers(self, s):
1235
for n in s.split(','):
1239
result.append(int(n))
1241
result.extend(range(int(x), int(y)+1))
1244
def scons_path(self, dir):
1245
return os.path.join(dir, 'src', 'script', 'scons.py')
1247
def scons_lib_dir_path(self, dir):
1248
return os.path.join(dir, 'src', 'engine')
1250
def prep_aegis_run(self, commands, removals):
1251
self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1252
removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1254
self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1255
self.scons = self.scons_path(self.aegis_tmpdir)
1256
self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1259
'mkdir %(aegis_tmpdir)s',
1260
(lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1261
'%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1262
'%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1265
def prep_subversion_run(self, commands, removals):
1266
self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1267
removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1269
self.scons = self.scons_path(self.svn_tmpdir)
1270
self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1273
'mkdir %(svn_tmpdir)s',
1274
'%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1277
def individual_run(self, run_number, archive_list, prepare=None):
1279
Performs an individual run of the default SCons invocations.
1286
prepare(commands, removals)
1288
save_scons = self.scons
1289
save_scons_wrapper = self.scons_wrapper
1290
save_scons_lib_dir = self.scons_lib_dir
1292
if self.outdir is None:
1293
self.outdir = self.orig_cwd
1294
elif not os.path.isabs(self.outdir):
1295
self.outdir = os.path.join(self.orig_cwd, self.outdir)
1297
if self.scons is None:
1298
self.scons = self.scons_path(self.orig_cwd)
1300
if self.scons_lib_dir is None:
1301
self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1303
if self.scons_wrapper is None:
1304
self.scons_wrapper = self.scons
1307
run_number = self.find_next_run_number(self.outdir, self.prefix)
1309
self.run_number = str(run_number)
1311
self.prefix_run = self.prefix + '-%03d' % run_number
1313
if self.targets0 is None:
1314
self.targets0 = self.startup_targets
1315
if self.targets1 is None:
1316
self.targets1 = self.targets
1317
if self.targets2 is None:
1318
self.targets2 = self.targets
1320
self.tmpdir = make_temp_file(prefix = self.name + '-')
1325
(os.chdir, 'cd %%s', self.tmpdir),
1328
for archive in archive_list:
1329
if not os.path.isabs(archive):
1330
archive = os.path.join(self.orig_cwd, archive)
1331
if os.path.isdir(archive):
1332
dest = os.path.split(archive)[1]
1333
commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1335
suffix = self.archive_splitext(archive)[1]
1336
unpack_command = self.unpack_map.get(suffix)
1337
if not unpack_command:
1338
dest = os.path.split(archive)[1]
1339
commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1341
commands.append(unpack_command + (archive,))
1344
(os.chdir, 'cd %%s', self.subdir),
1347
commands.extend(self.initial_commands)
1350
(lambda: read_tree('.'),
1351
'find * -type f | xargs cat > /dev/null'),
1353
(self.set_env, 'export %%s=%%s',
1354
'SCONS_LIB_DIR', self.scons_lib_dir),
1356
'%(python)s %(scons_wrapper)s --version',
1360
for run_command in self.run_commands:
1361
setattr(self, 'prof%d' % index, self.profile_name(index))
1366
self.logfile_name(index),
1372
(os.chdir, 'cd %%s', self.orig_cwd),
1375
if not os.environ.get('PRESERVE'):
1376
commands.extend(removals)
1378
commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1380
self.run_command_list(commands, self.__dict__)
1382
self.scons = save_scons
1383
self.scons_lib_dir = save_scons_lib_dir
1384
self.scons_wrapper = save_scons_wrapper
1388
def help_time(self):
1390
Usage: scons-time time [OPTIONS] FILE [...]
1392
-C DIR, --chdir=DIR Change to DIR before looking for files
1393
-f FILE, --file=FILE Read configuration from specified FILE
1394
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1395
-h, --help Print this help and exit
1396
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
1397
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
1398
--which=TIMER Plot timings for TIMER: total,
1399
SConscripts, SCons, commands.
1401
sys.stdout.write(self.outdent(help))
1404
def do_time(self, argv):
1407
logfile_path = lambda x: x
1411
short_opts = '?C:f:hp:t:'
1425
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1428
if o in ('-C', '--chdir'):
1430
elif o in ('-f', '--file'):
1431
self.config_file = a
1432
elif o in ('--fmt', '--format'):
1434
elif o in ('-?', '-h', '--help'):
1435
self.do_help(['help', 'time'])
1437
elif o in ('-p', '--prefix'):
1439
elif o in ('-t', '--tail'):
1441
elif o in ('--title',):
1443
elif o in ('--which',):
1444
if not a in self.time_strings.keys():
1445
sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1446
sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1450
if self.config_file:
1451
execfile(self.config_file, self.__dict__)
1454
os.chdir(self.chdir)
1455
logfile_path = lambda x, c=self.chdir: os.path.join(c, x)
1459
pattern = '%s*.log' % self.prefix
1460
args = self.args_to_files([pattern], tail)
1464
directory = self.chdir
1466
directory = os.getcwd()
1468
sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1469
sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1470
sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1475
args = self.args_to_files(args, tail)
1477
cwd_ = os.getcwd() + os.sep
1479
if format == 'ascii':
1481
columns = ("Total", "SConscripts", "SCons", "commands")
1482
self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1484
elif format == 'gnuplot':
1486
results = self.collect_results(args, self.get_debug_times,
1487
self.time_strings[which])
1489
self.gnuplot_results(results, fmt='%s %.6f')
1493
sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1496
if __name__ == '__main__':
1497
opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1502
if o in ('-?', '-h', '--help'):
1503
ST.do_help(['help'])
1505
elif o in ('-V', '--version'):
1506
sys.stdout.write('scons-time version\n')
1510
sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1513
ST.execute_subcommand(args)