本文我们主要介绍CentOS平台下通过python和graphviz生成数据结构关系图。
一、前置条件
为使用python和graphviz生成C语言的数据结构关系图,需提前安装好python3,这里不做介绍。这里介绍一下绘图工具graphviz和Linux命令行打开图片的工具eog等。
1、安装绘图工具graphviz
Graphviz(Graph Visualization Software)是一个由AT&T实验室启动的开源工具包,能够支持基于 DOT 脚本,文件扩展名通常是 .gv 或 .dot 的描述绘制图形。DOT 是一种文本图形描述语言,将生成的图形转换成多种输出格式的命令行工具,其输出格式包括PostScript,PDF,SVG,PNG,含注解的文本等。DOT 本身非常原始,提供了一种非常简单的描述图形的方法,同时意味着可以在命令行终端使用,或者被其它编程语言调用(Graphviz 就可以作为一个库使用)。这一点非常关键,基于 Graphviz 应用开发者不必掌握布局的复杂算法,而是可以把精力放在业务方面,将最后的图对象交给绘图引擎来处理即可。
yum install graphviz -y
2、安装命令行图片查看工具
GNOME之眼,图像查看器(eog)是GNOME桌面的官方图像查看器,可以用来在服务器端查看图片。它可以查看各种格式的单个图像文件,以及大型图像集合。eog可以通过插件系统进行扩展。
yum install eog -y
二、生成工具及使用方法
绘制关键数据结构的关联关系图,可以协助我们快速理解组织架构,加速理解代码逻辑;Linux平台下生成C语言数据结构关系图主要基于python+graphviz,python和graphviz工具是基础,需要辅助以python脚本,才能实现分析数据结构并生成用于绘图的dot语言;之后利用graphviz根据上一步中的临时生成文件的dot语言描述绘图。图形保存到xxx.svg文件中,.svg可以使用eog或者浏览器打开。
1、Python脚本分析工具
用于分析结构体关联关系的python脚本(analysis_dt.py),如下:
#!/usr/bin/python3
import os,re
prefix = '''digraph spdk {
graph [
rankdir = "LR"
//splines=polyline
//overlap=false
];
node [
fontsize = "16"
shape = "ellipse"\r
];
edge [
];
'''
middle_str = ''
edge_list = []
edge_string = ''
cur_indentation_level = 0
space4 = ' '
space8 = space4 + space4
space12 = space4 + space8
space16 = space4 + space12
node_database = {}
node_database['created'] = []
color_arrary = ['red', 'green', 'blue', 'black','blueviolet','brown', 'cadetblue','chocolate','crimson','cyan','darkgrey','deeppink',data_struct
with open(r'/tmp/713/data_struct', 'r') as file_input:
tmpline = file_input.readline()
while(tmpline):
tmpline = re.sub(r'([^a-zA-Z0-9]const )', ' ', tmpline)
#for match :struct device {
if re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*\{', tmpline):
m = re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*\{', tmpline)
cur_indentation_level += 1
if (cur_indentation_level == 1):
node_name = m.group(1)
node_str = space4 + '\"' + node_name + '\" [\n' + space8 + 'label = \" '+ node_name +'\l|\n' + space12 + '{|{\n'
node_database['created'].append(node_name)
try:
node_database[node_name]['node_str'] = node_str
except:
node_database[node_name] = {}
node_database[node_name]['node_str'] = node_str
#for match :struct device *parent;
elif re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*(\**)(\s*)([0-9a-zA-Z_\-]+)\s*;', tmpline) and cur_indentation_level > 0:
m = re.search(r'struct\s*([0-9a-zA-Z_\-]+)\s*(\**)(\s*)([0-9a-zA-Z_\-]+)\s*;', tmpline)
member_type = m.group(1)
node_database[node_name]['node_str'] += space16 + '<'+ member_type + '> ' + m.group(2) + m.group(3) + m.group(4) + '\l|\n'
try:
node_database[member_type]['included_by'].append(node_name)
except:
try:
node_database[member_type]['included_by'] = []
node_database[member_type]['included_by'].append(node_name)
except:
node_database[member_type] = {}
node_database[member_type]['included_by'] = []
node_database[member_type]['included_by'].append(node_name)
#print('%s included by %s'%(member_type, node_database[member_type]['included_by']))
if(member_type in node_database['created']):
tmp_edge_str = space4 + node_name + ':' + member_type + ' -> ' + member_type + ':' + 'head'
if not tmp_edge_str in edge_list:
edge_list.append(tmp_edge_str)
#for match : void *driver_data;
elif re.search(r'\s*[0-9a-zA-Z_\-]+\s*(\**[0-9a-zA-Z_\-]+)\s*;', tmpline) and cur_indentation_level > 0:
m = re.search(r'\s*[0-9a-zA-Z_\-]+\s*(\**[0-9a-zA-Z_\-]+)\s*;', tmpline)
node_database[node_name]['node_str'] += space16 + '<'+ m.group(1) + '> ' + m.group(1) + '\l|\n'
#for match:const char *init_name;
elif re.search(r'(.*)\s+(\**)(\s*)([0-9a-zA-Z_\-]+\s*);', tmpline) and cur_indentation_level > 0:
m = re.search(r'(.*)\s+(\**)(\s*)([0-9a-zA-Z_\-]+\s*);', tmpline)
node_database[node_name]['node_str'] += space16 + '<'+ m.group(2) + '> ' + m.group(2) + m.group(3) + m.group(4) + '\l|\n'
#for match:int *(*runtime_idle)(struct device *dev);
elif re.search(r'\s*[0-9a-zA-Z_\-]+\s*\**\s*\(\s*(\**\s*[0-9a-zA-Z_\-]+)\s*\)\s*\([^\)]*\)\s*;', tmpline) and cur_indentation_level > 0:
m = re.search(r'\s*[0-9a-zA-Z_\-]+\s*\**\s*\(\s*(\**\s*[0-9a-zA-Z_\-]+)\s*\)\s*\([^\)]*\)\s*;', tmpline)
node_database[node_name]['node_str'] += space16 + '<'+ m.group(1) + '> (' + m.group(1) + ')\l|\n'
#for match: };
elif re.search(r'\s*\}\s*;', tmpline):
if(cur_indentation_level >= 1):
cur_indentation_level -= 1
if (cur_indentation_level == 0):
node_database[node_name]['node_str'] += space12 + '}}\"\n'
node_database[node_name]['node_str'] += space8 + 'shape = \"record\"\n' + space4 + '];\n'
if 'included_by' in node_database[node_name]:
for parent_node in node_database[node_name]['included_by']:
if parent_node in node_database['created']:
tmp_edge_str = space4 + parent_node + ':' + node_name + ' -> ' + node_name + ':' + 'head'
if not tmp_edge_str in edge_list:
edge_list.append(tmp_edge_str)
tmpline = file_input.readline()
for tmpnode in node_database['created']:
middle_str = middle_str + node_database[tmpnode]['node_str']
for i, tmpstr in enumerate(edge_list):
edge_string += tmpstr + '[color="' + color_arrary[i%len(color_arrary)] + '"]\n'
print(prefix + middle_str + '\n' + edge_string + '}')
2、使用方法
绘制C语言结构体关系图方法和流程如下:
(1)把需要绘制关系图的关键数据结构复制粘贴到一个文本文件data_struct中;
(2)把python脚本中保存数据结构的文件路径(/tmp/713/data_struct )替换为自己的保存数据结构的文件路径(可自行修改脚本,通过参数传入文件路径);
(3)执行命令,自动生成关系图,命令如下:
python3 analysis_dt.py > tmpfile
dot -Tsvg tmpfile -o xxx.svg
其中第一条命令使用python分析数据结构并生成用于绘图的dot语言,第二条命令利用graphviz根据tmpfile中的dot语言描述绘图。图形保存到xxx.svg文件中,xxx可以自行命名;生成的xxx.svg文件可以在服务器的命令行使用eog打开,也可以下载到windows上使用浏览器打开,且可以实现缩放。
注意:这里也可以通过dot命令直接生成图片格式,如下:
dot -Tsvg tmpfile -o xxx.png 即可生成xxx.png图片。
这里以一个简单的结构体文本文件data_struct为Demo,查看其内结构体之间的关系。data_struct文本文件内容如下:
struct ovsdb {
char *name;
struct ovsdb_schema *schema;
struct ovsdb_storage *storage;
struct uuid prereq;
struct ovs_list monitors;
struct shash tables;
struct ovs_list triggers;
bool run_triggers;
bool run_triggers_now;
struct ovsdb_table *rbac_role;
bool need_txn_history;
unsigned int n_txn_history;
unsigned int n_txn_history_atoms;
struct ovs_list txn_history;
size_t n_atoms;
bool is_relay;
struct ovs_list txn_forward_new;
struct hmap txn_forward_sent;
struct ovsdb_compaction_state *snap_state;
};
struct ovsdb_storage {
struct ovsdb_log *log;
struct raft *raft;
char *unbacked_name;
struct ovsdb_error *error;
long long next_snapshot_min;
long long next_snapshot_max;
unsigned int n_read;
unsigned int n_written;
};
struct ovsdb_table {
struct ovsdb_table_schema *schema;
struct ovsdb_txn_table *txn_table;
struct hmap rows;
struct hmap *indexes;
bool log;
};
struct ovsdb_compaction_state {
pthread_t thread;
struct ovsdb *db;
struct json *data;
struct json *schema;
uint64_t applied_index;
struct seq *done;
uint64_t seqno;
uint64_t init_time;
uint64_t thread_time;
};
执行结果如下:
[root@localhost 713]# vi analysis_dt.py
[root@localhost 713]# python3 analysis_dt.py > tmpfile
[root@localhost 713]# ls
analysis_dt.py data_struct tmpfile
[root@localhost 713]# vi tmpfile
[root@localhost 713]# dot -Tsvg tmpfile -o my.svg
[root@localhost 713]# ls
analysis_dt.py data_struct my.svg tmpfile
[root@localhost 713]# eog my.svg
eog打开my.svg文件,结果如下:
图片
根据图片可以看出,成功展现了结构体之间的关系。
三、总结
graphviz很强大,可以用来画各种各样的图,本文只是简单总结一下用于画C语言结构体关系的方法,并做一个记录。小编在看代码的时候,一直想着有什么工具可以实现这个功能,检索了一圈,没找到什么很方便的工具。也有人推荐用draw.io,processon等,但是小编觉得这两个工具画结构体关系图终究是没有这种通过脚本工具的方法方便。因此总结成文,希望对有需要的朋友能够有帮助,也希望有更方便的解决方案的朋友可以反馈给小编,大家一起互帮互助,一起成长。