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