Создайте граф вызовов для файла с clang

Есть ли способ создать граф вызовов с помощью clang, который может удобно помещаться на странице?

то есть. Дано:

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

I current get enter image description here

используя:

$ 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 может удобно помещаться на странице (любой стандартный размер)?

Примечание: Код для выше взятого из Сгенерируйте граф вызовов для кода на С++

Обновление: Страница настройки = "8.5,11" дает следующее:

enter image description here

Ответ 1

Я думаю, что первое, что нужно сделать, это установить направление графика из нижнего уровня по умолчанию влево-вправо, вставив:

rankdir=LR;

... в верхней части файла .dot, после первого {. Это должно ориентировать график слева направо и тем самым сделать его гораздо более компактным для такого случая, который имеет длинные метки node. Точно как это можно было бы сделать, будет зависеть от формата callgraph.dot, но при условии, что он выглядит примерно так:

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

... то что-то вроде:

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

... выполнит эту работу.

Еще один подход, который я использовал в прошлом, заключается в том, чтобы вставить фиктивные узлы в ребра, чтобы уменьшить количество узлов с одинаковым рангом (и поэтому будет рисоваться в одной строке (с rankdir=TB, которая является по умолчанию) или столбца (с помощью rankdir=LR). Это удобно делать при записи файлов .dot вручную, но сложнее script.

Если вы хотите script вставлять лишние узлы в некоторых ребрах для распространения узлов, которые обычно находятся в одном ранге в нескольких рангах, вы можете сделать это, запустив dot -Tplain для вывода текстового файла *, который включает ( среди прочего) список узлов с координатами X и Y центра каждого node. Затем вы можете использовать gawk для чтения этого списка, найти любую большую группу узлов с той же координатой X (если rankdir=TB) или координатой Y (если rankdir=LR), а затем обработать оригинал .dot, чтобы вставить лишний пуст node до (скажем) половины узлов в этой группе, чтобы группа была распределена по двум рангом, а не по одному. У меня не было случая сделать это сам, хотя.

* См. Emden Gansner, Eleftherios Koutsofios и Stephen North (2006) Рисование графиков с точкой, Приложение B.

EDIT: как автоматически вставлять дополнительные узлы.

Указано .dot файл test1.dot следующим образом:

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

..., который отображает график.

enter image description here

... running 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 script, который я назвал 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, получим теперь:

enter image description here

... который дает нам то, что мы хотим, я думаю.