4 cmsRelvalreport.py: a script to run performance tests and produce reports in a automated way.
11 PR3_BASE=
'/afs/cern.ch/user/d/dpiparo/w0/perfreport3installation/'
12 PR3=PR3_BASE+
'/bin/perfreport'
13 PERFREPORT3_PATH=PR3_BASE+
'/share/perfreport'
15 PR3_PRODUCER_PLUGIN=
'/afs/cern.ch/user/d/dpiparo/w0/pr3/perfreport/plugins/cmssw_by_producer/libcmssw_by_producer.so'
18 PR2_BASE=
'/afs/cern.ch/user/g/gbenelli/public/PerfReport2/2.0.1/'
19 PR2=
'%s' %(PR2_BASE+
'/bin/perfreport')
20 PERFREPORT2_PATH=PR2_BASE+
'/share/perfreport'
23 cmssw_base=os.environ[
"CMSSW_BASE"]
24 cmssw_release_base=os.environ[
"CMSSW_RELEASE_BASE"]
25 pyrelvallocal=cmssw_base+
"/src/Configuration/PyReleaseValidation"
26 valperf=cmssw_base+
"/src/Validation/Performance"
28 if os.path.exists(pyrelvallocal):
30 print "Using LOCAL version of Configuration/PyReleaseValidation instead of the RELEASE version"
31 elif not os.path.exists(pyrelvallocal):
32 RELEASE=
'CMSSW_RELEASE_BASE'
37 VMPARSER=
'valgrindMemcheckParser.pl'
41 VMPARSERSTYLE=
'%s/src/Utilities/ReleaseScripts/data/valgrindMemcheckParser.css' %os.environ[
'CMSSW_RELEASE_BASE']
45 IGPROFANALYS=
'cmsIgProf_Analysis.py'
49 TIMEREPORTPARSER=
'cmsTimeReport.pl'
53 SIMPLEMEMPARSER=
'cmsSimplememchecker_parser.py'
57 TIMINGPARSER=
'cmsTiming_parser.py'
60 MAKESKIMDRIVERDIR=
'%s/src/Configuration/EventContent/test' %os.environ[RELEASE]
61 MAKESKIMDRIVER=
'%s/makeSkimDriver.py'%MAKESKIMDRIVERDIR
67 VFCE_LIB=
'/afs/cern.ch/user/m/moserro/public/vgfcelib'
68 PERL5_LIB=
'/afs/cern.ch/user/d/dpiparo/w0/PERLlibs/5.8.0'
73 STDOUTPROFILERS=[
'Memcheck_Valgrind',
78 PROFILERS=[
'ValgrindFCE',
81 'Edm_Size']+STDOUTPROFILERS
92 IgProfCounters={
'IgProfPerf':[
'PERF_TICKS'],
93 'IgProfMem':[
'MEM_TOTAL',
'MEM_LIVE',
'MEM_MAX']
95 IgProfProfiles={
'PERF_TICKS':
'IgProfPerf',
96 'MEM_TOTAL':
'IgProfMem',
97 'MEM_LIVE':
'IgProfMem',
107 return '%s%s%s' %(
'\033[1;31m',string,
'\033[1;0m')
109 return '%s%s%s' %(
'\033[1;32m',string,
'\033[1;0m')
111 return '%s%s%s' %(
'\033[1;33m',string,
'\033[1;0m')
116 Trivially removes an underscore if present as last char of a string
131 It executes command if the EXEC switch is True.
132 Catches exitcodes different from 0.
136 exit_code=os.system(command)
138 logger(
red(
'*** Seems like "%s" encountered problems.' %command))
147 level=0 output, level 1 debug.
149 message=
'%s %s' %(
yellow(
'[RelValreport]'),message)
155 if level==1
and DEBUG:
164 Class to read the trivial ASCII file containing the candles
170 candlesfile=open(filename,
'r')
172 if filename[-3:]==
'xml':
179 from xml.dom
import minidom
182 xmldoc = minidom.parse(filename)
185 candles_list = xmldoc.getElementsByTagName(
'candle')
190 for candle
in candles_list:
192 for child
in candle.childNodes:
193 if 'nodeName' not in child.__dict__:
197 tag_name=child.tagName
199 data=child.firstChild.data
202 info_dict[tag_name]=data
204 candles_dict_list.append(info_dict)
208 for candle_dict
in candles_dict_list:
210 command=candle_dict[
'command']
211 profiler=candle_dict[
'profiler']
212 meta=candle_dict[
'meta']
215 db_meta=candle_dict[
'db_meta']
219 reuse=candle_dict[
'reuse']
223 self.commands_profilers_meta_list.append([command,profiler,meta,reuse,db_meta])
227 for candle
in candlesfile.readlines():
229 if candle[0]!=
'#' and candle.strip(
' \n\t')!=
'':
232 splitted_candle=candle.split(
'@@@')
235 command=splitted_candle[0]
236 profiler=splitted_candle[1].strip(
' \t')
237 meta=splitted_candle[2].strip(
' \t')
238 info=[command,profiler,meta]
242 len_splitted_candle=len(splitted_candle)
244 if len_splitted_candle>3:
246 if 'reuse' in splitted_candle[3]:
253 if len_splitted_candle>4
or (len_splitted_candle>3
and not reuse):
254 cmssw_scram_version_string=splitted_candle[-1].strip(
' \t')
255 info.append(cmssw_scram_version_string)
260 self.commands_profilers_meta_list.append(info)
271 Class that represents the procedure of performance report creation
282 Launch the right function according to the profiler name.
286 elif self.profiler.find(
'IgProf')!=-1:
288 elif self.profiler.find(
'Edm_Size')!=-1:
290 elif self.
profiler==
'Memcheck_Valgrind':
292 elif self.
profiler==
'Timereport_Parser':
294 elif self.
profiler==
'Timing_Parser':
296 elif self.
profiler==
'SimpleMem_Parser':
303 raise 'No %s profiler found!'
307 Valgrind profile launcher.
310 os.environ[
"VALGRIND_LIB"]=VFCE_LIB
313 valgrind_options=
'time valgrind '+\
314 '--tool=callgrind '+\
318 if EXECUTABLE==
'cmsRun' and self.command.find(
'cmsDriver.py')!=-1:
319 profiler_line=
'%s --prefix "%s"' %(self.
command,valgrind_options)
322 profiler_line=
'%s %s' %(valgrind_options,self.
command)
330 IgProf profiler launcher.
334 igprof_options=
'igprof -d -t %s ' \
339 igprof_options+=
'-pp '
341 igprof_options+=
'-mp '
343 raise 'Unknown IgProf flavour: %s !'
347 if EXECUTABLE==
'cmsRun' and self.command.find(
'cmsDriver.py')!=-1:
348 profiler_line=
'%s --prefix "%s"' %(self.
command,igprof_options)
350 profiler_line=
'%s %s' %(igprof_options, self.
command)
358 Launch edm size profiler
366 clean_profiler_name,options=self.profiler.split(
'.')
367 content,nevts=options.split(
',')
368 outfilename=
'%s_%s.root'%(os.path.basename(self.
command)[:-6],content)
369 oldpypath=os.environ[
'PYTHONPATH']
370 os.environ[
'PYTHONPATH']+=
':%s' %MAKESKIMDRIVERDIR
371 execute(
'%s -i %s -o %s --outputcommands %s -n %s' %(MAKESKIMDRIVER,
376 os.environ[
'PYTHONPATH']=oldpypath
382 profiler_line=
'edmEventSize -o %s -d %s'\
391 Valgrind Memcheck profile launcher
400 xmlFileName = self.profile_name.replace(
",",
"-")[:-4] +
".xml"
401 valgrind_options=
'time valgrind --track-origins=yes '+\
402 '--tool=memcheck `cmsvgsupp` '+\
403 '--num-callers=20 '+\
405 '--xml-file=%s '%xmlFileName
408 if EXECUTABLE==
'cmsRun' and self.command.find(
'cmsDriver.py')!=-1:
416 exec_status =
execute(profiler_line)
420 newFileName = xmlFileName.replace(
'valgrind.xml',
'vlgd.xml')
421 compactCmd =
'xsltproc --output %s %s/test/filterOutValgrindLeakErrors.xsl %s' %(newFileName, valperf, xmlFileName)
444 Save the output of cmsRun on a file!
458 Just Run the command!
472 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.
479 if not os.path.exists(outdir)
and not fill_db
and not IgProf_option:
485 if not os.path.exists(db_name):
492 tmp_switch=
' -t %s' %tmp_dir
500 perfreport_command=
''
503 os.environ[
"PERFREPORT_PATH"]=
'%s/' %PERFREPORT2_PATH
504 perfreport_command=
'%s %s -ff -i %s -o %s' %(PR2,
509 os.environ[
"PERFREPORT_PATH"]=
'%s/' %PERFREPORT3_PATH
510 perfreport_command=
'%s %s -n5000 -u%s -ff -m \'scram_cmssw_version_string,%s\' -i %s %s -o %s' \
518 return execute(perfreport_command)
523 if self.profiler.find(
'IgProf')!=-1:
525 if not 'ANALYSE' in IgProf_option:
529 NumberOfEvents=self.command.split()[3]
530 sqlite_outputfile=self.profile_name.split(
".")[0].
replace(IgProfProfiles[IgProf_option[0]],IgProf_option[0])+
'___'+NumberOfEvents+
'_EndOfJob.sql3'
531 logger(
"Executing the report of the IgProf end of job profile")
532 exit=
execute(
'igprof-analyse --sqlite -d -v -g -r %s %s | sqlite3 %s'%(IgProf_option[0],self.
profile_name,sqlite_outputfile))
543 IgProfCounter=IgProf_option[1]
545 logger(
"Looking for IgProf intermediate event profile dumps")
547 IgProfDumps=glob.glob(
"IgProf.*.gz")
551 localFiles=os.listdir(
'.')
552 IgProfDumpProfilesPrevious=re.compile(
r"\w+.\d+.gz")
553 IgProfDumps=
filter(
lambda x: IgProfDumpProfilesPrevious.search(x),localFiles)
557 logger(
"Found the following IgProf intermediate event profile dumps:")
559 FirstDumpEvent=9999999
562 for dump
in IgProfDumps:
564 DumpEvent=dump.split(
".")[0].
split(
"___")[-1]
566 DumpEvent=dump.split(
".")[1]
568 DumpedProfileName=self.
profile_name[:-3]+
"___"+DumpEvent+
".gz"
569 if dump.startswith(
"IgProf"):
570 execute(
'mv %s %s'%(dump,DumpedProfileName))
572 if int(DumpEvent) < FirstDumpEvent:
573 FirstDumpEvent = int(DumpEvent)
574 if int(DumpEvent) > LastDumpEvent:
575 LastDumpEvent = int(DumpEvent)
581 logger(
"Executing the igprof-analyse analysis saving into igprof-navigator browseable SQLite3 format")
590 exit=exit+
execute(
'%s -c%s -i%s -t%s' %(IGPROFANALYS,IgProfCounter,DumpedProfileName,
"SQLite3"))
597 FirstDumpProfile=self.
profile_name[:-3]+
"___"+str(FirstDumpEvent)+
".gz"
598 LastDumpProfile=self.
profile_name[:-3]+
"___"+str(LastDumpEvent)+
".gz"
601 if len(IgProfDumps)>1:
602 logger(
"Executing the igprof-analyse regression between the first IgProf profile dump and the last one")
606 exit=exit+
execute(
'%s -c%s -i%s -r%s' %(IGPROFANALYS,IgProfCounter,LastDumpProfile,FirstDumpProfile))
608 logger(
"CANNOT execute any regressions: not enough IgProf intermediate event profile dumps!")
610 logger(
"Executing the igprof-analyse analysis merging the results by library via regexp and saving the result in igprof-navigator browseable SQLite3 format")
614 exit=exit+
execute(
'%s -c%s -i%s --library' %(IGPROFANALYS,IgProfCounter,LastDumpProfile))
617 logger(
"No IgProf intermediate event profile dumps found!")
627 perfreport_command=
''
629 os.environ[
"PERFREPORT_PATH"]=
'%s/' \
631 perfreport_command=
'%s %s -fe -i %s -o %s' \
637 os.environ[
"PERFREPORT_PATH"]=
'%s/' \
639 perfreport_command=
'%s %s -n5000 -u%s -fe -i %s -a -o %s' \
646 return execute(perfreport_command)
655 if self.
profiler==
'Memcheck_Valgrind':
657 os.environ[
'PERL5LIB']=PERL5_LIB
660 copyStyleFile=
'cp -pR %s %s'%(VMPARSERSTYLE,outdir)
662 report_commands=(
'%s --preset +prod,-prod1 %s > %s/edproduce.html'\
664 '%s --preset +prod1 %s > %s/esproduce.html'\
666 '%s -t beginJob %s > %s/beginjob.html'\
669 for command
in report_commands:
676 if self.
profiler==
'Timereport_Parser':
691 if self.
profiler==
'SimpleMem_Parser':
705 Here the objects of the Profile class are istantiated.
711 commands_profilers_meta_list=[]
714 if options.infile==
'':
715 logger(
'Single command found...')
716 commands_profilers_meta_list.append([options.command,
'',
'',
False,
''])
720 logger(
'List of commands found. Processing %s ...' %options.infile)
725 commands_profilers_meta_list=candles_file.get_commands_profilers_meta_list()
728 logger(
'Iterating through commands of executables to profile ...')
731 len_commands_profilers_meta_list=len(commands_profilers_meta_list)
734 precedent_profile_name=
''
735 precedent_reuseprofile=
False
736 for command,profiler_opt,meta,reuseprofile,db_metastring
in commands_profilers_meta_list:
740 logger(
'Processing command %d/%d' \
741 %(commands_counter,len_commands_profilers_meta_list))
742 logger(
'Process started on %s' %time.asctime())
748 reportdir=options.output
749 IgProf_counter=options.IgProf_counter
752 if options.infile!=
'':
754 reportdir=
'%s_%s' %(meta,options.output)
757 profile_name=
clean_name(
'%s_%s'%(meta,options.profile_name))
761 if profiler_opt.find(
'.')!=-1
and profiler_opt.find(
'IgProf')!=-1:
762 profiler_opt_split=profiler_opt.split(
'.')
763 profiler=profiler_opt_split[0]
764 IgProf_counter=profiler_opt_split[1:]
765 if profile_name[-3:]!=
'.gz':
769 elif profiler_opt
in STDOUTPROFILERS:
771 if profile_name[:-4]!=
'.log':
773 profiler=profiler_opt
777 profiler=profiler_opt
779 if precedent_reuseprofile:
780 profile_name=precedent_profile_name
782 precedent_profile_name=profile_name
787 profile_name=options.profile_name
788 reportdir=options.output
789 profiler=options.profiler
794 if precedent_profile_name!=
'':
795 if os.path.exists(precedent_profile_name):
796 logger(
'Reusing precedent profile: %s ...' %precedent_profile_name)
797 if profile_name!=precedent_profile_name:
798 logger(
'Copying the old profile to the new name %s ...' %profile_name)
799 execute(
'cp %s %s' %(precedent_profile_name, profile_name))
801 performance_profile=
Profile(command,
808 logger(
'Saving profile name to reuse it ...')
809 precedent_profile_name=profile_name
811 precedent_profile_name=
''
813 if not precedent_reuseprofile:
814 logger(
'Creating profile for command %d using %s ...' \
815 %(commands_counter,profiler))
816 exit_code=performance_profile.make_profile()
818 logger(
'The exit code was %s'%exit_code)
819 exitCodeSum=exitCodeSum+exit_code
820 logger(
'The exit code sum is %s'%exitCodeSum)
826 logger(
'Halting report creation procedure: unexpected exit code %s from %s ...' \
827 %(exit_code,profiler))
829 logger(
'Creating report for command %d using %s ...' \
830 %(commands_counter,profiler))
834 exit_code=performance_profile.make_report(fill_db=
True,
835 db_name=options.output,
836 metastring=db_metastring,
837 tmp_dir=options.pr_temp,
838 IgProf_option=IgProf_counter)
839 exitCodeSum=exitCodeSum+exit_code
841 exit_code=performance_profile.make_report(outdir=reportdir,
842 tmp_dir=options.pr_temp,
843 IgProf_option=IgProf_counter)
844 exitCodeSum=exitCodeSum+exit_code
847 precedent_reuseprofile=reuseprofile
848 if not precedent_reuseprofile:
849 precedent_profile_name=
''
851 logger(
'Process ended on %s\n' %time.asctime())
853 logger(
'Procedure finished on %s' %time.asctime())
854 logger(
"Exit code sum is %s"%exitCodeSum)
859 if __name__==
"__main__":
862 '----------------------------------------------------------------------------\n'+\
863 ' RelValreport: a tool for automation of benchmarking and report generation. \n'+\
864 '----------------------------------------------------------------------------\n\n'+\
865 'relvalreport.py <options>\n'+\
866 'relvalreport.py -i candles_150.txt -R -P -n 150.out -o 150_report\n'+\
867 ' - Executes the candles contained in the file candles_150.txt, create\n'+\
868 ' profiles, specified by -n, and reports, specified by -o.\n\n'+\
869 'Candles file grammar:\n'+\
870 'A candle is specified by the syntax:\n'+\
871 'executable_name @@@ profiler_name @@@ meta\n'+\
872 ' - executable_name: the name of the executable to benchmark.\n'+\
873 ' - profiler_name: the name of the profiler to use. The available are: %s.\n' %str(PROFILERS)+\
874 ' In case you want to use IgProf_mem or IgProf_perf, the counter (MEM_TOTAL,PERF_TICKS...)\n'+\
875 ' must be added with a ".": IgProf_mem.MEM_TOTAL.\n'+\
876 ' - meta: metastring that is used to change the name of the names specified in the command line\n'+\
877 ' in case of batch execution.'+\
878 'An example of candle file:\n\n'+\
879 ' ># My list of candles:\n'+\
881 ' >cmsDriver.py MU- -sSIM -e10_20 @@@ IgProf_perf.PERF_TICKS @@@ QCD_sim_IgProfperf\n'+\
882 ' >cmsDriver.py MU- -sRECO -e10_20 @@@ ValgrindFCE @@@ QCD_reco_Valgrind\n'+\
883 ' >cmsRun mycfg.cfg @@@ IgProf_mem.MEM_TOTAL @@@ Mycfg\n'
887 parser = optparse.OptionParser(usage)
889 parser.add_option(
'-p',
'--profiler',
890 help=
'Profilers are: %s' %str(PROFILERS) ,
894 parser.add_option(
'-c',
'--command',
895 help=
'Command to profile. If specified the infile is ignored.' ,
899 parser.add_option(
'-t',
900 help=
'The temp directory to store the PR service files. Default is PR_TEMP Ignored if PR is not used.',
906 parser.add_option(
'--db',
907 help=
'EXPERIMENTAL: Write results on the db.',
912 parser.add_option(
'-R',
'--Report',
913 help=
'Create a static html report. If db switch is on this is ignored.',
918 parser.add_option(
'-P',
'--Profile',
919 help=
'Create a profile for the selected profiler.',
926 parser.add_option(
'-n',
'--profile_name',
927 help=
'Profile name' ,
931 parser.add_option(
'-o',
'--output',
932 help=
'Outdir for the html report or db filename.' ,
938 parser.add_option(
'-i',
'--infile',
939 help=
'Name of the ASCII file containing the commands to profile.' ,
945 parser.add_option(
'-y',
946 help=
'Specify the IgProf counter or the CMSSW. '+\
947 'If a profiler different from '+\
948 'IgProf is selected this is ignored.' ,
950 dest=
'IgProf_counter')
952 parser.add_option(
'--executable',
953 help=
'Specify executable to monitor if different from cmsRun. '+\
954 'Only valid for IgProf.',
959 parser.add_option(
'--noexec',
960 help=
'Do not exec commands, just display them!',
965 (options,args) = parser.parse_args()
968 if options.infile==
'' and options.command==
'' and not (options.report
and not options.profile):
969 raise(
'Specify at least one command to profile!')
970 if options.profile_name==
'' and options.infile==
'':
971 raise(
'Specify a profile name!')
972 if not options.db
and options.output==
'' and options.infile==
'':
973 raise(
'Specify a db name or an output dir for the static report!')
975 if not options.profile:
976 if not os.path.exists(options.profile_name)
and options.infile==
'':
977 raise 'Profile %s does not exist!'
978 logger(
"WARNING: No profile will be generated. An existing one will be processed!")
980 if options.command!=
'' and options.infile!=
'':
981 raise(
'-c and -i options cannot coexist!')
983 if options.profiler==
'Memcheck_Valgrind' and not os.path.exists(VMPARSER):
984 raise(
'Couldn\'t find Valgrind Memcheck Parser Script! Please install it from Utilities/ReleaseScripts.')
986 if options.executable!=
'':
987 globals()[
'EXECUTABLE']=options.executable
990 globals()[
'EXEC']=
False
992 logger(
'Procedure started on %s' %time.asctime())
994 if options.infile ==
'':
996 for key,val
in options.__dict__.items():
998 logger (
'\t\t|- %s = %s' %(key, str(val)))
1001 logger(
"Exit code received from principal is: %s"%exit)
commands_profilers_meta_list
def get_commands_profilers_meta_list
def _profile_Memcheck_Valgrind
def _profile_SimpleMem_Parser
def _profile_Timereport_Parser
def _profile_Timing_Parser