mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-30 23:53:32 +01:00
bb46630513
These scripts are now a little more bulletproof, cache data a little better, and generate more information. Notably, they search for the vectors or edges to cut that would lower the size of the largest SCC.
220 lines
5.0 KiB
Python
Executable File
220 lines
5.0 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import re
|
|
import sys
|
|
import copy
|
|
import cPickle
|
|
import os
|
|
|
|
class Parser:
|
|
def __init__(self):
|
|
self.calls = {}
|
|
|
|
def enter_func(self, name):
|
|
if self.infunc and not self.extern:
|
|
self.calls.setdefault(self.infunc, set()).update( self.calledfns )
|
|
|
|
self.calledfns = set()
|
|
self.infunc = name
|
|
self.extern = False
|
|
|
|
def parse_callgraph_file(self, inp):
|
|
self.infunc = None
|
|
self.extern = False
|
|
self.calledfns = set()
|
|
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:]:
|
|
with open(fname, 'r') as f:
|
|
p.parse_callgraph_file(f)
|
|
|
|
sys.stdout.flush
|
|
|
|
print "Building callgraph"
|
|
callgraph = p.extract_callgraph()
|
|
|
|
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)
|
|
|
|
data = {
|
|
'callgraph' : callgraph,
|
|
'sccs' : sccs,
|
|
'closure' : closure,
|
|
'bottlenecks' : bottlenecks }
|
|
|
|
with open('callgraph.pkl', 'w') as f:
|
|
cPickle.dump(data, f)
|
|
|
|
|
|
|