]>
git.proxmox.com Git - mirror_frr.git/blob - python/callgraph-dot.py
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # callgraph json to graphviz generator for FRR
4 # Copyright (C) 2020 David Lamparter for NetDEF, Inc.
11 class FunctionNode(object):
14 def __init__(self
, name
):
16 FunctionNode
.funcs
[name
] = self
26 return '<"%s()" rank=%r>' % (self
.name
, self
.rank
)
28 def define(self
, attrs
):
30 self
.defs
.append((attrs
["filename"], attrs
["line"]))
33 def add_call(self
, called
, attrs
):
34 return CallEdge(self
, called
, attrs
)
44 def unlink(self
, other
):
45 self
.out
= list([edge
for edge
in self
.out
if edge
.o
!= other
])
46 other
.inb
= list([edge
for edge
in other
.inb
if edge
.i
!= other
])
51 return cls
.funcs
[name
]
52 return FunctionNode(name
)
55 class CallEdge(object):
56 def __init__(self
, i
, o
, attrs
):
59 self
.is_external
= attrs
["is_external"]
66 return '<"%s()" -> "%s()">' % (self
.i
.name
, self
.o
.name
)
71 return n
.split(".", 1)[0]
75 def calc_rank(queue
, direction
):
80 elem
= lambda x
: x
.calls()
83 elem
= lambda x
: x
.calld()
88 while len(nextq
) > 0 and cont
:
92 # sys.stderr.write('rank %d\n' % currank)
102 for other
in elem(node
):
105 if other
.rank
is None:
108 rank
= aggr(rank
, other
.rank
+ direction
)
123 class NodeGroup(set):
124 def __init__(self
, members
):
125 super().__init
__(members
)
128 def __init__(self
, graph
, fn
):
138 return '<Graph.Node "%s()"/%d>' % (self
._fn
.name
, len(self
._fns
))
141 return hash(self
._fn
.name
)
144 for called
in self
._fn
.calls():
145 if called
.name
== self
._fn
.name
:
147 if called
.name
in self
._graph
:
148 self
._calls
.add(self
._graph
[called
.name
])
149 self
._graph
[called
.name
]._calld
.add(self
)
151 def unlink(self
, other
):
152 self
._calls
.remove(other
)
153 other
._calld
.remove(self
)
165 def group(self
, members
):
166 assert self
in members
169 for g
in [m
._group
for m
in members
]:
178 if len(pregroups
) == 0:
179 group
= self
._graph
.NodeGroup(members
)
180 self
._graph
._groups
.append(group
)
181 elif len(pregroups
) == 1:
186 self
._graph
._groups
.remove(g
)
187 group
= self
._graph
.NodeGroup(members
)
188 self
._graph
._groups
.append(group
)
194 def merge(self
, other
):
195 self
._fns
.extend(other
._fns
)
196 self
._calls
= (self
._calls | other
._calls
) - {self
, other
}
197 self
._calld
= (self
._calld | other
._calld
) - {self
, other
}
198 for c
in other
._calls
:
201 c
._calld
.remove(other
)
203 for c
in other
._calld
:
206 c
._calls
.remove(other
)
208 del self
._graph
[other
._fn
.name
]
210 def __init__(self
, funcs
):
214 self
[fn
.name
] = self
.Node(self
, fn
)
215 for node
in self
.values():
220 nodes
= list(self
.values())
226 evalset
= set(node
.calls())
229 while prevevalset
!= evalset
:
230 prevevalset
= evalset
233 for evnode
in prevevalset
:
234 inbound
= set(evnode
.calld())
235 if inbound
<= candidates
:
236 candidates
.add(evnode
)
237 evalset |
= set(evnode
.calls()) - candidates
241 # if len(candidates) > 1:
242 # for candidate in candidates:
243 # if candidate != node:
244 # #node.merge(candidate)
245 # if candidate in nodes:
246 # nodes.remove(candidate)
247 node
.group(candidates
)
249 for candidate
in candidates
:
250 if candidate
in nodes
:
251 nodes
.remove(candidate
)
253 def calc_subgraphs(self
):
254 nodes
= list(self
.values())
259 self
._linear
_nodes
= []
262 sys
.stderr
.write("%d\n" % len(nodes
))
270 for calls
in now
.calls():
271 if calls
in down
[node
]:
280 for calld
in now
.calld():
281 if calld
in up
[node
]:
285 common
= up
[node
] & down
[node
]
288 self
._linear
_nodes
.append(node
)
292 self
._subgraphs
.append(sg
)
297 return self
._subgraphs
, self
._linear
_nodes
300 with
open(sys
.argv
[1], "r") as fd
:
305 ("lsp_processq_add", "work_queue_add"): [
308 "lsp_processq_complete",
311 ("mq_add_handler", "work_queue_add"): [
312 "meta_queue_process",
314 ("meta_queue_process", "work_queue_add"): [
315 "meta_queue_process",
317 # bgpd - label pool WQ
318 ("bgp_lp_get", "work_queue_add"): [
321 ("bgp_lp_event_chunk", "work_queue_add"): [
324 ("bgp_lp_event_zebra_up", "work_queue_add"): [
328 ("bgp_process", "work_queue_add"): [
332 ("bgp_add_eoiu_mark", "work_queue_add"): [
337 ("bgp_clear_route_table", "work_queue_add"): [
338 "bgp_clear_route_node",
339 "bgp_clear_node_queue_del",
340 "bgp_clear_node_complete",
343 ("rfapi_close", "work_queue_add"): [
344 "rfapi_deferred_close_workfunc",
346 ("rfapiRibUpdatePendingNode", "work_queue_add"): [
347 "rfapiRibDoQueuedCallback",
348 "rfapiRibQueueItemDelete",
353 for func
, fdata
in data
["functions"].items():
354 func
= nameclean(func
)
355 fnode
= FunctionNode
.get(func
).define(fdata
)
357 for call
in fdata
["calls"]:
358 if call
.get("type") in [None, "unnamed", "thread_sched"]:
359 if call
.get("target") is None:
361 tgt
= nameclean(call
["target"])
362 fnode
.add_call(FunctionNode
.get(tgt
), call
)
363 for fptr
in call
.get("funcptrs", []):
364 fnode
.add_call(FunctionNode
.get(nameclean(fptr
)), call
)
365 if tgt
== "work_queue_add":
366 if (func
, tgt
) not in extra_info
:
368 "%s:%d:%s(): work_queue_add() not handled\n"
369 % (call
["filename"], call
["line"], func
)
373 attrs
.update({"is_external": False, "type": "workqueue"})
374 for dst
in extra_info
[func
, tgt
]:
375 fnode
.add_call(FunctionNode
.get(dst
), call
)
376 elif call
["type"] == "install_element":
377 vty_node
= FunctionNode
.get("VTY_NODE_%d" % call
["vty_node"])
378 vty_node
.add_call(FunctionNode
.get(nameclean(call
["target"])), call
)
379 elif call
["type"] == "hook":
380 # TODO: edges for hooks from data['hooks']
383 n
= FunctionNode
.funcs
385 # fix some very low end functions cycling back very far to the top
387 n
["peer_free"].unlink(n
["bgp_timer_set"])
388 n
["peer_free"].unlink(n
["bgp_addpath_set_peer_type"])
389 if "bgp_path_info_extra_free" in n
:
390 n
["bgp_path_info_extra_free"].rank
= 0
393 n
["zlog_ref"].rank
= 0
394 if "mt_checkalloc" in n
:
395 n
["mt_checkalloc"].rank
= 0
397 queue
= list(FunctionNode
.funcs
.values())
398 queue
= calc_rank(queue
, 1)
399 queue
= calc_rank(queue
, -1)
401 sys
.stderr
.write("%d functions in cyclic set\n" % len(queue
))
409 sys
.stderr
.write("%d groups after automerge\n" % len(graph
._groups
))
413 return n
.startswith("rfapi") or n
.startswith("vnc") or ("_vnc_" in n
)
416 _vncstyle
= ',fillcolor="#ffffcc",style=filled'
417 cyclic_set_names
= set([fn
.name
for fn
in graph
.values()])
419 for i
, group
in enumerate(graph
._groups
):
422 gv_nodes
.append("\tsubgraph cluster_%d {" % i
)
423 gv_nodes
.append("\t\tcolor=blue;")
425 has_cycle_callers
= set(gn
.calld()) - group
427 set([edge
.i
.name
for edge
in gn
._fn
.inb
]) - cyclic_set_names
434 if has_cycle_callers
:
435 style
+= ",color=blue,penwidth=3"
437 style
+= ',fillcolor="#ffeebb",style=filled'
438 etext
+= '<br/><font point-size="10">(%d other callers)</font>' % (
443 '\t\t"%s" [shape=box,label=<%s%s>%s];'
444 % (gn
.name
, "<br/>".join([fn
.name
for fn
in gn
._fns
]), etext
, style
)
446 gv_nodes
.append("\t}")
450 set([edge
.i
.name
for edge
in gn
._fn
.inb
]) - cyclic_set_names
458 style
+= ',fillcolor="#ffeebb",style=filled'
459 etext
+= '<br/><font point-size="10">(%d other callers)</font>' % (
463 '\t"%s" [shape=box,label=<%s%s>%s];'
464 % (gn
.name
, "<br/>".join([fn
.name
for fn
in gn
._fns
]), etext
, style
)
468 for gn
in graph
.values():
469 for calls
in gn
.calls():
470 if gn
._group
== calls
._group
:
472 '\t"%s" -> "%s" [color="#55aa55",style=dashed];' % (gn
.name
, calls
.name
)
477 if len(nn
._group
) > 1:
478 return "cluster_%d" % nn
._group
.num
482 tup
= xname(gn
), calls
.name
483 if tup
[0] != tup
[1] and tup
not in edges
:
484 gv_edges
.append('\t"%s" -> "%s" [weight=0.0,w=0.0,color=blue];' % tup
)
487 with
open(sys
.argv
[2], "w") as fd
:
490 node [fontsize=13,fontname="Fira Sans"];
493 % "\n".join(gv_nodes
+ [""] + gv_edges
)