4 cmsRelvalreport.py: a script to run performance tests and produce reports in a automated way. 6 from __future__
import print_function
12 PR3_BASE=
'/afs/cern.ch/user/d/dpiparo/w0/perfreport3installation/' 13 PR3=PR3_BASE+
'/bin/perfreport' 14 PERFREPORT3_PATH=PR3_BASE+
'/share/perfreport' 16 PR3_PRODUCER_PLUGIN=
'/afs/cern.ch/user/d/dpiparo/w0/pr3/perfreport/plugins/cmssw_by_producer/libcmssw_by_producer.so' 19 PR2_BASE=
'/afs/cern.ch/user/g/gbenelli/public/PerfReport2/2.0.1/' 20 PR2=
'%s' %(PR2_BASE+
'/bin/perfreport')
21 PERFREPORT2_PATH=PR2_BASE+
'/share/perfreport' 24 cmssw_base=os.environ[
"CMSSW_BASE"]
25 cmssw_release_base=os.environ[
"CMSSW_RELEASE_BASE"]
26 pyrelvallocal=cmssw_base+
"/src/Configuration/PyReleaseValidation" 27 valperf=cmssw_base+
"/src/Validation/Performance" 29 if os.path.exists(pyrelvallocal):
31 print(
"Using LOCAL version of Configuration/PyReleaseValidation instead of the RELEASE version")
32 elif not os.path.exists(pyrelvallocal):
33 RELEASE=
'CMSSW_RELEASE_BASE' 38 VMPARSER=
'valgrindMemcheckParser.pl' 42 VMPARSERSTYLE=
'%s/src/Utilities/ReleaseScripts/data/valgrindMemcheckParser.css' %os.environ[
'CMSSW_RELEASE_BASE']
46 IGPROFANALYS=
'cmsIgProf_Analysis.py' 50 TIMEREPORTPARSER=
'cmsTimeReport.pl' 54 SIMPLEMEMPARSER=
'cmsSimplememchecker_parser.py' 58 TIMINGPARSER=
'cmsTiming_parser.py' 61 MAKESKIMDRIVERDIR=
'%s/src/Configuration/EventContent/test' %os.environ[RELEASE]
62 MAKESKIMDRIVER=
'%s/makeSkimDriver.py'%MAKESKIMDRIVERDIR
68 VFCE_LIB=
'/afs/cern.ch/user/m/moserro/public/vgfcelib' 69 PERL5_LIB=
'/afs/cern.ch/user/d/dpiparo/w0/PERLlibs/5.8.0' 74 STDOUTPROFILERS=[
'Memcheck_Valgrind',
79 PROFILERS=[
'ValgrindFCE',
82 'Edm_Size']+STDOUTPROFILERS
93 IgProfCounters={
'IgProfPerf':[
'PERF_TICKS'],
94 'IgProfMem':[
'MEM_TOTAL',
'MEM_LIVE',
'MEM_MAX']
96 IgProfProfiles={
'PERF_TICKS':
'IgProfPerf',
97 'MEM_TOTAL':
'IgProfMem',
98 'MEM_LIVE':
'IgProfMem',
108 return '%s%s%s' %(
'\033[1;31m',string,
'\033[1;0m')
110 return '%s%s%s' %(
'\033[1;32m',string,
'\033[1;0m')
112 return '%s%s%s' %(
'\033[1;33m',string,
'\033[1;0m')
117 Trivially removes an underscore if present as last char of a string 132 It executes command if the EXEC switch is True. 133 Catches exitcodes different from 0. 137 exit_code=os.system(command)
139 logger(
red(
'*** Seems like "%s" encountered problems.' %command))
148 level=0 output, level 1 debug. 150 message=
'%s %s' %(
yellow(
'[RelValreport]'),message)
156 if level==1
and DEBUG:
165 Class to read the trivial ASCII file containing the candles 171 candlesfile=open(filename,
'r') 173 if filename[-3:]==
'xml':
180 from xml.dom
import minidom
183 xmldoc = minidom.parse(filename)
186 candles_list = xmldoc.getElementsByTagName(
'candle')
191 for candle
in candles_list:
193 for child
in candle.childNodes:
194 if 'nodeName' not in child.__dict__:
198 tag_name=child.tagName
200 data=child.firstChild.data
203 info_dict[tag_name]=data
205 candles_dict_list.append(info_dict)
209 for candle_dict
in candles_dict_list:
211 command=candle_dict[
'command']
212 profiler=candle_dict[
'profiler']
213 meta=candle_dict[
'meta']
216 db_meta=candle_dict[
'db_meta']
220 reuse=candle_dict[
'reuse']
224 self.commands_profilers_meta_list.append([command,profiler,meta,reuse,db_meta])
228 for candle
in candlesfile.readlines():
230 if candle[0]!=
'#' and candle.strip(
' \n\t')!=
'':
233 splitted_candle=candle.split(
'@@@')
236 command=splitted_candle[0]
237 profiler=splitted_candle[1].
strip(
' \t')
238 meta=splitted_candle[2].
strip(
' \t')
239 info=[command,profiler,meta]
243 len_splitted_candle=len(splitted_candle)
245 if len_splitted_candle>3:
247 if 'reuse' in splitted_candle[3]:
254 if len_splitted_candle>4
or (len_splitted_candle>3
and not reuse):
255 cmssw_scram_version_string=splitted_candle[-1].
strip(
' \t')
256 info.append(cmssw_scram_version_string)
261 self.commands_profilers_meta_list.append(info)
272 Class that represents the procedure of performance report creation 283 Launch the right function according to the profiler name. 287 elif self.profiler.find(
'IgProf')!=-1:
289 elif self.profiler.find(
'Edm_Size')!=-1:
291 elif self.
profiler==
'Memcheck_Valgrind':
293 elif self.
profiler==
'Timereport_Parser':
295 elif self.
profiler==
'Timing_Parser':
297 elif self.
profiler==
'SimpleMem_Parser':
304 raise 'No %s profiler found!' 308 Valgrind profile launcher. 311 os.environ[
"VALGRIND_LIB"]=VFCE_LIB
314 valgrind_options=
'time valgrind '+\
315 '--tool=callgrind '+\
319 if EXECUTABLE==
'cmsRun' and self.command.find(
'cmsDriver.py')!=-1:
320 profiler_line=
'%s --prefix "%s"' %(self.
command,valgrind_options)
323 profiler_line=
'%s %s' %(valgrind_options,self.
command)
331 IgProf profiler launcher. 335 igprof_options=
'igprof -d -t %s ' \
340 igprof_options+=
'-pp ' 342 igprof_options+=
'-mp ' 344 raise 'Unknown IgProf flavour: %s !' 348 if EXECUTABLE==
'cmsRun' and self.command.find(
'cmsDriver.py')!=-1:
349 profiler_line=
'%s --prefix "%s"' %(self.
command,igprof_options)
351 profiler_line=
'%s %s' %(igprof_options, self.
command)
359 Launch edm size profiler 367 clean_profiler_name,options=self.profiler.split(
'.')
368 content,nevts=options.split(
',')
369 outfilename=
'%s_%s.root'%(os.path.basename(self.
command)[:-6],content)
370 oldpypath=os.environ[
'PYTHONPATH']
371 os.environ[
'PYTHONPATH']+=
':%s' %MAKESKIMDRIVERDIR
372 execute(
'%s -i %s -o %s --outputcommands %s -n %s' %(MAKESKIMDRIVER,
377 os.environ[
'PYTHONPATH']=oldpypath
383 profiler_line=
'edmEventSize -o %s -d %s'\
392 Valgrind Memcheck profile launcher 401 xmlFileName = self.profile_name.replace(
",",
"-")[:-4] +
".xml" 402 valgrind_options=
'time valgrind --track-origins=yes '+\
403 '--tool=memcheck `cmsvgsupp` '+\
404 '--num-callers=20 '+\
406 '--xml-file=%s '%xmlFileName
409 if EXECUTABLE==
'cmsRun' and self.command.find(
'cmsDriver.py')!=-1:
417 exec_status =
execute(profiler_line)
421 newFileName = xmlFileName.replace(
'valgrind.xml',
'vlgd.xml')
422 compactCmd =
'xsltproc --output %s %s/test/filterOutValgrindLeakErrors.xsl %s' %(newFileName, valperf, xmlFileName)
445 Save the output of cmsRun on a file! 459 Just Run the command! 473 Make a performance report with CMSSW scripts for CMSSW internal profiling (Timing/SimpleMemoryCheck) and Memcheck, PR2 for edmEventSize and Callgrind (NOTE PR2 is not supported anymore and is not currently in the CMSSW external, running froma privat AFS!), igprof-analyse for all IgProf profiling. 480 if not os.path.exists(outdir)
and not fill_db
and not IgProf_option:
486 if not os.path.exists(db_name):
493 tmp_switch=
' -t %s' %tmp_dir
501 perfreport_command=
'' 504 os.environ[
"PERFREPORT_PATH"]=
'%s/' %PERFREPORT2_PATH
505 perfreport_command=
'%s %s -ff -i %s -o %s' %(PR2,
510 os.environ[
"PERFREPORT_PATH"]=
'%s/' %PERFREPORT3_PATH
511 perfreport_command=
'%s %s -n5000 -u%s -ff -m \'scram_cmssw_version_string,%s\' -i %s %s -o %s' \
519 return execute(perfreport_command)
524 if self.profiler.find(
'IgProf')!=-1:
526 if not 'ANALYSE' in IgProf_option:
530 NumberOfEvents=self.command.split()[3]
531 sqlite_outputfile=self.profile_name.split(
".")[0].
replace(IgProfProfiles[IgProf_option[0]],IgProf_option[0])+
'___'+NumberOfEvents+
'_EndOfJob.sql3' 532 logger(
"Executing the report of the IgProf end of job profile")
533 exit=
execute(
'igprof-analyse --sqlite -d -v -g -r %s %s | sqlite3 %s'%(IgProf_option[0],self.
profile_name,sqlite_outputfile))
544 IgProfCounter=IgProf_option[1]
546 logger(
"Looking for IgProf intermediate event profile dumps")
548 IgProfDumps=glob.glob(
"IgProf.*.gz")
552 localFiles=os.listdir(
'.')
553 IgProfDumpProfilesPrevious=re.compile(
r"\w+.\d+.gz")
554 IgProfDumps=[x
for x
in localFiles
if IgProfDumpProfilesPrevious.search(x)]
558 logger(
"Found the following IgProf intermediate event profile dumps:")
560 FirstDumpEvent=9999999
563 for dump
in IgProfDumps:
565 DumpEvent=dump.split(
".")[0].
split(
"___")[-1]
567 DumpEvent=dump.split(
".")[1]
569 DumpedProfileName=self.
profile_name[:-3]+
"___"+DumpEvent+
".gz" 570 if dump.startswith(
"IgProf"):
571 execute(
'mv %s %s'%(dump,DumpedProfileName))
573 if int(DumpEvent) < FirstDumpEvent:
574 FirstDumpEvent =
int(DumpEvent)
575 if int(DumpEvent) > LastDumpEvent:
576 LastDumpEvent =
int(DumpEvent)
582 logger(
"Executing the igprof-analyse analysis saving into igprof-navigator browseable SQLite3 format")
591 exit=exit+
execute(
'%s -c%s -i%s -t%s' %(IGPROFANALYS,IgProfCounter,DumpedProfileName,
"SQLite3"))
598 FirstDumpProfile=self.
profile_name[:-3]+
"___"+
str(FirstDumpEvent)+
".gz" 602 if len(IgProfDumps)>1:
603 logger(
"Executing the igprof-analyse regression between the first IgProf profile dump and the last one")
607 exit=exit+
execute(
'%s -c%s -i%s -r%s' %(IGPROFANALYS,IgProfCounter,LastDumpProfile,FirstDumpProfile))
609 logger(
"CANNOT execute any regressions: not enough IgProf intermediate event profile dumps!")
611 logger(
"Executing the igprof-analyse analysis merging the results by library via regexp and saving the result in igprof-navigator browseable SQLite3 format")
615 exit=exit+
execute(
'%s -c%s -i%s --library' %(IGPROFANALYS,IgProfCounter,LastDumpProfile))
618 logger(
"No IgProf intermediate event profile dumps found!")
628 perfreport_command=
'' 630 os.environ[
"PERFREPORT_PATH"]=
'%s/' \
632 perfreport_command=
'%s %s -fe -i %s -o %s' \
638 os.environ[
"PERFREPORT_PATH"]=
'%s/' \
640 perfreport_command=
'%s %s -n5000 -u%s -fe -i %s -a -o %s' \
647 return execute(perfreport_command)
656 if self.
profiler==
'Memcheck_Valgrind':
658 os.environ[
'PERL5LIB']=PERL5_LIB
661 copyStyleFile=
'cp -pR %s %s'%(VMPARSERSTYLE,outdir)
663 report_commands=(
'%s --preset +prod,-prod1 %s > %s/edproduce.html'\
665 '%s --preset +prod1 %s > %s/esproduce.html'\
667 '%s -t beginJob %s > %s/beginjob.html'\
670 for command
in report_commands:
677 if self.
profiler==
'Timereport_Parser':
692 if self.
profiler==
'SimpleMem_Parser':
706 Here the objects of the Profile class are istantiated. 712 commands_profilers_meta_list=[]
715 if options.infile==
'':
716 logger(
'Single command found...')
717 commands_profilers_meta_list.append([options.command,
'',
'',
False,
''])
721 logger(
'List of commands found. Processing %s ...' %options.infile)
726 commands_profilers_meta_list=candles_file.get_commands_profilers_meta_list()
729 logger(
'Iterating through commands of executables to profile ...')
732 len_commands_profilers_meta_list=len(commands_profilers_meta_list)
735 precedent_profile_name=
'' 736 precedent_reuseprofile=
False 737 for command,profiler_opt,meta,reuseprofile,db_metastring
in commands_profilers_meta_list:
741 logger(
'Processing command %d/%d' \
742 %(commands_counter,len_commands_profilers_meta_list))
743 logger(
'Process started on %s' %time.asctime())
749 reportdir=options.output
750 IgProf_counter=options.IgProf_counter
753 if options.infile!=
'':
755 reportdir=
'%s_%s' %(meta,options.output)
758 profile_name=
clean_name(
'%s_%s'%(meta,options.profile_name))
762 if profiler_opt.find(
'.')!=-1
and profiler_opt.find(
'IgProf')!=-1:
763 profiler_opt_split=profiler_opt.split(
'.')
764 profiler=profiler_opt_split[0]
765 IgProf_counter=profiler_opt_split[1:]
766 if profile_name[-3:]!=
'.gz':
770 elif profiler_opt
in STDOUTPROFILERS:
772 if profile_name[:-4]!=
'.log':
774 profiler=profiler_opt
778 profiler=profiler_opt
780 if precedent_reuseprofile:
781 profile_name=precedent_profile_name
783 precedent_profile_name=profile_name
788 profile_name=options.profile_name
789 reportdir=options.output
790 profiler=options.profiler
795 if precedent_profile_name!=
'':
796 if os.path.exists(precedent_profile_name):
797 logger(
'Reusing precedent profile: %s ...' %precedent_profile_name)
798 if profile_name!=precedent_profile_name:
799 logger(
'Copying the old profile to the new name %s ...' %profile_name)
800 execute(
'cp %s %s' %(precedent_profile_name, profile_name))
802 performance_profile=
Profile(command,
809 logger(
'Saving profile name to reuse it ...')
810 precedent_profile_name=profile_name
812 precedent_profile_name=
'' 814 if not precedent_reuseprofile:
815 logger(
'Creating profile for command %d using %s ...' \
816 %(commands_counter,profiler))
817 exit_code=performance_profile.make_profile()
819 logger(
'The exit code was %s'%exit_code)
820 exitCodeSum=exitCodeSum+exit_code
821 logger(
'The exit code sum is %s'%exitCodeSum)
827 logger(
'Halting report creation procedure: unexpected exit code %s from %s ...' \
828 %(exit_code,profiler))
830 logger(
'Creating report for command %d using %s ...' \
831 %(commands_counter,profiler))
835 exit_code=performance_profile.make_report(fill_db=
True,
836 db_name=options.output,
837 metastring=db_metastring,
838 tmp_dir=options.pr_temp,
839 IgProf_option=IgProf_counter)
840 exitCodeSum=exitCodeSum+exit_code
842 exit_code=performance_profile.make_report(outdir=reportdir,
843 tmp_dir=options.pr_temp,
844 IgProf_option=IgProf_counter)
845 exitCodeSum=exitCodeSum+exit_code
848 precedent_reuseprofile=reuseprofile
849 if not precedent_reuseprofile:
850 precedent_profile_name=
'' 852 logger(
'Process ended on %s\n' %time.asctime())
854 logger(
'Procedure finished on %s' %time.asctime())
855 logger(
"Exit code sum is %s"%exitCodeSum)
860 if __name__==
"__main__":
863 '----------------------------------------------------------------------------\n'+\
864 ' RelValreport: a tool for automation of benchmarking and report generation. \n'+\
865 '----------------------------------------------------------------------------\n\n'+\
866 'relvalreport.py <options>\n'+\
867 'relvalreport.py -i candles_150.txt -R -P -n 150.out -o 150_report\n'+\
868 ' - Executes the candles contained in the file candles_150.txt, create\n'+\
869 ' profiles, specified by -n, and reports, specified by -o.\n\n'+\
870 'Candles file grammar:\n'+\
871 'A candle is specified by the syntax:\n'+\
872 'executable_name @@@ profiler_name @@@ meta\n'+\
873 ' - executable_name: the name of the executable to benchmark.\n'+\
874 ' - profiler_name: the name of the profiler to use. The available are: %s.\n' %
str(PROFILERS)+\
875 ' In case you want to use IgProf_mem or IgProf_perf, the counter (MEM_TOTAL,PERF_TICKS...)\n'+\
876 ' must be added with a ".": IgProf_mem.MEM_TOTAL.\n'+\
877 ' - meta: metastring that is used to change the name of the names specified in the command line\n'+\
878 ' in case of batch execution.'+\
879 'An example of candle file:\n\n'+\
880 ' ># My list of candles:\n'+\
882 ' >cmsDriver.py MU- -sSIM -e10_20 @@@ IgProf_perf.PERF_TICKS @@@ QCD_sim_IgProfperf\n'+\
883 ' >cmsDriver.py MU- -sRECO -e10_20 @@@ ValgrindFCE @@@ QCD_reco_Valgrind\n'+\
884 ' >cmsRun mycfg.cfg @@@ IgProf_mem.MEM_TOTAL @@@ Mycfg\n' 888 parser = optparse.OptionParser(usage)
890 parser.add_option(
'-p',
'--profiler',
891 help=
'Profilers are: %s' %
str(PROFILERS) ,
895 parser.add_option(
'-c',
'--command',
896 help=
'Command to profile. If specified the infile is ignored.' ,
900 parser.add_option(
'-t',
901 help=
'The temp directory to store the PR service files. Default is PR_TEMP Ignored if PR is not used.',
907 parser.add_option(
'--db',
908 help=
'EXPERIMENTAL: Write results on the db.',
913 parser.add_option(
'-R',
'--Report',
914 help=
'Create a static html report. If db switch is on this is ignored.',
919 parser.add_option(
'-P',
'--Profile',
920 help=
'Create a profile for the selected profiler.',
927 parser.add_option(
'-n',
'--profile_name',
928 help=
'Profile name' ,
932 parser.add_option(
'-o',
'--output',
933 help=
'Outdir for the html report or db filename.' ,
939 parser.add_option(
'-i',
'--infile',
940 help=
'Name of the ASCII file containing the commands to profile.' ,
946 parser.add_option(
'-y',
947 help=
'Specify the IgProf counter or the CMSSW. '+\
948 'If a profiler different from '+\
949 'IgProf is selected this is ignored.' ,
951 dest=
'IgProf_counter')
953 parser.add_option(
'--executable',
954 help=
'Specify executable to monitor if different from cmsRun. '+\
955 'Only valid for IgProf.',
960 parser.add_option(
'--noexec',
961 help=
'Do not exec commands, just display them!',
966 (options,args) = parser.parse_args()
969 if options.infile==
'' and options.command==
'' and not (options.report
and not options.profile):
970 raise(
'Specify at least one command to profile!')
971 if options.profile_name==
'' and options.infile==
'':
972 raise(
'Specify a profile name!')
973 if not options.db
and options.output==
'' and options.infile==
'':
974 raise(
'Specify a db name or an output dir for the static report!')
976 if not options.profile:
977 if not os.path.exists(options.profile_name)
and options.infile==
'':
978 raise 'Profile %s does not exist!' 979 logger(
"WARNING: No profile will be generated. An existing one will be processed!")
981 if options.command!=
'' and options.infile!=
'':
982 raise(
'-c and -i options cannot coexist!')
984 if options.profiler==
'Memcheck_Valgrind' and not os.path.exists(VMPARSER):
985 raise(
'Couldn\'t find Valgrind Memcheck Parser Script! Please install it from Utilities/ReleaseScripts.')
987 if options.executable!=
'':
988 globals()[
'EXECUTABLE']=options.executable
991 globals()[
'EXEC']=
False 993 logger(
'Procedure started on %s' %time.asctime())
995 if options.infile ==
'':
997 for key,val
in options.__dict__.items():
999 logger (
'\t\t|- %s = %s' %(key,
str(val)))
1002 logger(
"Exit code received from principal is: %s"%exit)
commands_profilers_meta_list
def _profile_Timereport_Parser(self)
def __init__(self, filename)
def _profile_edmsize(self)
def logger(message, level=0)
def make_report(self, fill_db=False, db_name=None, tmp_dir=None, outdir=None, IgProf_option=None, metastring=None)
def replace(string, replacements)
S & print(S &os, JobReport::InputFile const &f)
def __init__(self, command, profiler, profile_name)
def _profile_igprof(self)
def _profile_Memcheck_Valgrind(self)
def _profile_valgrindfce(self)
def _profile_Timing_Parser(self)
def _profile_SimpleMem_Parser(self)
def get_commands_profilers_meta_list(self)