學習翻譯自:Adding Statistics Collection - OMNeT++ Technical Articles
Part 5 - Adding Statistics Collection
①展示收發包的數量:tictoc14
為了大致了解運行時每個節點收發包的數量,我們給module對應的class添加兩個計數器:numSent、numReceived
class Txc14 : public cSimpleModule { private: long numSent; long numReceived; protected:
它們需要在initialize()中設置為0並且用關鍵字WATCH加以監視,這樣我們就可以在運行時監視其變化了。現在我們可以使用Find/inspect對象對話框來了解有多少包被不同的節點收發了。
打開方式見下邊兩張圖,打開結果其實是一樣的,都是一個 Find Objects對話框。



需要注意的是,在具體的仿真model中,幾乎不可能得到完全相同的數字,我們唯一能確定的就是intuniform()正常工作了。但是實際中仿真,我們可以通過這種方式很快地了解到model中各個節點的狀態。
這些信息在顯示時放在module的icon之上。在display關鍵字中使用t=這個tag指明文本,我們唯一需要修正的是運行時顯示出來的字符串。以下代碼做了這項工作:
void Txc14 :: refreashDisplay() const { char buf[40]; sprintf(buf,"rcvd: %ld sent: %ld",numReceived,numSent); getDisplayString().setTagArg("t",0,buf); }
結果就像下邊這樣:

總結:tictoc14
- 本節的目的是:將每個節點收發包的數量顯示到界面中;
- 存儲收發包數量的變量,在cc文件的simple module類中定義,就像數據都是private權限那樣,這里兩個變量也是private權限:
class Txc14 : public cSimpleModule { private: long numSent; long numReceived;
- 這兩個變量需要在initialize()中初始化,並且用WATCH加以監視:
numSent=0; numReceived=0; WATCH(numSent); WATCH(numReceived);
- 把界面更新的工作放在refreshDisplay中,在我們的程序中,我們只需要在這個函數中寫刷新界面的代碼,而不用在某處調用它,程序運行過程中,IDE會自動調用它刷新整個界面:
void Txc14 :: refreashDisplay() const { char buf[40]; sprintf(buf,"rcvd: %ld sent: %ld",numReceived,numSent); getDisplayString().setTagArg("t",0,buf); }
與之對應的是,這個函數在類中,也應該聲明為const:
virtual void refreshDisplay() const override;
②添加統計信息
之前的仿真Model中我們收集到了一些統計信息,我們可以對其進行一些操作。例如,你可能對消息到底目的地前經歷的平均跳數比較感興趣。
我們將在每個消息到達時,把它所經歷的跳數記錄到一個輸出向量中(例如一系列的(time,value)對,並根據time進行排序)。我們也可以記錄下每個節點的平均值、標准差、最小值、最大值並且在仿真結束時把它們寫入文件中。然后我們就可以用OMNET++ IDE中的工具來分析這個文件。
例如,我們添加一個output vector對象(用來記錄信息,最后把這個對象中的信息保存到Tictoc15-#0.vec中)和一個histogram對象(用於計算平均值等等)到class中:
class Txc15 : public cSimpleModule { private: long numSent; long numReceived; cLongHistogram hopCountStats; cOutVector hopCountVector; protected:
每當一個消息到達目的節點時,我們更新統計信息。把下段代碼加入handleMessage()中:
hopCountVector.record(hopcount);
hopCountStats.collect(hopcount);
hopCountVector.record()函數將數據寫入Tictoc15-#0.vec中。對於大的仿真model和長時間運行之后,vec文件會變的很大。為了解決這種情況,我們可以在ini文件中特別指明開啟/關閉vector,我們也可以指定仿真時間間隔(在時間間隔以外的數據記錄將被丟棄)。
當開啟了新的仿真過程時,已存在的Tictoc15-#0.vec/sca文件就被刪除了。
數值數據(仿真過程中通過histogram對象收集到)只能在finish()函數中手動記錄。finish()方法在仿真成功完成(即它不因錯誤而終止的時候)之后被調用。recordScalar()方法負責把數據寫入到Tictoc15-#0.sca文件中。
void Txc15::finish() { //該方法在OMNET++仿真結束時調用 EV<<"Sent: "<<numSent<<endl; EV<<"Received: "<<numReceived<<endl; EV<<"Hop count, min: "<<hopCountStats.getMin()<<endl; EV<<"Hop count, max: "<<hopCountStats.getMax()<<endl; EV<<"Hop count, mean: "<<hopCountStats.getMean()<<endl; EV<<"Hop count, stddev: "<<hopCountStats.getStddev()<<endl; recordScalar("#sent",numSent); recordScalar("#received",numReceived); hopCountStats.recordAs("hop count"); }
該sca文件將被存儲在result/子目錄下。
我們也可以在仿真過程中顯示出這些數據。為了實現這一目的,右擊一個module,選擇Open Details。在彈出的檢查框中我們可以看到hopCountStats和hopCountVector對象。我們也可以跟進一步查看這兩個對象中記錄的數據,在之前的檢查框中右擊這兩個變量,點擊Open Graphical View:


最開始的圖像是空的,但是在用Fast和Express模式下運行,就能得到用於展示的足夠的數據。一段時間后,我們可以得到以下的圖像:


當我們認為已經收集到了足夠多的數據,我們可以停止仿真然后在線下分析結果文件(Tictoc15-#0.vec和Tictoc15-#0.sca)。我們需要從菜單中選擇Simulate->Conclude Simulation或者點圖標
,這將調用finish()函數並把數據寫入sca文件。
③不修改model完成數據統計:tictoc16
在之前,我們在我們的model中添加了數據統計的代碼,不過在編寫代碼時,我們通常不知道用戶需要哪些數據。
OMNET++提供了額外的機制來記錄數值和事件。所有Model都可以發送攜帶數值或對象signals。Model的創建人員只需要確定發送哪些signals,哪些數據附加到這些signals上,什么時候發送它們。使用者可以監聽那些處理和記錄它們的數據項目的signals。這種方式下的model代碼就不必包含任何完全明了的統計數據,使用者不用看C++代碼就可以自由統計某些信息了。
我們將重寫在上一個例子最后中用以統計數據的代碼,本例中將使用signals。我們可以安全地從我們的model中移除所有與數據統計相關的變量。比如,cOutVector和cLongHistogram就不再需要了。我們只需要一個攜帶了到達目的節點時,消息經歷的hopCount的signal就可以了。
首先,我們需要定義signal,下段代碼中arrivalSignal就是之后要用到的signal變量:
class Txc16 : public cSimpleModule { private: simsignal_t arrivalSignal; protected:
在使用signals前,我們必須注冊所有signals。注冊代碼通常放在initialize()方法中:
void Txc16 :: initialize() { arrivalSignal = registerSignal("arrival")
之后我們就可以發送signal了,本例中的發送時機選擇消息到達目的節點時:
void Txc16 :: handleMessage(cMessage * msg) { TicTocMsg16 *ttmsg = check_and_cast<TicTocMsg16 *>(msg); if(ttmsg->getDestination()==getIndex()){ //消息到達目的地時 int hopcount = ttmsg->getHopCount(); //發送signal emit(arrivalSignal , hopcount); EV<<"Message "<<ttmsg<<" arrived after "<<hopcount<<" hops.\n";
由於我們不用保存任何數據,所以finish()方法可以刪去了,我們再也不用它了。
最后一步就是在NED文件中定義signal了。在NED文件中聲明signals,這樣我們就可以在一個地方了解到所有關於module的信息了。我們可以看到它的parameters、它的input和output gates、signals和它代表的統計數據。
simple Txc16 { parameters: @signal[arrival](type="long"); @statistic[hopCount](title="hop count";source="arrival";record=vector,stats;interpolationmode=none); @display("i=block/routing");
現在我們也可以定義一個需要被默認采集的統計數據。在我們之前的例子中已經收集到了一些關於到達信息的hop count的統計信息(最大值、最小值、均值、總和等),所以讓我們在本例中收集相同的數據。
source關鍵字指明了附加我們的統計數據的signal;record關鍵字告訴我們需要如何處理收到的數據。本例中我們需要將每個值都保存到vector file(vector關鍵字)中,此外還要統計上段中說到的那些統計信息(stats關鍵字)。NED文件寫完之后,我們就完成了我們的model。
現在我們需要查看tic[1] module中關於hopCount的直方圖,此外我們不需要記錄tic 0,1,2的vector data。我們可以不接觸C++和NED文件來實現添加直方圖並且移除不需要的vector的目的,只需要打開ini文件並且修改統計數據的記錄語句:
[Config Tictoc16] network = Tictoc16 **.tic[1].hopCount.result-recording-modes = +histogram **.tic[0..2].hopCount.result-recording-modes = -vector
④添加figures(形式):tictoc17
OMNET++可以在canvas上展示一系列的figures,例如文本、幾何圖形、圖像。這些figures可以是靜態的,也可以根據仿真過程中發生的事件動態變化。本例中,我們展示了靜態描述文本、動態顯示hop count的文本。
我們在ned文件中創建figures,需要在parameters用@figure說明:
network Tictoc17 { parameters: @figure[description](type=text;pos=5,20;font=,,bold; text="Random routing example - displaying last hop count"); @figure[lasthopcount](type=text;pos=5,35;text="last hopCount: N/A");
這里創建了兩個文本figure,它們的名字是description和lasthopcount,並且設置了它們的位置坐標。font參數說明了文本字體,有三個分量——typeface,size,style。這三個分量中的每一個都可以略去,這樣實際中會代之以默認值。本例中我們只是設置了字體的style為bold。
默認情況下lasthopcount中的文本是靜態的,但是當消息到達時需要修改它。要做到這一點,需要修改handleMessage()函數:
if(hasGUI()){ char label[50]; //把last hop count寫為string形式 sprintf(label,"last hopCount = %d",hopcount); //定義一個指向figure的指針 cCanvas * canvas = getParentModule()->getCanvas(); cTextFigure *textFigure = check_and_cast<cTextFigure*>(canvas->getFigure("lasthopcount")); //更新文本 textFigure->setText(label);
}
cc文件中用cTextFigure這個class代表figure。figure types有很多種,所有都是繼承自cFigure的子類。我們在得到hopCount變量之后,即可寫入代碼並更新文本。
對上文代碼的解釋,我們要在network的canvas上畫figures,getParentModule()函數返回這個節點的父module,比如network。getCanvas()函數返回network的canvas,getFigure()可以通過Figure名得到figure。之后我們用setText()函數更新figure文本。
當我們運行仿真時,在第一個消息到達前,figure會顯示“last hopCount:N/A”。之后,每當一個消息到達它的目的地時,這個文本都會更新:

如果對布局不滿意,比如figure文本和節點重疊在一塊了,可以點擊“re-layout”:

總結:tictoc17
- 本例中,我們學習了在界面上動態、靜態顯示文本的方法;
- NED文件中,要先對這兩個文本進行定義,在network下的parameters下,用@figure進行標注:
network Tic17 { parameters: @figure[description](type=text;pos=5,20;font=,,bold; text="Random routing example - displaying last hop count") ; @figure[lasthopcount](type=text;pos=5,35;text="last hopCount: N/A");
這里創建了兩個文本類型的figure,名字分別是description、lasthopcount。這兩個文本在此時是靜態的。
- 對文本進行動態修改的代碼,在handleMessage()中:
if (hasGUI()) { char label[50];//要動態顯示的內容 sprintf(label,"last hopCount = %d",hopcount); //獲取指向canvas的指針 cCanvas * canvas = getParentModuel->getCanvas(); //獲取指向canvas中文本lasthopcount的指針,這個lasthopcount就是之前我們在ned中定義的那個靜態文本 cTextFigure * textFigure = check_and_cast<cTextFigure *>(canvas->getFigure("lasthopcount")); //更新這個文本內容(動態更新) textFigure->setText(label); }
- 在第一個消息到達前,文本是之前的靜態文本;在一個消息到達后,handleMessage處理消息,刷新頁面,更新文本使之變成動態文本。
在最后幾步中,我們收集並且展示了統計數據。下一節中我們將會展示如何在IDE中查看或者分析它們。
