2012-11-24 02:57:22 +08:00
#!/usr/bin/env python
2011-09-09 21:21:26 +08:00
import testlog_parser , sys , os , xml , glob , re
2011-09-06 23:30:28 +08:00
from table_formatter import *
from optparse import OptionParser
2012-01-16 15:04:26 +08:00
numeric_re = re . compile ( " ( \ d+) " )
cvtype_re = re . compile ( " (8U|8S|16U|16S|32S|32F|64F)C( \ d { 1,3}) " )
cvtypes = { ' 8U ' : 0 , ' 8S ' : 1 , ' 16U ' : 2 , ' 16S ' : 3 , ' 32S ' : 4 , ' 32F ' : 5 , ' 64F ' : 6 }
convert = lambda text : int ( text ) if text . isdigit ( ) else text
keyselector = lambda a : cvtype_re . sub ( lambda match : " " + str ( cvtypes . get ( match . group ( 1 ) , 7 ) + ( int ( match . group ( 2 ) ) - 1 ) * 8 ) + " " , a )
alphanum_keyselector = lambda key : [ convert ( c ) for c in numeric_re . split ( keyselector ( key ) ) ]
2011-11-22 21:57:44 +08:00
2011-10-18 20:31:08 +08:00
def getSetName ( tset , idx , columns , short = True ) :
2012-01-13 20:37:10 +08:00
if columns and len ( columns ) > idx :
2011-10-18 20:31:08 +08:00
prefix = columns [ idx ]
else :
prefix = None
if short and prefix :
return prefix
name = tset [ 0 ] . replace ( " .xml " , " " ) . replace ( " _ " , " \n " )
if prefix :
2011-12-16 12:42:34 +08:00
return prefix + " \n " + ( " - " * int ( len ( max ( prefix . split ( " \n " ) , key = len ) ) * 1.5 ) ) + " \n " + name
2011-10-18 20:31:08 +08:00
return name
2011-09-06 23:30:28 +08:00
if __name__ == " __main__ " :
if len ( sys . argv ) < 2 :
print >> sys . stderr , " Usage: \n " , os . path . basename ( sys . argv [ 0 ] ) , " <log_name1>.xml [<log_name2>.xml ...] "
exit ( 0 )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
parser = OptionParser ( )
2017-10-23 19:51:16 +08:00
parser . add_option ( " -o " , " --output " , dest = " format " , help = " output results in text format (can be ' txt ' , ' html ' , ' markdown ' or ' auto ' - default) " , metavar = " FMT " , default = " auto " )
2011-09-06 23:30:28 +08:00
parser . add_option ( " -m " , " --metric " , dest = " metric " , help = " output metric " , metavar = " NAME " , default = " gmean " )
2018-01-27 22:30:37 +08:00
parser . add_option ( " -u " , " --units " , dest = " units " , help = " units for output values (s, ms (default), us, ns or ticks) " , metavar = " UNITS " , default = " ms " )
2011-09-06 23:30:28 +08:00
parser . add_option ( " -f " , " --filter " , dest = " filter " , help = " regex to filter tests " , metavar = " REGEX " , default = None )
2011-10-18 20:31:08 +08:00
parser . add_option ( " " , " --module " , dest = " module " , default = None , metavar = " NAME " , help = " module prefix for test names " )
parser . add_option ( " " , " --columns " , dest = " columns " , default = None , metavar = " NAMES " , help = " comma-separated list of column aliases " )
2011-09-06 23:30:28 +08:00
parser . add_option ( " " , " --no-relatives " , action = " store_false " , dest = " calc_relatives " , default = True , help = " do not output relative values " )
2013-02-26 23:37:37 +08:00
parser . add_option ( " " , " --with-cycles-reduction " , action = " store_true " , dest = " calc_cr " , default = False , help = " output cycle reduction percentages " )
parser . add_option ( " " , " --with-score " , action = " store_true " , dest = " calc_score " , default = False , help = " output automatic classification of speedups " )
2013-11-20 23:57:20 +08:00
parser . add_option ( " " , " --progress " , action = " store_true " , dest = " progress_mode " , default = False , help = " enable progress mode " )
2014-03-28 19:11:10 +08:00
parser . add_option ( " " , " --regressions " , dest = " regressions " , default = None , metavar = " LIST " , help = " comma-separated custom regressions map: \" [r][c]#current-#reference \" (indexes of columns are 0-based, \" r \" - reverse flag, \" c \" - color flag for base data) " )
2011-09-06 23:30:28 +08:00
parser . add_option ( " " , " --show-all " , action = " store_true " , dest = " showall " , default = False , help = " also include empty and \" notrun \" lines " )
2011-09-09 21:21:26 +08:00
parser . add_option ( " " , " --match " , dest = " match " , default = None )
parser . add_option ( " " , " --match-replace " , dest = " match_replace " , default = " " )
2013-02-25 23:03:26 +08:00
parser . add_option ( " " , " --regressions-only " , dest = " regressionsOnly " , default = None , metavar = " X-FACTOR " , help = " show only tests with performance regressions not " )
2014-03-02 20:08:37 +08:00
parser . add_option ( " " , " --intersect-logs " , dest = " intersect_logs " , default = False , help = " show only tests present in all log files " )
2018-01-27 22:30:37 +08:00
parser . add_option ( " " , " --show_units " , action = " store_true " , dest = " show_units " , help = " append units into table cells " )
2011-09-06 23:30:28 +08:00
( options , args ) = parser . parse_args ( )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
options . generateHtml = detectHtmlOutputType ( options . format )
if options . metric not in metrix_table :
options . metric = " gmean "
2011-10-18 20:31:08 +08:00
if options . metric . endswith ( " % " ) or options . metric . endswith ( " $ " ) :
2011-09-06 23:30:28 +08:00
options . calc_relatives = False
2011-10-18 20:31:08 +08:00
options . calc_cr = False
if options . columns :
2011-12-16 12:42:34 +08:00
options . columns = [ s . strip ( ) . replace ( " \\ n " , " \n " ) for s in options . columns . split ( " , " ) ]
2012-10-17 07:18:30 +08:00
2014-03-28 19:11:10 +08:00
if options . regressions :
assert not options . progress_mode , ' unsupported mode '
def parseRegressionColumn ( s ) :
""" Format: ' [r][c]<uint>-<uint> ' """
reverse = s . startswith ( ' r ' )
if reverse :
s = s [ 1 : ]
addColor = s . startswith ( ' c ' )
if addColor :
s = s [ 1 : ]
parts = s . split ( ' - ' , 1 )
link = ( int ( parts [ 0 ] ) , int ( parts [ 1 ] ) , reverse , addColor )
assert link [ 0 ] != link [ 1 ]
return link
options . regressions = [ parseRegressionColumn ( s ) for s in options . regressions . split ( ' , ' ) ]
2018-01-27 22:30:37 +08:00
show_units = options . units if options . show_units else None
2011-09-06 23:30:28 +08:00
# expand wildcards and filter duplicates
2012-01-06 15:41:04 +08:00
files = [ ]
seen = set ( )
2011-09-06 23:30:28 +08:00
for arg in args :
if ( " * " in arg ) or ( " ? " in arg ) :
2012-01-06 15:41:04 +08:00
flist = [ os . path . abspath ( f ) for f in glob . glob ( arg ) ]
flist = sorted ( flist , key = lambda text : str ( text ) . replace ( " M " , " _ " ) )
files . extend ( [ x for x in flist if x not in seen and not seen . add ( x ) ] )
2011-09-06 23:30:28 +08:00
else :
2012-01-06 15:41:04 +08:00
fname = os . path . abspath ( arg )
2012-01-06 16:44:28 +08:00
if fname not in seen and not seen . add ( fname ) :
2012-01-06 15:41:04 +08:00
files . append ( fname )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
# read all passed files
test_sets = [ ]
for arg in files :
try :
tests = testlog_parser . parseLogFile ( arg )
if options . filter :
expr = re . compile ( options . filter )
2011-09-09 21:21:26 +08:00
tests = [ t for t in tests if expr . search ( str ( t ) ) ]
if options . match :
tests = [ t for t in tests if t . get ( " status " ) != " notrun " ]
2011-09-06 23:30:28 +08:00
if tests :
test_sets . append ( ( os . path . basename ( arg ) , tests ) )
except IOError as err :
sys . stderr . write ( " IOError reading \" " + arg + " \" - " + str ( err ) + os . linesep )
except xml . parsers . expat . ExpatError as err :
sys . stderr . write ( " ExpatError reading \" " + arg + " \" - " + str ( err ) + os . linesep )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
if not test_sets :
sys . stderr . write ( " Error: no test data found " + os . linesep )
quit ( )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
setsCount = len ( test_sets )
2014-03-28 19:11:10 +08:00
if options . regressions is None :
reference = - 1 if options . progress_mode else 0
options . regressions = [ ( i , reference , False , True ) for i in range ( 1 , len ( test_sets ) ) ]
for link in options . regressions :
( i , ref , reverse , addColor ) = link
assert i > = 0 and i < setsCount
assert ref < setsCount
# find matches
2011-09-06 23:30:28 +08:00
test_cases = { }
2012-10-17 07:18:30 +08:00
2011-09-09 21:21:26 +08:00
name_extractor = lambda name : str ( name )
if options . match :
reg = re . compile ( options . match )
name_extractor = lambda name : reg . sub ( options . match_replace , str ( name ) )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
for i in range ( setsCount ) :
for case in test_sets [ i ] [ 1 ] :
2011-09-09 21:21:26 +08:00
name = name_extractor ( case )
2011-10-18 20:31:08 +08:00
if options . module :
name = options . module + " :: " + name
2011-09-06 23:30:28 +08:00
if name not in test_cases :
test_cases [ name ] = [ None ] * setsCount
test_cases [ name ] [ i ] = case
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
# build table
getter = metrix_table [ options . metric ] [ 1 ]
2013-11-20 23:57:20 +08:00
getter_score = metrix_table [ " score " ] [ 1 ] if options . calc_score else None
getter_p = metrix_table [ options . metric + " % " ] [ 1 ] if options . calc_relatives else None
getter_cr = metrix_table [ options . metric + " $ " ] [ 1 ] if options . calc_cr else None
2018-01-27 22:30:37 +08:00
tbl = table ( ' %s ( %s ) ' % ( metrix_table [ options . metric ] [ 0 ] , options . units ) , options . format )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
# header
2011-10-12 22:41:36 +08:00
tbl . newColumn ( " name " , " Name of Test " , align = " left " , cssclass = " col_name " )
2014-03-28 19:11:10 +08:00
for i in range ( setsCount ) :
tbl . newColumn ( str ( i ) , getSetName ( test_sets [ i ] , i , options . columns , False ) , align = " center " )
def addHeaderColumns ( suffix , description , cssclass ) :
for link in options . regressions :
( i , ref , reverse , addColor ) = link
if reverse :
i , ref = ref , i
current_set = test_sets [ i ]
current = getSetName ( current_set , i , options . columns )
if ref > = 0 :
reference_set = test_sets [ ref ]
reference = getSetName ( reference_set , ref , options . columns )
else :
reference = ' previous '
tbl . newColumn ( str ( i ) + ' - ' + str ( ref ) + suffix , ' %s \n vs \n %s \n ( %s ) ' % ( current , reference , description ) , align = ' center ' , cssclass = cssclass )
2011-10-18 20:31:08 +08:00
if options . calc_cr :
2014-03-28 19:11:10 +08:00
addHeaderColumns ( suffix = ' $ ' , description = ' cycles reduction ' , cssclass = ' col_cr ' )
2011-09-06 23:30:28 +08:00
if options . calc_relatives :
2014-03-28 19:11:10 +08:00
addHeaderColumns ( suffix = ' % ' , description = ' x-factor ' , cssclass = ' col_rel ' )
2013-02-26 23:37:37 +08:00
if options . calc_score :
2014-03-28 19:11:10 +08:00
addHeaderColumns ( suffix = ' S ' , description = ' score ' , cssclass = ' col_name ' )
2012-10-17 07:18:30 +08:00
2011-09-06 23:30:28 +08:00
# rows
2012-01-09 15:45:19 +08:00
prevGroupName = None
2011-09-06 23:30:28 +08:00
needNewRow = True
2012-01-09 15:45:19 +08:00
lastRow = None
2011-11-22 21:57:44 +08:00
for name in sorted ( test_cases . iterkeys ( ) , key = alphanum_keyselector ) :
2011-09-06 23:30:28 +08:00
cases = test_cases [ name ]
if needNewRow :
2012-01-09 15:45:19 +08:00
lastRow = tbl . newRow ( )
2011-09-06 23:30:28 +08:00
if not options . showall :
needNewRow = False
tbl . newCell ( " name " , name )
2012-10-17 07:18:30 +08:00
2012-01-09 15:45:19 +08:00
groupName = next ( c for c in cases if c ) . shortName ( )
if groupName != prevGroupName :
prop = lastRow . props . get ( " cssclass " , " " )
if " firstingroup " not in prop :
lastRow . props [ " cssclass " ] = prop + " firstingroup "
prevGroupName = groupName
2011-09-06 23:30:28 +08:00
for i in range ( setsCount ) :
case = cases [ i ]
if case is None :
2014-03-02 20:08:37 +08:00
if options . intersect_logs :
needNewRow = False
break
2011-09-06 23:30:28 +08:00
tbl . newCell ( str ( i ) , " - " )
else :
status = case . get ( " status " )
if status != " run " :
2014-03-28 19:11:10 +08:00
tbl . newCell ( str ( i ) , status , color = " red " )
2011-09-06 23:30:28 +08:00
else :
val = getter ( case , cases [ 0 ] , options . units )
if val :
needNewRow = True
2018-01-27 22:30:37 +08:00
tbl . newCell ( str ( i ) , formatValue ( val , options . metric , show_units ) , val )
2014-03-28 19:11:10 +08:00
if needNewRow :
for link in options . regressions :
( i , reference , reverse , addColor ) = link
if reverse :
i , reference = reference , i
tblCellID = str ( i ) + ' - ' + str ( reference )
case = cases [ i ]
if case is None :
if options . calc_relatives :
tbl . newCell ( tblCellID + " % " , " - " )
if options . calc_cr :
tbl . newCell ( tblCellID + " $ " , " - " )
if options . calc_score :
tbl . newCell ( tblCellID + " $ " , " - " )
else :
status = case . get ( " status " )
if status != " run " :
tbl . newCell ( str ( i ) , status , color = " red " )
if status != " notrun " :
needNewRow = True
if options . calc_relatives :
tbl . newCell ( tblCellID + " % " , " - " , color = " red " )
if options . calc_cr :
tbl . newCell ( tblCellID + " $ " , " - " , color = " red " )
if options . calc_score :
tbl . newCell ( tblCellID + " S " , " - " , color = " red " )
else :
val = getter ( case , cases [ 0 ] , options . units )
def getRegression ( fn ) :
if fn and val :
for j in reversed ( range ( i ) ) if reference < 0 else [ reference ] :
r = cases [ j ]
if r is not None and r . get ( " status " ) == ' run ' :
return fn ( case , r , options . units )
valp = getRegression ( getter_p ) if options . calc_relatives or options . progress_mode else None
valcr = getRegression ( getter_cr ) if options . calc_cr else None
val_score = getRegression ( getter_score ) if options . calc_score else None
if not valp :
color = None
elif valp > 1.05 :
color = ' green '
elif valp < 0.95 :
color = ' red '
else :
color = None
if addColor :
if not reverse :
2018-01-27 22:30:37 +08:00
tbl . newCell ( str ( i ) , formatValue ( val , options . metric , show_units ) , val , color = color )
2014-03-28 19:11:10 +08:00
else :
r = cases [ reference ]
if r is not None and r . get ( " status " ) == ' run ' :
val = getter ( r , cases [ 0 ] , options . units )
2018-01-27 22:30:37 +08:00
tbl . newCell ( str ( reference ) , formatValue ( val , options . metric , show_units ) , val , color = color )
2014-03-28 19:11:10 +08:00
if options . calc_relatives :
tbl . newCell ( tblCellID + " % " , formatValue ( valp , " % " ) , valp , color = color , bold = color )
if options . calc_cr :
tbl . newCell ( tblCellID + " $ " , formatValue ( valcr , " $ " ) , valcr , color = color , bold = color )
if options . calc_score :
tbl . newCell ( tblCellID + " S " , formatValue ( val_score , " S " ) , val_score , color = color , bold = color )
2011-09-06 23:30:28 +08:00
if not needNewRow :
tbl . trimLastRow ( )
2013-02-25 23:03:26 +08:00
if options . regressionsOnly :
for r in reversed ( range ( len ( tbl . rows ) ) ) :
2014-03-28 19:11:10 +08:00
for i in range ( 1 , len ( options . regressions ) + 1 ) :
val = tbl . rows [ r ] . cells [ len ( tbl . rows [ r ] . cells ) - i ] . value
2013-02-25 23:03:26 +08:00
if val is not None and val < float ( options . regressionsOnly ) :
2014-03-28 19:11:10 +08:00
break
else :
2013-02-25 23:03:26 +08:00
tbl . rows . pop ( r )
2011-09-06 23:30:28 +08:00
# output table
if options . generateHtml :
2011-09-09 20:15:09 +08:00
if options . format == " moinwiki " :
tbl . htmlPrintTable ( sys . stdout , True )
else :
htmlPrintHeader ( sys . stdout , " Summary report for %s tests from %s test logs " % ( len ( test_cases ) , setsCount ) )
tbl . htmlPrintTable ( sys . stdout )
htmlPrintFooter ( sys . stdout )
2011-09-06 23:30:28 +08:00
else :
tbl . consolePrintTable ( sys . stdout )
2013-02-25 23:03:26 +08:00
if options . regressionsOnly :
sys . exit ( len ( tbl . rows ) )