#!/usr/bin/env python
import os, sys, getopt, re

__revision__ = "$Id: hanganalyze.py,v 1.1.1.2 2007/09/05 10:36:17 egor_starostin Exp $"
__version__  = __revision__.split()[2]

def displayHelp(msg):
  if msg:
    print >>sys.stderr, msg
  print >>sys.stderr, "hanganalyze.py v%s, (c) 2005,2007 Egor Starostin" % __version__
  print >>sys.stderr, "usage: hanganalyze.py [-f tracefile.trc] [-h] [-k]"
  print >>sys.stderr, "  -f specify tracefile to parse"
  print >>sys.stderr, "  -h this screen"
  print >>sys.stderr, "  -k keep trace file in {user_dump_dest} directory"

def makeTrcFile():
  prfx = "__hanganalyze%s" % os.getpid()
  cmdfile = open("%s.sql" % prfx,"w")
  cmdfile.write("""set head off
set feedb off
set trims on
spool %s
connect / as sysdba
exec dbms_application_info.set_client_info('hanganalyze.py');
alter session set tracefile_identifier='hanganalyze';
alter session set events 'immediate trace name hanganalyze level 2';
select * from (
select par.value||','||p.spid
from v$parameter par, v$process p, v$session s
where p.addr=s.paddr and par.name='user_dump_dest'
and client_info='hanganalyze.py'
order by last_call_et)
where rownum < 2;
exit
/
""" % prfx)
  cmdfile.close()
  if sys.platform == 'win32':
    os.system("sqlplus -s /nolog @%s.sql >nul" % prfx)
  else:
    os.system("sqlplus -s /nolog @%s.sql >/dev/null" % prfx)
  sout = open("%s.lst" % prfx).readlines()
  os.unlink("%s.sql" % prfx)
  os.unlink("%s.lst" % prfx)
  (udd,spid) = sout[-1].split(',')

  trctail = "%s_hanganalyze.trc" % spid.rstrip("\n")
  tlen = len(trctail)
  files = filter(lambda x: x[-tlen:].lower() == trctail.lower(),os.listdir(udd))
  return ("%s/%s" % (udd,files[0]))

def uniqlist(l):
  h = {}; 
  for e in l: h[e] = 1
  return h.keys()

def parseTrcFile(f):
  stnodespat  = re.compile("^State of nodes")
  instnodes = False
  inhang = {}
  sid = {}
  parent = {}
  child = {}
  for l in open(f):
    l = l.rstrip("\n")
    if stnodespat.search(l):
      instnodes = True
      continue
    if instnodes:
      if l[:10] == '==========':
        instnodes = False
      elif l[0] == '(':
        l = l[1:] # remove (
        l = l[:-2] # remove ):
        fnames = l.split('/')
        sidi = fnames.index('sid')
        seriali = fnames.index('sess_srno')
        statei = fnames.index('state')
      elif l[0] == '[':
        items = l.split('/')
        nodenum = items[0][1:-1] # remove '[' and ']'
        sid[nodenum] = "[%s, %s]" % (items[sidi],items[seriali])
        predecessor = items[fnames.index('predecessor')]
        adjlist = items[fnames.index('[adjlist]')]
        adjlist = [e[1:] for e in adjlist.split(']')[:-1]] # [1][2] => [1,2]
        if items[statei] == 'IN_HANG':
          inhang[nodenum] = adjlist
        else:
          # predecessor
          if predecessor != 'none':
            if not child.has_key(nodenum): child[nodenum] = []
            if not parent.has_key(predecessor): parent[predecessor] = []
            child[nodenum].append(predecessor)
            parent[predecessor].append(nodenum)
          # adjlist
          if adjlist and not parent.has_key(nodenum): parent[nodenum] = []
          for e in adjlist:
            if not child.has_key(e): child[e] = []
            if not parent.has_key(e): parent[e] = []
            child[e].append(nodenum)
            parent[nodenum].append(e)
      else:
        pass # just ignore unknown lines (like 'Dumping Proces information ...')
  for k in parent.keys():
    parent[k] = uniqlist(parent[k])
  for k in child.keys():
    child[k] = uniqlist(child[k])
  print "Results for trace file %s:\n" % f
  return (inhang,sid,parent,child)

def printTree(k,lvl):
  for el in child[k]:
    print '%s%s' % ('  '*(lvl),sid[el])
    if child.has_key(el):
      printTree(el,lvl+1)

#
# begin
#

if __name__ == '__main__':
  inhang = {}
  sid = {}
  parent = {}
  child = {}
  keepFile = False
  try:
    opts, args = getopt.getopt(sys.argv[1:],'hf:k',['help','file=','keep'])
  except getopt.error, msg:
    displayHelp(msg)
    sys.exit(1)

  trcFile = ''
  for opt,val in opts:
    if opt in ('-h','--help'): displayHelp(''); sys.exit(0)
    if opt in ('-k','--keep'): keepFile = True
    if opt in ('-f','--file'): trcFile = val; keepFile = True # don't remove file when we explicitly specify it

  if not trcFile:
    trcFile = makeTrcFile()
  (inhang,sid,parent,child) = parseTrcFile(trcFile)
  if not keepFile:
    os.unlink(trcFile)
  # deadlocks
  if inhang:
    print "-- DEADLOCK(S) [sid, serial#]\n"
    for e in inhang:
      print sid[e],'<->',
      for x in inhang[e]:
        print sid[x],
      print ""
    print "\n"
  else:
    print "-- NO DEADLOCKS\n"
  # locks
  if parent:
    lvl = 0
    print "-- LOCK TREES [sid, serial#]\n"
    for k in [e for e in parent.keys() if parent[e] == []]:
      print '%s%s' % ('  '*lvl,sid[k])
      printTree(k,lvl+1)
      print ""
  else:
    print "-- NO LOCKS\n"

