作為一名Linux系統(tǒng)下的C語(yǔ)言開(kāi)發(fā),經(jīng)常需要閱讀源碼,但是有些源碼實(shí)在是難以閱讀,各種龐大的結(jié)構(gòu)體交雜,分分鐘把你繞暈,讓你頭昏眼花,遲遲無(wú)法梳理清楚。這時(shí)候,一個(gè)能夠幫你梳理數(shù)據(jù)結(jié)構(gòu)的工具就顯得極其重要,讓你能夠很清晰的看出各個(gè)數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系。
本文我們主要介紹CentOS平臺(tái)下通過(guò)python和graphviz生成數(shù)據(jù)結(jié)構(gòu)關(guān)系圖。
一、前置條件
為使用python和graphviz生成C語(yǔ)言的數(shù)據(jù)結(jié)構(gòu)關(guān)系圖,需提前安裝好python3,這里不做介紹。這里介紹一下繪圖工具graphviz和Linux命令行打開(kāi)圖片的工具eog等。
1、安裝繪圖工具graphviz
Graphviz(Graph Visualization Software)是一個(gè)由AT&T實(shí)驗(yàn)室啟動(dòng)的開(kāi)源工具包,能夠支持基于 DOT 腳本,文件擴(kuò)展名通常是 .gv 或 .dot 的描述繪制圖形。DOT 是一種文本圖形描述語(yǔ)言,將生成的圖形轉(zhuǎn)換成多種輸出格式的命令行工具,其輸出格式包括PostScript,PDF,SVG,PNG,含注解的文本等。DOT 本身非常原始,提供了一種非常簡(jiǎn)單的描述圖形的方法,同時(shí)意味著可以在命令行終端使用,或者被其它編程語(yǔ)言調(diào)用(Graphviz 就可以作為一個(gè)庫(kù)使用)。這一點(diǎn)非常關(guān)鍵,基于 Graphviz 應(yīng)用開(kāi)發(fā)者不必掌握布局的復(fù)雜算法,而是可以把精力放在業(yè)務(wù)方面,將最后的圖對(duì)象交給繪圖引擎來(lái)處理即可。
yum install graphviz -y
2、安裝命令行圖片查看工具
GNOME之眼,圖像查看器(eog)是GNOME桌面的官方圖像查看器,可以用來(lái)在服務(wù)器端查看圖片。它可以查看各種格式的單個(gè)圖像文件,以及大型圖像集合。eog可以通過(guò)插件系統(tǒng)進(jìn)行擴(kuò)展。
yum install eog -y
二、生成工具及使用方法
繪制關(guān)鍵數(shù)據(jù)結(jié)構(gòu)的關(guān)聯(lián)關(guān)系圖,可以協(xié)助我們快速理解組織架構(gòu),加速理解代碼邏輯;Linux
平臺(tái)下生成C
語(yǔ)言數(shù)據(jù)結(jié)構(gòu)關(guān)系圖主要基于python+graphviz
,python
和graphviz
工具是基礎(chǔ),需要輔助以python
腳本,才能實(shí)現(xiàn)分析數(shù)據(jù)結(jié)構(gòu)并生成用于繪圖的dot
語(yǔ)言;之后利用graphviz
根據(jù)上一步中的臨時(shí)生成文件的dot
語(yǔ)言描述繪圖。圖形保存到xxx.svg
文件中,.svg
可以使用eog
或者瀏覽器打開(kāi)。
1、python腳本分析工具
用于分析結(jié)構(gòu)體關(guān)聯(lián)關(guān)系的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'structs*([0-9a-zA-Z_-]+)s*{', tmpline):
m = re.search(r'structs*([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 = "< head > '+ 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'structs*([0-9a-zA-Z_-]+)s*(**)(s*)([0-9a-zA-Z_-]+)s*;', tmpline) and cur_indentation_level > 0:
m = re.search(r'structs*([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語(yǔ)言結(jié)構(gòu)體關(guān)系圖方法和流程如下:
(1)把需要繪制關(guān)系圖的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)復(fù)制粘貼到一個(gè)文本文件data_struct中;
(2)把python腳本中保存數(shù)據(jù)結(jié)構(gòu)的文件路徑(/tmp/713/data_struct )替換為自己的保存數(shù)據(jù)結(jié)構(gòu)的文件路徑(可自行修改腳本,通過(guò)參數(shù)傳入文件路徑);
(3)執(zhí)行命令,自動(dòng)生成關(guān)系圖,命令如下:
python3 analysis_dt.py > tmpfile
dot -Tsvg tmpfile -o xxx.svg
其中第一條命令使用python分析數(shù)據(jù)結(jié)構(gòu)并生成用于繪圖的dot語(yǔ)言,第二條命令利用graphviz根據(jù)tmpfile中的dot語(yǔ)言描述繪圖。圖形保存到xxx.svg文件中,xxx可以自行命名;生成的xxx.svg文件可以在服務(wù)器的命令行使用eog打開(kāi),也可以下載到windows上使用瀏覽器打開(kāi),且可以實(shí)現(xiàn)縮放。
注意:這里也可以通過(guò)dot命令直接生成圖片格式,如下:
dot -Tsvg tmpfile -o xxx.png
即可生成xxx.png圖片。
這里以一個(gè)簡(jiǎn)單的結(jié)構(gòu)體文本文件data_struct為Demo,查看其內(nèi)結(jié)構(gòu)體之間的關(guān)系。data_struct文本文件內(nèi)容如下:
struct ovsdb {
char *name;
struct ovsdb_schema *schema;
struct ovsdb_storage *storage; /* If nonnull, log for transactions. */
struct uuid prereq;
struct ovs_list monitors; /* Contains "struct ovsdb_monitor"s. */
struct shash tables; /* Contains "struct ovsdb_table *"s. */
/* Triggers. */
struct ovs_list triggers; /* Contains "struct ovsdb_trigger"s. */
bool run_triggers;
bool run_triggers_now;
struct ovsdb_table *rbac_role;
/* History trasanctions for incremental monitor transfer. */
bool need_txn_history; /* Need to maintain history of transactions. */
unsigned int n_txn_history; /* Current number of history transactions. */
unsigned int n_txn_history_atoms; /* Total number of atoms in history. */
struct ovs_list txn_history; /* Contains "struct ovsdb_txn_history_node. */
size_t n_atoms; /* Total number of ovsdb atoms in the database. */
/* Relay mode. */
bool is_relay; /* True, if database is in relay mode. */
/* List that holds transactions waiting to be forwarded to the server. */
struct ovs_list txn_forward_new;
/* Hash map for transactions that are already sent and waits for reply. */
struct hmap txn_forward_sent;
/* Database compaction. */
struct ovsdb_compaction_state *snap_state;
};
struct ovsdb_storage {
/* There are three kinds of storage:
*
* - Standalone, backed by a disk file. 'log' is nonnull, 'raft' is
* null.
*
* - Clustered, backed by a Raft cluster. 'log' is null, 'raft' is
* nonnull.
*
* - Memory only, unbacked. 'log' and 'raft' are null. */
struct ovsdb_log *log;
struct raft *raft;
char *unbacked_name; /* Name of the unbacked storage. */
/* All kinds of storage. */
struct ovsdb_error *error; /* If nonnull, a permanent error. */
long long next_snapshot_min; /* Earliest time to take next snapshot. */
long long next_snapshot_max; /* Latest time to take next snapshot. */
/* Standalone only. */
unsigned int n_read;
unsigned int n_written;
};
struct ovsdb_table {
struct ovsdb_table_schema *schema;
struct ovsdb_txn_table *txn_table; /* Only if table is in a transaction. */
struct hmap rows; /* Contains "struct ovsdb_row"s. */
/* An array of schema- >n_indexes hmaps, each of which contains "struct
* ovsdb_row"s. Each of the hmap_nodes in indexes[i] are at index 'i' at
* the end of struct ovsdb_row, following the 'fields' member. */
struct hmap *indexes;
bool log; /* True if logging is enabled for this table. */
};
struct ovsdb_compaction_state {
pthread_t thread; /* Thread handle. */
struct ovsdb *db; /* Copy of a database data to compact. */
struct json *data; /* 'db' as a serialized json. */
struct json *schema; /* 'db' schema json. */
uint64_t applied_index; /* Last applied index reported by the storage
* at the moment of a database copy. */
/* Completion signaling. */
struct seq *done;
uint64_t seqno;
uint64_t init_time; /* Time spent by the main thread preparing. */
uint64_t thread_time; /* Time spent for compaction by the thread. */
};
執(zhí)行結(jié)果如下:
[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打開(kāi)my.svg文件,結(jié)果如下:
根據(jù)圖片可以看出,成功展現(xiàn)了結(jié)構(gòu)體之間的關(guān)系。
三、總結(jié)
graphviz很強(qiáng)大,可以用來(lái)畫(huà)各種各樣的圖,本文只是簡(jiǎn)單總結(jié)一下用于畫(huà)C語(yǔ)言結(jié)構(gòu)體關(guān)系的方法,并做一個(gè)記錄。小編在看代碼的時(shí)候,一直想著有什么工具可以實(shí)現(xiàn)這個(gè)功能,檢索了一圈,沒(méi)找到什么很方便的工具。也有人推薦用draw.io,processon等,但是小編覺(jué)得這兩個(gè)工具畫(huà)結(jié)構(gòu)體關(guān)系圖終究是沒(méi)有這種通過(guò)腳本工具的方法方便。因此總結(jié)成文,希望對(duì)有需要的朋友能夠有幫助,也希望有更方便的解決方案的朋友可以反饋給小編,大家一起互幫互助,一起成長(zhǎng)。
評(píng)論
查看更多