對於使用網路模擬軟體來做網路效能分析的人而言,步驟通常是先設計出符合自己需要的網路模擬環境,設定其不同的參數,執行模擬,收集結果資料,最後把資料使用圖片或表格把結果呈現出來以方便分析實驗。一般而言,對於ns2的初學者而言,總是會遇到一個問題,就是網路模擬程式跑完後,接下來該如何分析。這是非常重要的一個過程,所以希望ns2的初學者能好好的研究此章節的內容,相信一定會對大家的研究有相當的幫助。
本
節打算以一個簡單的網路環境為範例,介紹如何使用一些工具來分析和呈現模擬結果,這包含了如何去量測End-to-End
Delay、Jitter、Packet Loss、和Throughput。而採用的方法是去分析traffic
trace檔案的方式,這種方法的優點是簡單且不需要去修改到ns2核心的部份,但缺點是若是模擬資料若是太多,traffic
trace的檔案會太大,這樣會增加分析所需要的時間。另外一種方法,是去更改ns2核心,增加或修改一些檔案,把所需要量測的參數直接記錄下來,這種方
法的優點是模擬結束後,所需要量測的數據已經完全記錄下來,但缺點是要動到ns2核心的部分,對於初學者而言,這是一個很大的門檻,這個方法筆者留到後面
的章節在做介紹。
筆
者先對要模擬的環境做一個簡單的介紹。這個網路的環境包含了四個網路節點(n0,n1,n2,n3),如下圖所示。網路節點n0到節點n2之間,和節點
n1到節點n2之間的網路頻寬(bandwidth)是2Mbps,延遲時間(propagation
delay)是10ms。網路拓樸中的頻寬瓶頸是在節點n2到節點n3之間,頻寬為1.7Mbps,延遲的時間為20ms。每個網路節都是採用
DropTail
queue的方式,且在節點n2到節點n3之間的最大佇列長度是10個封包的長度。在節點n0到n3之間會有一條FTP的連線,FTP應用程式是架構在
TCP之上,所以在寫模擬環境的描述語言的時候,必需先建立一條TCP的連線,在來源端n0上使用TCP
agent產生”tcp”來發送TCP的封包;在目的地端n3使用TCPsink
agent產生”sink”來接受TCP的資料、並產生回覆封包(ACK)回傳送端、最後把接收的TCP封包釋放。最後要把這兩個agent連起來
(connect),連線才能建立。若是沒有額外的參數設定,TCP封包的長度為1Kbytes。在這裡順便補充說明一下,對於ns2模擬參數內定值設定
是在ns-allinone-2.27\ns-2.27\tcl\lib目錄下的ns-default.tcl,有想要進一步瞭解的人,可以去查看此檔。
另外,在節點n1到n3之間有一條固定的傳輸速率的連線(Constant
Bit Rate,CBR),CBR應用程式是架構在UDP之上,因此必需在n1使用UDP
agent來產生”udp”用來發送UDP封包,在n3上使用Null
agent來產生”sink”以接收由n1傳送過來的UDP封包,然後把接收的封包釋放。CBR的傳送速度為1Mbps,每一個封包大小為
1Kbytes。CBR是在0.1秒開始傳送,在4.5秒結束傳輸;FTP是在1.0秒開始傳送,4.0秒結束傳輸。
[Simulation
Topology]

