aboutsummaryrefslogtreecommitdiff
path: root/scripts/maint/analyze_callgraph.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/maint/analyze_callgraph.py')
-rwxr-xr-xscripts/maint/analyze_callgraph.py259
1 files changed, 259 insertions, 0 deletions
diff --git a/scripts/maint/analyze_callgraph.py b/scripts/maint/analyze_callgraph.py
new file mode 100755
index 0000000000..8ce5827f07
--- /dev/null
+++ b/scripts/maint/analyze_callgraph.py
@@ -0,0 +1,259 @@
+#!/usr/bin/python
+
+import re
+import sys
+import copy
+import cPickle
+import os
+
+class Parser:
+ def __init__(self):
+ self.calls = {}
+ self.definedIn = {}
+
+ def enter_func(self, name):
+ if self.infunc and not self.extern and self.calledfns:
+ if self.infunc in self.definedIn:
+ #print "{}: {} or {}?".format(
+ # self.infunc, self.definedIn[self.infunc], self.module)
+ self.definedIn[self.infunc] = 'nil'
+ else:
+ self.definedIn[self.infunc] = self.module
+ self.calls.setdefault(self.infunc, set()).update( self.calledfns )
+
+ self.calledfns = set()
+ self.infunc = name
+ self.extern = False
+
+ def parse_callgraph_file(self, inp, module):
+ self.infunc = None
+ self.extern = False
+ self.calledfns = set()
+ self.module = module
+
+ for line in inp:
+ m = re.match(r"Call graph node for function: '([^']+)'", line)
+ if m:
+ self.enter_func(m.group(1))
+ continue
+ m = re.match(r" CS<[^>]+> calls external node", line)
+ if m:
+ self.extern = True
+ m = re.match(r" CS<[^>]+> calls function '([^']+)'", line)
+ if m:
+ self.calledfns.add(m.group(1))
+ self.enter_func(None)
+
+ def extract_callgraph(self):
+ c = self.calls
+ self.calls = {}
+ return c
+
+
+def transitive_closure(g):
+ passno = 0
+ changed = True
+ g = copy.deepcopy(g)
+ import random
+ while changed:
+ passno += 1
+ changed = False
+ keys = g.keys()
+ idx = 0
+ for k in keys:
+ idx += 1
+ print "Pass %d/?: %d/%d\r" %(passno, idx, len(keys)),
+ sys.stdout.flush()
+ newset = g[k].copy()
+ for fn in g[k]:
+ newset.update(g.get(fn, set()))
+ if len(newset) != len(g[k]):
+ g[k].update( newset )
+ changed = True
+
+ print
+
+ return g
+
+def strongly_connected_components(g):
+ # From https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm, done stupidly.
+ index_of = {}
+ index = [ 0 ]
+ lowlink = {}
+ S = []
+ onStack = set()
+
+ all_sccs = []
+
+ def strongconnect(fn):
+ index_of[fn] = index[0]
+ lowlink[fn] = index[0]
+ index[0] += 1
+ S.append(fn)
+ onStack.add(fn)
+
+ for w in g.get(fn, []):
+ if w not in index_of:
+ strongconnect(w)
+ lowlink[fn] = min(lowlink[fn], lowlink[w])
+ elif w in onStack:
+ lowlink[fn] = min(lowlink[fn], index_of[w])
+
+ if lowlink[fn] == index_of[fn]:
+ this_scc = []
+ all_sccs.append(this_scc)
+ while True:
+ w = S.pop()
+ onStack.remove(w)
+ this_scc.append(w)
+ if w == fn:
+ break
+
+ for v in g.keys():
+ if v not in index_of:
+ strongconnect(v)
+
+ return all_sccs
+
+def biggest_component(sccs):
+ return max(len(c) for c in sccs)
+
+def connection_bottlenecks(callgraph):
+
+ callers = {}
+ for fn in callgraph:
+ for fn2 in callgraph[fn]:
+ callers.setdefault(fn2, set()).add(fn)
+
+ components = strongly_connected_components(callgraph)
+ components.sort(key=len)
+ big_component_fns = components[-1]
+ size = len(big_component_fns)
+
+ function_bottlenecks = fn_results = []
+
+ total = len(big_component_fns)
+ idx = 0
+ for fn in big_component_fns:
+ idx += 1
+ print "Pass 1/3: %d/%d\r"%(idx, total),
+ sys.stdout.flush()
+ cg2 = copy.deepcopy(callgraph)
+ del cg2[fn]
+
+ fn_results.append( (size - biggest_component(strongly_connected_components(cg2)), fn) )
+
+ print
+ bcf_set = set(big_component_fns)
+
+ call_bottlenecks = fn_results = []
+ result_set = set()
+ total = len(big_component_fns)
+ idx = 0
+ for fn in big_component_fns:
+ fn_callers = callers[fn].intersection(bcf_set)
+ idx += 1
+ if len(fn_callers) != 1:
+ continue
+
+ print "Pass 2/3: %d/%d\r"%(idx, total),
+ sys.stdout.flush()
+
+ caller = fn_callers.pop()
+ assert len(fn_callers) == 0
+ cg2 = copy.deepcopy(callgraph)
+ cg2[caller].remove(fn)
+
+ fn_results.append( (size - biggest_component(strongly_connected_components(cg2)), fn, "called by", caller) )
+ result_set.add( (caller, fn) )
+
+ print
+
+ total = len(big_component_fns)
+ idx = 0
+ for fn in big_component_fns:
+ fn_calls = callgraph[fn].intersection(bcf_set)
+ idx += 1
+ if len(fn_calls) != 1:
+ continue
+
+ print "Pass 3/3: %d/%d\r"%(idx, total),
+ sys.stdout.flush()
+
+ callee = fn_calls.pop()
+ if (fn, callee) in result_set:
+ continue
+
+ assert len(fn_calls) == 0
+ cg2 = copy.deepcopy(callgraph)
+ cg2[fn].remove(callee)
+
+ fn_results.append( (size - biggest_component(strongly_connected_components(cg2)), callee, "called by", fn) )
+
+ print
+
+
+ return (function_bottlenecks, call_bottlenecks)
+
+if __name__ == '__main__':
+ p = Parser()
+ for fname in sys.argv[1:]:
+ modname = re.sub(r'.*/', '', fname).replace('.callgraph', '.c')
+ with open(fname, 'r') as f:
+ p.parse_callgraph_file(f, modname)
+
+ sys.stdout.flush()
+
+ print "Building callgraph"
+ callgraph = p.extract_callgraph()
+ inModule = p.definedIn
+
+ print "Deriving module callgraph"
+ modCallgraph = {}
+ for fn in callgraph:
+ fnMod = inModule[fn]
+ for called in callgraph[fn]:
+ try:
+ calledMod = inModule[called]
+ except KeyError:
+ continue
+ modCallgraph.setdefault(fnMod, set()).add(calledMod)
+ del modCallgraph['nil']
+
+ print "Finding strongly connected components"
+ sccs = strongly_connected_components(callgraph)
+
+ print "Finding the transitive closure of the callgraph.."
+ closure = transitive_closure(callgraph)
+
+ print "Finding bottlenecks..."
+ bottlenecks = connection_bottlenecks(callgraph)
+
+ print "Finding module SCCs"
+ modSCCS = strongly_connected_components(modCallgraph)
+
+ print "Finding module TC"
+ modTC = transitive_closure(modCallgraph)
+
+ print "Finding module bottlenecks"
+ modB = connection_bottlenecks(modCallgraph)
+
+ data = {
+ 'callgraph' : callgraph,
+ 'sccs' : sccs,
+ 'closure' : closure,
+ 'bottlenecks' : bottlenecks,
+ 'modules' : p.definedIn,
+ 'modItems' : {
+ 'callgraph' : modCallgraph,
+ 'sccs' : modSCCS,
+ 'closure' : modTC,
+ 'bottlenecks' : modB,
+ }
+ }
+
+ with open('callgraph.pkl', 'w') as f:
+ cPickle.dump(data, f)
+
+
+