ATLAS Offline Software
pmontree.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
4 
5 __author__ = "Frank Winklmeier"
6 __version__ = "$Revision: 270227 $"
7 __doc__ = "Script to create dependency tree of perfmon stats"
8 
9 import sys
10 import operator
11 import re
12 
13 import PerfMonComps.PerfMonSerializer as pmon_ser
14 
15 # Global for command line options
16 opt = None
17 
19  def __init__(self, name):
20  self.name = name
21  self.step = ''
22  self.dvmem = 0
23  self.dmalloc = 0
24  self.nmalloc = 0
25  self._vmem = [0,0] # kB
26  self._malloc = [0,0] # kB
27  self._nmalloc = [0,0] # number
28 
29  def set(self, line, i):
30  """Set members via match object or stream line and index i"""
31 
32  fields = line.split()
33  i = int(fields[1])
34  step = fields[0][1:-1] # slice/comp
35  self.step = step.split('/')[0]
36  if self.step=='dso':
37  self._vmem[i] = float(fields[2])
38  else:
39  self._vmem[i] = float(fields[6])#*1024
40  self._malloc[i] = float(fields[8])
41  self._nmalloc[i] = float(fields[9])
42  return
43 
44  def wrapup(self):
45  self.dvmem = self._vmem[1]-self._vmem[0]
46  self.dmalloc = self._malloc[1]-self._malloc[0]
47  self.nmalloc = self._nmalloc[1]-self._nmalloc[0]
48 
49 
51  """Resource user with dependents
52  """
53  def __init__(self, name):
54  super(ResUser, self).__init__(name)
55  self.dep = [] # Dependent users (children)
56  self.dvmem_self = None
57 
58  def _show(self, level=0, showFct=None):
59  if showFct and not showFct(self):
60  s = ''
61  else:
62  indent = (' '*level*(not opt.flat))
63  s = '\n' + indent + self._node()
64 
65  for d in self.dep:
66  s += d._show(level+1, showFct)
67  return s
68 
69  def calcSelf(self, children=None):
70  self.dvmem_self = self.dvmem
71 
72  def show(self, showFct=None):
73  print( self._show(0, showFct), )
74 
75  def _node(self):
76  return self.name
77 
78  def purge(self, purgeFct):
79  """Remove all objects passing purgeFct()
80  """
81  # Mark dependents for deletion
82  for d in self.dep:
83  if purgeFct(d):
84  d.name = None
85 
86  # Recursively call children
87  for d in self.dep:
88  d.purge(purgeFct)
89 
90  # Remove from list
91  self.dep = [ d for d in self.dep if d.name is not None ]
92 
93 class Comp(ResUser):
94  """Component (Algorithm, Tool, Service, etc.)
95  """
96  def __init__(self, name):
97  super(Comp, self).__init__(name)
98  self.symbol = '++'
99 
100  def calcSelf(self, children = None):
101  """Calculate self dvmem (subtract dvmem of all direct children)
102  Additional classes to be considered children can be supplied as list.
103  """
104  self.dvmem_self = self.dvmem
105  for d in self.dep:
106  if isinstance(d, Comp) or (children and d.__class__ in children):
107  self.dvmem_self -= d.dvmem
108 
109  d.calcSelf(children)
110 
111  def _node(self):
112  # FIXME !!
113  dvmem_self = self.dvmem_self
114  if self.dvmem_self is None:
115  dvmem_self = 0.
116  #
117  return '|%s %s [%.0f kB, %.0f kB]' % (
118  self.symbol,self.name,self.dvmem,dvmem_self
119  )
120 
121 
122 class Callback(Comp):
123  """Callback
124  """
125  def __init__(self, name):
126  super(Callback, self).__init__(name)
127  self.symbol = '=='
128 
129  def _node(self):
130  return '|%s %s [%.0f kB, %.0f kB]' % (self.symbol,self.name,self.dvmem,self.dvmem_self)
131 
132 
134  """Shared library
135  """
136  def __init__(self, name):
137  super(SharedLib, self).__init__(name)
138  self.symbol = '--'
139 
140  def _node(self):
141  return '|%s %s [%.0f kB]' % (self.symbol,self.name,self.dvmem)
142 
143 
144 def sliceCompIdx(line):
145  fields = line.split()
146  # should be of the form "/step/comp-name"
147  step = fields[0][1:-1]
148  step = step.split('/')[0]
149  comp = fields[0][len(step)+2:]
150  idx = int(fields[1])
151  return step,comp,idx
152 
153 def getResUser(f, resTree, steps=['ini'], current=None):
154  """Get resource user tree"""
155 
156  # Remember my parent
157  parent = current
158 
159  for line in f:
160  if line.startswith(('#','/io/')):
161  continue
162 
163  step,name,idx = sliceCompIdx(line)
164  if step not in steps:
165  continue
166 
167  # startAud
168  if idx==0:
169  if step in ['cbk']:
170  comp = Callback(name)
171  elif step=='dso':
172  comp = SharedLib(name)
173  else:
174  comp = Comp(name)
175 
176  comp.set(line, idx)
177  if current:
178  current.dep.append(comp)
179  getResUser(f, resTree, steps, comp)
180  else:
181  current = comp
182  resTree.append(comp)
183  continue
184 
185 
186  # stopAud
187  if idx==1:
188  if name != current.name:
189  raise RuntimeError( "stop for %s within scope of %s" % (name, current.name) )
190  else:
191  current.set(line, idx)
192  current.wrapup()
193  # Remove absolute address from callback
194  if step=='cbk':
195  offset = current.name.split(']+')[1]
196  i = current.name.find('[')
197  if i>0:
198  current.name = '%s{+%s}' % (current.name[:i],offset)
199 
200  if parent is None:
201  current=None
202  continue
203  else:
204  return
205 
206 
207 def readEvents(f):
208  """Read components for evt slice
209  """
210  reEvent = re.compile(r'AthenaEventLoopMgr\s*INFO\s*===>>> done processing event.*')
211  evt = None
212  comps = [] # [ {name : [Comp]} ]
213  for line in f:
214  m = reEvent.match(line)
215  if m:
216  if evt:
217  evt+=1
218  else:
219  evt=0
220  comps.append({})
221  if evt is None:
222  continue
223 
224  '''
225  ## FIX ME : This bit needs to be checked
226  ## It's not obvious what reAud is inteded to be...
227  m = reAud.match(line)
228  if m and m.group('slice')!='evt':
229  continue
230 
231  if m and m.group('action')=='start':
232  comp = Comp(m.group('comp'))
233  comp.set(m, 0)
234  if comp.name not in comps[evt]:
235  comps[evt][comp.name] = []
236  comps[evt][comp.name].append(comp)
237 
238  if m and m.group('action')=='stop':
239  comp = comps[evt][comp.name][-1]
240  comp.set(m, 1)
241  comp.wrapup()
242  '''
243 
244  return comps
245 
246 
247 def resAvg(res):
248  """Calculate average of list of resources"""
249  if len(res)==0:
250  return None
251  a = Comp(res[0].name)
252  a.step = res[0].step
253  for r in res:
254  a.dvmem += r.dvmem
255  a.dmalloc += r.dmalloc
256  a.nmalloc += r.nmalloc
257 
258  a.dvmem /= float(len(res))
259  a.dmalloc /= float(len(res))
260  a.nmalloc /= float(len(res))
261  return a
262 
263 
264 def calcEventAvg(comps, sliceObj=slice(None)): # noqa: B008
265 
266  tmp = {} # { comp: [] }
267  for evt in comps[sliceObj]:
268  for comp in evt.keys():
269  if comp not in tmp:
270  tmp[comp] = []
271  tmp[comp] += evt[comp]
272 
273  avg = []
274  for comp in tmp.keys():
275  avg.append(resAvg(tmp[comp]))
276 
277  return avg
278 
279 
280 
281 def getCompList(resTree, resList):
282  """
283  Create flat list of components from resource tree.
284  Call with resList = [].
285  """
286  for r in resTree:
287  if isinstance(r, ResUser):
288  resList.append(r)
289  for d in r.dep:
290  getCompList([d], resList)
291 
292  return resList[:]
293 
294 
295 def diff(table, opt, attrgetter=operator.attrgetter('dvmem')):
296 
297  tmp = {} # {name : [mem1,mem2]}
298  for i,t in enumerate(table):
299  for comp in t:
300  label = comp.symbol + ' ' + comp.name
301  if label not in tmp:
302  tmp[label] = [0]*len(table)
303  tmp[label][i] = attrgetter(comp)
304 
305  # Convert to list
306  if opt.min is not None:
307  limit = opt.min + 0.00001
308  cmpTable = [ [k]+v for k,v in tmp.iteritems() if abs(v[1]-v[0])>limit ]
309  else:
310  cmpTable = [ [k]+v for k,v in tmp.iteritems() ]
311 
312  if opt.diff and len(table)==2:
313  cmpTable.sort( lambda x,y : (int(x[2]-x[1])-int(y[2]-y[1])), reverse=True )
314  for c in cmpTable:
315  print( "%-60s %10.0f %10.0f %10.0f" % (c[0],c[1],c[2],c[2]-c[1]) )
316  else:
317  cmpTable.sort( lambda x,y : int(x[1]-y[1]), reverse=True)
318  for c in cmpTable:
319  print( "%-60s" % c[0], )
320  print( "%10.0f "*(len(c)-1) % tuple(c[1:]) )
321 
322  return
323 
324 
325 def printTable(compList, opt):
326  for c in sorted(compList,key=operator.attrgetter('dvmem'), reverse=True):
327  if c.nmalloc!=0:
328  avgmalloc = c.dmalloc*1024/c.nmalloc
329  else:
330  avgmalloc = 0
331  print( "%-60s %10.0f %10.0f %10.0f %10.0f" %\
332  (c.name,c.dvmem,c.dmalloc,c.nmalloc,avgmalloc) )
333 
334 def main():
335  import argparse
336  parser = argparse.ArgumentParser(
337  description="Create callgraph profile from log file",
338  usage="%prog [Options] LOGFILE [LOGFILE]")
339 
340  arg = parser.add_argument
341  arg('files',
342  nargs='*',
343  help='list of files to process')
344 
345  arg("-m", "--min", action="store", type="float",
346  help="only show entries with VMem>MIN in kB")
347 
348  arg("-d", "--diff", action="store_true",
349  help="show difference (only for two files)")
350 
351  arg("-s", "--self", action="store_true",
352  help="use self instead of inclusive VMem for sorting/filtering")
353 
354  arg("-l", "--libself", action="store_true",
355  help="include libraries into self-VMem")
356 
357  arg("-c", "--slice", action="store", default="ini",
358  help="slice to analyze [ini]")
359 
360  arg("-f", "--flat", action="store_true",
361  help="do not indent tree")
362 
363  global opt
364  opt= parser.parse_args()
365 
366  if len(opt.files)==0:
367  parser.print_help()
368  return 1
369 
370  if opt.diff and len(opt.files)!=2:
371  print( "Can only calculate difference if two files are given" )
372  return 1
373 
374  slices = [opt.slice]
375  if opt.slice=='ini':
376  slices += ['cbk', 'dso']
377 
378  # evt slice
379  if opt.slice=='evt':
380  table = []
381  for f in opt.files:
382  table.append(calcEventAvg(readEvents(open(f,'r')))[:])
383 
384  #diff(table, opt, operator.attrgetter('nmalloc'))
385  printTable(table[0], opt)
386  return 0
387 
388  # Read files
389  resTreeList = []
390  for f in opt.files:
391  z = []
392  fstream = pmon_ser.extract_pmon_files(f)['data']
393  getResUser(fstream, z, slices)
394  del fstream
395  resTreeList.append(z[:])
396 
397  # Calculate self-VMem
398  if not opt.libself:
399  children = [SharedLib]
400  else:
401  children = None
402  for r in resTreeList[-1]:
403  r.calcSelf(children)
404 
405  # Diff
406  if len(opt.files)>1:
407 
408  print( '#'*80 )
409  for i,f in enumerate(opt.files):
410  print( "# [%d] %s" % (i+1,f) )
411  if opt.diff:
412  print( "# [3] difference [2]-[1]" )
413  print( '#'*80 )
414 
415  table = [ getCompList(t,[]) for t in resTreeList ]
416  if opt.self:
417  diff(table, opt, operator.attrgetter('dvmem_self'))
418  else:
419  diff(table, opt)
420  return 0
421 
422  # Only one file
423  resTree = resTreeList[0]
424 
425  if opt.min is not None:
426  # Use VMem or self-VMem for filtering
427  def vmem( c ):
428  result = c.dvmem_self if (opt.self is True and hasattr(c,'dvmem_self')) else c.dvmem
429  return result
430  for r in resTree:
431  r.show(lambda c: vmem(c)>opt.min)
432  else:
433  for r in resTree:
434  r.show()
435 
436  return 0
437 
438 
439 if __name__ == "__main__":
440  try:
441  sys.exit(main())
442  except IOError as e:
443  (code, msg) = e
444  if (code==32):
445  pass # ignore broken pipe exception
446  else:
447  raise e
448  except KeyboardInterrupt:
449  sys.exit(1)
pmontree.Resource.dvmem
dvmem
Definition: pmontree.py:22
pmontree.Resource._malloc
_malloc
Definition: pmontree.py:26
pmontree.main
def main()
Definition: pmontree.py:334
pmontree.Resource._vmem
_vmem
Definition: pmontree.py:25
pmontree.getResUser
def getResUser(f, resTree, steps=['ini'], current=None)
Definition: pmontree.py:153
pmontree.Callback.__init__
def __init__(self, name)
Definition: pmontree.py:125
pmontree.SharedLib.__init__
def __init__(self, name)
Definition: pmontree.py:136
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
pmontree.Resource.wrapup
def wrapup(self)
Definition: pmontree.py:44
pmontree.ResUser._show
def _show(self, level=0, showFct=None)
Definition: pmontree.py:58
pmontree.Resource.nmalloc
nmalloc
Definition: pmontree.py:24
pmontree.Comp._node
def _node(self)
Definition: pmontree.py:111
pmontree.ResUser
Definition: pmontree.py:50
pmontree.SharedLib
Definition: pmontree.py:133
pmontree.getCompList
def getCompList(resTree, resList)
Definition: pmontree.py:281
pmontree.Resource.dmalloc
dmalloc
Definition: pmontree.py:23
pmontree.calcEventAvg
def calcEventAvg(comps, sliceObj=slice(None))
Definition: pmontree.py:264
pmontree.ResUser.dvmem_self
dvmem_self
Definition: pmontree.py:56
pmontree.ResUser.calcSelf
def calcSelf(self, children=None)
Definition: pmontree.py:69
pmontree.ResUser._node
def _node(self)
Definition: pmontree.py:75
pmontree.Comp.calcSelf
def calcSelf(self, children=None)
Definition: pmontree.py:100
pmontree.Resource.set
def set(self, line, i)
Definition: pmontree.py:29
pmontree.ResUser.__init__
def __init__(self, name)
Definition: pmontree.py:53
pmontree.ResUser.purge
def purge(self, purgeFct)
Definition: pmontree.py:78
pmontree.Resource
Definition: pmontree.py:18
pmontree.readEvents
def readEvents(f)
Definition: pmontree.py:207
pmontree.ResUser.show
def show(self, showFct=None)
Definition: pmontree.py:72
pmontree.SharedLib.symbol
symbol
Definition: pmontree.py:138
pmontree.diff
def diff(table, opt, attrgetter=operator.attrgetter('dvmem'))
Definition: pmontree.py:295
pmontree.Comp.__init__
def __init__(self, name)
Definition: pmontree.py:96
pmontree.sliceCompIdx
def sliceCompIdx(line)
Definition: pmontree.py:144
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename T::value_type > sorted(T begin, T end)
Helper function to create a sorted vector from an unsorted one.
create_dcsc_inputs_sqlite.arg
list arg
Definition: create_dcsc_inputs_sqlite.py:48
pmontree.Resource.name
name
Definition: pmontree.py:20
pmontree.Resource._nmalloc
_nmalloc
Definition: pmontree.py:27
pmontree.Comp.symbol
symbol
Definition: pmontree.py:98
pmontree.Resource.__init__
def __init__(self, name)
Definition: pmontree.py:19
Trk::open
@ open
Definition: BinningType.h:40
pmontree.Resource.step
step
Definition: pmontree.py:21
pmontree.ResUser.dep
dep
Definition: pmontree.py:55
pmontree.resAvg
def resAvg(res)
Definition: pmontree.py:247
pmontree.Callback
Definition: pmontree.py:122
Muon::print
std::string print(const MuPatSegment &)
Definition: MuonTrackSteering.cxx:28
pmontree.printTable
def printTable(compList, opt)
Definition: pmontree.py:325
pmontree.Comp
Definition: pmontree.py:93
pickleTool.object
object
Definition: pickleTool.py:30
pmontree.SharedLib._node
def _node(self)
Definition: pmontree.py:140
pmontree.Callback._node
def _node(self)
Definition: pmontree.py:129
readCCLHist.float
float
Definition: readCCLHist.py:83