一、背景
有很多方法可以从一个程序中获取信息。最直接的方法是将信息直接打印到标准输出中,但是随着程序中打印语句数量的增加,处理大量输出的任务将变得越来越复杂。最终,您可能会觉得有必要以某种方式控制正在打印的信息,可能是通过打开和关闭某些类别的打印,或增加或减少您想要的信息量。如果您继续沿着这条路径运行,您可能会发现您已经重新实现了NS_LOG机制(请参见使用日志记录模块)。为了避免这种情况,您首先要考虑的事情之一是使用NS_LOG本身。
如果您发现现有的日志输出中没有您需要的信息,您可以编辑ns-3的核心,并简单地将感兴趣的信息添加到输出流中。这肯定比添加您自己的打印语句要好,因为它遵循ns-3编码约定,并且作为现有核心的补丁可能对其他人有用。
但是,在ns-3中不能保证NS_LOG输出的稳定性,同时,NS_LOG输出仅在调试构建中可用,因此依赖于它会带来性能损失。当然,如果在任何预定义的输出机制中都不存在感兴趣的信息,那么这种方法就会失败。
ns-3提供了一种称为跟踪(Tracing)的机制,它避免了批量输出机制中固有的一些问题。它有几个重要的优点。首先,您可以通过只跟踪您感兴趣的事件来减少您必须管理的数据量(对于大型模拟,将所有内容转储(dumping)到磁盘上进行后处理可能会创建I/O瓶颈)。其次,如果使用此方法,可以直接控制输出的格式,从而避免使用sed、awk、perl或python脚本进行后处理步骤。如果您愿意,可以将您的输出直接格式化为gnuplot可以接受的形式(参见GnuplotHelper)。
二、fourth.cc
由于跟踪(Tracing)系统与属性(Attributes)集成,并且属性与对象(Objects)一起工作,因此必须为跟踪源提供一个ns-3对象。下一个代码片段声明并定义了一个我们可以使用的简单对象:
class MyObject : public Object
{
public:
/**
* Register this type.
* \return The TypeId.
*/
static TypeId GetTypeId()
{
static TypeId tid = TypeId("MyObject")
.SetParent<Object>()
.SetGroupName("Tutorial")
.AddConstructor<MyObject>()
//添加跟踪源
.AddTraceSource("MyInteger",
"An integer value to trace.",
MakeTraceSourceAccessor(&MyObject::m_myInt),
"ns3::TracedValueCallback::Int32");
return tid;
}
MyObject()
{
}
//跟踪值声明
TracedValue<int32_t> m_myInt; //!< The traced value.
};
.AddTraceSource
中的第一个参数是这个跟踪源的名称,使它在配置系统中可见。第二个参数是一个帮助字符串。第三个参数 &MyObject::m_myInt
是添加到类中的跟踪值,它始终是一个类数据成员。最后一个参数用于为正确的回调函数签名生成文档,这对于更一般的回调类型特别有用。
TracedValue<>
声明提供了驱动回调进程的基础架构。每当更改基础值时,跟踪值机制都将同时提供该变量的旧值和新值。
void
IntTrace(int32_t oldValue, int32_t newValue)
{
std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
}
直接对应于回调函数的签名,该函数将在跟踪值更改时被调用。
int
main(int argc, char* argv[])
{
//创建跟踪源所在的MyObject实例
Ptr<MyObject> myObject = CreateObject<MyObject>();
//创建跟踪源和跟踪接收器之间的连接
myObject->TraceConnectWithoutContext("MyInteger", MakeCallback(&IntTrace));
//为m_myInt赋值
myObject->m_myInt = 1234;
return 0;
}
MakeCallback
函数完成了底层的ns-3回调对象的创建,并将其与函数IntTrace关联。
fourth.cc运行结果:
当我们执行代码时,m_myInt
被赋值为1234,跟踪源触发并自动将前后的值提供给跟踪接收器,随后函数IntTrace将其打印到标准输出。
三、Tracing:结合third.cc使用
更典型的情况是,在系统中选择跟踪源时使用配置路径(config path)。
回想一下在third.cc中,【3.3 STA移动情况输出】部分,我们定义了一个跟踪接收器来打印从我们模拟的移动性模型中获得的路线变化信息。
void
CourseChange(std::string context, Ptr<const MobilityModel> model)
{
Vector position = model->GetPosition();
NS_LOG_UNCOND(context <<
" x = " << position.x << ", y = " << position.y);
}
当我们将“CourseChange”跟踪源连接到跟踪接收器时,使用了一个配置路径来指定源:
std::ostringstream oss;
oss << "/NodeList/" << wifiStaNodes.Get(nWifi - 1)->GetId()
<< "/$ns3::MobilityModel/CourseChange";
Config::Connect(oss.str(), MakeCallback(&CourseChange));
四、Tracing的使用方法
4.1 可用的Tracing源
访问ns-3项目网站,查看官方文档,选择了“API文档”,在侧边栏中,选择All TraceSources
,查看并选择可用的源
All TraceSources
4.2 确定配置路径
官方文档链接:Config Paths
此外,可以在命令行输入:(CourseChange可根据实际使用修改)
find . -name '*.cc' | xargs grep CourseChange | grep Connect
得到输出:
4.3 确定回调函数的返回类型和形式化参数
-
回调的返回值将永远是空(viod)。
-
TracedCallback的正式参数列表可以从声明中的模板参数列表中找到,因为声明中的模板参数列表与回调函数的形式化参数之间存在一对一的对应关系。
-
更简单的方法:去官方文档中找
举例:
打开文件:src/mobility/examples/main-random-topology.cc
#include "ns3/core-module.h"
#include "ns3/mobility-module.h"
using namespace ns3;
/**
* Function called when there is a course change
* \param context event context
* \param position a pointer to the mobility model
*/
static void
CourseChange(std::string context, Ptr<const MobilityModel> position)
{
Vector pos = position->GetPosition();
std::cout << Simulator::Now() << ", pos=" << position << ", x=" << pos.x << ", y=" << pos.y
<< ", z=" << pos.z << std::endl;
}
int
main(int argc, char* argv[])
{
···
//将“坐标改变”跟踪源连接到CourseChange函数
Config::Connect("/NodeList/*/$ns3::MobilityModel/CourseChange", MakeCallback(&CourseChange));
···
return 0;
}
CourseChange(std::string context, Ptr<const MobilityModel> position)
形式化参数解读:
- 【参数1:context】由跟踪源代码提供的字符串。
- 【参数2:model】模拟正在改变路线的移动模型。
- 如果回调是使用ConnectWithoutContext进行连接的,则context参数将被省略
4.3.1 从声明中的模板参数列表中找回调函数所需的形参
在main-random-topology.cc文件中include的头文件mobility-model.h(路径:/ns-3-allinone/ns-3.39/src/mobility/model)里可以找到:
ns3::TracedCallback<Ptr<const MobilityModel>> m_courseChangeTrace;
所以回调函数的形参可以配置为:
void
CourseChange(Ptr<const MobilityModel> model)
{
...
}
如果想要context,需要在主函数中调用config::connect,并使用一个回调函数,该函数来接受context字符串,即:
void
CourseChange(std::string context, Ptr<const MobilityModel> model)
{
...
}
如果你想确保你的CourseChangeCallback函数只在本地文件中可见,可以添加关键字static
,即源代码third.cc中使用的:
static void
CourseChange(std::string path, Ptr<const MobilityModel> model)
{
...
}
五、关于模板函数
在src文件夹下打开终端,输入: find . -name '*.h' | xargs grep TracedCallback
,即在.h文件中寻找所有包含字符串TracedCallback
的语句,可以看到traced-callback.h文件中的一系列模板式语句。
并且,mobility-model.h文件中也include了traced-callback.h:
#include "ns3/traced-callback.h"
打开traced-callback.h文件(路径:src/core/model/traced-callback.h),可以看到文档注释:
//An ns3::TracedCallback has almost exactly the same API as a normal ns3::Callback
//but instead of forwarding calls to a single function (as an ns3::Callback normally does),
//it forwards calls to a chain of ns3::Callback.
即TracedCallback与Callback中具有相同的API,但是TracedCallback会将调用转发至回调链,而不是将调用转发到单个函数
traced-callback.h代码片段:
template <typename... Ts>
class TracedCallback
{
···
}
这告诉我们,TracedCallback是一个模板化的类。它有多个可能的类型参数。对比前面提到的声明,即:
在main-random-topology.cc文件中include的头文件mobility-model.h(路径:/ns-3-allinone/ns-3.39/src/mobility/model)里可以找到:
ns3::TracedCallback<Ptr<const MobilityModel>> m_courseChangeTrace;
模板类声明中的类型名Ts对应于上面声明中的Ptr< const MobilityModel >,所有其他类型参数都为默认值。