[Tcl Script]
|
# 產生一個模擬的物件
set ns [new
Simulator]
#針對不同的資料流定義不同的顏色,這是要給NAM用的
$ns color 1 Blue
$ns color 2 Red
#開啟一個NAM trace
file
set nf [open
out.nam w]
$ns namtrace-all
$nf
#開啟一個trace file,用來記錄封包傳送的過程
set nd [open
out.tr w]
$ns trace-all
$nd
#定義一個結束的程序
proc finish {} {
global ns nf nd
$ns flush-trace
close $nf
close $nd
#以背景執行的方式去執行NAM
exec nam out.nam &
exit 0
}
#產生四個網路節點
set n0 [$ns
node]
set n1 [$ns
node]
set n2 [$ns
node]
set n3 [$ns
node]
#把節點連接起來
$ns duplex-link
$n0 $n2 2Mb 10ms DropTail
$ns duplex-link
$n1 $n2 2Mb 10ms DropTail
$ns duplex-link
$n2 $n3 1.7Mb 20ms DropTail
#設定ns2到n3之間的Queue
Size為10個封包大小
$ns queue-limit $n2 $n3 10
#設定節點的位置,這是要給NAM用的
$ns
duplex-link-op $n0 $n2 orient right-down
$ns
duplex-link-op $n1 $n2 orient right-up
$ns
duplex-link-op $n2 $n3 orient right
#觀測n2到n3之間queue的變化,這是要給NAM用的
$ns duplex-link-op $n2 $n3 queuePos 0.5
#建立一條TCP的連線
set tcp [new Agent/TCP]
$tcp set class_ 2
$ns attach-agent $n0 $tcp
set sink [new Agent/TCPSink]
$ns attach-agent
$n3 $sink
$ns connect $tcp
$sink
#在NAM中,TCP的連線會以藍色表示
$tcp set fid_ 1
#在TCP連線之上建立FTP應用程式
set ftp [new
Application/FTP]
$ftp attach-agent
$tcp
$ftp set type_
FTP
#建立一條UDP的連線
set udp [new
Agent/UDP]
$ns attach-agent
$n1 $udp
set null [new
Agent/Null]
$ns attach-agent
$n3 $null
$ns connect $udp
$null
#在NAM中,UDP的連線會以紅色表示
$udp set fid_ 2
#在UDP連線之上建立CBR應用程式
set cbr [new
Application/Traffic/CBR]
$cbr
attach-agent $udp
$cbr set type_
CBR
$cbr set
packet_size_ 1000
$cbr set rate_
1mb
$cbr set random_
false
#設定FTP和CBR資料傳送開始和結束時間
$ns at 0.1
"$cbr start"
$ns at 1.0
"$ftp start"
$ns at 4.0
"$ftp stop"
$ns at 4.5
"$cbr stop"
#結束TCP的連線(不一定需要寫下面的程式碼來實際結束連線)
$ns at 4.5
"$ns detach-agent $n0 $tcp ; $ns detach-agent $n3 $sink"
#在模擬環境中,5秒後去呼叫finish來結束模擬(這樣要注意模擬環境中
#的5秒並不一定等於實際模擬的時間
$ns at 5.0
"finish"
#執行模擬
$ns run
|

模擬結束後,會產生兩個檔案,一個是out.nam,
這是給NAM用的,用來把模擬的過程用視覺化的方式呈現出來,這可以讓使用者用”看”的方式去瞭解封包傳送是如何從來源端送到接收端。另一個檔案是
out.tr,這個檔案記錄了模擬過程中封包傳送中所有的事件,例如第一筆記錄是一個CBR的封包,長度為1000bytes,在時間0.1秒的時候,從
n1傳送到n2。這個檔案對我們做效能分析很重要,所以要先對這個檔案的格式做仔細的介紹。
|
+ 0.1 1 2
cbr 1000 ------- 2
1.0 3.1 0
0
- 0.1 1 2
cbr 1000 ------- 2
1.0 3.1 0
0
+ 0.108 1
2 cbr 1000 ------- 2
1.0 3.1 1
1
- 0.108 1
2 cbr 1000 ------- 2
1.0 3.1 1
1
r
0.114 1 2
cbr 1000 ------- 2
1.0 3.1 0
0
+ 0.114 2
3 cbr 1000 ------- 2
1.0 3.1 0
0
- 0.114 2
3 cbr 1000 ------- 2
1.0 3.1 0
0
+ 0.116 1
2 cbr 1000 ------- 2
1.0 3.1 2
2
- 0.116 1
2 cbr 1000 ------- 2
1.0 3.1 2
2
r 0.122 1
2 cbr 1000 ------- 2
1.0 3.1 1
1
+ 0.122 2
3 cbr 1000 ------- 2
1.0 3.1 1
1
.................................................................
|

