3
# scons-time - run SCons timings and collect statistics
5
# A script for running a configuration through SCons with a standard
6
# set of invocations to collect timing and memory statistics and to
7
# capture the results in a consistent set of output files for display
12
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation
14
# Permission is hereby granted, free of charge, to any person obtaining
15
# a copy of this software and associated documentation files (the
16
# "Software"), to deal in the Software without restriction, including
17
# without limitation the rights to use, copy, modify, merge, publish,
18
# distribute, sublicense, and/or sell copies of the Software, and to
19
# permit persons to whom the Software is furnished to do so, subject to
20
# the following conditions:
22
# The above copyright notice and this permission notice shall be included
23
# in all copies or substantial portions of the Software.
25
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
from __future__ import division
33
from __future__ import nested_scopes
35
__revision__ = "src/script/scons-time.py 5134 2010/08/16 23:02:40 bdeegan"
49
# Pre-2.4 Python has no sorted() function.
51
# The pre-2.4 Python list.sort() method does not support
52
# list.sort(key=) nor list.sort(reverse=) keyword arguments, so
53
# we must implement the functionality of those keyword arguments
54
# by hand instead of passing them to list.sort().
55
def sorted(iterable, cmp=None, key=None, reverse=False):
57
result = [(key(x), x) for x in iterable]
61
# Pre-2.3 Python does not support list.sort(None).
66
result = [t1 for t0,t1 in result]
71
if os.environ.get('SCONS_HORRIBLE_REGRESSION_TEST_HACK') is not None:
72
# We can't apply the 'callable' fixer until the floor is 2.6, but the
73
# '-3' option to Python 2.6 and 2.7 generates almost ten thousand
74
# warnings. This hack allows us to run regression tests with the '-3'
75
# option by replacing the callable() built-in function with a hack
76
# that performs the same function but doesn't generate the warning.
77
# Note that this hack is ONLY intended to be used for regression
78
# testing, and should NEVER be used for real runs.
79
from types import ClassType
81
if hasattr(obj, '__call__'): return True
82
if isinstance(obj, (ClassType, type)): return True
85
def make_temp_file(**kw):
87
result = tempfile.mktemp(**kw)
89
result = os.path.realpath(result)
90
except AttributeError:
91
# Python 2.1 has no os.path.realpath() method.
95
save_template = tempfile.template
98
tempfile.template = prefix
99
result = tempfile.mktemp(**kw)
101
tempfile.template = save_template
104
def HACK_for_exec(cmd, *args):
106
For some reason, Python won't allow an exec() within a function
107
that also declares an internal function (including lambda functions).
108
This function is a hack that calls exec() in a function with no
111
if not args: exec(cmd)
112
elif len(args) == 1: exec cmd in args[0]
113
else: exec cmd in args[0], args[1]
115
class Plotter(object):
116
def increment_size(self, largest):
118
Return the size of each horizontal increment line for a specified
119
maximum value. This returns a value that will provide somewhere
120
between 5 and 9 horizontal lines on the graph, on some set of
121
boundaries that are multiples of 10/100/1000/etc.
129
multiplier = multiplier * 10
130
return i * multiplier
132
def max_graph_value(self, largest):
133
# Round up to next integer.
134
largest = int(largest) + 1
135
increment = self.increment_size(largest)
136
return ((largest + increment - 1) // increment) * increment
139
def __init__(self, points, type, title, label, comment, fmt="%s %s"):
144
self.comment = comment
147
def print_label(self, inx, x, y):
149
print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y)
151
def plot_string(self):
153
title_string = 'title "%s"' % self.title
155
title_string = 'notitle'
156
return "'-' %s with lines lt %s" % (title_string, self.type)
158
def print_points(self, fmt=None):
162
print '# %s' % self.comment
163
for x, y in self.points:
164
# If y is None, it usually represents some kind of break
165
# in the line's index number. We might want to represent
166
# this some way rather than just drawing the line straight
167
# between the two points on either side.
172
def get_x_values(self):
173
return [ p[0] for p in self.points ]
175
def get_y_values(self):
176
return [ p[1] for p in self.points ]
178
class Gnuplotter(Plotter):
180
def __init__(self, title, key_location):
183
self.key_location = key_location
185
def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'):
187
line = Line(points, type, title, label, comment, fmt)
188
self.lines.append(line)
190
def plot_string(self, line):
191
return line.plot_string()
193
def vertical_bar(self, x, type, label, comment):
194
if self.get_min_x() <= x and x <= self.get_max_x():
195
points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
196
self.line(points, type, label, comment)
198
def get_all_x_values(self):
200
for line in self.lines:
201
result.extend(line.get_x_values())
202
return [r for r in result if not r is None]
204
def get_all_y_values(self):
206
for line in self.lines:
207
result.extend(line.get_y_values())
208
return [r for r in result if not r is None]
213
except AttributeError:
215
self.min_x = min(self.get_all_x_values())
223
except AttributeError:
225
self.max_x = max(self.get_all_x_values())
233
except AttributeError:
235
self.min_y = min(self.get_all_y_values())
243
except AttributeError:
245
self.max_y = max(self.get_all_y_values())
256
print 'set title "%s"' % self.title
257
print 'set key %s' % self.key_location
259
min_y = self.get_min_y()
260
max_y = self.max_graph_value(self.get_max_y())
261
incr = (max_y - min_y) / 10.0
262
start = min_y + (max_y / 2.0) + (2.0 * incr)
263
position = [ start - (i * incr) for i in range(5) ]
266
for line in self.lines:
267
line.print_label(inx, line.points[0][0]-1,
268
position[(inx-1) % len(position)])
271
plot_strings = [ self.plot_string(l) for l in self.lines ]
272
print 'plot ' + ', \\\n '.join(plot_strings)
274
for line in self.lines:
281
tar = tarfile.open(name=fname, mode='r')
288
zf = zipfile.ZipFile(fname, 'r')
289
for name in zf.namelist():
290
dir = os.path.dirname(name)
295
open(name, 'w').write(zf.read(name))
298
for dirpath, dirnames, filenames in os.walk(dir):
300
fn = os.path.join(dirpath, fn)
301
if os.path.isfile(fn):
302
open(fn, 'rb').read()
304
def redirect_to_file(command, log):
305
return '%s > %s 2>&1' % (command, log)
307
def tee_to_file(command, log):
308
return '%s 2>&1 | tee %s' % (command, log)
312
class SConsTimer(object):
314
Usage: scons-time SUBCOMMAND [ARGUMENTS]
315
Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
317
Available subcommands:
318
func Extract test-run data for a function
320
mem Extract --debug=memory data from test runs
321
obj Extract --debug=count data from test runs
322
time Extract --debug=time data from test runs
323
run Runs a test configuration
327
name_spaces = ' '*len(name)
332
default_settings = makedict(
334
aegis_project = None,
337
initial_commands = [],
338
key_location = 'bottom left',
339
orig_cwd = os.getcwd(),
342
python = '"%s"' % sys.executable,
343
redirect = redirect_to_file,
345
scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer',
346
scons_lib_dir = None,
347
scons_wrapper = None,
348
startup_targets = '--help',
350
subversion_url = None,
364
'.tar.gz' : (untar, '%(tar)s xzf %%s'),
365
'.tgz' : (untar, '%(tar)s xzf %%s'),
366
'.tar' : (untar, '%(tar)s xf %%s'),
367
'.zip' : (unzip, '%(unzip)s %%s'),
378
'%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
379
'%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
380
'%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
391
'pre-read' : 'Memory before reading SConscript files:',
392
'post-read' : 'Memory after reading SConscript files:',
393
'pre-build' : 'Memory before building targets:',
394
'post-build' : 'Memory after building targets:',
397
memory_string_all = 'Memory '
399
default_stage = stages[-1]
402
'total' : 'Total build time',
403
'SConscripts' : 'Total SConscript file execution time',
404
'SCons' : 'Total SCons execution time',
405
'commands' : 'Total command execution time',
408
time_string_all = 'Total .* time'
413
self.__dict__.update(self.default_settings)
415
# Functions for displaying and executing commands.
417
def subst(self, x, dictionary):
419
return x % dictionary
421
# x isn't a string (it's probably a Python function),
425
def subst_variables(self, command, dictionary):
427
Substitutes (via the format operator) the values in the specified
428
dictionary into the specified command.
430
The command can be an (action, string) tuple. In all cases, we
431
perform substitution on strings and don't worry if something isn't
432
a string. (It's probably a Python function to be executed.)
444
action = self.subst(action, dictionary)
445
string = self.subst(string, dictionary)
446
return (action, string, args)
448
def _do_not_display(self, msg, *args):
451
def display(self, msg, *args):
453
Displays the specified message.
455
Each message is prepended with a standard prefix of our name
465
sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg))
467
def _do_not_execute(self, action, *args):
470
def execute(self, action, *args):
472
Executes the specified action.
474
The action is called if it's a callable Python function, and
475
otherwise passed to os.system().
480
os.system(action % args)
482
def run_command_list(self, commands, dict):
484
Executes a list of commands, substituting values from the
485
specified dictionary.
487
commands = [ self.subst_variables(c, dict) for c in commands ]
488
for action, string, args in commands:
489
self.display(string, *args)
491
status = self.execute(action, *args)
495
def log_display(self, command, log):
496
command = self.subst(command, self.__dict__)
498
command = self.redirect(command, log)
501
def log_execute(self, command, log):
502
command = self.subst(command, self.__dict__)
503
output = os.popen(command).read()
505
sys.stdout.write(output)
506
open(log, 'wb').write(output)
510
def archive_splitext(self, path):
512
Splits an archive name into a filename base and extension.
514
This is like os.path.splitext() (which it calls) except that it
515
also looks for '.tar.gz' and treats it as an atomic extensions.
517
if path.endswith('.tar.gz'):
518
return path[:-7], path[-7:]
520
return os.path.splitext(path)
522
def args_to_files(self, args, tail=None):
524
Takes a list of arguments, expands any glob patterns, and
525
returns the last "tail" files from the list.
529
files.extend(sorted(glob.glob(a)))
532
files = files[-tail:]
536
def ascii_table(self, files, columns,
537
line_function, file_function=lambda x: x,
540
header_fmt = ' '.join(['%12s'] * len(columns))
541
line_fmt = header_fmt + ' %s'
543
print header_fmt % columns
546
t = line_function(file, *args, **kw)
549
diff = len(columns) - len(t)
552
t.append(file_function(file))
553
print line_fmt % tuple(t)
555
def collect_results(self, files, function, *args, **kw):
559
base = os.path.splitext(file)[0]
560
run, index = base.split('-')[-2:]
565
value = function(file, *args, **kw)
572
r.append((run, value))
576
def doc_to_help(self, obj):
578
Translates an object's __doc__ string into help text.
580
This strips a consistent number of spaces from each line in the
581
help text, essentially "outdenting" the text to the left-most
587
return self.outdent(doc)
589
def find_next_run_number(self, dir, prefix):
591
Returns the next run number in a directory for the specified prefix.
593
Examines the contents the specified directory for files with the
594
specified prefix, extracts the run numbers from each file name,
595
and returns the next run number after the largest it finds.
597
x = re.compile(re.escape(prefix) + '-([0-9]+).*')
598
matches = [x.match(e) for e in os.listdir(dir)]
599
matches = [_f for _f in matches if _f]
602
run_numbers = [int(m.group(1)) for m in matches]
603
return int(max(run_numbers)) + 1
605
def gnuplot_results(self, results, fmt='%s %.3f'):
607
Prints out a set of results in Gnuplot format.
609
gp = Gnuplotter(self.title, self.key_location)
611
for i in sorted(results.keys()):
613
t = self.run_titles[i]
617
gp.line(results[i], i+1, t, None, t, fmt=fmt)
619
for bar_tuple in self.vertical_bars:
621
x, type, label, comment = bar_tuple
623
x, type, label = bar_tuple
625
gp.vertical_bar(x, type, label, comment)
629
def logfile_name(self, invocation):
631
Returns the absolute path of a log file for the specificed
634
name = self.prefix_run + '-%d.log' % invocation
635
return os.path.join(self.outdir, name)
637
def outdent(self, s):
639
Strip as many spaces from each line as are found at the beginning
640
of the first line in the list.
642
lines = s.split('\n')
645
spaces = re.match(' *', lines[0]).group(0)
646
def strip_initial_spaces(l, s=spaces):
647
if l.startswith(spaces):
650
return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n'
652
def profile_name(self, invocation):
654
Returns the absolute path of a profile file for the specified
657
name = self.prefix_run + '-%d.prof' % invocation
658
return os.path.join(self.outdir, name)
660
def set_env(self, key, value):
661
os.environ[key] = value
665
def get_debug_times(self, file, time_string=None):
667
Fetch times from the --debug=time strings in the specified file.
669
if time_string is None:
670
search_string = self.time_string_all
672
search_string = time_string
673
contents = open(file).read()
675
sys.stderr.write('file %s has no contents!\n' % repr(file))
677
result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
678
result = [ float(r) for r in result ]
679
if not time_string is None:
683
sys.stderr.write('file %s has no results!\n' % repr(file))
687
def get_function_profile(self, file, function):
689
Returns the file, line number, function name, and cumulative time.
693
except ImportError, e:
694
sys.stderr.write('%s: func: %s\n' % (self.name, e))
695
sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces)
696
sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces)
698
statistics = pstats.Stats(file).stats
699
matches = [ e for e in statistics.items() if e[0][2] == function ]
701
return r[0][0], r[0][1], r[0][2], r[1][3]
703
def get_function_time(self, file, function):
705
Returns just the cumulative time for the specified function.
707
return self.get_function_profile(file, function)[3]
709
def get_memory(self, file, memory_string=None):
711
Returns a list of integers of the amount of memory used. The
712
default behavior is to return all the stages.
714
if memory_string is None:
715
search_string = self.memory_string_all
717
search_string = memory_string
718
lines = open(file).readlines()
719
lines = [ l for l in lines if l.startswith(search_string) ][-4:]
720
result = [ int(l.split()[-1]) for l in lines[-4:] ]
725
def get_object_counts(self, file, object_name, index=None):
727
Returns the counts of the specified object_name.
729
object_string = ' ' + object_name + '\n'
730
lines = open(file).readlines()
731
line = [ l for l in lines if l.endswith(object_string) ][0]
732
result = [ int(field) for field in line.split()[:4] ]
733
if index is not None:
734
result = result[index]
741
def execute_subcommand(self, argv):
743
Executes the do_*() function for the specified subcommand (argv[0]).
747
cmdName = self.command_alias.get(argv[0], argv[0])
749
func = getattr(self, 'do_' + cmdName)
750
except AttributeError:
751
return self.default(argv)
755
sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e))
757
traceback.print_exc(file=sys.stderr)
758
sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName))
760
def default(self, argv):
762
The default behavior for an unknown subcommand. Prints an
763
error message and exits.
765
sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0]))
766
sys.stderr.write('Type "%s help" for usage.\n' % self.name)
771
def do_help(self, argv):
777
func = getattr(self, 'do_' + arg)
778
except AttributeError:
779
sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg))
782
help = getattr(self, 'help_' + arg)
783
except AttributeError:
784
sys.stdout.write(self.doc_to_help(func))
789
doc = self.doc_to_help(self.__class__)
791
sys.stdout.write(doc)
799
Usage: scons-time func [OPTIONS] FILE [...]
801
-C DIR, --chdir=DIR Change to DIR before looking for files
802
-f FILE, --file=FILE Read configuration from specified FILE
803
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
804
--func=NAME, --function=NAME Report time for function NAME
805
-h, --help Print this help and exit
806
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
807
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
808
--title=TITLE Specify the output plot TITLE
810
sys.stdout.write(self.outdent(help))
813
def do_func(self, argv):
817
function_name = '_main'
820
short_opts = '?C:f:hp:t:'
835
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
838
if o in ('-C', '--chdir'):
840
elif o in ('-f', '--file'):
842
elif o in ('--fmt', '--format'):
844
elif o in ('--func', '--function'):
846
elif o in ('-?', '-h', '--help'):
847
self.do_help(['help', 'func'])
849
elif o in ('--max',):
851
elif o in ('-p', '--prefix'):
853
elif o in ('-t', '--tail'):
855
elif o in ('--title',):
859
exec open(self.config_file, 'rU').read() in self.__dict__
866
pattern = '%s*.prof' % self.prefix
867
args = self.args_to_files([pattern], tail)
871
directory = self.chdir
873
directory = os.getcwd()
875
sys.stderr.write('%s: func: No arguments specified.\n' % self.name)
876
sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
877
sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name))
882
args = self.args_to_files(args, tail)
884
cwd_ = os.getcwd() + os.sep
886
if format == 'ascii':
890
f, line, func, time = \
891
self.get_function_profile(file, function_name)
892
except ValueError, e:
893
sys.stderr.write("%s: func: %s: %s\n" %
894
(self.name, file, e))
896
if f.startswith(cwd_):
898
print "%.3f %s:%d(%s)" % (time, f, line, func)
900
elif format == 'gnuplot':
902
results = self.collect_results(args, self.get_function_time,
905
self.gnuplot_results(results)
909
sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format))
916
Usage: scons-time mem [OPTIONS] FILE [...]
918
-C DIR, --chdir=DIR Change to DIR before looking for files
919
-f FILE, --file=FILE Read configuration from specified FILE
920
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
921
-h, --help Print this help and exit
922
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
923
--stage=STAGE Plot memory at the specified stage:
924
pre-read, post-read, pre-build,
925
post-build (default: post-build)
926
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
927
--title=TITLE Specify the output plot TITLE
929
sys.stdout.write(self.outdent(help))
932
def do_mem(self, argv):
935
logfile_path = lambda x: x
936
stage = self.default_stage
939
short_opts = '?C:f:hp:t:'
953
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
956
if o in ('-C', '--chdir'):
958
elif o in ('-f', '--file'):
960
elif o in ('--fmt', '--format'):
962
elif o in ('-?', '-h', '--help'):
963
self.do_help(['help', 'mem'])
965
elif o in ('-p', '--prefix'):
967
elif o in ('--stage',):
968
if not a in self.stages:
969
sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
972
elif o in ('-t', '--tail'):
974
elif o in ('--title',):
978
HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
982
logfile_path = lambda x: os.path.join(self.chdir, x)
986
pattern = '%s*.log' % self.prefix
987
args = self.args_to_files([pattern], tail)
991
directory = self.chdir
993
directory = os.getcwd()
995
sys.stderr.write('%s: mem: No arguments specified.\n' % self.name)
996
sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
997
sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name))
1002
args = self.args_to_files(args, tail)
1004
cwd_ = os.getcwd() + os.sep
1006
if format == 'ascii':
1008
self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path)
1010
elif format == 'gnuplot':
1012
results = self.collect_results(args, self.get_memory,
1013
self.stage_strings[stage])
1015
self.gnuplot_results(results)
1019
sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format))
1028
Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1030
-C DIR, --chdir=DIR Change to DIR before looking for files
1031
-f FILE, --file=FILE Read configuration from specified FILE
1032
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1033
-h, --help Print this help and exit
1034
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
1035
--stage=STAGE Plot memory at the specified stage:
1036
pre-read, post-read, pre-build,
1037
post-build (default: post-build)
1038
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
1039
--title=TITLE Specify the output plot TITLE
1041
sys.stdout.write(self.outdent(help))
1044
def do_obj(self, argv):
1047
logfile_path = lambda x: x
1048
stage = self.default_stage
1051
short_opts = '?C:f:hp:t:'
1065
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1068
if o in ('-C', '--chdir'):
1070
elif o in ('-f', '--file'):
1071
self.config_file = a
1072
elif o in ('--fmt', '--format'):
1074
elif o in ('-?', '-h', '--help'):
1075
self.do_help(['help', 'obj'])
1077
elif o in ('-p', '--prefix'):
1079
elif o in ('--stage',):
1080
if not a in self.stages:
1081
sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
1082
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1085
elif o in ('-t', '--tail'):
1087
elif o in ('--title',):
1091
sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name)
1092
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1095
object_name = args.pop(0)
1097
if self.config_file:
1098
HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1101
os.chdir(self.chdir)
1102
logfile_path = lambda x: os.path.join(self.chdir, x)
1106
pattern = '%s*.log' % self.prefix
1107
args = self.args_to_files([pattern], tail)
1111
directory = self.chdir
1113
directory = os.getcwd()
1115
sys.stderr.write('%s: obj: No arguments specified.\n' % self.name)
1116
sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1117
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
1122
args = self.args_to_files(args, tail)
1124
cwd_ = os.getcwd() + os.sep
1126
if format == 'ascii':
1128
self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name)
1130
elif format == 'gnuplot':
1133
for s in self.stages:
1136
stage_index = stage_index + 1
1138
results = self.collect_results(args, self.get_object_counts,
1139
object_name, stage_index)
1141
self.gnuplot_results(results)
1145
sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format))
1154
Usage: scons-time run [OPTIONS] [FILE ...]
1156
--aegis=PROJECT Use SCons from the Aegis PROJECT
1157
--chdir=DIR Name of unpacked directory for chdir
1158
-f FILE, --file=FILE Read configuration from specified FILE
1159
-h, --help Print this help and exit
1160
-n, --no-exec No execute, just print command lines
1161
--number=NUMBER Put output in files for run NUMBER
1162
--outdir=OUTDIR Put output files in OUTDIR
1163
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
1164
--python=PYTHON Time using the specified PYTHON
1165
-q, --quiet Don't print command lines
1166
--scons=SCONS Time using the specified SCONS
1167
--svn=URL, --subversion=URL Use SCons from Subversion URL
1168
-v, --verbose Display output of commands
1170
sys.stdout.write(self.outdent(help))
1173
def do_run(self, argv):
1176
run_number_list = [None]
1178
short_opts = '?f:hnp:qs:v'
1197
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1200
if o in ('--aegis',):
1201
self.aegis_project = a
1202
elif o in ('-f', '--file'):
1203
self.config_file = a
1204
elif o in ('-?', '-h', '--help'):
1205
self.do_help(['help', 'run'])
1207
elif o in ('-n', '--no-exec'):
1208
self.execute = self._do_not_execute
1209
elif o in ('--number',):
1210
run_number_list = self.split_run_numbers(a)
1211
elif o in ('--outdir',):
1213
elif o in ('-p', '--prefix'):
1215
elif o in ('--python',):
1217
elif o in ('-q', '--quiet'):
1218
self.display = self._do_not_display
1219
elif o in ('-s', '--subdir'):
1221
elif o in ('--scons',):
1223
elif o in ('--svn', '--subversion'):
1224
self.subversion_url = a
1225
elif o in ('-v', '--verbose'):
1226
self.redirect = tee_to_file
1228
self.svn_co_flag = ''
1230
if not args and not self.config_file:
1231
sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name)
1232
sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name))
1235
if self.config_file:
1236
exec open(self.config_file, 'rU').read() in self.__dict__
1239
self.archive_list = args
1241
archive_file_name = os.path.split(self.archive_list[0])[1]
1244
self.subdir = self.archive_splitext(archive_file_name)[0]
1247
self.prefix = self.archive_splitext(archive_file_name)[0]
1250
if self.subversion_url:
1251
prepare = self.prep_subversion_run
1252
elif self.aegis_project:
1253
prepare = self.prep_aegis_run
1255
for run_number in run_number_list:
1256
self.individual_run(run_number, self.archive_list, prepare)
1258
def split_run_numbers(self, s):
1260
for n in s.split(','):
1264
result.append(int(n))
1266
result.extend(list(range(int(x), int(y)+1)))
1269
def scons_path(self, dir):
1270
return os.path.join(dir, 'src', 'script', 'scons.py')
1272
def scons_lib_dir_path(self, dir):
1273
return os.path.join(dir, 'src', 'engine')
1275
def prep_aegis_run(self, commands, removals):
1276
self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
1277
removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
1279
self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
1280
self.scons = self.scons_path(self.aegis_tmpdir)
1281
self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir)
1284
'mkdir %(aegis_tmpdir)s',
1285
(lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'),
1286
'%(aegis)s -cp -ind -p %(aegis_parent_project)s .',
1287
'%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .',
1290
def prep_subversion_run(self, commands, removals):
1291
self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
1292
removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
1294
self.scons = self.scons_path(self.svn_tmpdir)
1295
self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir)
1298
'mkdir %(svn_tmpdir)s',
1299
'%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1302
def individual_run(self, run_number, archive_list, prepare=None):
1304
Performs an individual run of the default SCons invocations.
1311
prepare(commands, removals)
1313
save_scons = self.scons
1314
save_scons_wrapper = self.scons_wrapper
1315
save_scons_lib_dir = self.scons_lib_dir
1317
if self.outdir is None:
1318
self.outdir = self.orig_cwd
1319
elif not os.path.isabs(self.outdir):
1320
self.outdir = os.path.join(self.orig_cwd, self.outdir)
1322
if self.scons is None:
1323
self.scons = self.scons_path(self.orig_cwd)
1325
if self.scons_lib_dir is None:
1326
self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd)
1328
if self.scons_wrapper is None:
1329
self.scons_wrapper = self.scons
1332
run_number = self.find_next_run_number(self.outdir, self.prefix)
1334
self.run_number = str(run_number)
1336
self.prefix_run = self.prefix + '-%03d' % run_number
1338
if self.targets0 is None:
1339
self.targets0 = self.startup_targets
1340
if self.targets1 is None:
1341
self.targets1 = self.targets
1342
if self.targets2 is None:
1343
self.targets2 = self.targets
1345
self.tmpdir = make_temp_file(prefix = self.name + '-')
1350
(os.chdir, 'cd %%s', self.tmpdir),
1353
for archive in archive_list:
1354
if not os.path.isabs(archive):
1355
archive = os.path.join(self.orig_cwd, archive)
1356
if os.path.isdir(archive):
1357
dest = os.path.split(archive)[1]
1358
commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
1360
suffix = self.archive_splitext(archive)[1]
1361
unpack_command = self.unpack_map.get(suffix)
1362
if not unpack_command:
1363
dest = os.path.split(archive)[1]
1364
commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
1366
commands.append(unpack_command + (archive,))
1369
(os.chdir, 'cd %%s', self.subdir),
1372
commands.extend(self.initial_commands)
1375
(lambda: read_tree('.'),
1376
'find * -type f | xargs cat > /dev/null'),
1378
(self.set_env, 'export %%s=%%s',
1379
'SCONS_LIB_DIR', self.scons_lib_dir),
1381
'%(python)s %(scons_wrapper)s --version',
1385
for run_command in self.run_commands:
1386
setattr(self, 'prof%d' % index, self.profile_name(index))
1391
self.logfile_name(index),
1397
(os.chdir, 'cd %%s', self.orig_cwd),
1400
if not os.environ.get('PRESERVE'):
1401
commands.extend(removals)
1403
commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir))
1405
self.run_command_list(commands, self.__dict__)
1407
self.scons = save_scons
1408
self.scons_lib_dir = save_scons_lib_dir
1409
self.scons_wrapper = save_scons_wrapper
1413
def help_time(self):
1415
Usage: scons-time time [OPTIONS] FILE [...]
1417
-C DIR, --chdir=DIR Change to DIR before looking for files
1418
-f FILE, --file=FILE Read configuration from specified FILE
1419
--fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1420
-h, --help Print this help and exit
1421
-p STRING, --prefix=STRING Use STRING as log file/profile prefix
1422
-t NUMBER, --tail=NUMBER Only report the last NUMBER files
1423
--which=TIMER Plot timings for TIMER: total,
1424
SConscripts, SCons, commands.
1426
sys.stdout.write(self.outdent(help))
1429
def do_time(self, argv):
1432
logfile_path = lambda x: x
1436
short_opts = '?C:f:hp:t:'
1450
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
1453
if o in ('-C', '--chdir'):
1455
elif o in ('-f', '--file'):
1456
self.config_file = a
1457
elif o in ('--fmt', '--format'):
1459
elif o in ('-?', '-h', '--help'):
1460
self.do_help(['help', 'time'])
1462
elif o in ('-p', '--prefix'):
1464
elif o in ('-t', '--tail'):
1466
elif o in ('--title',):
1468
elif o in ('--which',):
1469
if not a in self.time_strings.keys():
1470
sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
1471
sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1475
if self.config_file:
1476
HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__)
1479
os.chdir(self.chdir)
1480
logfile_path = lambda x: os.path.join(self.chdir, x)
1484
pattern = '%s*.log' % self.prefix
1485
args = self.args_to_files([pattern], tail)
1489
directory = self.chdir
1491
directory = os.getcwd()
1493
sys.stderr.write('%s: time: No arguments specified.\n' % self.name)
1494
sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory))
1495
sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
1500
args = self.args_to_files(args, tail)
1502
cwd_ = os.getcwd() + os.sep
1504
if format == 'ascii':
1506
columns = ("Total", "SConscripts", "SCons", "commands")
1507
self.ascii_table(args, columns, self.get_debug_times, logfile_path)
1509
elif format == 'gnuplot':
1511
results = self.collect_results(args, self.get_debug_times,
1512
self.time_strings[which])
1514
self.gnuplot_results(results, fmt='%s %.6f')
1518
sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format))
1521
if __name__ == '__main__':
1522
opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version'])
1527
if o in ('-?', '-h', '--help'):
1528
ST.do_help(['help'])
1530
elif o in ('-V', '--version'):
1531
sys.stdout.write('scons-time version\n')
1535
sys.stderr.write('Type "%s help" for usage.\n' % ST.name)
1538
ST.execute_subcommand(args)
1542
# indent-tabs-mode:nil
1544
# vim: set expandtab tabstop=4 shiftwidth=4: