12

有没有办法用clang创建一个可以合理地放在页面上的调用图?

即给出:

#include<iostream>
using namespace std;
int main()
{
    int a;
    cin>>a;
    cout<<a;
    cout<<a;
    return 0;
}

我目前得到在此处输入图像描述

通过使用:

$ clang++ main.cpp -S -emit-llvm -o - |
opt -analyze -std-link-opts -dot-callgraph
$ cat callgraph.dot | c++filt |
sed 's,>,\\>,g; s,-\\>,->,g; s,<,\\<,g' |
gawk '/external node/{id=$1}$1!=id' | dot -Tpng -ocallgraph.png

(这似乎需要付出很多努力来做一些我没想到会如此困难的事情)。我想在水平轴上得到一些更合理的东西。Unflatten似乎没有任何影响(至少在这个文件上,在其他文件上似乎影响很小)。

有没有办法确保png生成的文件可以舒适地放在页面上(任何标准尺寸)?

注意:上述代码取自为 C++ 代码生成调用图

更新:设置 page="8.5,11" 给出以下内容:

在此处输入图像描述

4

1 回答 1

6

我认为首先要做的是将图形的方向从默认的从下到上排名设置为从左到右,方法是插入:

rankdir=LR;

... 在文件顶部附近.dot,在第一个{. 这应该使图形从左到右定向,从而使其对于具有长节点标签的这种情况更加紧凑。究竟如何做到这一点取决于callgraph.dot但是的格式,假设它看起来像这样:

digraph G {
    node [shape=rectangle];
    ...

...然后是这样的:

sed 's/digraph G {/digraph G { \n rankdir=LR;/'

......会做的工作。

我过去采用的另一种方法是将虚拟节点插入边缘以减少具有相同等级的节点数量(因此将绘制在同一行(使用rankdir=TB,这是默认值)或列(使用rankdir=LR). 这在.dot手动编写文件时很简单,但很难编写脚本。

如果您想编写脚本在某些边中插入额外的节点以将通常处于相同等级的节点分散到多个等级中,您可以通过运行dot -Tplain以输出纯文本文件* 来执行此操作,其中包括(除其他外)节点列表每个节点中心的 X 和 Y 坐标。然后,您可以使用gawk读取该列表,找到具有相同 X 坐标 (if rankdir=TB) 或 Y坐标 (if ) 的任何大型节点组,rankdir=LR然后处理原始.dot文件以在之前插入一个额外的空白节点(例如)该组中的一半节点,以便该组分布在两个等级而不是一个等级上。不过,我自己没有机会这样做。

*参见 Emden Gansner、Eleftherios Koutsofios 和 Stephen North (2006) 带点的绘图,附录 B。

编辑:如何自动插入额外的节点。

给定一个.dot文件test1.dot如下:

digraph G {
    n1 -> n20 
    n1 -> n21 
    n1 -> n22 
    n20 -> n3 
    n21 -> n3 
    n22 -> n3
}

...这会产生所示的图表。

在此处输入图像描述

...运行dot -Tplain test1.dot >test1.plain给出文件test1.plain

graph 1 2.75 2.5
node n1 1.375 2.25 0.75 0.5 n1 solid ellipse black lightgrey
node n20 0.375 1.25 0.75 0.5 n20 solid ellipse black lightgrey
node n21 1.375 1.25 0.75 0.5 n21 solid ellipse black lightgrey
node n22 2.375 1.25 0.75 0.5 n22 solid ellipse black lightgrey
node n3 1.375 0.25 0.75 0.5 n3 solid ellipse black lightgrey
edge n1 n20 4 1.1726 2.0394 1.0313 1.9019 0.83995 1.7159 0.68013 1.5605 solid black
edge n1 n21 4 1.375 1.9958 1.375 1.8886 1.375 1.7599 1.375 1.6405 solid black
edge n1 n22 4 1.5774 2.0394 1.7187 1.9019 1.9101 1.7159 2.0699 1.5605 solid black
edge n20 n3 4 0.57736 1.0394 0.71875 0.90191 0.91005 0.71592 1.0699 0.56054 solid black
edge n21 n3 4 1.375 0.99579 1.375 0.88865 1.375 0.7599 1.375 0.64045 solid black
edge n22 n3 4 2.1726 1.0394 2.0313 0.90191 1.8399 0.71592 1.6801 0.56054 solid black
stop

因此,我们现在可以一起处理这两个文件。我将为此使用 Python,因为在 Python 中比在 Awk 中更容易做到这一点。为了这个示例,我将等级中的节点数限制为 2,并且我使用了默认从下到上排序定义的等级,而不是我的从左到右排序上面已经建议了。我不确切知道.dot正在输出哪种文件clang,因此可能需要稍微修改此示例以考虑到这一点。

import sys,re;

plain = open(sys.argv[2])
nodesInRank = {}
for line in plain:
    x = line.split()
    rankloc = 3   # rank is in the y column for the vertical case. 
                  # Change this to rankloc = 2 for the horizontal case
    if len(x) > 0 and x[0] == "node":
        nodesInRank[x[rankloc]] = nodesInRank.get(x[rankloc],[]) + [x[1]]

maxNodesInRank = 2
dummies = set()
for n in nodesInRank.values():
    if len(n) > maxNodesInRank:
        dummies = dummies | set(n[:len(n)//2])

dot = open(sys.argv[1])
for line in dot:
    line = line.rstrip()
    line2 = ""
    for d in dummies:
        m = "-> +%s" % (d)
        if re.search(m,line):
            line = re.sub(m,"-> dummy_%s [dir = none]\n dummy_%s -> %s" % (d,d,d),line)
            line2 = '\tdummy_%s [shape=none, width=0, height=0, label=""];' % (d)
    print (line)
    if len(line2) > 0:
        print (line2)

鉴于此 Python 脚本(我已调用breakrank.py),我现在可以将其运行为:

python breakrank.py test1.dot test1.plain >test_dummy.dot

...其中包含以下内容test_dummy.dot

digraph G {
    n1 -> dummy_n20 [dir = none]
 dummy_n20 -> n20
    dummy_n20 [shape=none, width=0, height=0, label=""];
    n1 -> n21
    n1 -> n22
    n20 -> n3
    n21 -> n3
    n22 -> n3
}

如果我们运行它dot,我们现在得到:

在此处输入图像描述

...我想,这给了我们想要的东西。

于 2013-04-25T22:42:59.070 回答