簡介
一幅圖抵得上千言萬語,這在描述復雜的計算機系統時尤為正確。當系統環境變得更加復雜時,用圖將它們表示出來並記入文檔就顯得更加重要。例如,虛擬化技術有很多優點,但它們通常會讓環境變得更加復雜和更難理解。Graphviz 是一個可以創建圖表的靈活應用程序,可以輕松實現腳本化。本文將介紹 Graphviz 的安裝、使用,以及其中使用 DOT 語言的基礎知識,並提供了一些示例腳本。本文有助於您了解 Graphviz 應用程序的基礎知識,以及如何編寫自動創建圖表的腳本。
還有另外一個我覺得更加牛逼的作用,在使用 markdown 時無法直接將圖片直接粘貼上去,還得想辦法將圖片傳到某一個中間服務再引入,有么有?而 graphviz 可以完美的解決這個問題,在 markdown 中也可以直接編寫相應的代碼生成相應的圖表,使用起來相當 easy,越用越 happy。
針對 graphviz 想要了解更加詳細內容,請查看其對對應的 官方網址
安裝 graphviz
Graphviz 是一個開源工具,可以運行在類似於 UNIX® 的大多數平台和 Microsoft® Windows® 之上。graphviz 支持 Windows、Mac OS X、FreeBSD、Solaris、Linux 等多種系統
- Ubuntu 或 Debian 下安裝:
sudo apt-get install graphviz 或 sudo yum install graphviz
- Mac 下安裝使用 brew 命令:
我本人 mac 上安裝的時候並沒有裝 ruby,故需要先安裝 ruby 再進行安裝 graphviz
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null ; brew install caskroom/cask/brew-cask 2> /dev/null brew install graphviz
- Windows 下安裝:
由於本人一直使用的是 mac 和 linux,故此次試驗並沒有在 windows 上做驗證,請查看官方文檔進行安裝。
DOT 語言基礎
安裝 Graphviz 之后,就可以開始使用工具創建圖表,第一步是創建一個 DOT 文件。該 DOT 文件是一個文本文件,描述了圖表的組成元素以及它們之間的關系,以便該工具可以生成這些組成元素和它們之間的關系的圖形化表示。
1. 編寫以 dot 為后綴的源代碼文件 hello.dot,內容如下:
digraph { hello -> world; }
2. 使用 dot 命令編譯
dot hello.dot -T png -o hello.png
完整的命令格式為:
<cmd> <inputfile> -T <format> -o <outputfile>
其中graphviz 的 <cmd>
有好幾種,每種使用方法都完全相同,差別只在於渲染出來的圖片效果不一樣。 man
中的簡介是這樣的
<cmd> | 介紹 |
---|---|
dot | 渲染的圖具有明確方向性。 |
neato | 渲染的圖缺乏方向性。 |
twopi | 渲染的圖采用放射性布局。 |
circo | 渲染的圖采用環型布局。 |
fdp | 渲染的圖缺乏方向性。 |
sfdp | 渲染大型的圖,圖片缺乏方向性。 |
可以透過 man <cmd>
取得進一步說明。但還是親自用用比較容易理解。在本文中,凡沒有說明的圖,預設都是以 dot
渲染出來的。
3. 查看效果
運行 2 中的命令后會生成對應的 png 文件,雙擊打開查看效果如下所示:
基礎應用
1. 編寫一個案例 dot 文件為:example1.dot
graph example1 { Server1 -- Server2 Server2 -- Server3 Server3 -- Server1 }
上述內容中共有三個節點(Server1、Server2 和 Server3)。--
定義了節點之間的聯系。這個使用 DOT 語言的例子說明,Server2、 Server2 連接到 Server3,而 Server3 連接到 Server1。
創建 DOT 文件后,可以運行 dot
命令來生成圖表,如 2 中所示。–
T png
將輸出格式指定為 PNG,而 –o example1.png
指定必須將輸出保存到一個名叫 example1.png 的文件中。
2. 從 DOT 文件生成圖像
$ dot example1.dot –T png –o example1.png
生成的圖表如下:
第一個示例 (example1.dot) 描述了一個無向圖,即用不帶箭頭的直線表示節點之間的聯系的圖。
有向圖不僅能表示節點之間的聯系,而且能用箭頭表示節點之間流動的方向。要創建有向圖,可以在 DOT 文件的第一行上指定 digraph
而非 graph
,並在節點之間使用 ->
,而不是 --
。
3. 生成有向圖,創建 dot 文件:example2.dot
digraph example2 { Server1 -> Server2 Server2 -> Server3 Server3 -> Server1 }
使用 dot 命令生成對應的圖表如下:
你也可以輕松控制圖中每個節點的形狀、顏色和標簽。具體方法是列出每個節點的名稱,然后將選項放在名稱后面的括號中。例如,代碼行
Server1[shape=box, label="Server1\nWebServer", fillcolor="#ABACBA", style=filled]
定義了 Server1 節點應該是一個長方形,有一個 Server1\nWeb Server 標簽(\n 表示一個新行),顏色設為十六進制 (hex) 顏色 #ABACBA,而且該顏色應該填充了節點。
4. 生成有顏色和形狀的圖表
創建 dot 文件:example3.dot
digraph example3 { Server1 -> Server2 Server2 -> Server3 Server3 -> Server1 Server1 [shape=box, label="Server1\nWeb Server", fillcolor="#ABACBA", style=filled] Server2 [shape=triangle, label="Server2\nApp Server", fillcolor="#DDBCBC", style=filled] Server3 [shape=circle, label="Server3\nDatabase Server", fillcolor="#FFAA22", style=filled] }
對應的生成相應圖表如下:
編寫腳本創建 Graphviz 圖表
在了解了 Graphviz DOT 語言的基礎知識之后,您可以開始創建腳本,從而動態創建一個 DOT 文件。這允許您動態創建始終准確且保持最新的圖表。
以下示例是一個 bash shell 腳本(hmc_to_dot.sh),它連接到 Hardware Management Console (HMC),收集托管服務器和邏輯分區 (LPAR) 的相關信息,然后使用這些信息來創建 DOT 輸出。
#!/bin/bash HMC="$1" serverlist=`ssh -q -o "BatchMode yes" $HMC lssyscfg -r sys -F "name" | sort` echo "graph hmc_graph{" for server in $serverlist; do echo " \"$HMC\" -- \"$server\" " lparlist=`ssh -q -o "BatchMode yes" $HMC lssyscfg -m $server -r lpar -F "name" | sort` for lpar in $lparlist; do echo " \"$server\" -- \"$lpar\" " done done echo "}"
通過提供一個 HMC 服務器名稱作為參數傳遞給腳本,便可運行此腳本。該腳本將傳遞的第一個參數設置為 $HMC 變量。設置 $serverlist 變量的方法是連接到 HMC 並獲得該 HMC 控制的所有托管服務器的清單。在這些托管服務器上進行循環,而腳本將為每台托管服務器打印一行 "HMC" -- "server" ,這表明 Graphviz 在每台 HMC 與其托管服務器之間繪制了一條直線。此外針對每台托管服務器,腳本再次連接到 HMC 並獲得該托管系統上的 LPAR 清單,然后通過它們循環打印一行 "server" -- "LPAR"。這表明 Graphviz 在每台托管服務器與其 LPAR 之間都繪制了一條直線。(此腳本要求您在運行腳本的服務器與 HMC 之間設置 Secure Shell (SSH) 密鑰身份驗證)。
命令執行如下:
./hmc_to_dot.sh hmc_name
以上執行對應輸出的內容如下所示:
graph hmc_graph{ "hmc01" -- "test520" "test520" -- "lpar2" "test520" -- "lpar3" "hmc01" -- "test570" "test570" -- "aixtest01" "test570" -- "aixtest02" "test570" -- "aixtest03" "hmc01" -- "test510" "test510" -- "lpar1" }
你可以輕松從腳本生成圖,具體方法是運行以下命令:
./hmc_to_dot.sh hmc_server_name | dot -T png -o hmc_graph.png
運行腳本,該腳本會動態創建 DOT 語言,然后將這些輸出傳遞給 dot 命令,以便讓它創建一個文件名為 hmc_graph.png 的圖表。下圖顯示了創建的圖表。
基礎語法總結(供有識之士參考)
具體的圖說明內容包含屬性(attr)、節點(node)、邊(edge)和子圖(subgraph)說明。
節點屬性如下 :
Name | Default | Values |
---|---|---|
color | black | node shape color |
comment | any string (format-dependent) | |
distortion | 0.0 | node distortion for shape=polygon |
fillcolor | lightgrey/black | node fill color |
fixedsize | false | label text has no affect on node size |
fontcolor | black | type face color |
fontname | Times-Roman | font family |
fontsize | 14 | point size of label |
group | name of node’s group | |
height | .5 | height in inches |
label | node name | any string |
layer | overlay range | all, id or id:id |
orientation | 0.0 | node rotation angle |
peripheries | shape-dependent | number of node boundaries |
regular | false | force polygon to be regular |
shape | ellipse | node shape; see Section 2.1 and Appendix E |
shapefile | external EPSF or SVG custom shape file | |
sides | 4 | number of sides for shape=polygon |
skew | 0.0 | skewing of node for shape=polygon |
style | graphics options, e.g. bold, dotted, filled; cf. Section 2.3 | |
URL | URL associated with node (format-dependent) | |
width | .75 | width in inches |
z | 0.0 | z coordinate for VRML output |
邊框屬性:
Name | Default | Values |
---|---|---|
arrowhead | normal | style of arrowhead at head end |
arrowsize | 1.0 | scaling factor for arrowheads |
arrowtail | normal | style of arrowhead at tail end |
color | black | edge stroke color |
comment | any string (format-dependent) | |
constraint | true | use edge to affect node ranking |
decorate | if set, draws a line connecting labels with their edges | |
dir | forward | forward, back, both, or none |
fontcolor | black | type face color |
fontname | Times-Roman | font family |
fontsize | 14 | point size of label |
headlabel | label placed near head of edge | |
headport | n,ne,e,se,s,sw,w,nw | |
headURL | URL attached to head label if output format is ismap | |
label | edge label | |
labelangle | -25.0 | angle in degrees which head or tail label is rotated off edge |
labeldistance | 1.0 | scaling factor for distance of head or tail label from node |
labelfloat | false | lessen constraints on edge label placement |
labelfontcolor | black | type face color for head and tail labels |
labelfontname | Times-Roman | font family for head and tail labels |
labelfontsize | 14 | point size for head and tail labels |
layer | overlay range | all, id or id:id |
lhead | name of cluster to use as head of edge | |
ltail | name of cluster to use as tail of edge | |
minlen | 1 | minimum rank distance between head and tail |
samehead | tag for head node; edge heads with the same tag are | |
sametail | merged onto the same port | |
style | tag for tail node; edge tails with the same tag are merged onto the same port | |
taillabel | graphics options, e.g. bold, dotted, filled; cf. Section 2.3 | |
tailport | label placed near tail of edge n,ne,e,se,s,sw,w,nw | |
tailURL | URL attached to tail label if output format is ismap | |
weight | 1 | integer cost of stretching an edge |
圖屬性如下:
Name | Default | Values |
---|---|---|
bgcolor | background color for drawing, plus initial fill color | |
center | false | center drawing on page |
clusterrank | local | may be global or none |
color | black | for clusters, outline color, and fill color if fillcolor not defined |
comment | any string (format-dependent) | |
compound | false | allow edges between clusters |
concentrate | false | enables edge concentrators |
fillcolor | black | cluster fill color |
fontcolor | black | type face color |
fontname | Times-Roman | font family |
fontpath | list of directories to search for fonts | |
fontsize | 14 | point size of label |
label | any string | |
labeljust | centered | ”l” and ”r” for left- and right-justified cluster labels, respectively |
labelloc | top | ”t” and ”b” for top- and bottom-justified cluster labels, respectively |
layers | id:id:id… | |
margin | .5 | margin included in page, inches |
mclimit | 1.0 | scale factor for mincross iterations |
nodesep | .25 | separation between nodes, in inches. |
nslimit | if set to f, bounds network simplex iterations by (f)(number of nodes) when setting x-coordinates | |
nslimit1 | if set to f, bounds network simplex iterations by (f)(number of nodes) when ranking nodes | |
ordering | if out out edge order is preserved | |
orientation | portrait | if rotate is not used and the value is landscape, use landscape orientation |
page | unit of pagination, e.g. “8.5,11” | |
pagedir | BL | traversal order of pages |
quantum | if quantum ¿ 0.0, node label dimensions will be rounded to integral multiples of quantum | |
rank | same, min, max, source or sink | |
rankdir | TB | LR (left to right) or TB (top to bottom) |
ranksep | .75 | separation between ranks, in inches. |
ratio | approximate aspect ratio desired, fill or auto | |
remincross | if true and there are multiple clusters, re-run crossing minimization | |
rotate | If 90, set orientation to landscape | |
samplepoints | 8 | number of points used to represent ellipses and circles on output (cf. Appendix C |
searchsize | 30 | maximum edges with negative cut values to check when looking for a minimum one during network simplex |
size | maximum drawing size, in inches | |
style | graphics options, e.g. filled for clusters | |
URL | URL associated with graph (format-dependent) |
具體實例
這里列舉幾個常用到的例子,還有更加詳細的例子還可以參考 官網例子中心
下面是一個二叉樹代碼:
digraph g { node [shape = record,height=.1]; node0[label = "<f0> |<f1> G|<f2> "]; node1[label = "<f0> |<f1> E|<f2> "]; node2[label = "<f0> |<f1> B|<f2> "]; node3[label = "<f0> |<f1> F|<f2> "]; node4[label = "<f0> |<f1> R|<f2> "]; node5[label = "<f0> |<f1> H|<f2> "]; node6[label = "<f0> |<f1> Y|<f2> "]; node7[label = "<f0> |<f1> A|<f2> "]; node8[label = "<f0> |<f1> C|<f2> "]; "node0":f2 -> "node4":f1; "node0":f0 -> "node1":f1; "node1":f0 -> "node2":f1; "node1":f2 -> "node3":f1; "node2":f2 -> "node8":f1; "node2":f0 -> "node7":f1; "node4":f2 -> "node6":f1; "node4":f0 -> "node5":f1; }
python 調用
除了可以使用 dot
文件編寫圖形外,也可以使用python編寫相關的代碼,生成圖形文件, 安裝 python 對應的 graphviz 相應的模塊
pip install pygraphviz
引用 pygraphviz
import pygraphviz as pgv
初始化圖類
G = pgv.AGraph()
增加節點和邊
G.add_node('a') # adds node 'a' G.add_edge('b','c') # adds edge 'b'-'c' (and also nodes 'b', 'c')
設置屬性
G.graph_attr['label'] = 'test graphf' G.node_attr['shape'] = 'circle' G.edge_attr['color'] = 'red'
設置輸出的格式
G.layout() # default to neato G.layout(prog='dot') # use do
輸出到文件
G.draw('file.png') # write previously positioned graph to PNG file G.draw('file.ps',prog='circo') # use circo to position, write PS file
效果如下圖所示:
結束語
由於當今的動態虛擬環境的存在,系統在不斷變化,很難保持文檔與圖表的時效性和准確性。本文介紹了 Graphviz 的基礎知識,以及如何通過編寫腳本,利用 Graphviz 的強大功能來創建最新、最准確的自動化環境圖表。您可以從 cron 運行這些腳本,並將圖表放在 Web 服務器上,這樣就可以在您需要時隨時重新創建圖表,並通過 Web 瀏覽器輕松訪問它們。
總結使用案例
案例一:
digraph startgame { label="游戲資源更新流程" rankdir="TB" start[label="啟動游戲" shape=circle style=filled] ifwifi[label="網絡環境判斷是否 WIFI" shape=diamond] needupdate[label="是否有資源需要更新" shape=diamond] startslientdl[label="靜默下載" shape=box] enterhall[label="進入游戲大廳" shape=box] enterroom[label="進入房間" shape=box] resourceuptodate[label="資源不完整" shape=diamond] startplay[label="正常游戲" shape=circle fillcolor=blue] warning[label="提醒玩家是否更新" shape=diamond] startdl[label="進入下載界面" shape=box] //{rank=same; needupdate, enterhall} {shape=diamond; ifwifi, needupdate} start -> ifwifi ifwifi->needupdate[label="是"] ifwifi->enterhall[label="否"] needupdate->startslientdl[label="是"] startslientdl->enterhall needupdate->enterhall[label="否"] enterhall -> enterroom enterroom -> resourceuptodate resourceuptodate -> warning[label="是"] resourceuptodate -> startplay[label="否"] warning -> startdl[label="確認下載"] warning -> enterhall[label="取消下載"] startdl -> enterhall[label="取消下載"] startdl -> startplay[label="下載完成"] }
案例二:
digraph G{ size = "5, 5";//圖片大小 main[shape=box];/*形狀*/ main->parse; parse->execute; main->init[style = dotted];//虛線 main->cleanup; edge[color = green]; // 連接線的顏色 execute->{make_string; printf}//連接兩個 init->make_string; main->printf[style=bold, label="100 times"];//線的 label make_string[label = "make a\nstring"]// \n, 這個node的label,注意和上一行的區別 node[shape = box, style = filled, color = ".7.3 1.0"];//一個node的屬性 execute->compare; }