每一筆記錄的開始都是封包事件發生的原因,若是r則表示封包被某個節點所接收,若是+則表示進入了佇列,若是-則表示離開佇列,若是d則表示封包被佇列所丟棄。接著的第二個欄位表示的是事件發生的時間;欄位三和欄位四表示事件發生的地點(從from
node到to node);欄位五表示封包的型態;欄位六是封包的大小,欄位七是封包的旗標標註;欄位八表示封包是屬於那一個資料流;欄位九和欄位十是表示封包的來源端和目的端,這兩個欄位的格式是a.b,a代表節點編號,b表示埠號(port
number);欄位十一表示封包的序號;最後欄位十二表示封包的id。以前面trace file的第一筆為例,意思就是說有一個封包pakcet id為0,資料流id為2,序號為0,長度為1000
bytes,型態為CBR,它是從來源端1.0要到目的地3.1,在時間0.1秒的時候,從節點1進入了節點2的佇列中。
接下來,筆者先簡單介紹awk,然後如何使用awk去分析trace
file,以得到Throughput、Delay、Jitter、和Loss Rate。
[awk]
A.簡介
awk是一種程式語言。
它具有一般程式語言常見的功能。因awk語言具有某些特點,如:使用直譯器(Interpreter)不需先行編譯;變數無型別之分(Typeless),可使用文字當陣列的註標(Associative
Array)等特色。因此,使用awk撰寫程式比起使用其它語言更簡潔便利且節省時間。awk還具有一些內建功能,使得awk擅於處理具資料列(Record),欄位(Field)型態的資料;此外,
awk內建有pipe的功能,可將處理中的資料傳送給外部的 Shell命令加以處理, 再將Shell命令處理後的資料傳回awk程式,這個特點也使得awk程式很容易使用系統資源。
B. awk是如何運作的
為便於解釋awk程式架構,以及相關的術語,筆者就以上面trace
file為例,來加以介紹。
a.名詞定義:
1. 資料列:awk從資料檔上讀取的基本單位,以trace file為例,awk讀入的
第一筆資料列為 ”+ 0.1
1 2 cbr
1000 ------- 2
1.0 3.1 0
0”
第二筆資料列為 “- 0.1
1 2 cbr 1000
------- 2 1.0
3.1 0 0”
一般而言,一筆資料列相當於資料檔上的一行資料。
2. 欄位(Field):為資料列上被分隔開的子字串。
以資料列”+ 0.1
1 2 cbr
1000 ------- 2
1.0 3.1 0
0”為例,
|
一
|
二
|
三
|
四
|
五
|
六
|
七
|
八
|
九
|
十
|
十一
|
十二
|
|
+
|
0.1
|
1
|
2
|
cbr
|
1000
|
-------
|
2
|
1.0
|
3.1
|
0
|
0
|
一般而言是以空白字元來分隔相鄰的欄位。
當awk讀入資料列後,會把每個欄位的值存入欄位變數。
|
欄位變數
|
意義
|
|
$0
|
為一字串, 其內容為目前awk所讀入的資料列.
|
|
$1
|
代表 $0 上第一個欄位的資料.
|
|
$2
|
代表 $0 上第二欄個位的資料.
|
|
……
|
……
|
b.程式主要節構:
Pattern1 {
Actions1 }
Pattern2 {
Actions2 }
……………………………
Pattern3 {
Actions3 }
一般常用”關係判斷式”來當成Pattern。例如:
x > 3 用來判斷變數x是否大於3
x == 5
用來判斷變數x是否等於5
awk提供c語言常見的關係運算元,如:>、<、>=、<=、==、!=等等
Actions是由許多awk指令所構成,而awk的指令與c語言中的指令非常類似。
IO指令:print 、 printf( ) 、getline ......
流程控制指令 : if ( ...) {...} else {…}、 while(…){…} ……
在awk程式的流程為先判斷Pattern的結果,若為真True則執行相對應的Actions,若為假False則不執行相對的Actions。若是處理的過程中沒有Pattern,awk會無條件的去執行Actions。
c.工作流程: 執行awk時, 它會反複進行下列四步驟。
1. 自動從指定的資料檔中讀取一筆資料列。
2. 自動更新(Update)相關的內建變數之值。
3. 逐次執行程式中 所有 的
Pattern { Actions } 指令。
4. 當執行完程式中所有
Pattern { Actions }時,若資料檔中還有未讀取的料,則反覆執行步驟1到步驟4。
awk會自動重覆進行上述的四個步驟,所以使用者不須在程式中寫這個迴圈。
[End-to-End Delay]
筆者把量測CBR封包端點到端點間延遲時間的awk程式,寫在檔案measure-delay.awk檔案中,讀者可以參考此範例,修改成符合讀者需求的程式。
BEGIN {#程式初始化,設定一變數以記錄目前最高處理封包的ID。
highest_packet_id = 0;
}
{ action = $1;
time = $2;
node_1 = $3;
node_2 = $4;
type = $5;
flow_id = $8;
node_1_address = $9;
node_2_address = $10;
seq_no = $11;
packet_id = $12;
#記錄目前最高的packet ID
if ( packet_id > highest_packet_id )
highest_packet_id = packet_id;
#記錄封包的傳送時間
if ( start_time[packet_id] == 0 )
start_time[packet_id] = time;
#記錄CBR (flow_id=2) 的接收時間
if ( flow_id == 2 && action != "d" ) { if ( action == "r" ) { end_time[packet_id] = time;
}
} else {#把不是flow_id=2的封包或者是flow_id=2但此封包被drop的時間設為-1
end_time[packet_id] = -1;
}
}
END {#當資料列全部讀取完後,開始計算有效封包的端點到端點延遲時間
for ( packet_id = 0; packet_id <= highest_packet_id; packet_id++ ) { start = start_time[packet_id];
end = end_time[packet_id];
packet_duration = end - start;
#只把接收時間大於傳送時間的記錄列出來
if ( start < end ) printf("%f %f\n", start, packet_duration); }
}
執行方法: ($為shell的提示符號)
$awk -f measure-delay.awk out.tr
若是要把結果存到檔案,可使用導向的方式。(把結果存到cbr_delay檔案中)
$awk -f measure-delay.awk out.tr > cbr_delay
執行結果:
0.100000 0.038706
0.108000 0.038706
0.116000 0.038706
0.124000 0.038706
0.132000 0.038706
………………………
[Jitter]
Jitter
就是延遲時間變化量delay
variance,由於網路的狀態隨時都在變化,有時候流量大,有時候流量小,當流量大的時候,許多封包就必需在節點的佇列中等待被傳送,因此每個封包從
傳送端到目的地端的時間不一定會相同,而這個不同的差異就是所謂的Jitter。Jitter越大,則表示網路越不穩定。筆者把量測CBR
flow的Jitter的awk寫在檔案measure-jitter.awk內。
BEGIN {
#程式初始化
old_time=0;
old_seq_no=0;
i=0;
}
{
action = $1;
time = $2;
node_1 = $3;
node_2 = $4;
type = $5;
flow_id = $8;
node_1_address = $9;
node_2_address = $10;
seq_no = $11;
packet_id = $12;
#判斷是否為n2傳送到n3,且封包型態為cbr,動作為接受封包
if(node_1==2 && node_2==3 && type=="cbr"
&& action=="r") {
#求出目前封包的序號和上次成功接收的序號差值
dif=seq_no-old_seq_no;
#處理第一個接收封包
if(dif==0)
dif=1;
#求出jitter
jitter[i]=(time-old_time)/dif;
seq[i]=seq_no;
i=i+1;
old_seq_no=seq_no;
old_time=time;
}
}
END {
for (j=1; j <i ;j++)
printf("%d\t%f\n",seq[j],jitter[j]);
}
執行方法: ($為shell的提示符號)
$awk -f measure-jitter.awk out.tr
若是要把結果存到檔案,可使用導向的方式。(把結果存到cbr_jitter檔案中)
$awk -f measure-jitter.awk out.tr > cbr_jitter
執行結果:
1 0.008000
2 0.008000
3 0.008000
4 0.008000
……………………
[另一種計算Jitter的方法---更精確的方式]
|
# ================================================================================
# NormalJitter.awk
# Version now: 0.1
# Last Modified Date: 2004-10-23,19:39:54
# == Usage ==
# awk -f NormalJitter.awk out.tr
# == Programed By ==
# 查輝(ZHA HUI), Wuhan, China, Email: zhahui AT
gmail.com
# == Description ==
# 本awk程式給出了另外一種jitter的計算方法,這種方法中jitter的計算是基于以下公式:
# jitter =((recvtime(j)-sendtime(j))-(recvtime(i)-sendtime(i)))/(j-i),
其中 j>i 。
# == Attention ==
# NormalJitter.awk中關於jitter的計算完全基于柯志亨博士的measure-delay.awk程式中delay的
# 計算。而measure-delay.awk在柯博士網頁中的ns2類比例子中是正確的,但是對于不同的例子需要根
# 據情況進行一定的修改,並可能需要加入某些魯棒性處理代碼(例如對于第一個包的處理,對于丟包的處
# 理等)。
# == Reference ==
#
http://140.116.72.80/~smallko/ns2/ns2.htm
# == Feedback ==
# 如有任何關於本程式jitter計算的問題,請致信
# 柯志亨(ChihHeng, Ke)博士 smallko2001 AT pchome.com.tw 或者與本人聯繫。
# == Acknowledgements ==
# Dr. ChihHeng, Ke provided valuable
documents and awk files upon my requests.
#
================================================================================
BEGIN {
#程式初始化,設定一變數以記錄目前最高處理封包的ID。
highest_packet_id = 0;
}
{
action = $1;
time = $2;
node_1 = $3;
node_2 = $4;
type = $5;
flow_id = $8;
node_1_address = $9;
node_2_address = $10;
seq_no = $11;
packet_id = $12;
#記錄目前最高的packet ID
if ( packet_id >
highest_packet_id ) {
highest_packet_id = packet_id;
}
#記錄封包的傳送時間
if ( start_time[packet_id] == 0
) {
# 記錄下包的seq_no -- ZHA
pkt_seqno[packet_id] = seq_no;
start_time[packet_id] = time;
}
#記錄CBR (flow_id=2) 的接收時間
if ( flow_id == 2 &&
action != "d" ) {
if ( action ==
"r" ) {
end_time[packet_id] = time;
}
} else {
#把不是flow_id=2的封包或者是flow_id=2但此封包被drop的時間設為-1
end_time[packet_id] = -1;
}
}
END {
#
初始化jitter計算所需變量 -- ZHA
last_seqno
= 0;
last_delay
= 0;
seqno_diff
= 0;
#當資料列全部讀取完後,開始計算有效封包的端點到端點延遲時間
for ( packet_id = 0;
packet_id <= highest_packet_id; packet_id++ ) {
start =
start_time[packet_id];
end =
end_time[packet_id];
packet_duration = end - start;
#只把接收時間大於傳送時間的記錄列出來
if (
start < end ) {
# 得到了delay值(packet_duration)后計算jitter -- ZHA
seqno_diff = pkt_seqno[packet_id] - last_seqno;
delay_diff = packet_duration - last_delay;
if
(seqno_diff == 0) {
jitter
=0;
} else {
jitter =
delay_diff/seqno_diff;
}
printf("%f %f\n", start, jitter);
last_seqno = pkt_seqno[packet_id];
last_delay = packet_duration;
}
}
}
|
[Loss]
筆者把量測CBR Packet Loss的情況寫在檔案measure-drop.awk內。
BEGIN {
#程式初始化,設定一變數記錄packet被drop的數目
fsDrops
= 0;
numFs
= 0;
}
{
action = $1;
time = $2;
node_1 = $3;
node_2 = $4;
src = $5;
flow_id = $8;
node_1_address = $9;
node_2_address = $10;
seq_no = $11;
packet_id = $12;
#統計從n1送出多少packets
if
(node_1==1 && node_2==2 && action == "+")
numFs++;
#統計flow_id為2,且被drop的封包
if
(flow_id==2 && action == "d")
fsDrops++;
}
END {
printf("number
of packets sent:%d lost:%d\n", numFs, fsDrops);
}
執行方法: ($為shell的提示符號)
$awk -f measure-drop.awk out.tr
執行結果:
number of packets sent: 550 lost:8
這代表CBR送出了550個封包,但其中8個封包丟掉了。
[Throughput]
筆者把量測CBR Throughput的情況寫在檔案measure-throughput.awk內。在這裡的Throughput是指average
throughput。
BEGIN {
init=0;
i=0;
}
{
action
= $1;
time
= $2;
node_1
= $3;
node_2
= $4;
src
= $5;
pktsize
= $6;
flow_id
= $8;
node_1_address
= $9;
node_2_address
= $10;
seq_no
= $11;
packet_id
= $12;
if(action=="r"
&& node_1==2 && node_2==3 && flow_id==2) {
pkt_byte_sum[i+1]=pkt_byte_sum[i]+
pktsize;
if(init==0)
{
start_time
= time;
init
= 1;
}
end_time[i]
= time;
i
= i+1;
}
}
END {
#為了畫圖好看,把第一筆記錄的throughput設為零,以表示傳輸開始
printf("%.2f\t%.2f\n",
end_time[0], 0);
for(j=1
; j<i ; j++){
th
= pkt_byte_sum[j] / (end_time[j] - start_time)*8/1000;
printf("%.2f\t%.2f\n",
end_time[j], th);
}
#為了畫圖好看,把第後一筆記錄的throughput再設為零,以表示傳輸結束
printf("%.2f\t%.2f\n",
end_time[i-1], 0);
}
執行方法: ($為shell的提示符號)
$awk -f measure-throughput.awk out.tr
若是要把結果存到檔案,可使用導向的方式。(把結果存到cbr_throughput檔案中)
$awk -f measure-throughput.awk out.tr > cbr_throughput
執行結果:
0.14 0.00
0.15 1000.00
0.15 1000.00
0.16 1000.00
……………………
介紹完了如何量測End-to-End Delay、Jitter、Packet
Loss、和Throughput後,最後就是要把量測的數據畫出來。這裡筆者介紹xgraph和gnuplot,但是xgraph畫出來的圖真的有點醜,所以就不仔細介紹。筆者會把重心放在gnuplot。
[xgraph]
在Shell的提示符號後輸入startxwin.bat,接著會出現一個新的視窗,在此視窗輸入xgraph
cbr_delay,就可以把前面所存下來的檔案畫出來。xgraph的運作是把第一排當作x軸的資料,第二排當作是y軸的資料,然後把圖給畫出來。
cbr-delay的圖:

在一剛開始的時候,由於只有CBR的封包,所以End-to-End
Delay Time都是固定的,但在1.0秒後,網路多了FTP的封包,這使得CBR封包和FTP封包必須互相的搶奪網路的資源,因此End-to-End
Delay Time變得不在固定,但等到FTP傳輸結束後,CBR封包的End-to-End Delay Time又變成是固定值了。
cbr-jitter的圖:

Jitter的變化情況跟End-to-End的原因是相同的,都是由於FTP封包的加入才會指得End-to-End
Delay Time會產生變化。
cbr-throughput的圖:

從圖可以很清楚地看出,從0.1秒到4.5秒,CBR的傳輸速率大都維持在1Mbps。
看了上面這三張圖,不知道讀者是否有一種感覺,就是真的有點醜。是不是想換個工具呢?用Excel嗎?筆者認為還是一樣醜,所以筆者強力推薦使用接下來要介紹的gnuplot。
[gnuplot]
A. 簡介
gnuplot 是一個命令導向的交談式繪圖程式(command-driven interactive function plotting program)。使用者輸入的每一項命令,可以逐步設定或
修改繪圖環境。它以圖形表達數據或涵數,使我們可以藉由圖形做更進一步的分析。
B.
如何使用gnuplot ($為cygwin shell的提示符號)
a.
$ startxwin.bat

b. 在新開出來的視窗輸入gnuplot
$gnuplot

c. 執行 GNUPLOT 程式時,GNUPLOT
首先檢查是否設定環境參數 DISPLAY, 若有則依其設定。當其確定為 X 環境時,將輸出模式設定為 X11。筆者以cbr_delay為例,先簡單示範如何把圖給畫出來。畫圖的指令是plot,要畫的檔案cbr_delay。
gnuplot> plot “cbr_delay”

圖是畫出來了,但是筆者要的不是把數據用打點的方式畫出來,而是要把這些點連起來。沒關係,接下來,只要學著修改環境變數,就可以畫出理想的圖了。
C. 修改環境變數
a.座標軸(Axis):繪圖參數在設定座標軸方面的參數可分為變數名稱、標點、網格、顯示範圍、
座標軸顯示方式與顯示與否等六方面的設定。不過筆者只介紹幾個常用的設定,詳細的設定可以參考http://phi.sinica.edu.tw/aspac/reports/94/94002/。
|
功能
|
繪圖參數名稱
|
|
標點設定
|
xtics,ydtics
|
|
網格設定
|
grid
|
|
座標顯示方式
|
logscale
|
|
顯示範圍設定
|
autoscale,xrange,yrange
|
|
座標軸顯示與否
|
xzeroaxis,yzeroaxis
|
&nb