omnet++:官方文档翻译总结(四)
学习翻译自: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中查看或者分析它们。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性