5 # Copyright The SCons Foundation
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
19 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 scons-time - run SCons timings and collect statistics
29 A script for running a configuration through SCons with a standard set
30 of invocations to collect timing and memory statistics and to capture
31 the results in a consistent set of output files for display and analysis.
44 def HACK_for_exec(cmd
, *args
):
46 For some reason, Python won't allow an exec() within a function
47 that also declares an internal function (including lambda functions).
48 This function is a hack that calls exec() in a function with no
56 exec(cmd
, args
[0], args
[1])
60 def increment_size(self
, largest
):
62 Return the size of each horizontal increment line for a specified
63 maximum value. This returns a value that will provide somewhere
64 between 5 and 9 horizontal lines on the graph, on some set of
65 boundaries that are multiples of 10/100/1000/etc.
73 multiplier
= multiplier
* 10
76 def max_graph_value(self
, largest
):
77 # Round up to next integer.
78 largest
= int(largest
) + 1
79 increment
= self
.increment_size(largest
)
80 return ((largest
+ increment
- 1) // increment
) * increment
84 def __init__(self
, points
, type, title
, label
, comment
, fmt
="%s %s"):
89 self
.comment
= comment
92 def print_label(self
, inx
, x
, y
):
94 print('set label %s "%s" at %0.1f,%0.1f right' % (inx
, self
.label
, x
, y
))
96 def plot_string(self
):
98 title_string
= 'title "%s"' % self
.title
100 title_string
= 'notitle'
101 return "'-' %s with lines lt %s" % (title_string
, self
.type)
103 def print_points(self
, fmt
=None):
107 print('# %s' % self
.comment
)
108 for x
, y
in self
.points
:
109 # If y is None, it usually represents some kind of break
110 # in the line's index number. We might want to represent
111 # this some way rather than just drawing the line straight
112 # between the two points on either side.
117 def get_x_values(self
):
118 return [p
[0] for p
in self
.points
]
120 def get_y_values(self
):
121 return [p
[1] for p
in self
.points
]
124 class Gnuplotter(Plotter
):
126 def __init__(self
, title
, key_location
):
129 self
.key_location
= key_location
131 def line(self
, points
, type, title
=None, label
=None, comment
=None, fmt
='%s %s'):
133 line
= Line(points
, type, title
, label
, comment
, fmt
)
134 self
.lines
.append(line
)
136 def plot_string(self
, line
):
137 return line
.plot_string()
139 def vertical_bar(self
, x
, type, label
, comment
):
140 if self
.get_min_x() <= x
<= self
.get_max_x():
141 points
= [(x
, 0), (x
, self
.max_graph_value(self
.get_max_y()))]
142 self
.line(points
, type, label
, comment
)
144 def get_all_x_values(self
):
146 for line
in self
.lines
:
147 result
.extend(line
.get_x_values())
148 return [r
for r
in result
if r
is not None]
150 def get_all_y_values(self
):
152 for line
in self
.lines
:
153 result
.extend(line
.get_y_values())
154 return [r
for r
in result
if r
is not None]
159 except AttributeError:
161 self
.min_x
= min(self
.get_all_x_values())
169 except AttributeError:
171 self
.max_x
= max(self
.get_all_x_values())
179 except AttributeError:
181 self
.min_y
= min(self
.get_all_y_values())
189 except AttributeError:
191 self
.max_y
= max(self
.get_all_y_values())
202 print('set title "%s"' % self
.title
)
203 print('set key %s' % self
.key_location
)
205 min_y
= self
.get_min_y()
206 max_y
= self
.max_graph_value(self
.get_max_y())
207 incr
= (max_y
- min_y
) / 10.0
208 start
= min_y
+ (max_y
/ 2.0) + (2.0 * incr
)
209 position
= [start
- (i
* incr
) for i
in range(5)]
212 for line
in self
.lines
:
213 line
.print_label(inx
, line
.points
[0][0] - 1,
214 position
[(inx
- 1) % len(position
)])
217 plot_strings
= [self
.plot_string(l
) for l
in self
.lines
]
218 print('plot ' + ', \\\n '.join(plot_strings
))
220 for line
in self
.lines
:
226 with tarfile
.open(name
=fname
, mode
='r') as tar
:
229 tar
.extract(tarinfo
, filter="tar")
236 with zipfile
.ZipFile(fname
, 'r') as zf
:
237 for name
in zf
.namelist():
238 dir = os
.path
.dirname(name
)
243 with
open(name
, 'wb') as f
:
244 f
.write(zf
.read(name
))
248 for dirpath
, dirnames
, filenames
in os
.walk(dir):
250 fn
= os
.path
.join(dirpath
, fn
)
251 if os
.path
.isfile(fn
):
252 with
open(fn
, 'rb') as f
:
256 def redirect_to_file(command
, log
):
257 return '%s > %s 2>&1' % (command
, log
)
260 def tee_to_file(command
, log
):
261 return '%s 2>&1 | tee %s' % (command
, log
)
268 Usage: scons-time SUBCOMMAND [ARGUMENTS]
269 Type "scons-time help SUBCOMMAND" for help on a specific subcommand.
271 Available subcommands:
272 func Extract test-run data for a function
274 mem Extract --debug=memory data from test runs
275 obj Extract --debug=count data from test runs
276 time Extract --debug=time data from test runs
277 run Runs a test configuration
281 name_spaces
= ' ' * len(name
)
283 default_settings
= makedict(
287 key_location
='bottom left',
288 orig_cwd
=os
.getcwd(),
291 python
='"%s"' % sys
.executable
,
292 redirect
=redirect_to_file
,
294 scons_flags
='--debug=count --debug=memory --debug=time --debug=memoizer',
297 startup_targets
='--help',
313 '.tar.gz': (untar
, '%(tar)s xzf %%s'),
314 '.tgz': (untar
, '%(tar)s xzf %%s'),
315 '.tar': (untar
, '%(tar)s xf %%s'),
316 '.zip': (unzip
, '%(unzip)s %%s'),
327 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s',
328 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s',
329 '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s',
340 'pre-read': 'Memory before reading SConscript files:',
341 'post-read': 'Memory after reading SConscript files:',
342 'pre-build': 'Memory before building targets:',
343 'post-build': 'Memory after building targets:',
346 memory_string_all
= 'Memory '
348 default_stage
= stages
[-1]
351 'total': 'Total build time',
352 'SConscripts': 'Total SConscript file execution time',
353 'SCons': 'Total SCons execution time',
354 'commands': 'Total command execution time',
357 time_string_all
= 'Total .* time'
362 self
.__dict
__.update(self
.default_settings
)
364 # Functions for displaying and executing commands.
366 def subst(self
, x
, dictionary
):
368 return x
% dictionary
370 # x isn't a string (it's probably a Python function),
374 def subst_variables(self
, command
, dictionary
):
376 Substitutes (via the format operator) the values in the specified
377 dictionary into the specified command.
379 The command can be an (action, string) tuple. In all cases, we
380 perform substitution on strings and don't worry if something isn't
381 a string. (It's probably a Python function to be executed.)
393 action
= self
.subst(action
, dictionary
)
394 string
= self
.subst(string
, dictionary
)
395 return (action
, string
, args
)
397 def _do_not_display(self
, msg
, *args
):
400 def display(self
, msg
, *args
):
402 Displays the specified message.
404 Each message is prepended with a standard prefix of our name
414 sys
.stdout
.write(fmt
% (self
.name
, time
.strftime('%H:%M:%S'), msg
))
416 def _do_not_execute(self
, action
, *args
):
419 def execute(self
, action
, *args
):
421 Executes the specified action.
423 The action is called if it's a callable Python function, and
424 otherwise passed to os.system().
429 os
.system(action
% args
)
431 def run_command_list(self
, commands
, dict):
433 Executes a list of commands, substituting values from the
434 specified dictionary.
436 commands
= [self
.subst_variables(c
, dict) for c
in commands
]
437 for action
, string
, args
in commands
:
438 self
.display(string
, *args
)
440 status
= self
.execute(action
, *args
)
444 def log_display(self
, command
, log
):
445 command
= self
.subst(command
, self
.__dict
__)
447 command
= self
.redirect(command
, log
)
450 def log_execute(self
, command
, log
):
451 command
= self
.subst(command
, self
.__dict
__)
452 p
= os
.popen(command
)
455 # TODO: convert to subrocess, os.popen is obsolete. This didn't work:
456 # process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
457 # output = process.stdout.read()
458 # process.stdout.close()
461 sys
.stdout
.write(output
)
463 # Not sure we need to write binary here
464 with
open(log
, 'w') as f
:
467 def archive_splitext(self
, path
):
469 Splits an archive name into a filename base and extension.
471 This is like os.path.splitext() (which it calls) except that it
472 also looks for '.tar.gz' and treats it as an atomic extensions.
474 if path
.endswith('.tar.gz'):
475 return path
[:-7], path
[-7:]
477 return os
.path
.splitext(path
)
479 def args_to_files(self
, args
, tail
=None):
481 Takes a list of arguments, expands any glob patterns, and
482 returns the last "tail" files from the list.
486 files
.extend(sorted(glob
.glob(a
)))
489 files
= files
[-tail
:]
493 def ascii_table(self
, files
, columns
,
494 line_function
, file_function
=lambda x
: x
,
497 header_fmt
= ' '.join(['%12s'] * len(columns
))
498 line_fmt
= header_fmt
+ ' %s'
500 print(header_fmt
% columns
)
503 t
= line_function(file, *args
, **kw
)
506 diff
= len(columns
) - len(t
)
509 t
.append(file_function(file))
510 print(line_fmt
% tuple(t
))
512 def collect_results(self
, files
, function
, *args
, **kw
):
516 base
= os
.path
.splitext(file)[0]
517 run
, index
= base
.split('-')[-2:]
522 value
= function(file, *args
, **kw
)
529 r
.append((run
, value
))
533 def doc_to_help(self
, obj
):
535 Translates an object's __doc__ string into help text.
537 This strips a consistent number of spaces from each line in the
538 help text, essentially "outdenting" the text to the left-most
544 return self
.outdent(doc
)
546 def find_next_run_number(self
, dir, prefix
):
548 Returns the next run number in a directory for the specified prefix.
550 Examines the contents the specified directory for files with the
551 specified prefix, extracts the run numbers from each file name,
552 and returns the next run number after the largest it finds.
554 x
= re
.compile(re
.escape(prefix
) + r
'-([0-9]+).*')
555 matches
= [x
.match(e
) for e
in os
.listdir(dir)]
556 matches
= [_f
for _f
in matches
if _f
]
559 run_numbers
= [int(m
.group(1)) for m
in matches
]
560 return int(max(run_numbers
)) + 1
562 def gnuplot_results(self
, results
, fmt
='%s %.3f'):
564 Prints out a set of results in Gnuplot format.
566 gp
= Gnuplotter(self
.title
, self
.key_location
)
568 for i
in sorted(results
.keys()):
570 t
= self
.run_titles
[i
]
574 gp
.line(results
[i
], i
+ 1, t
, None, t
, fmt
=fmt
)
576 for bar_tuple
in self
.vertical_bars
:
578 x
, type, label
, comment
= bar_tuple
580 x
, type, label
= bar_tuple
582 gp
.vertical_bar(x
, type, label
, comment
)
586 def logfile_name(self
, invocation
):
588 Returns the absolute path of a log file for the specificed
591 name
= self
.prefix_run
+ '-%d.log' % invocation
592 return os
.path
.join(self
.outdir
, name
)
594 def outdent(self
, s
):
596 Strip as many spaces from each line as are found at the beginning
597 of the first line in the list.
599 lines
= s
.split('\n')
602 spaces
= re
.match(' *', lines
[0]).group(0)
604 def strip_initial_spaces(line
, s
=spaces
):
605 if line
.startswith(spaces
):
606 line
= line
[len(spaces
):]
609 return '\n'.join([strip_initial_spaces(l
) for l
in lines
]) + '\n'
611 def profile_name(self
, invocation
):
613 Returns the absolute path of a profile file for the specified
616 name
= self
.prefix_run
+ '-%d.prof' % invocation
617 return os
.path
.join(self
.outdir
, name
)
619 def set_env(self
, key
, value
):
620 os
.environ
[key
] = value
624 def get_debug_times(self
, file, time_string
=None):
626 Fetch times from the --debug=time strings in the specified file.
628 if time_string
is None:
629 search_string
= self
.time_string_all
631 search_string
= time_string
632 with
open(file) as f
:
635 sys
.stderr
.write('file %s has no contents!\n' % repr(file))
637 result
= re
.findall(r
'%s: ([\d.]*)' % search_string
, contents
)[-4:]
638 result
= [float(r
) for r
in result
]
639 if time_string
is not None:
643 sys
.stderr
.write('file %s has no results!\n' % repr(file))
647 def get_function_profile(self
, file, function
):
649 Returns the file, line number, function name, and cumulative time.
653 except ImportError as e
:
654 sys
.stderr
.write('%s: func: %s\n' % (self
.name
, e
))
655 sys
.stderr
.write('%s This version of Python is missing the profiler.\n' % self
.name_spaces
)
656 sys
.stderr
.write('%s Cannot use the "func" subcommand.\n' % self
.name_spaces
)
658 statistics
= pstats
.Stats(file).stats
659 matches
= [e
for e
in statistics
.items() if e
[0][2] == function
]
661 return r
[0][0], r
[0][1], r
[0][2], r
[1][3]
663 def get_function_time(self
, file, function
):
665 Returns just the cumulative time for the specified function.
667 return self
.get_function_profile(file, function
)[3]
669 def get_memory(self
, file, memory_string
=None):
671 Returns a list of integers of the amount of memory used. The
672 default behavior is to return all the stages.
674 if memory_string
is None:
675 search_string
= self
.memory_string_all
677 search_string
= memory_string
678 with
open(file) as f
:
679 lines
= f
.readlines()
680 lines
= [l
for l
in lines
if l
.startswith(search_string
)][-4:]
681 result
= [int(l
.split()[-1]) for l
in lines
[-4:]]
686 def get_object_counts(self
, file, object_name
, index
=None):
688 Returns the counts of the specified object_name.
690 object_string
= ' ' + object_name
+ '\n'
691 with
open(file) as f
:
692 lines
= f
.readlines()
693 line
= [l
for l
in lines
if l
.endswith(object_string
)][0]
694 result
= [int(field
) for field
in line
.split()[:4]]
695 if index
is not None:
696 result
= result
[index
]
701 def execute_subcommand(self
, argv
):
703 Executes the do_*() function for the specified subcommand (argv[0]).
707 cmdName
= self
.command_alias
.get(argv
[0], argv
[0])
709 func
= getattr(self
, 'do_' + cmdName
)
710 except AttributeError:
711 return self
.default(argv
)
714 except TypeError as e
:
715 sys
.stderr
.write("%s %s: %s\n" % (self
.name
, cmdName
, e
))
717 traceback
.print_exc(file=sys
.stderr
)
718 sys
.stderr
.write("Try '%s help %s'\n" % (self
.name
, cmdName
))
720 def default(self
, argv
):
722 The default behavior for an unknown subcommand. Prints an
723 error message and exits.
725 sys
.stderr
.write('%s: Unknown subcommand "%s".\n' % (self
.name
, argv
[0]))
726 sys
.stderr
.write('Type "%s help" for usage.\n' % self
.name
)
731 def do_help(self
, argv
):
737 func
= getattr(self
, 'do_' + arg
)
738 except AttributeError:
739 sys
.stderr
.write('%s: No help for "%s"\n' % (self
.name
, arg
))
742 help = getattr(self
, 'help_' + arg
)
743 except AttributeError:
744 sys
.stdout
.write(self
.doc_to_help(func
))
749 doc
= self
.doc_to_help(self
.__class
__)
751 sys
.stdout
.write(doc
)
759 Usage: scons-time func [OPTIONS] FILE [...]
761 -C DIR, --chdir=DIR Change to DIR before looking for files
762 -f FILE, --file=FILE Read configuration from specified FILE
763 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
764 --func=NAME, --function=NAME Report time for function NAME
765 -h, --help Print this help and exit
766 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
767 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
768 --title=TITLE Specify the output plot TITLE
770 sys
.stdout
.write(self
.outdent(help))
773 def do_func(self
, argv
):
777 function_name
= '_main'
780 short_opts
= '?C:f:hp:t:'
795 opts
, args
= getopt
.getopt(argv
[1:], short_opts
, long_opts
)
798 if o
in ('-C', '--chdir'):
800 elif o
in ('-f', '--file'):
802 elif o
in ('--fmt', '--format'):
804 elif o
in ('--func', '--function'):
806 elif o
in ('-?', '-h', '--help'):
807 self
.do_help(['help', 'func'])
809 elif o
in ('--max',):
811 elif o
in ('-p', '--prefix'):
813 elif o
in ('-t', '--tail'):
815 elif o
in ('--title',):
819 with
open(self
.config_file
, 'r') as f
:
821 exec(config
, self
.__dict
__)
828 pattern
= '%s*.prof' % self
.prefix
829 args
= self
.args_to_files([pattern
], tail
)
833 directory
= self
.chdir
835 directory
= os
.getcwd()
837 sys
.stderr
.write('%s: func: No arguments specified.\n' % self
.name
)
838 sys
.stderr
.write('%s No %s*.prof files found in "%s".\n' % (self
.name_spaces
, self
.prefix
, directory
))
839 sys
.stderr
.write('%s Type "%s help func" for help.\n' % (self
.name_spaces
, self
.name
))
844 args
= self
.args_to_files(args
, tail
)
846 cwd_
= os
.getcwd() + os
.sep
848 if format
== 'ascii':
852 f
, line
, func
, time
= \
853 self
.get_function_profile(file, function_name
)
854 except ValueError as e
:
855 sys
.stderr
.write("%s: func: %s: %s\n" %
856 (self
.name
, file, e
))
858 if f
.startswith(cwd_
):
860 print("%.3f %s:%d(%s)" % (time
, f
, line
, func
))
862 elif format
== 'gnuplot':
864 results
= self
.collect_results(args
, self
.get_function_time
,
867 self
.gnuplot_results(results
)
871 sys
.stderr
.write('%s: func: Unknown format "%s".\n' % (self
.name
, format
))
878 Usage: scons-time mem [OPTIONS] FILE [...]
880 -C DIR, --chdir=DIR Change to DIR before looking for files
881 -f FILE, --file=FILE Read configuration from specified FILE
882 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
883 -h, --help Print this help and exit
884 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
885 --stage=STAGE Plot memory at the specified stage:
886 pre-read, post-read, pre-build,
887 post-build (default: post-build)
888 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
889 --title=TITLE Specify the output plot TITLE
891 sys
.stdout
.write(self
.outdent(help))
894 def do_mem(self
, argv
):
897 def _logfile_path(x
):
900 logfile_path
= _logfile_path
902 stage
= self
.default_stage
905 short_opts
= '?C:f:hp:t:'
919 opts
, args
= getopt
.getopt(argv
[1:], short_opts
, long_opts
)
922 if o
in ('-C', '--chdir'):
924 elif o
in ('-f', '--file'):
926 elif o
in ('--fmt', '--format'):
928 elif o
in ('-?', '-h', '--help'):
929 self
.do_help(['help', 'mem'])
931 elif o
in ('-p', '--prefix'):
933 elif o
in ('--stage',):
934 if a
not in self
.stages
:
935 sys
.stderr
.write('%s: mem: Unrecognized stage "%s".\n' % (self
.name
, a
))
938 elif o
in ('-t', '--tail'):
940 elif o
in ('--title',):
944 with
open(self
.config_file
, 'r') as f
:
946 HACK_for_exec(config
, self
.__dict
__)
950 def _logfile_path_join(x
):
951 return os
.path
.join(self
.chdir
, x
)
953 logfile_path
= _logfile_path_join
956 pattern
= '%s*.log' % self
.prefix
957 args
= self
.args_to_files([pattern
], tail
)
961 directory
= self
.chdir
963 directory
= os
.getcwd()
965 sys
.stderr
.write('%s: mem: No arguments specified.\n' % self
.name
)
966 sys
.stderr
.write('%s No %s*.log files found in "%s".\n' % (self
.name_spaces
, self
.prefix
, directory
))
967 sys
.stderr
.write('%s Type "%s help mem" for help.\n' % (self
.name_spaces
, self
.name
))
972 args
= self
.args_to_files(args
, tail
)
974 # cwd_ = os.getcwd() + os.sep
976 if format
== 'ascii':
978 self
.ascii_table(args
, tuple(self
.stages
), self
.get_memory
, logfile_path
)
980 elif format
== 'gnuplot':
982 results
= self
.collect_results(args
, self
.get_memory
,
983 self
.stage_strings
[stage
])
985 self
.gnuplot_results(results
)
989 sys
.stderr
.write('%s: mem: Unknown format "%s".\n' % (self
.name
, format
))
998 Usage: scons-time obj [OPTIONS] OBJECT FILE [...]
1000 -C DIR, --chdir=DIR Change to DIR before looking for files
1001 -f FILE, --file=FILE Read configuration from specified FILE
1002 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1003 -h, --help Print this help and exit
1004 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1005 --stage=STAGE Plot memory at the specified stage:
1006 pre-read, post-read, pre-build,
1007 post-build (default: post-build)
1008 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1009 --title=TITLE Specify the output plot TITLE
1011 sys
.stdout
.write(self
.outdent(help))
1014 def do_obj(self
, argv
):
1018 def _logfile_path(x
):
1021 logfile_path
= _logfile_path
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 a
not 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 with
open(self
.config_file
, 'r') as f
:
1075 HACK_for_exec(config
, self
.__dict
__)
1078 os
.chdir(self
.chdir
)
1080 def _logfile_path_join(x
):
1081 return os
.path
.join(self
.chdir
, x
)
1083 logfile_path
= _logfile_path_join
1086 pattern
= '%s*.log' % self
.prefix
1087 args
= self
.args_to_files([pattern
], tail
)
1091 directory
= self
.chdir
1093 directory
= os
.getcwd()
1095 sys
.stderr
.write('%s: obj: No arguments specified.\n' % self
.name
)
1096 sys
.stderr
.write('%s No %s*.log files found in "%s".\n' % (self
.name_spaces
, self
.prefix
, directory
))
1097 sys
.stderr
.write('%s Type "%s help obj" for help.\n' % (self
.name_spaces
, self
.name
))
1102 args
= self
.args_to_files(args
, tail
)
1104 cwd_
= os
.getcwd() + os
.sep
1106 if format
== 'ascii':
1108 self
.ascii_table(args
, tuple(self
.stages
), self
.get_object_counts
, logfile_path
, object_name
)
1110 elif format
== 'gnuplot':
1113 for s
in self
.stages
:
1116 stage_index
= stage_index
+ 1
1118 results
= self
.collect_results(args
, self
.get_object_counts
,
1119 object_name
, stage_index
)
1121 self
.gnuplot_results(results
)
1125 sys
.stderr
.write('%s: obj: Unknown format "%s".\n' % (self
.name
, format
))
1134 Usage: scons-time run [OPTIONS] [FILE ...]
1136 --chdir=DIR Name of unpacked directory for chdir
1137 -f FILE, --file=FILE Read configuration from specified FILE
1138 -h, --help Print this help and exit
1139 -n, --no-exec No execute, just print command lines
1140 --number=NUMBER Put output in files for run NUMBER
1141 --outdir=OUTDIR Put output files in OUTDIR
1142 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1143 --python=PYTHON Time using the specified PYTHON
1144 -q, --quiet Don't print command lines
1145 --scons=SCONS Time using the specified SCONS
1146 --svn=URL, --subversion=URL Use SCons from Subversion URL
1147 -v, --verbose Display output of commands
1149 sys
.stdout
.write(self
.outdent(help))
1152 def do_run(self
, argv
):
1155 run_number_list
= [None]
1157 short_opts
= '?f:hnp:qs:v'
1175 opts
, args
= getopt
.getopt(argv
[1:], short_opts
, long_opts
)
1178 if o
in ('-f', '--file'):
1179 self
.config_file
= a
1180 elif o
in ('-?', '-h', '--help'):
1181 self
.do_help(['help', 'run'])
1183 elif o
in ('-n', '--no-exec'):
1184 self
.execute
= self
._do
_not
_execute
1185 elif o
in ('--number',):
1186 run_number_list
= self
.split_run_numbers(a
)
1187 elif o
in ('--outdir',):
1189 elif o
in ('-p', '--prefix'):
1191 elif o
in ('--python',):
1193 elif o
in ('-q', '--quiet'):
1194 self
.display
= self
._do
_not
_display
1195 elif o
in ('-s', '--subdir'):
1197 elif o
in ('--scons',):
1199 elif o
in ('--svn', '--subversion'):
1200 self
.subversion_url
= a
1201 elif o
in ('-v', '--verbose'):
1202 self
.redirect
= tee_to_file
1204 self
.svn_co_flag
= ''
1206 if not args
and not self
.config_file
:
1207 sys
.stderr
.write('%s: run: No arguments or -f config file specified.\n' % self
.name
)
1208 sys
.stderr
.write('%s Type "%s help run" for help.\n' % (self
.name_spaces
, self
.name
))
1211 if self
.config_file
:
1212 with
open(self
.config_file
, 'r') as f
:
1214 exec(config
, self
.__dict
__)
1217 self
.archive_list
= args
1219 archive_file_name
= os
.path
.split(self
.archive_list
[0])[1]
1222 self
.subdir
= self
.archive_splitext(archive_file_name
)[0]
1225 self
.prefix
= self
.archive_splitext(archive_file_name
)[0]
1228 if self
.subversion_url
:
1229 prepare
= self
.prep_subversion_run
1231 for run_number
in run_number_list
:
1232 self
.individual_run(run_number
, self
.archive_list
, prepare
)
1234 def split_run_numbers(self
, s
):
1236 for n
in s
.split(','):
1240 result
.append(int(n
))
1242 result
.extend(list(range(int(x
), int(y
) + 1)))
1245 def scons_path(self
, dir):
1246 return os
.path
.join(dir, 'scripts', 'scons.py')
1248 def scons_lib_dir_path(self
, dir):
1249 """build the path to the engine.
1251 this used to join src/engine, but no longer.
1255 def prep_subversion_run(self
, commands
, removals
):
1256 self
.svn_tmpdir
= tempfile
.mkdtemp(prefix
=self
.name
+ '-svn-')
1257 removals
.append((shutil
.rmtree
, 'rm -rf %%s', self
.svn_tmpdir
))
1259 self
.scons
= self
.scons_path(self
.svn_tmpdir
)
1260 self
.scons_lib_dir
= self
.scons_lib_dir_path(self
.svn_tmpdir
)
1263 '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s',
1266 def individual_run(self
, run_number
, archive_list
, prepare
=None):
1268 Performs an individual run of the default SCons invocations.
1275 prepare(commands
, removals
)
1277 save_scons
= self
.scons
1278 save_scons_wrapper
= self
.scons_wrapper
1279 save_scons_lib_dir
= self
.scons_lib_dir
1281 if self
.outdir
is None:
1282 self
.outdir
= self
.orig_cwd
1283 elif not os
.path
.isabs(self
.outdir
):
1284 self
.outdir
= os
.path
.join(self
.orig_cwd
, self
.outdir
)
1286 if self
.scons
is None:
1287 self
.scons
= self
.scons_path(self
.orig_cwd
)
1289 if self
.scons_lib_dir
is None:
1290 self
.scons_lib_dir
= self
.scons_lib_dir_path(self
.orig_cwd
)
1292 if self
.scons_wrapper
is None:
1293 self
.scons_wrapper
= self
.scons
1296 run_number
= self
.find_next_run_number(self
.outdir
, self
.prefix
)
1298 self
.run_number
= str(run_number
)
1300 self
.prefix_run
= self
.prefix
+ '-%03d' % run_number
1302 if self
.targets0
is None:
1303 self
.targets0
= self
.startup_targets
1304 if self
.targets1
is None:
1305 self
.targets1
= self
.targets
1306 if self
.targets2
is None:
1307 self
.targets2
= self
.targets
1309 self
.tmpdir
= tempfile
.mkdtemp(prefix
=self
.name
+ '-')
1312 (os
.chdir
, 'cd %%s', self
.tmpdir
),
1315 for archive
in archive_list
:
1316 if not os
.path
.isabs(archive
):
1317 archive
= os
.path
.join(self
.orig_cwd
, archive
)
1318 if os
.path
.isdir(archive
):
1319 dest
= os
.path
.split(archive
)[1]
1320 commands
.append((shutil
.copytree
, 'cp -r %%s %%s', archive
, dest
))
1322 suffix
= self
.archive_splitext(archive
)[1]
1323 unpack_command
= self
.unpack_map
.get(suffix
)
1324 if not unpack_command
:
1325 dest
= os
.path
.split(archive
)[1]
1326 commands
.append((shutil
.copyfile
, 'cp %%s %%s', archive
, dest
))
1328 commands
.append(unpack_command
+ (archive
,))
1331 (os
.chdir
, 'cd %%s', self
.subdir
),
1334 commands
.extend(self
.initial_commands
)
1337 (lambda: read_tree('.'),
1338 'find * -type f | xargs cat > /dev/null'),
1340 (self
.set_env
, 'export %%s=%%s',
1341 'SCONS_LIB_DIR', self
.scons_lib_dir
),
1343 '%(python)s %(scons_wrapper)s --version',
1347 for run_command
in self
.run_commands
:
1348 setattr(self
, 'prof%d' % index
, self
.profile_name(index
))
1353 self
.logfile_name(index
),
1359 (os
.chdir
, 'cd %%s', self
.orig_cwd
),
1362 if not os
.environ
.get('PRESERVE'):
1363 commands
.extend(removals
)
1364 commands
.append((shutil
.rmtree
, 'rm -rf %%s', self
.tmpdir
))
1366 self
.run_command_list(commands
, self
.__dict
__)
1368 self
.scons
= save_scons
1369 self
.scons_lib_dir
= save_scons_lib_dir
1370 self
.scons_wrapper
= save_scons_wrapper
1374 def help_time(self
):
1376 Usage: scons-time time [OPTIONS] FILE [...]
1378 -C DIR, --chdir=DIR Change to DIR before looking for files
1379 -f FILE, --file=FILE Read configuration from specified FILE
1380 --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT
1381 -h, --help Print this help and exit
1382 -p STRING, --prefix=STRING Use STRING as log file/profile prefix
1383 -t NUMBER, --tail=NUMBER Only report the last NUMBER files
1384 --which=TIMER Plot timings for TIMER: total,
1385 SConscripts, SCons, commands.
1387 sys
.stdout
.write(self
.outdent(help))
1390 def do_time(self
, argv
):
1394 def _logfile_path(x
):
1397 logfile_path
= _logfile_path
1402 short_opts
= '?C:f:hp:t:'
1416 opts
, args
= getopt
.getopt(argv
[1:], short_opts
, long_opts
)
1419 if o
in ('-C', '--chdir'):
1421 elif o
in ('-f', '--file'):
1422 self
.config_file
= a
1423 elif o
in ('--fmt', '--format'):
1425 elif o
in ('-?', '-h', '--help'):
1426 self
.do_help(['help', 'time'])
1428 elif o
in ('-p', '--prefix'):
1430 elif o
in ('-t', '--tail'):
1432 elif o
in ('--title',):
1434 elif o
in ('--which',):
1435 if a
not in list(self
.time_strings
.keys()):
1436 sys
.stderr
.write('%s: time: Unrecognized timer "%s".\n' % (self
.name
, a
))
1437 sys
.stderr
.write('%s Type "%s help time" for help.\n' % (self
.name_spaces
, self
.name
))
1441 if self
.config_file
:
1442 with
open(self
.config_file
, 'r') as f
:
1444 HACK_for_exec(config
, self
.__dict
__)
1447 os
.chdir(self
.chdir
)
1449 def _logfile_path_join(x
):
1450 return os
.path
.join(self
.chdir
, x
)
1452 logfile_path
= _logfile_path_join
1455 pattern
= '%s*.log' % self
.prefix
1456 args
= self
.args_to_files([pattern
], tail
)
1460 directory
= self
.chdir
1462 directory
= os
.getcwd()
1464 sys
.stderr
.write('%s: time: No arguments specified.\n' % self
.name
)
1465 sys
.stderr
.write('%s No %s*.log files found in "%s".\n' % (self
.name_spaces
, self
.prefix
, directory
))
1466 sys
.stderr
.write('%s Type "%s help time" for help.\n' % (self
.name_spaces
, self
.name
))
1471 args
= self
.args_to_files(args
, tail
)
1473 cwd_
= os
.getcwd() + os
.sep
1475 if format
== 'ascii':
1477 columns
= ("Total", "SConscripts", "SCons", "commands")
1478 self
.ascii_table(args
, columns
, self
.get_debug_times
, logfile_path
)
1480 elif format
== 'gnuplot':
1482 results
= self
.collect_results(args
, self
.get_debug_times
,
1483 self
.time_strings
[which
])
1485 self
.gnuplot_results(results
, fmt
='%s %.6f')
1489 sys
.stderr
.write('%s: time: Unknown format "%s".\n' % (self
.name
, format
))
1493 if __name__
== '__main__':
1494 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'h?V', ['help', 'version'])
1499 if o
in ('-?', '-h', '--help'):
1500 ST
.do_help(['help'])
1502 elif o
in ('-V', '--version'):
1503 sys
.stdout
.write('scons-time version\n')
1507 sys
.stderr
.write('Type "%s help" for usage.\n' % ST
.name
)
1510 ST
.execute_subcommand(args
)
1514 # indent-tabs-mode:nil
1516 # vim: set expandtab tabstop=4 shiftwidth=4: