今天學學ns-3 Tutorial的第5章Tweaking 5. Tweaking(NS3妙招) 5.1 使用Logging模組
在first.cc指令碼中已經見過使用logging的語句了,下面看看logging子系統的具體情況。
1.Logging 概述
大型系統都有日誌功能,NS-3也不例外。NS-3認為各種資訊層級(例如:error、warning、info、debug…)的日誌都是有用的,所以提供了一個可選的、多層級的訊息日誌系統。日誌既可以完全關閉,也可以按模組啟動或全域啟用。
若要記錄debugging info、warnings、error messages,或你想從你的模型中快速得到輸出結果,日誌系統都是你的首選。目前,有7種層級的日誌訊息: LOG_ERROR,記錄錯誤,對應宏NS_LOG_ERROR LOG_WARN,記錄警告,對應宏NS_LOG_WARN LOG_DEBUG,記錄調試資訊,對應宏NS_LOG_DEBUG LOG_INFO,記錄一般資訊,對應宏NS_LOG_INFO LOG_FUNCTION,記錄被調用函數的資訊。宏NS_LOG_FUNCTION,用於記錄成員函數;宏NS_LOG_FUNCTION_NOARGS,用於記錄靜態函數資訊。 LOG_LOGIC,記錄函數的邏輯過程,對應宏NS_LOG_LOGIC LOG_ALL,記錄所有上面提到的資訊,無對應宏。
還有個無條件日誌,對應宏為NS_LOG_UNCOND
下面我們使用上面的一些知識,來擷取一些first.cc類比過程的有趣細節。
2.啟動Logging
Just to get our bearings,先運行一下原myfirst指令碼:
./waf –run scratch/myfirst
如果想讓UdpEchoClientApplication輸出更多資訊,那麼在命令列下設定環境變數:
export NS_LOG=UdpEchoClientApplication=level_all
再運行指令碼可以得到如下結果:
從上圖中可以看到,UdpEchoClientApplication從建立、配參數、發送、接收,直到最後結束銷毀的方法都被顯示出來了。這些額外的訊息是NS_LOG_FUNCTION層級的,它顯示了函數調用的過程。
在有些情況下,可能並不能從上面的日誌中知曉訊息來源於哪個對象或函數,要弄清到底是誰發出的訊息,可以使用或運算子追加prefix_func到NS_LOG環境變數中,例如:
export ‘NS_LOG=UdpEchoClientApplication=level_all|prefix_func’
注意上面的引號”,或號|,中間不能有空格
白色高亮顯示的部分說明了由send函數發出的1024位元組的資料包。其他所有來自於UdpEchoClientApplication的訊息都追加了一個函數名首碼,這樣就可以知道這個訊息到底是誰發出的了。例如,下面可以類似地查看UdpEchoServerApplication:
export ‘NS_LOG=UdpEchoClientApplication=level_all|prefix_func:UdpEchoServerApplication=level_all|prefix_func ’
注意上面的引號‘’、或運算號|、串連號:
如果你還想查看log訊息產生的時間,那麼可以使用或運算子號追加prefix_time,例如:
export ‘NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time:UdpEchoServerApplication=level_all|prefix_func|prefix_time’
上面圖中高亮顯示的部分就是追加prefix_time後的結果。
ps.令我好奇的是,我在自己電腦上做實驗,在2.00737s 時UdpEchoClientApplication:HandleRead被調用,官方tutorial中的時間也是2.00737s。莫非類比過程用時與主機硬體效能無關。這一點反映了什麼問題,在分析類比結果時應注意什麼,暫時還不知道,先標記一下。
為了看到類比過程中所有的隱藏細節,我們可以設定下列系統變數:
export ‘NS_LOG=*=level_all|prefix_func|prefix_time’
之後運行,將輸出的大量結果儲存到log.out中:
./waf –run scratch/myfirst > log.out 2>&1
註:> log.out 表示把結果輸出到log.out 中; 2>&1表示把標準錯誤輸出重新導向到標準輸出;上面的語句也可以這麼寫:./waf –run scratch/myfirst &> log.out ,即把標準輸出和標準錯誤輸出全都重新導向到file中。
當你不知道類比過程發生了什麼錯誤時,或者想手工排查一下有無問題時,可以考慮使用上面這個設定。
3.在代碼中加如Logging
除了設定命令列環境變數NS_LOG外,在代碼中也可以追加各個層級的日誌。之前在first.cc中有一句:NS_LOG_COMPONENT_DEFINE (“FirstScriptExample”);
例如我們可以在構建節點過程前時,使用NS_LOG_INFO加一些提示訊息:
...NS_LOG_INFO ("Creating Topology");NodeContainer nodes;...
下面修改環境變數,然後執行指令碼:
export NS_LOG=FirstScriptExample=info
./waf –run scratch/myfirst
5.2 使用命令列參數
1. Overriding default attributes
在不改變ns-3指令碼的情況下,改變其行為的方法是使用命令列參數。NS-3設計了一套機制來解析這些參數,並把它們作為全域或局部變數完成設定。
在使用命令列參數系統前,要在指令碼中聲明它。這需要在主函數中寫下面的句子:
intmain (int argc, char *argv[]){...CommandLine cmd;cmd.Parse (argc, argv);...}
這兩行開啟了ns-3全域變數和屬性系統。如果不加上的話,後面的操作是不會有預想結果的:
輸入命令:
./waf –run “scratch/myfirst –PrintHelp”
會顯示有哪些Print命令可用,包括PrintGroups、PrintTypeIds、PrintGroup、PrintAttributes、PrintGlobals
現在我們關注一下PrintAttributes,查看一下ns3::PointToPointNetDevice的屬性有哪些:
./waf –run “scratch/myfirst –PrintAttributes=ns3::PointToPointNetDevice”
這些屬性有一部分我們在程式裡設定過,而另一些還未設定,其值為預設值。例如:DataRate我們在first.cc中設定為5Mbps,而其預設值為32768bps,如果不在指令碼中明確修改該值,那麼它將以預設值運行。實驗就不做了,思路就是去掉相關指令碼語句,然後設定日誌環境變數為
export ‘NS_LOG=UdpEchoServerApplication=level_all|prefix_time’
然後運行程式查看日誌情況,會發現echoServer接收到資料包時的時間變長了(速率慢了)。這裡的重點是要介紹在運行時如何改變屬性的預設值,可以使用下面的命令:
./waf –run “scratch/myfirst –ns3::PointToPointNetDevice::DataRate=5Mbps”
這與在代碼中修改DataRate的效果一樣,但方式不同。下面在看一例,先查看PointToPointChannel的屬性,然後修改其部分屬性:
./waf –run “scratch/myfirst –PrintAttributes=ns3::PointToPointChannel”
./waf –run “scratch/myfirst –ns3::PointToPointNetDevice::DataRate=5Mbps –ns3::PointToPointChannel::Delay=5ms”
上面的命令在修改DataRate後,又修改了通道延遲Delay的值、MaxPackets的值,兩個設定在中間用空格隔開。
./waf –run “scratch/myfirst –ns3::PointToPointNetDevice::DataRate=5Mbps –ns3::PointToPointChannel::Delay=5ms –ns3::UdpEchoClient::MaxPackets=2”
2.hook自己的值
使用AddValue,可以把自己的hook加到命令列中去。下面作為一個例子,我們要使用這個功能設定echo包的發送數量。首先要在first.cc中增加一個局部變數nPckets:
intmain (int argc, char *argv[]){uint32_t nPackets = 1;CommandLine cmd;cmd.AddValue("nPackets", "Number of packets to echo", nPackets);cmd.Parse (argc, argv);...echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));
並使用cmd.AddValue()、cmd.Parse()方法將其加入命令列系統,而且在後面設定MaxPackets屬性的地方使用nPackets替換原有的常量1。
現在運行指令碼,並使用–PrintHelp列印出可設定命令列參數,你會看到Program Argument(老版本的似乎是User argument)那一列中有nPackets。
./waf –run “scratch/myfirst –PrintHelp”
./waf –run “scratch/myfirst –nPackets=2”
從上圖中可以看到,echo發了兩個來回。 5.3 使用Tracing系統
類比程式運行後要得到輸出結果,這是進行類比的目標。NS-3的Tracing系統就是用於產生輸出結果的主要機制。標準的C++輸出有一些問題,所以ns3提供了一套通用時間跟蹤子系統來解決輸出問題。這個系統的目標有3個: 對於基本的任務,Tracing系統允許使用者對一般tracing源產生標準tracing,並且自訂哪個對象產生這些Tracing。 Intermediate users中級使用者必須能夠擴充tracing系統,改變輸出格式,或插入新的Tracing源,同時不改變類比核心模組。 進階使用者能夠修改類比核心模組,增加新的Tracing源和sinks。
NS-3的Tracing系統由相互獨立的Tracing sources和Tracing sinks組成,以及一個串連source和sinks的標準機制。Tracing sources是在類比過程中能夠發出事件訊號並能對相關資料進行訪問的實體。例如,跟蹤源可能指出何時一個資料包被網路裝置接收,而且能對這個包的內容進行訪問。
Tracing sources對其本身而言用處不大,他們必須串連到別的代碼,即利用源提供的資訊完成某些工作sinks上。Tracing sinks是事件和時間的消費者,而Tracing sources是生產者。例如,一個串連到某個source上的sink,可以列印出我們感興趣的已接受資料包。
Tracing Sources與Tracing sinks之間清晰的劃分,有利於使用者將新類型的sinks串連到已存在的sources上,而不用編寫或重新編譯模擬器核心模組。使用者可以在自己的代碼中定義新的sink,然後串連在核心模組定義的source上。下面我們將瀏覽一下預定義的Tracing sources和sinks,以及如何自訂。而《ns-3 manual》一書中有詳細講解。
1.ASCII Tracing
ns3提供了一些工具來封裝底層Tracing系統,使使用者能夠配置一些簡單的包跟蹤。
下面我們試著以ASCII檔案來查看輸出結果。開啟scratch/myfirst.cc中,在Simulator::Run()之前增加下列代碼:
AsciiTraceHelper ascii;pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
ascii.CreateFileStream ()方法將產生一個對象,用於代表檔案myfirst.tr,並將這個對象交給EnableAsciiAll()。EnableAsciiAll()告訴工具你想對point-to-point裝置使用ASCII Tracing,開啟對該裝置的Tracing,並且希望使用trace sinks將有關資料包的資訊以ASCII格式輸出。下面編譯運行一下指令碼:
./waf –run scratch/myfirst
運行後,會發現目前的目錄下出現了myfirst.tr檔案,開啟以後內容如下: 2 /NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue ns3::PppHeader (Point-to-Point Protocol: IP (0x0021)) ns3::Ipv4Header (tos 0x0 DSCP Default ECN Not-ECT ttl 64 id 0 protocol 17 offset (bytes) 0 flags [none] length: 1052 10.1.1.1 > 10.1.1.2) ns3::UdpHeader (length: 1032 49153 > 9) Payload (size=1024) 2 /NodeList/0/DeviceList/0/ ns3::PointToPointNetDevice/TxQueue/Dequeuens3::PppHeader(Point−to−PointProtocol:IP(0x0021))ns3::Ipv4Header(tos0x0DSCPDefaultECNNot−ECTttl64id0protocol17offset(bytes)0f