CMS 3D CMS Logo

cmsRelvalreport.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 r'''
4 cmsRelvalreport.py: a script to run performance tests and produce reports in a automated way.
5 '''
6 
7 import glob,re #Used for IgProf Analyse work with IgProf Mem profile dumps
8 # Configuration parameters:#############################################################
9 
10 # Perfreport 3 and 2 coordinates:
11 PR3_BASE='/afs/cern.ch/user/d/dpiparo/w0/perfreport3installation/'
12 PR3=PR3_BASE+'/bin/perfreport'# executable
13 PERFREPORT3_PATH=PR3_BASE+'/share/perfreport' #path to xmls
14 #PR3_PRODUCER_PLUGIN=PR3_BASE+'/lib/libcmssw_by_producer.so' #plugin for fpage
15 PR3_PRODUCER_PLUGIN='/afs/cern.ch/user/d/dpiparo/w0/pr3/perfreport/plugins/cmssw_by_producer/libcmssw_by_producer.so'
16 
17 #PR2_BASE='/afs/cern.ch/user/d/dpiparo/w0/perfreport2.1installation/'
18 PR2_BASE='/afs/cern.ch/user/g/gbenelli/public/PerfReport2/2.0.1/'
19 PR2='%s' %(PR2_BASE+'/bin/perfreport')# executable
20 PERFREPORT2_PATH=PR2_BASE+'/share/perfreport' #path to xmls
21 
22 import os
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"
27 #Set the path depending on the presence of a locally checked out version of PyReleaseValidation
28 if os.path.exists(pyrelvallocal):
29  RELEASE='CMSSW_BASE'
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'
33 
34 #Eliminating the full paths to all the scripts used they are assumed to be in the release by default, being in the scripts/ directory of their package
35 # Valgrind Memcheck Parser coordinates:
36 #VMPARSER='%s/src/Utilities/ReleaseScripts/scripts/valgrindMemcheckParser.pl' %os.environ['CMSSW_RELEASE_BASE']#This has to always point to 'CMSSW_RELEASE_BASE'
37 VMPARSER='valgrindMemcheckParser.pl'
38 
39 #Not a script... need to keep the full path to the release
40 # Valgrind Memcheck Parser output style file coordinates:
41 VMPARSERSTYLE='%s/src/Utilities/ReleaseScripts/data/valgrindMemcheckParser.css' %os.environ['CMSSW_RELEASE_BASE']#This has to always point to 'CMSSW_RELEASE_BASE'
42 
43 # IgProf_Analysis coordinates:
44 #IGPROFANALYS='%s/src/Validation/Performance/scripts/cmsIgProf_Analysis.py'%os.environ[RELEASE]
45 IGPROFANALYS='cmsIgProf_Analysis.py'
46 
47 # Timereport parser
48 #TIMEREPORTPARSER='%s/src/Validation/Performance/scripts/cmsTimeReport.pl'%os.environ[RELEASE]
49 TIMEREPORTPARSER='cmsTimeReport.pl'
50 
51 # Simple memory parser
52 #SIMPLEMEMPARSER='%s/src/Validation/Performance/scripts/cmsSimplememchecker_parser.py' %os.environ[RELEASE]
53 SIMPLEMEMPARSER='cmsSimplememchecker_parser.py'
54 
55 # Timing Parser
56 #TIMINGPARSER='%s/src/Validation/Performance/scripts/cmsTiming_parser.py' %os.environ[RELEASE]
57 TIMINGPARSER='cmsTiming_parser.py'
58 
59 # makeSkimDriver
60 MAKESKIMDRIVERDIR='%s/src/Configuration/EventContent/test' %os.environ[RELEASE]
61 MAKESKIMDRIVER='%s/makeSkimDriver.py'%MAKESKIMDRIVERDIR
62 
63 ########################################################################################
64 
65 
66 # Library to include to run valgrind fce
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'
69 
70 
71 
72 # the profilers that use the stout of the app..
73 STDOUTPROFILERS=['Memcheck_Valgrind',
74  'Timereport_Parser',
75  'Timing_Parser',
76  'SimpleMem_Parser']
77 # Profilers list
78 PROFILERS=['ValgrindFCE',
79  'IgProf_perf',
80  'IgProf_mem',
81  'Edm_Size']+STDOUTPROFILERS
82 
83 
84 # name of the executable to benchmark. It can be different from cmsRun in future
85 EXECUTABLE='cmsRun'
86 
87 # Command execution and debug switches
88 EXEC=True
89 DEBUG=True
90 
91 #Handy dictionaries to handle the mapping between IgProf Profiles and counters:
92 IgProfCounters={'IgProfPerf':['PERF_TICKS'],
93  'IgProfMem':['MEM_TOTAL','MEM_LIVE','MEM_MAX']
94  }
95 IgProfProfiles={'PERF_TICKS':'IgProfPerf',
96  'MEM_TOTAL':'IgProfMem',
97  'MEM_LIVE':'IgProfMem',
98  'MEM_MAX':'IgProfMem'
99  }
100 import time
101 import optparse
102 import sys
103 
104 
105 #######################################################################
106 def red(string):
107  return '%s%s%s' %('\033[1;31m',string,'\033[1;0m')
108 def green(string):
109  return '%s%s%s' %('\033[1;32m',string,'\033[1;0m')
110 def yellow(string):
111  return '%s%s%s' %('\033[1;33m',string,'\033[1;0m')
112 #######################################################################
113 
114 def clean_name(name):
115  '''
116  Trivially removes an underscore if present as last char of a string
117  '''
118  i=-1
119  is_dirty=True
120  while(is_dirty):
121  if name[i]=='_':
122  name=name[:-1]
123  else:
124  return name
125  i-=1
126 
127 #######################################################################
128 
129 def execute(command):
130  '''
131  It executes command if the EXEC switch is True.
132  Catches exitcodes different from 0.
133  '''
134  logger('%s %s ' %(green('[execute]'),command))
135  if EXEC:
136  exit_code=os.system(command)
137  if exit_code!=0:
138  logger(red('*** Seems like "%s" encountered problems.' %command))
139  return exit_code
140  else:
141  return 0
142 
143 #######################################################################
144 
145 def logger(message,level=0):
146  '''
147  level=0 output, level 1 debug.
148  '''
149  message='%s %s' %(yellow('[RelValreport]'),message)
150 
151  sys.stdout.flush()
152 
153  if level==0:
154  print message
155  if level==1 and DEBUG:
156  print message
157 
158  sys.stdout.flush()
159 
160 #######################################################################
161 
163  '''
164  Class to read the trivial ASCII file containing the candles
165  '''
166  def __init__(self, filename):
167 
169 
170  candlesfile=open(filename,'r')
171 
172  if filename[-3:]=='xml':
173  command=''
174  profiler=''
175  meta=''
176  db_meta=''
177  reuse=False
178 
179  from xml.dom import minidom
180 
181  # parse the config
182  xmldoc = minidom.parse(filename)
183 
184  # get the candles
185  candles_list = xmldoc.getElementsByTagName('candle')
186 
187  # a list of dictionaries to store the info
188  candles_dict_list=[]
189 
190  for candle in candles_list:
191  info_dict={}
192  for child in candle.childNodes:# iteration over candle node children
193  if 'nodeName' not in child.__dict__:# if just a text node skip!
194  #print 'CONTINUE!'
195  continue
196  # We pick the info from the node
197  tag_name=child.tagName
198  #print 'Manipulating a %s ...'%tag_name
199  data=child.firstChild.data
200  #print 'Found the data: %s !' %data
201  # and we put it in the dictionary
202  info_dict[tag_name]=data
203  # to store it in a list
204  candles_dict_list.append(info_dict)
205 
206  # and now process what was parsed
207 
208  for candle_dict in candles_dict_list:
209  # compulsory params!!
210  command=candle_dict['command']
211  profiler=candle_dict['profiler']
212  meta=candle_dict['meta']
213  # other params
214  try:
215  db_meta=candle_dict['db_meta']
216  except:
217  db_meta=None
218  try:
219  reuse=candle_dict['reuse']
220  except:
221  reuse=False
222 
223  self.commands_profilers_meta_list.append([command,profiler,meta,reuse,db_meta])
224 
225  # The file is a plain ASCII
226  else:
227  for candle in candlesfile.readlines():
228  # Some parsing of the file
229  if candle[0]!='#' and candle.strip(' \n\t')!='': # if not a comment or an empty line
230  if candle[-1]=='\n': #remove trail \n if it's there
231  candle=candle[:-1]
232  splitted_candle=candle.split('@@@') #separate
233 
234  # compulsory fields
235  command=splitted_candle[0]
236  profiler=splitted_candle[1].strip(' \t')
237  meta=splitted_candle[2].strip(' \t')
238  info=[command,profiler,meta]
239 
240  # FIXME: AN .ini or xml config??
241  # do we have something more?
242  len_splitted_candle=len(splitted_candle)
243  reuse=False
244  if len_splitted_candle>3:
245  # is it a reuse statement?
246  if 'reuse' in splitted_candle[3]:
247  reuse=True
248  info.append(reuse)
249  else:
250  info.append(reuse)
251 
252  # we have one more field or we hadn't a reuse in the last one
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)
256  else:
257  info.append(None)
258 
259 
260  self.commands_profilers_meta_list.append(info)
261 
262  #----------------------------------------------------------------------
263 
265  return self.commands_profilers_meta_list
266 
267 #######################################################################
268 
269 class Profile:
270  '''
271  Class that represents the procedure of performance report creation
272  '''
273  def __init__(self,command,profiler,profile_name):
274  self.command=command
275  self.profile_name=profile_name
276  self.profiler=profiler
277 
278  #------------------------------------------------------------------
279  # edit here if more profilers added
280  def make_profile(self):
281  '''
282  Launch the right function according to the profiler name.
283  '''
284  if self.profiler=='ValgrindFCE':
285  return self._profile_valgrindfce()
286  elif self.profiler.find('IgProf')!=-1:
287  return self._profile_igprof()
288  elif self.profiler.find('Edm_Size')!=-1:
289  return self._profile_edmsize()
290  elif self.profiler=='Memcheck_Valgrind':
291  return self._profile_Memcheck_Valgrind()
292  elif self.profiler=='Timereport_Parser':
293  return self._profile_Timereport_Parser()
294  elif self.profiler=='Timing_Parser':
295  return self._profile_Timing_Parser()
296  elif self.profiler=='SimpleMem_Parser':
297  return self._profile_SimpleMem_Parser()
298  elif self.profiler=='':
299  return self._profile_None()
300  elif self.profiler=='None': #adding this for the case of candle ASCII file non-profiling commands
301  return self._profile_None()
302  else:
303  raise 'No %s profiler found!'
304  #------------------------------------------------------------------
306  '''
307  Valgrind profile launcher.
308  '''
309  # ValgrindFCE needs a special library to run
310  os.environ["VALGRIND_LIB"]=VFCE_LIB
311 
312  profiler_line=''
313  valgrind_options= 'time valgrind '+\
314  '--tool=callgrind '+\
315  '--fce=%s ' %(self.profile_name)
316 
317  # If we are using cmsDriver we should use the prefix switch
318  if EXECUTABLE=='cmsRun' and self.command.find('cmsDriver.py')!=-1:
319  profiler_line='%s --prefix "%s"' %(self.command,valgrind_options)
320 
321  else:
322  profiler_line='%s %s' %(valgrind_options,self.command)
323  #'--trace-children=yes '+\
324 
325  return execute(profiler_line)
326 
327  #------------------------------------------------------------------
328  def _profile_igprof(self):
329  '''
330  IgProf profiler launcher.
331  '''
332  profiler_line=''
333 
334  igprof_options='igprof -d -t %s ' \
335  %EXECUTABLE # IgProf profile not general only for CMSRUN!
336 
337  # To handle Igprof memory and performance profiler in one function
338  if self.profiler=='IgProf_perf':
339  igprof_options+='-pp '
340  elif self.profiler=='IgProf_mem':
341  igprof_options+='-mp '
342  else:
343  raise 'Unknown IgProf flavour: %s !'
344  igprof_options+='-z -o %s' %(self.profile_name)
345 
346  # If we are using cmsDriver we should use the prefix switch
347  if EXECUTABLE=='cmsRun' and self.command.find('cmsDriver.py')!=-1:
348  profiler_line='%s --prefix "%s"' %(self.command,igprof_options)
349  else:
350  profiler_line='%s %s' %(igprof_options, self.command)
351 
352  return execute(profiler_line)
353 
354  #------------------------------------------------------------------
355 
356  def _profile_edmsize(self):
357  '''
358  Launch edm size profiler
359  '''
360  # In this case we replace the name to be clear
361  input_rootfile=self.command
362 
363  # Skim the content if requested!
364  if '.' in self.profiler:
365 
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,
372  self.command,
373  outfilename,
374  content,
375  nevts))
376  os.environ['PYTHONPATH']=oldpypath
377  #execute('rm %s' %outfilename)
378  self.command=outfilename
379  self.profiler=clean_profiler_name
380 
381 
382  profiler_line='edmEventSize -o %s -d %s'\
383  %(self.profile_name,self.command)
384 
385  return execute(profiler_line)
386 
387  #------------------------------------------------------------------
388 
390  '''
391  Valgrind Memcheck profile launcher
392  '''
393  profiler_line=''
394  #Adding cms suppression of useless messages (cmsvgsupp)
395  #Removing leak-checking (done with igprof)
396  #'--leak-check=no '+\ (no is the default)
397  #'--show-reachable=yes '+\
398  #'--track-fds=yes '
399  #Adding xml logging
400  xmlFileName = self.profile_name.replace(",","-")[:-4] + ".xml"
401  valgrind_options='time valgrind --track-origins=yes '+\
402  '--tool=memcheck `cmsvgsupp` '+\
403  '--num-callers=20 '+\
404  '--xml=yes '+\
405  '--xml-file=%s '%xmlFileName
406 
407  # If we are using cmsDriver we should use the prefix switch
408  if EXECUTABLE=='cmsRun' and self.command.find('cmsDriver.py')!=-1:
409  #Replacing 2>&1 |tee with >& in the shell command to preserve the return code significance:
410  # using tee return would be 0 even if the command failed before the pipe:
411  profiler_line='%s --prefix "%s" >& %s' %(self.command,valgrind_options,self.profile_name)
412 
413  else:
414  profiler_line='%s %s >& %s' %(valgrind_options,self.command,self.profile_name)
415  #'--trace-children=yes '+\
416  exec_status = execute(profiler_line)
417 
418  # compact the xml by removing the Leak_* errors
419  if not exec_status:
420  newFileName = xmlFileName.replace('valgrind.xml', 'vlgd.xml')
421  compactCmd = 'xsltproc --output %s %s/test/filterOutValgrindLeakErrors.xsl %s' %(newFileName, valperf, xmlFileName)
422  execute(compactCmd)
423 
424  return exec_status
425  #-------------------------------------------------------------------
426 
428  return self._save_output()
429 
430  #-------------------------------------------------------------------
431 
433  return self._save_output()
434 
435  #-------------------------------------------------------------------
436 
438  return self._save_output()
439 
440  #-------------------------------------------------------------------
441 
442  def _save_output(self):
443  '''
444  Save the output of cmsRun on a file!
445  '''
446 # # a first maquillage about the profilename:
447 # if self.profile_name[-4:]!='.log':
448 # self.profile_name+='.log'
449  #Replacing 2>&1 |tee with >& in the shell command to preserve the return code significance:
450  # using tee return would be 0 even if the command failed before the pipe:
451  profiler_line='%s >& %s' %(self.command,self.profile_name)
452  return execute(profiler_line)
453 
454  #-------------------------------------------------------------------
455 
456  def _profile_None(self):
457  '''
458  Just Run the command!
459  '''
460  return execute(self.command)
461 
462  #-------------------------------------------------------------------
463 
464  def make_report(self,
465  fill_db=False,
466  db_name=None,
467  tmp_dir=None,
468  outdir=None,
469  IgProf_option=None,
470  metastring=None):
471  '''
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.
473  '''
474 
475  if outdir==None or outdir==self.profile_name:
476  outdir=self.profile_name+'_outdir'
477 
478  #Create the directory where the report will be stored:
479  if not os.path.exists(outdir) and not fill_db and not IgProf_option:
480  #Added an IgProf condition to avoid the creation of a directory that we will not need anymore, since we will put all info in the filenames
481  execute('mkdir %s' %outdir)
482 
483  if fill_db:
484  db_option='-a'
485  if not os.path.exists(db_name):
486  db_option='-A'
487 
488  # temp in the local dir for PR
489  tmp_switch=''
490  if tmp_dir!='':
491  execute('mkdir %s' %tmp_dir)
492  tmp_switch=' -t %s' %tmp_dir
493 
494  #Handle the various profilers:
495 
496  #####################################################################
497 
498  # Profiler is ValgrindFCE:
499  if self.profiler=='ValgrindFCE':
500  perfreport_command=''
501  # Switch for filling the db
502  if not fill_db:
503  os.environ["PERFREPORT_PATH"]='%s/' %PERFREPORT2_PATH
504  perfreport_command='%s %s -ff -i %s -o %s' %(PR2,
505  tmp_switch,
506  self.profile_name,
507  outdir)
508  else:
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' \
511  %(PR3,
512  tmp_switch,
513  PR3_PRODUCER_PLUGIN,
514  metastring,
515  self.profile_name,
516  db_option,
517  db_name)
518  return execute(perfreport_command)
519 
520  #####################################################################
521 
522  # Profiler is IgProf:
523  if self.profiler.find('IgProf')!=-1:
524  #First the case of IgProf PERF and MEM reporting:
525  if not 'ANALYSE' in IgProf_option:
526  #Switch to the use of igprof-analyse instead of PerfReport!
527  #Will use the ANALYSE case for regressions between early event dumps and late event dumps of the profiles
528  #Following Andreas suggestion, add the number of events for the EndOfJob report
529  NumberOfEvents=self.command.split()[3] #FIXME: this is quite hardcoded... but should be stable...
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))
533  return exit
534  #Then the "ANALYSE" case that we want to use to add to the same directories (Perf, MemTotal, MemLive)
535  #also some other analyses and in particular:
536  #1-the first 7 lines of the ASCII analysis of the IgProf profile dumps (total of the counters)
537  #2-the dump at different event numbers,
538  #3-the diff between the first and last dump,
539  #4-the counters grouped by library using regexp at the last dump:
540  else: #We use IgProf Analysis
541  #Set the IgProfCounter from the ANALYSE.MEM_TOT style IgProf_option
542  #print IgProf_option
543  IgProfCounter=IgProf_option[1]
544  #Add here the handling of the new IgProf.N.gz files so that they will get preserved and not overwritten:
545  logger("Looking for IgProf intermediate event profile dumps")
546  #Check if there are IgProf.N.gz dump files:
547  IgProfDumps=glob.glob("IgProf.*.gz")
548  #in case there are none check if they have already been mv'd to another name to avoid overwriting
549  #(MEM_LIVE usually re-uses MEM_TOTAL, so the IgProf.N.gz files will already have a MemTotal name...)
550  if not IgProfDumps:
551  localFiles=os.listdir('.')
552  IgProfDumpProfilesPrevious=re.compile(r"\w+.\d+.gz")
553  IgProfDumps=[x for x in localFiles if IgProfDumpProfilesPrevious.search(x)]
554  #Now if there are dumps execute the following analyses:
555  if IgProfDumps:
556  IgProfDumps.sort()
557  logger("Found the following IgProf intermediate event profile dumps:")
558  logger(IgProfDumps)
559  FirstDumpEvent=9999999
560  LastDumpEvent=0
561  exit=0
562  for dump in IgProfDumps:
563  if "___" in dump:
564  DumpEvent=dump.split(".")[0].split("___")[-1]
565  else:
566  DumpEvent=dump.split(".")[1]
567  #New naming convention using ___ as separator
568  DumpedProfileName=self.profile_name[:-3]+"___"+DumpEvent+".gz"
569  if dump.startswith("IgProf"):
570  execute('mv %s %s'%(dump,DumpedProfileName))
571  #Keep a tab of the first and last dump event for the diff analysis
572  if int(DumpEvent) < FirstDumpEvent:
573  FirstDumpEvent = int(DumpEvent)
574  if int(DumpEvent) > LastDumpEvent:
575  LastDumpEvent = int(DumpEvent)
576  #Eliminating the ASCII analysis to get the totals, Giulio will provide this information in igprof-navigator with a special query
577  #First type of analysis: dump first 7 lines of ASCII analysis:
578  #logger("Executing the igprof-analyse analysis to dump the ASCII 7 lines output with the totals for the IgProf counter")
579  #exit=execute('%s -o%s -i%s -t%s' %(IGPROFANALYS,outdir,DumpedProfileName,"ASCII"))
580  #Second type of analysis: dump the report in sqlite format to be ready to be browsed with igprof-navigator
581  logger("Executing the igprof-analyse analysis saving into igprof-navigator browseable SQLite3 format")
582  #exit=exit+execute('%s -o%s -i%s -t%s' %(IGPROFANALYS,outdir,DumpedProfileName,"SQLite3"))
583  #Execute all types of analyses available with the current profile (using the dictionary IgProfProfile):
584  #To avoid this we should use a further input in SimulationCandles.txt IgProfMem.ANALYSE.MEM_TOTAL maybe the cleanest solution.
585  #for IgProfile in IgProfCounters.keys():
586  # if DumpedProfileName.find(IgProfile)>0:
587  # for counter in IgProfCounters[IgProfile]:
588  #Check that the file does not exist:
589  #if not os.path.exists(DumpedProfileName.split(".")[0].replace(IgProfProfiles[counter],counter)+".sql3"):
590  exit=exit+execute('%s -c%s -i%s -t%s' %(IGPROFANALYS,IgProfCounter,DumpedProfileName,"SQLite3"))
591  #else:
592  # print "File %s already exists will not process profile"%DumpedProfileName.split(".")[0].replace(IgProfProfiles[counter],counter)+".sql3"
593  #FIXME:
594  #Issue with multiple profiles in the same dir: assuming Perf and Mem will always be in separate dirs
595  #Potential ssue with multiple steps?
596  #Adapting to new igprof naming scheme:
597  FirstDumpProfile=self.profile_name[:-3]+"___"+str(FirstDumpEvent)+".gz"
598  LastDumpProfile=self.profile_name[:-3]+"___"+str(LastDumpEvent)+".gz"
599  #Third type of analysis: execute the diff analysis:
600  #Check there are at least 2 IgProf intermediate event dump profiles to do a regression!
601  if len(IgProfDumps)>1:
602  logger("Executing the igprof-analyse regression between the first IgProf profile dump and the last one")
603  #for IgProfile in IgProfCounters.keys():
604  # if DumpedProfileName.find(IgProfile)>0:
605  # IgProfCounter=IgProfCounters[IgProfile]
606  exit=exit+execute('%s -c%s -i%s -r%s' %(IGPROFANALYS,IgProfCounter,LastDumpProfile,FirstDumpProfile))
607  else:
608  logger("CANNOT execute any regressions: not enough IgProf intermediate event profile dumps!")
609  #Fourth type of analysis: execute the grouped by library igprof-analyse:
610  logger("Executing the igprof-analyse analysis merging the results by library via regexp and saving the result in igprof-navigator browseable SQLite3 format")
611  #for IgProfile in IgProfCounters.keys():
612  # if DumpedProfileName.find(IgProfile)>0:
613  # IgProfCounter=IgProfCounters[IgProfile]
614  exit=exit+execute('%s -c%s -i%s --library' %(IGPROFANALYS,IgProfCounter,LastDumpProfile))
615  #If they are not there at all (no dumps)
616  else:
617  logger("No IgProf intermediate event profile dumps found!")
618  exit=0
619 
620  return exit
621 
622 
623  #####################################################################
624 
625  # Profiler is EdmSize:
626  if 'Edm_Size' in self.profiler:
627  perfreport_command=''
628  if not fill_db:
629  os.environ["PERFREPORT_PATH"]='%s/' \
630  %PERFREPORT2_PATH
631  perfreport_command='%s %s -fe -i %s -o %s' \
632  %(PR2,
633  tmp_switch,
634  self.profile_name,
635  outdir)
636  else:
637  os.environ["PERFREPORT_PATH"]='%s/' \
638  %PERFREPORT3_PATH
639  perfreport_command='%s %s -n5000 -u%s -fe -i %s -a -o %s' \
640  %(PR3,
641  tmp_switch,
642  PR3_PRODUCER_PLUGIN,
643  self.profile_name,
644  db_name)
645 
646  return execute(perfreport_command)
647 
648  #FIXME: probably need to move this somewhere else now that we use return statements
649  if tmp_dir!='':
650  execute('rm -r %s' %tmp_dir)
651 
652  #####################################################################
653 
654  # Profiler is Valgrind Memcheck
655  if self.profiler=='Memcheck_Valgrind':
656  # Three pages will be produced:
657  os.environ['PERL5LIB']=PERL5_LIB
658  report_coordinates=(VMPARSER,self.profile_name,outdir)
659  # Copy the Valgrind Memcheck parser style file in the outdir
660  copyStyleFile='cp -pR %s %s'%(VMPARSERSTYLE,outdir)
661  execute(copyStyleFile)
662  report_commands=('%s --preset +prod,-prod1 %s > %s/edproduce.html'\
663  %report_coordinates,
664  '%s --preset +prod1 %s > %s/esproduce.html'\
665  %report_coordinates,
666  '%s -t beginJob %s > %s/beginjob.html'\
667  %report_coordinates)
668  exit=0
669  for command in report_commands:
670  exit= exit + execute(command)
671  return exit
672  #####################################################################
673 
674  # Profiler is TimeReport parser
675 
676  if self.profiler=='Timereport_Parser':
677  return execute('%s %s %s' %(TIMEREPORTPARSER,self.profile_name,outdir))
678 
679  #####################################################################
680 
681  # Profiler is Timing Parser
682 
683  if self.profiler=='Timing_Parser':
684  return execute('%s -i %s -o %s' %(TIMINGPARSER,self.profile_name,outdir))
685 
686 
687  #####################################################################
688 
689  # Profiler is Simple memory parser
690 
691  if self.profiler=='SimpleMem_Parser':
692  return execute('%s -i %s -o %s' %(SIMPLEMEMPARSER,self.profile_name,outdir))
693 
694  #####################################################################
695 
696  # no profiler
697 
698  if self.profiler=='' or self.profiler=='None': #Need to catch the None case, since otherwise we get no return code (crash for pre-requisite step running).
699  return 0 #Used to be pass, but we need a return 0 to handle exit code properly!
700 
701 #############################################################################################
702 
703 def principal(options):
704  '''
705  Here the objects of the Profile class are istantiated.
706  '''
707  #Add a global exit code variable, that is the sum of all exit codes, to return it at the end:
708  exitCodeSum=0
709  # Build a list of commands for programs to benchmark.
710  # It will be only one if -c option is selected
711  commands_profilers_meta_list=[]
712 
713  # We have only one
714  if options.infile=='':
715  logger('Single command found...')
716  commands_profilers_meta_list.append([options.command,'','',False,''])
717 
718  # We have more: we parse the list of candles
719  else:
720  logger('List of commands found. Processing %s ...' %options.infile)
721 
722  # an object that represents the candles file:
723  candles_file = Candles_file(options.infile)
724 
725  commands_profilers_meta_list=candles_file.get_commands_profilers_meta_list()
726 
727 
728  logger('Iterating through commands of executables to profile ...')
729 
730  # Cycle through the commands
731  len_commands_profilers_meta_list=len(commands_profilers_meta_list)
732 
733  commands_counter=1
734  precedent_profile_name=''
735  precedent_reuseprofile=False
736  for command,profiler_opt,meta,reuseprofile,db_metastring in commands_profilers_meta_list:
737 
738  exit_code=0
739 
740  logger('Processing command %d/%d' \
741  %(commands_counter,len_commands_profilers_meta_list))
742  logger('Process started on %s' %time.asctime())
743 
744  # for multiple directories and outputs let's put the meta
745  # just before the output profile and the outputdir
746  profile_name=''
747  profiler=''
748  reportdir=options.output
749  IgProf_counter=options.IgProf_counter
750 
751 
752  if options.infile!='': # we have a list of commands
753 
754  reportdir='%s_%s' %(meta,options.output) #Usually options.output is not used
755  reportdir=clean_name(reportdir) #Remove _
756 
757  profile_name=clean_name('%s_%s'%(meta,options.profile_name)) #Also options.profile_name is usually not used... should clean up...
758 
759  # profiler is igprof: we need to disentangle the profiler and the counter
760 
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:] #This way can handle IgProfMem.ANALYSE.MEM_TOT etc.
765  if profile_name[-3:]!='.gz':
766  profile_name+='.gz'
767 
768  # Profiler is Timereport_Parser
769  elif profiler_opt in STDOUTPROFILERS:
770  # a first maquillage about the profilename:
771  if profile_name[:-4]!='.log':
772  profile_name+='.log'
773  profiler=profiler_opt
774 
775  # profiler is not igprof
776  else:
777  profiler=profiler_opt
778 
779  if precedent_reuseprofile:
780  profile_name=precedent_profile_name
781  if reuseprofile:
782  precedent_profile_name=profile_name
783 
784 
785 
786  else: # we have a single command: easy job!
787  profile_name=options.profile_name
788  reportdir=options.output
789  profiler=options.profiler
790 
791 
792 
793  # istantiate a Profile object
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))
800 
801  performance_profile=Profile(command,
802  profiler,
803  profile_name)
804 
805  # make profile if needed
806  if options.profile:
807  if reuseprofile:
808  logger('Saving profile name to reuse it ...')
809  precedent_profile_name=profile_name
810  else:
811  precedent_profile_name=''
812 
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()
817  print exit_code
818  logger('The exit code was %s'%exit_code)
819  exitCodeSum=exitCodeSum+exit_code #Add all exit codes into the global exitCodeSum in order to return it on cmsRelvareport.py exit.
820  logger('The exit code sum is %s'%exitCodeSum)
821 
822 
823  # make report if needed
824  if options.report:
825  if exit_code!=0:
826  logger('Halting report creation procedure: unexpected exit code %s from %s ...' \
827  %(exit_code,profiler))
828  else:
829  logger('Creating report for command %d using %s ...' \
830  %(commands_counter,profiler))
831 
832  # Write into the db instead of producing html if this is the case:
833  if options.db:
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 #this is to also check that the reporting works... a little more ambitious testing... could do without for release integration
840  else:
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 #this is to also check that the reporting works... a little more ambitious testing... could do without for release integration
845 
846  commands_counter+=1
847  precedent_reuseprofile=reuseprofile
848  if not precedent_reuseprofile:
849  precedent_profile_name=''
850 
851  logger('Process ended on %s\n' %time.asctime())
852 
853  logger('Procedure finished on %s' %time.asctime())
854  logger("Exit code sum is %s"%exitCodeSum)
855  return exitCodeSum
856 
857 ###################################################################################################
858 
859 if __name__=="__main__":
860 
861  usage='\n'+\
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'+\
880  ' >\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'
884 
885 
886 
887  parser = optparse.OptionParser(usage)
888 
889  parser.add_option('-p', '--profiler',
890  help='Profilers are: %s' %str(PROFILERS) ,
891  default='',
892  dest='profiler')
893 
894  parser.add_option('-c', '--command',
895  help='Command to profile. If specified the infile is ignored.' ,
896  default='',
897  dest='command')
898 
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.',
901  default='',
902  dest='pr_temp')
903 
904  #Flags
905 
906  parser.add_option('--db',
907  help='EXPERIMENTAL: Write results on the db.',
908  action='store_true',
909  default=False,
910  dest='db')
911 
912  parser.add_option('-R','--Report',
913  help='Create a static html report. If db switch is on this is ignored.',
914  action='store_true',
915  default=False,
916  dest='report')
917 
918  parser.add_option('-P','--Profile',
919  help='Create a profile for the selected profiler.',
920  action='store_true',
921  default=False,
922  dest='profile')
923 
924  # Output location for profile and report
925 
926  parser.add_option('-n', '--profile_name',
927  help='Profile name' ,
928  default='',
929  dest='profile_name')
930 
931  parser.add_option('-o', '--output',
932  help='Outdir for the html report or db filename.' ,
933  default='',
934  dest='output')
935 
936  #Batch functionality
937 
938  parser.add_option('-i', '--infile',
939  help='Name of the ASCII file containing the commands to profile.' ,
940  default='',
941  dest='infile')
942 
943  # ig prof options
944 
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.' ,
949  default=None,
950  dest='IgProf_counter')
951 
952  parser.add_option('--executable',
953  help='Specify executable to monitor if different from cmsRun. '+\
954  'Only valid for IgProf.',
955  default='',
956  dest='executable')
957 
958  # Debug options
959  parser.add_option('--noexec',
960  help='Do not exec commands, just display them!',
961  action='store_true',
962  default=False,
963  dest='noexec')
964 
965  (options,args) = parser.parse_args()
966 
967  # FAULT CONTROLS
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!')
974 
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!")
979 
980  if options.command!='' and options.infile!='':
981  raise('-c and -i options cannot coexist!')
982 
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.')
985 
986  if options.executable!='':
987  globals()['EXECUTABLE']=options.executable
988 
989  if options.noexec:
990  globals()['EXEC']=False
991 
992  logger('Procedure started on %s' %time.asctime())
993 
994  if options.infile == '':
995  logger('Script options:')
996  for key,val in options.__dict__.items():
997  if val!='':
998  logger ('\t\t|- %s = %s' %(key, str(val)))
999  logger ('\t\t|')
1000  exit=principal(options)
1001  logger("Exit code received from principal is: %s"%exit)
1002  #Mind you! exit codes in Linux are all 0 if they are even! We can easily make the code 1
1003  if exit: #This is different than 0 only if there have been at least one non-zero exit(return) code in the cmsRelvalreport.py
1004  exit=1
1005  sys.exit(exit)
1006 
def _profile_Timereport_Parser(self)
def __init__(self, filename)
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)
def __init__(self, command, profiler, profile_name)
def principal(options)
def _profile_Memcheck_Valgrind(self)
Definition: logger.py:1
def execute(command)
def green(string)
def clean_name(name)
def yellow(string)
#define str(s)
double split
Definition: MVATrainer.cc:139