src/core/tracing.h
author Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
Thu, 30 Aug 2007 15:45:46 +0200
changeset 1407 853d1696aece
parent 1398 607b6e86e143
child 1408 3ddff4d9b27c
permissions -rw-r--r--
add section on TraceContexts in tutorial

/**
 * \defgroup TraceSourceList List of trace sources
 */

/**
 * \defgroup tracing Tracing
 *
 * The flexibility of the ns-3 tracing system comes at the cost of quite
 * a bit of complexity so, before trying to use the low-level aspects
 * of the tracing API, it is important to focus on some basic definitions:
 *
 * - A trace source is an object instance which can report trace events
 *   to a set of listening trace sinks.
 *
 * - A trace sink is a user-provided callback (a function) which can
 *   be connected to a set of trace sources to receive the events generated
 *   by each trace source.
 *
 * - A trace resolver is an object which allows users to establish 
 *   connections between a set of trace sources and a set of trace sinks.
 *
 * So, what does it look like in practice ? First, let's look at trace
 * sources. We have two types of trace sources: numeric, and, normal 
 * trace sources. Numeric trace sources behave as normal c++ integers 
 * or c++ floating point numbers except that they report as trace events 
 * each change of their value. For example:
 * \code
 * class MyModel 
 * {
 * public:
 *   void DoSomething (void) 
 *   {
 *     // use the "int" trace source just 
 *     // like any other "int" variable.
 *     m_cwnd *= 2;
 *     m_cwnd += 4;
 *     if (m_cwnd > 100)
 *       {
 *         // do something.
 *       }
 *   }
 * private:
 *   // declare an instance of a "int" trace source
 *   SVTraceSource<int> m_cwnd;
 * };
 * \endcode
 * Normal trace sources, on the other hand, allow you to trace the
 * call of arbitrary functions and methods, as shown below. They are
 * typically used to track "rx", "tx", or "drop" events but could
 * also be used to track route change events, or position change
 * events:
 * \code
 * class MyModel 
 * {
 * public:
 *   void DoSomething (Packet packet, double value) 
 *   {
 *     m_doSomething (packet, value);
 *     // do something
 *   }
 * private:
 *   // report every "something" function call.
 *   CallbackTraceSource<Packet,double> m_doSomething;
 * };
 * \endcode
 * Every type of trace source derives from the ns3::TraceSource base class.
 * As of today, the set of concrete subclasses is relatively short:
 * ns3::CallbackTraceSource, ns3::SvTraceSource, ns3::UvTraceSource, and,
 * ns3::FvTraceSource.
 *
 * To receive these trace events, a user should specify a set of trace sinks.
 * For example, to receive the "int" and the "something" events shown in the
 * examples above, a user would declare the following functions:
 * \code
 * // oldValue and newValue contain the previous and new values of 
 * // the connected SVTraceSource<int> trace source.
 * void 
 * CwndTraceSink (const TraceContext &context, int64_t oldValue, int64_t newValue)
 * {
 *   // for example, print the new value:
 *   std::cout << "cwnd=" << newValue << std::endl;
 * }
 * void 
 * DoSomethingTraceSink (const TraceContext &context, Packet packet, double value)
 * {
 *   // for example, print the arguments
 *   std::cout << "value=" << value << ", packet " << packet << std::endl;
 * }
 * \endcode
 * Each of these sink function takes, as a first argument, a reference to a 
 * const TraceContext object. This context object contains information which
 * describes the instance of the connected trace source: that information is
 * setup during the connection process and does not change afterwards
 * The type and the number of the other arguments to each trace sink depends
 * on the type of the connected trace source: it conveys per-event information
 * from the trace source to the trace sink. For example, UVTraceSource and 
 * SVTraceSource trace sources require two extra arguments. The former requires
 * two unsigned 64 bit integers while the latter requires two signed 64 bit 
 * integers. More generally, users can consult the \ref TraceSourceList
 * to figure out the arguments which a trace sink is required to receive
 * for each trace source.
 *
 * Since there is no working magic here, defining a trace sink does not connect
 * it directly to a set of trace sources. To connect a trace sink, a user must call
 * ns3::NodeList::Connect. The second argument is a callback to the user's trace sink.
 * That callback is easy to construct: call ns3::MakeCallback and you are done. The
 * first argument is a string whose format is similar to a unix path and which is 
 * used to uniquely identify the set of trace sources you want to connect to.
 * The set of acceptable path strings is also documented in the \ref TraceSourceList.
 *
 * So, what does this look like from the perspective of a user ? If we wanted to 
 * connect to a trace source defined somewhere deep into the a set of NetDevice objects
 * located in some nodes of the system, we could write the following:
 * \code
 * void 
 * DoSomethingTraceSink (const TraceContext &context, Packet packet)
 * {
 *   // for example, print the arguments
 *   std::cout << "packet: " << packet << std::endl;
 * }
 * // connect the above sink to a matching trace source
 * NodeList::Connect ("/nodes/* /devices/* /rx", MakeCallback &DoSomethingTraceSink);
 * \endcode
 *
 * The connection path string "/nodes/* /devices/* /rx" matches the "rx" trace source
 * located in every netdevice located in every node. The syntax of that path string
 * is loosely based on regular expressions so, a user could conceivably connect
 * to the trace sources present in only one node identified by node index:
 * "/nodex/3/devices/* /rx".
 *
 * The matching algorithm used here is very useful since it allows you to connect
 * at once a large set of trace sources to a single sink but it introduces another 
 * problem: it becomes impossible when you receive an event in your trace sink to
 * know from _which_ trace source the event is coming from. In our example, the
 * trace source might be coming from the NetDevice number 2 of Node 10 or Netdevice
 * number 0 of Node 5. In both cases, you might need to know which of these NetDevice
 * is generating this event, if only to generate some ascii trace dump.
 *
 * It turns out that there are many ways to get this information. The simplest
 * way to get this information is to use the builtin printing facility of
 * the TraceContext object:
 * \code
 * void 
 * DoSomethingTraceSink (const TraceContext &context, Packet packet)
 * {
 *   // for example, print the arguments
 *   std::cout << "context=\"" << context << "\" packet: " << packet << std::endl;
 * }
 * \endcode
 * The above code is going to generate output which looks like the following:
 * \code
 * context="nodeid=2 device=0 dev-rx" packet: IPV4(tos 0x0 ttl 64 id 0 offset ...
 * context="nodeid=1 device=0 dev-rx" packet: IPV4(tos 0x0 ttl 64 id 0 offset ...
 * ...
 * \endcode
 *
 * Another more advanced way to get information out of a TraceContext is to call its
 * ns3::TraceContext::GetElement method. This method takes as its first and only
 * argument an instance of the object we want to read and the list of available
 * object instances we can read from a TraceContext is documented, once again,
 * in the \ref TraceSourceList. For example, we could write the following to
 * generate adhoc trace output:
 * \code
 * void DeviceRxSink (const TraceContext &context, const Packet &packet)
 * {
 *   NodeListIndex nodeIndex;
 *   NodeNetDeviceIndex deviceIndex;
 *   context.GetElement (nodeIndex);
 *   context.GetElement (deviceIndex);
 *   std::cout << "node-index=" << nodeIndex.Get ();
 *   std::cout << ", device-index=" << deviceIndex.Get ();
 *   std::cout << ", packet: " << packet;
 *   std::cout << std::endl;
 * }
 * \endcode
 *
 * The hard part of this tracing framework is the "connection" step: there is a point
 * in the simulation scenario where the user is expected to specify which trace sources
 * should be connected to which trace sinks. There are many ways to do this: the
 * users who want to could implement the "quick and dirty" way, that is, they could
 * write adhoc code to connect their trace sinks to the trace sources using the 
 * TraceSource::AddCallback method. For example, they could patch their models to
 * the following:
 * \code
 * class MyModel 
 * {
 * public:
 *   void DoSomething (void) 
 *   {
 *     // ...
 *   }
 *   SVTraceSource<int> *GetCwndTraceSource (void) const
 *   {
 *     return &m_cwnd;
 *   }
 * private:
 *   // declare an instance of a "int" trace source
 *   SVTraceSource<int> m_cwnd;
 * };
 * \endcode
 * And, then, call directly the GetCwndTraceSource method:
 * \code
 * CwndTraceSink (const TraceContext &context, int64_t oldValue, int64_t newValue)
 * {
 *   // for example, print the new value:
 *   std::cout << "cwnd=" << newValue << std::endl;
 * }
 * // create a model instance
 * MyModel *model = ...;
 * SVTraceSource<int> *cwnd = model->GetCwndTraceSource ();
 * // connect the trace sink to the cwnd trace source of
 * // this model instance.
 * cwnd->AddCallback (MakeCallback (&CwndTraceSink), 
 *                    TraceContext ());
 * \endcode
 * 
 * The solution described above is simple to implement for a model
 * author but it is hard to extend and becomes quickly cumbersome
 * to use with complex models made of composite objects. TraceResolvers
 * deal with these problems and offer a simple API to connect large
 * sets of trace sources to a single sink (as is typical for simulations
 * where users want to receive the "ipv4 rx" events from all nodes).
 *
 * The user-visible API to connect and disconnect trace sources to
 * and from trace sinks is quite small: ns3::Object::Connect
 * and ns3::Object::Disconnect both take a "namespace" regexp string
 * and a callback. The input callback is connected to each trace source
 * which matches the namespace regexp string. The format of the namespace
 * string depends on the set of models implemented by the simulator. 
 * The following diagram shows a small part of the namespace exported
 * by the current version of the simulator:
 *
 * \image html namespace-2.png ns-3 namespace
 * 
 * In this namespace, the "rx" trace source of the PointToPointNetdevice 
 * index 0 within node index 3 is uniquely identified by the namespace
 * string "/nodes/3/devices/0/rx". It is also possible to match all
 * such "rx" trace sources with a single namespace string using 
 * a limited form of regular expressions: "/nodes/3/devices/* /rx" 
 * identifies the "rx" trace source within all NetDevices within node
 * index 3. Similarly, "/nodes/* /devices/* /rx" identifies the "rx"
 * trace source within all NetDevices within all nodes. It is thus
 * possible to connect a single trace sink to a set of matching trace
 * sources in a single operation:
 * \code
 * void DeviceRxSink (const TraceContext &context, const Packet &packet)
 * {
 *   // context contains enough information to uniquely identify
 *   // the trace source instance.
 *   std::cout << "context: \"" << context << "\"";
 *   // packet is the per-event information conveyed from the
 *   // trace source to the trace sink.
 *   std:: cout << " packet: " << packet << std::endl;
 * }
 * NodeList::Connect ("/nodes/* /devices/* /rx", MakeCallback (&DeviceRxSink));
 * \endcode
 * Which, at runtime, is going to generate output looking like this:
 * \code
 * context: "nodeid=2 device=0 dev-rx" packet: IPV4(tos 0x0 ttl 64 id 0 offset ...
 * context: "nodeid=1 device=0 dev-rx" packet: IPV4(tos 0x0 ttl 64 id 0 offset ...
 * ...
 * \endcode
 * In the example above, we see that the ns3::TraceContext contains three 
 * ns3::TraceContextElement which were printed using a space separator:
 *  - nodeid=i
 *  - device=j
 *  - dev-rx
 *
 * Of course, the user could also extract each of these elements from
 * the context to generate a different output:
 * \code
 * void DeviceRxSink (const TraceContext &context, const Packet &packet)
 * {
 *   NodeListIndex nodeIndex;
 *   NodeNetDeviceIndex deviceIndex;
 *   context.Get (nodeIndex);
 *   context.Get (deviceIndex);
 *   std::cout << "node-index=" << nodeIndex.Get ();
 *   std::cout << ", device-index=" << deviceIndex.Get ();
 *   std::cout << ", packet: " << packet;
 *   std::cout << std::endl;
 * }
 * NodeList::Connect ("/nodes/* /devices/* /rx", MakeCallback (&DeviceRxSink));
 * \endcode
 * Extracting TraceContextElement objects from a TraceContext in this manner
 * raises a few obvious questions:
 *   - how do I know which trace context elements are present in a given
 *     TraceContext ?
 *   - how are these elements added to the TraceContext ?
 *
 * 
 *
 * 
 * Connecting trace sinks to a set of existing trace sources is nice but
 * model developers also often need to be able to create new trace sources
 * and hook them into the namespace resolution system. Creating new trace
 * sources is pretty easy: it is a matter of instantiating a proper
 * subclass of the ns3::TraceSource base class. However, hooking each
 * new trace source in the overall namespace resolution system requires
 * some new effort. The core requirement is that the user needs to
 * subclass from the ns3::Object base class which provides the most
 * basic ns3::Object::Connect, and, ns3::Object::Disconnect methods.
 * These two methods are simple forwarding methods to the virtual 
 * ns3::Object::GetTraceResolver method which does the bulk of the work
 * required to setup properly trace sources.
 *
 * Every subclass of the ns3::Object base class which wishes to export
 * a set of trace sources and make them available through the Connect/Disconnect
 * functions needs to override the ns3::Object::GetTraceResolver method.
 * This method needs to return a subclass of the ns3::TraceResolver
 * base class which implements the ns3::TraceResolver::Connect and
 * ns3::TraceResolver::Disconnect methods. Providing an implementation
 * of these two methods is a bit complicated so, a default implementation
 * named CompositeTraceResolver is provided:
 * \code
 * class MyModel : public Object
 * {
 * public:
 *   void DoSomething (void) 
 *   {
 *     // change value of m_cwnd
 *   }
 * protected:
 *   virtual Ptr<TraceResolver> GetTraceResolver (void)
 *   {
 *     // create the composite resolver
 *     Ptr<CompositeTraceResolver> resolver = Create<CompositeTraceResolver> ();
 *     resolver->AddSource ("cwnd", m_cwnd);
 *     resolver->AddSource ("rx", m_rx);
 *     return resolver;
 *   }
 * private:
 *   SVTraceSource<int> m_cwnd;
 *   CallbackTraceSource<Packet> m_rx;
 * };
 * void MyTraceSink (const TraceContext &context, Packet packet)
 * {
 *   std::cout << context << " packet: " << packet << std::endl;
 * }
 * object->Connect ("/.../rx", MakeCallback (&MyTraceSink));
 * \endcode
 * 
 * The above example is enough to export a trace source as a member of the
 * tracing namespace so, it would be enough to allow a user to perform a
 * pair of Connect/Disconnect operations but it would not be enough to allow
 * a TraceContext to contain information about these trace sources. Specifically, 
 * printing the content of the TraceContext as shown above would give no 
 * information whatsoever about the type of trace source which was connected.
 *
 * but it is not enough to allow the TraceContext
 * stored in each TraceSource to store information about these trace sources.
 *
 * - A trace source: a trace source is an object instance which is a 
 *   subclass of the ns3::TraceSource base class. Each instance
 *   of a trace source should be used to report a single type of
 *   event. For example, if you want to report ipv4 rx and tx events, 
 *   you should use two different trace source instances.
 *
 *   - Trace sinks: a trace sink is a callback, that is, a function,
 *     which is provided by the user of a model to receive the events
 *     reported by a set of trace sources within that model. A trace 
 *     sink is said to be "connected" once it has been associated
 *     to a set of trace sources.
 *
 *   - Trace context: each trace source instance is associated with a single
 *     instance of an immutable trace context. Each ns3::TraceContext stores 
 *     a set of trace context element instances, each of which is a subclass 
 *     of the ns3::TraceContextElement base class. Whenever a trace sink
 *     provided by a user is called because a trace event was reported on
 *     a connected trace source, the trace context associated to the
 *     relevant trace source is passed as an extra argument to the user's
 *     trace sink.
 *
 *   - instrumentation of models is done through a set of trace source 
 *     instances, each of which is a subclass of the ns3::TraceSource
 *     base class.
 *
 *   
 */