1#!/usr/bin/env python3 2# 3# BLIS 4# An object-based framework for developing high-performance BLAS-like 5# libraries. 6# 7# Copyright (C) 2018, The University of Texas at Austin 8# Copyright (C) 2018 - 2019, Advanced Micro Devices, Inc. 9# 10# Redistribution and use in source and binary forms, with or without 11# modification, are permitted provided that the following conditions are 12# met: 13# - Redistributions of source code must retain the above copyright 14# notice, this list of conditions and the following disclaimer. 15# - Redistributions in binary form must reproduce the above copyright 16# notice, this list of conditions and the following disclaimer in the 17# documentation and/or other materials provided with the distribution. 18# - Neither the name(s) of the copyright holder(s) nor the names of its 19# contributors may be used to endorse or promote products derived 20# from this software without specific prior written permission. 21# 22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33# 34# 35 36# Import modules 37import os 38import sys 39import getopt 40import re 41import subprocess 42import time 43import statistics 44 45 46def print_usage(): 47 48 my_print( " " ) 49 my_print( " %s" % script_name ) 50 my_print( " " ) 51 my_print( " Field G. Van Zee" ) 52 my_print( " " ) 53 my_print( " Repeatedly run a test driver and accumulate statistics for the" ) 54 my_print( " output." ) 55 my_print( " " ) 56 my_print( " Usage:" ) 57 my_print( " " ) 58 my_print( " %s [options] drivername" % script_name ) 59 my_print( " " ) 60 my_print( " Arguments:" ) 61 my_print( " " ) 62 my_print( " drivername The filename/path of the test driver to run. The" ) 63 my_print( " test driver must output its performance data to" ) 64 my_print( " standard output." ) 65 my_print( " " ) 66 my_print( " The following options are accepted:" ) 67 my_print( " " ) 68 my_print( " -c num performance column index" ) 69 my_print( " Find the performance result in column index <num> of" ) 70 my_print( " the test driver's output. Here, a column is defined" ) 71 my_print( " as a contiguous sequence of non-whitespace characters," ) 72 my_print( " with the column indices beginning at 0. By default," ) 73 my_print( " the second-to-last column index in the output is used." ) 74 my_print( " " ) 75 my_print( " -d delay sleep() delay" ) 76 my_print( " Wait <delay> seconds after each execution of the" ) 77 my_print( " test driver. The default delay is 0." ) 78 my_print( " " ) 79 my_print( " -n niter number of iterations" ) 80 my_print( " Execute the test driver <niter> times. The default" ) 81 my_print( " value is 10." ) 82 my_print( " " ) 83 my_print( " -q quiet; summary only" ) 84 my_print( " Do not output statistics after every new execution of" ) 85 my_print( " the test driver; instead, only output the final values" ) 86 my_print( " after all iterations are complete. The default is to" ) 87 my_print( " output updated statistics after each iteration." ) 88 my_print( " " ) 89 my_print( " -h help" ) 90 my_print( " Output this information and exit." ) 91 my_print( " " ) 92 93 94# ------------------------------------------------------------------------------ 95 96def my_print( s ): 97 98 sys.stdout.write( "%s\n" % s ) 99 #sys.stdout.flush() 100 101# ------------------------------------------------------------------------------ 102 103# Global variables. 104script_name = None 105output_name = None 106 107def main(): 108 109 global script_name 110 global output_name 111 112 # Obtain the script name. 113 path, script_name = os.path.split(sys.argv[0]) 114 115 output_name = script_name 116 117 # Default values for optional arguments. 118 #perf_col = 9 119 perf_col = -1 120 delay = 0 121 niter = 10 122 quiet = False 123 124 # Process our command line options. 125 try: 126 opts, args = getopt.getopt( sys.argv[1:], "c:d:n:hq" ) 127 128 except getopt.GetoptError as err: 129 # print help information and exit: 130 my_print( str(err) ) # will print something like "option -a not recognized" 131 print_usage() 132 sys.exit(2) 133 134 for opt, optarg in opts: 135 if opt == "-c": 136 perf_col = optarg 137 elif opt == "-d": 138 delay = optarg 139 elif opt == "-n": 140 niter = optarg 141 elif opt == "-q": 142 quiet = True 143 elif opt == "-h": 144 print_usage() 145 sys.exit() 146 else: 147 print_usage() 148 sys.exit() 149 150 # Print usage if we don't have exactly one argument. 151 if len( args ) != 1: 152 print_usage() 153 sys.exit() 154 155 # Acquire our only mandatory argument: the name of the test driver. 156 driverfile = args[0] 157 158 #my_print( "test driver: %s" % driverfile ) 159 #my_print( "column num: %s" % perf_col ) 160 #my_print( "delay: %s" % delay ) 161 #my_print( "num iter: %s" % niter ) 162 163 # Build a list of iterations. 164 iters = range( int(niter) ) 165 166 # Run the test driver once to detect the number of lines of output. 167 p = subprocess.run( driverfile, stdout=subprocess.PIPE ) 168 lines0 = p.stdout.decode().splitlines() 169 num_lines0 = int(len(lines0)) 170 171 # Initialize the list of lists (one list per performance result). 172 aperf = [] 173 for i in range( num_lines0 ): 174 aperf.append( [] ) 175 176 for it in iters: 177 178 # Run the test driver. 179 p = subprocess.run( driverfile, stdout=subprocess.PIPE ) 180 181 # Acquire the lines of output. 182 lines = p.stdout.decode().splitlines() 183 184 # Accumulate the test driver's latest results into aperf. 185 for i in range( num_lines0 ): 186 187 # Parse the current line to find the performance value. 188 line = lines[i] 189 words = line.split() 190 if perf_col == -1: 191 perf = words[ len(words)-2 ] 192 else: 193 perf = words[ int(perf_col) ] 194 195 # As unlikely as it is, guard against Inf and NaN. 196 if float(perf) == float('Inf') or \ 197 float(perf) == -float('Inf') or \ 198 float(perf) == float('NaN'): perf = 0.0 199 200 # Add the performance value to the list at the ith entry of aperf. 201 aperf[i].append( float(perf) ) 202 203 # Compute stats for the current line. 204 avgp = statistics.mean( aperf[i] ) 205 maxp = max( aperf[i] ) 206 minp = min( aperf[i] ) 207 208 # Only compute stdev() when we have two or more data points. 209 if len( aperf[i] ) > 1: stdp = statistics.stdev( aperf[i] ) 210 else: stdp = 0.0 211 212 # Construct a string to match the performance value and then 213 # use that string to search-and-replace with four format specs 214 # for the min, avg, max, and stdev values computed above. 215 search = '%8s' % perf 216 newline = re.sub( str(search), ' %7.2f %7.2f %7.2f %6.2f', line ) 217 218 # Search for the column index range that would be present if this were 219 # matlab-compatible output. The index range will typically be 1:n, 220 # where n is the number of columns of data. 221 found_index = False 222 for word in words: 223 if re.match( '1:', word ): 224 index_str = word 225 found_index = True 226 break 227 228 # If we find the column index range, we need to update it to reflect 229 # the replacement of one column of data with four, for a net increase 230 # of columns. We do so via another instance of re.sub() in which we 231 # search for the old index string and replace it with the new one. 232 if found_index: 233 last_col = int(index_str[2]) + 3 234 new_index_str = '1:%1s' % last_col 235 newline = re.sub( index_str, new_index_str, newline ) 236 237 # If the quiet flag was not give, output the intermediate results. 238 if not quiet: 239 print( newline % ( float(minp), float(avgp), float(maxp), float(stdp) ) ) 240 241 # Flush stdout after each set of output prior to sleeping. 242 sys.stdout.flush() 243 244 # Sleep for a bit until the next iteration. 245 time.sleep( int(delay) ) 246 247 # If the quiet flag was given, output the final results. 248 if quiet: 249 250 for i in range( num_lines0 ): 251 252 # Parse the current line to find the performance value (only 253 # needed for call to re.sub() below). 254 line = lines0[i] 255 words = line.split() 256 if perf_col == -1: 257 perf = words[ len(words)-2 ] 258 else: 259 perf = words[ int(perf_col) ] 260 261 # Compute stats for the current line. 262 avgp = statistics.mean( aperf[i] ) 263 maxp = max( aperf[i] ) 264 minp = min( aperf[i] ) 265 266 # Only compute stdev() when we have two or more data points. 267 if len( aperf[i] ) > 1: stdp = statistics.stdev( aperf[i] ) 268 else: stdp = 0.0 269 270 # Construct a string to match the performance value and then 271 # use that string to search-and-replace with four format specs 272 # for the min, avg, max, and stdev values computed above. 273 search = '%8s' % perf 274 newline = re.sub( str(search), ' %7.2f %7.2f %7.2f %6.2f', line ) 275 276 # Search for the column index range that would be present if this were 277 # matlab-compatible output. The index range will typically be 1:n, 278 # where n is the number of columns of data. 279 found_index = False 280 for word in words: 281 if re.match( '1:', word ): 282 index_str = word 283 found_index = True 284 break 285 286 # If we find the column index range, we need to update it to reflect 287 # the replacement of one column of data with four, for a net increase 288 # of columns. We do so via another instance of re.sub() in which we 289 # search for the old index string and replace it with the new one. 290 if found_index: 291 last_col = int(index_str[2]) + 3 292 new_index_str = '1:%1s' % last_col 293 newline = re.sub( index_str, new_index_str, newline ) 294 295 # Output the results for the current line. 296 print( newline % ( float(minp), float(avgp), float(maxp), float(stdp) ) ) 297 298 # Flush stdout afterwards. 299 sys.stdout.flush() 300 301 302 # Return from main(). 303 return 0 304 305 306 307 308if __name__ == "__main__": 309 main() 310