From f4b1b82f47b58ebabfb6158e4d0ef5fd143c7c85 Mon Sep 17 00:00:00 2001 From: Mathieu Lacage Date: Thu, 29 Mar 2007 15:27:39 +0200 Subject: [PATCH] rework the tracing architecture description --- src/common/array-trace-resolver.h | 2 +- src/common/callback-trace-source.h | 2 +- src/common/composite-trace-resolver.h | 2 +- src/common/sv-trace-source.h | 2 +- src/common/trace-context.h | 2 +- src/common/trace-root.h | 273 +++++++++++++++++--------- src/common/uv-trace-source.h | 2 +- 7 files changed, 184 insertions(+), 101 deletions(-) diff --git a/src/common/array-trace-resolver.h b/src/common/array-trace-resolver.h index 5a80ad92a..af4e231bd 100644 --- a/src/common/array-trace-resolver.h +++ b/src/common/array-trace-resolver.h @@ -30,7 +30,7 @@ namespace ns3 { /** * \brief a helper class to offer trace resolution for an array of objects. - * \ingroup tracing + * \ingroup lowleveltracing */ template class ArrayTraceResolver : public TraceResolver diff --git a/src/common/callback-trace-source.h b/src/common/callback-trace-source.h index ccba70e32..7adefbbb3 100644 --- a/src/common/callback-trace-source.h +++ b/src/common/callback-trace-source.h @@ -32,7 +32,7 @@ namespace ns3 { /** * \brief log arbitrary number of parameters to a matching ns3::Callback - * \ingroup tracing + * \ingroup lowleveltracing * * Whenever operator () is invoked on this class, the call and its arguments * are forwarded to the internal matching ns3::Callback. diff --git a/src/common/composite-trace-resolver.h b/src/common/composite-trace-resolver.h index 637d04330..a9603c609 100644 --- a/src/common/composite-trace-resolver.h +++ b/src/common/composite-trace-resolver.h @@ -33,7 +33,7 @@ namespace ns3 { /** * \brief a helper class to aggregate contained TraceResolver and other trace sources. - * \ingroup tracing + * \ingroup lowleveltracing */ class CompositeTraceResolver : public TraceResolver { diff --git a/src/common/sv-trace-source.h b/src/common/sv-trace-source.h index 56f33289c..ce5511705 100644 --- a/src/common/sv-trace-source.h +++ b/src/common/sv-trace-source.h @@ -62,7 +62,7 @@ class UVTraceSource; /** * \brief trace variables of type "signed integer" - * \ingroup tracing + * \ingroup lowleveltracing * * This template class implements a POD type: it * behaves like any other variable of type "signed integer" diff --git a/src/common/trace-context.h b/src/common/trace-context.h index a22e2cba3..74108b8da 100644 --- a/src/common/trace-context.h +++ b/src/common/trace-context.h @@ -29,7 +29,7 @@ namespace ns3 { /** * \brief Provide context to trace sources - * \ingroup tracing + * \ingroup lowleveltracing * * Instances of this class are used to hold context * for each trace source. Each instance holds a list of diff --git a/src/common/trace-root.h b/src/common/trace-root.h index f5187e4a6..a5756998a 100644 --- a/src/common/trace-root.h +++ b/src/common/trace-root.h @@ -25,10 +25,9 @@ #include "ns3/callback.h" /** - * \defgroup tracing Tracing + * \defgroup lowleveltracing Low-level tracing * - * The low-level tracing framework is built around a few very simple - * concepts: + * This low-level API is built around a few concepts: * - There can be any number of trace source objects. Each trace source * object can generate any number of trace events. The current * trace source objects are: ns3::CallbackTraceSourceSource, ns3::UVTraceSource, @@ -41,27 +40,78 @@ * a trace sink which is connected to multiple trace sources to identify * from which source each event is coming from. * - * To allow the user to connect his own trace sinks to each trace source - * defined by any of the models he is using, the tracing framework defines - * a hierarchical namespace. The root of this namespace is accessed through - * the ns3::TraceRoot class. The namespace is represented as a string made - * of multiple elements, each of which is separated from the other elements - * by the '/' character. A namespace string always starts with a '/'. + * To define new trace sources, a model author needs to instante one trace source + * object for each kind of tracing event he wants to export. The trace source objects + * currently defined are: + * - ns3::CallbackTraceSourceSource: this trace source can be used to convey any kind of + * trace event to the user. It is a functor, that is, it is a variable + * which behaves like a function which will forward every event to every + * connected trace sink (i.e., ns3::Callback). This trace source takes + * up to four arguments and forwards these 4 arguments together with the + * ns3::TraceContext which identifies this trace source to the connected + * trace sinks. + * - ns3::UVTraceSource: this trace source is used to convey key state variable + * changes to the user. It behaves like a normal integer unsigned variable: + * you can apply every normal arithmetic operator to it. It will forward + * every change in the value of the variable back to every connected trace + * sink by providing a TraceContext, the old value and the new value. + * - ns3::SVTraceSource: this is the signed integer equivalent of + * ns3::UVTraceSource. + * - ns3::FVTraceSource: this is the floating point equivalent of + * ns3::UVTraceSource and ns3::SVTraceSource. * - * By default, the simulation models provide a '/nodes' tracing root. This - * '/nodes' namespace is structured as follows: + * For example, to define a trace source which notifies you of a new packet + * being transmitted, you would have to: * \code + * class MyModel + * { + * public: + * void Tx (Packet const &p); + * private: + * CallbackTraceSource m_txTrace; + * }; + * + * void + * MyModel::Tx (Packet const &p) + * { + * // trace packet tx event. + * m_txTrace (p); + * // ... send the packet for real. + * } + * \endcode + * + * Once the model author has instantiated these objects and has wired them + * in his simulation code (that is, he calls them wherever he wants to trigger + * a trace event), he needs to make these trace sources available to users + * to allow them to connect any number of trace sources to any number + * of user trace sinks. While it would be possible to make each model + * export directly each of his trace source instances and request users to + * invoke a source->Connect (callback) method to perform the connection + * explicitely, it was felt that this was a bit cumbersome to do. + * + * As such, the ``connection'' between a set of sources and a sink is + * performed through a third-party class, the TraceResolver, which + * can be used to automate the connection of multiple matching trace sources + * to a single sink. This TraceResolver works by defining a hierarchical + * tracing namespace: the root of this namespace is accessed through the + * ns3::TraceRoot class. The namespace is represented as a string made of + * multiple elements, each of which is separated from the other elements + * by the '/' character. A namespace string always starts with a '/'. + * + * By default, the current simulation models provide a '/nodes' tracing root. + * This '/nodes' namespace is structured as follows: + * \code + * /nodes/n/arp * /nodes/n/udp * /nodes/n/ipv4 * /tx * /rx * /drop * /interfaces/n/netdevice - * (NetDevice only) /queue/ - * /enque - * /deque - * /drop - * /nodes/n/arp + * /queue/ + * /enque + * /deque + * /drop * \endcode * * The 'n' element which follows the /nodes and /interfaces namespace elements @@ -120,57 +170,73 @@ * } * \endcode * - * To define new trace sources, a model author needs to instante one trace source - * object for each kind of tracing event he wants to export. The trace source objects - * currently defined are: - * - ns3::CallbackTraceSourceSource: this trace source can be used to convey any kind of - * trace event to the user. It is a functor, that is, it is a variable - * which behaves like a function which will forward every event to every - * connected trace sink (i.e., ns3::Callback). This trace source takes - * up to four arguments and forwards these 4 arguments together with the - * ns3::TraceContext which identifies this trace source to the connected - * trace sinks. - * - ns3::UVTraceSource: this trace source is used to convey key state variable - * changes to the user. It behaves like a normal integer unsigned variable: - * you can apply every normal arithmetic operator to it. It will forward - * every change in the value of the variable back to every connected trace - * sink by providing a TraceContext, the old value and the new value. - * - ns3::SVTraceSource: this is the signed integer equivalent of - * ns3::UVTraceSource. - * - ns3::FVTraceSource: this is the floating point equivalent of - * ns3::UVTraceSource and ns3::SVTraceSource. + * The hierarchical global namespace described here is not implemented + * in a single central location: it was felt that doing this would make + * it too hard to introduce user-specific models which could hook + * automatically into the overal tracing system. If the tracing + * namespace was implemented in a single central location, every model + * author would have had to modify this central component to make + * his own model available to trace users. * - * Once the model author has instantiated these objects and has wired them - * in his simulation code (that is, he calls them wherever he wants to - * trigger a trace event), he needs to hook these trace sources into the - * global tracing namespace. The first step to do this is to define a method - * which returns a pointer to a ns3::TraceResolver object and which takes - * as argument a reference to a const ns3::TraceContext. The name of this method - * depends on how you will hook into the global tracing namespace. Before - * we get there, you need to implement this method. To do this, you could - * attempt to do everything by hand: define a subclass of the - * ns3::TraceResolver base class and implement its DoConnect, DoDisconnect - * and DoLookup methods. Because doing this can be a bit tedious, our - * tracing framework provides a number of helper template classes which - * should save you from having to implement your own in most cases: - * - ns3::CompositeTraceResolver: this subclass of ns3::TraceResolver - * can be used to aggregate together multiple trace sources and - * multiple other ns3::TraceResolver instances. - * - ns3::ArrayTraceResolver: this subclass of ns3::TraceResolver - * can be used to match any number of elements within an array - * where every element is identified by its index. + * Instead, the handling of the namespace is distributed across every relevant + * model: every model implements only the part of the namespace it is + * really responsible for. To do this, every model is expected + * to provide an instance of a TraceResolver whose + * responsability is to recursively provide access to the trace sources + * defined in its model. Each TraceResolver instance should be a subclass + * of the TraceResolver base class which implements either the DoLookup + * or the DoConnect and DoDisconnect methods. Because implementing these + * methods can be a bit tedious, our tracing framework provides a number + * of helper template classes which should save the model author from + * having to implement his own in most cases: + * - ns3::CompositeTraceResolver: this subclass of ns3::TraceResolver can + * be used to aggregate together multiple trace sources and multiple other + * ns3::TraceResolver instances. + * - ns3::ArrayTraceResolver: this subclass of ns3::TraceResolver can be + * used to match any number of elements within an array where every element + * is identified by its index. * - * Once you can instantiate your own ns3::TraceResolver object instance, - * you have to hook it up into the global namespace. There are two ways - * to do this: + * Once you can instantiate your own ns3::TraceResolver object instance, you + * have to hook it up into the global namespace. There are two ways to do this: * - you can hook your ns3::TraceResolver creation method as a new trace * root by using the ns3::TraceRoot::Register method - * - you can hook your new ns3::TraceResolver creation method into - * the container of your model. - * For example, if you wrote a new l3 protocol, all you have to do - * to hook into your container L3Demux class is to implement - * the pure virtual method inherited from the L3Protocol class - * whose name is ns3::L3protocol::CreateTraceResolver. + * - you can hook your new ns3::TraceResolver creation method into the + * container of your model. This step will obvsiouly depend on which model + * contains your own model but, if you wrote a new l3 protocol, all you + * would have to do to hook into your container L3Demux class is to implement + * the pure virtual method inherited from the L3Protocol class whose name is + * ns3::L3protocol::CreateTraceResolver. + * + * So, in most cases, exporting a model's trace sources is a matter of + * implementing a method CreateTraceResolver as shown below: + * \code + * class MyModel + * { + * public: + * enum TraceType { + * TX, + * RX, + * ... + * }; + * TraceResolver *CreateTraceResolver (TraceContext const &context); + * void Tx (Packet const &p); + * private: + * CallbackTraceSource m_txTrace; + * }; + * + * TraceResolver * + * MyModel::CreateTraceResolver (TraceContext const &context) + * { + * CompositeTraceResolver *resolver = new CompositeTraceResolver (context); + * resolver->Add ("tx", m_txTrace, MyModel::TX); + * return resolver; + * } + * void + * MyModel::Tx (Packet const &p) + * { + * m_txTrace (p); + * } + * \endcode * * If you really want to have fun and implement your own ns3::TraceResolver * subclass, you need to understand the basic Connection and Disconnection @@ -189,38 +255,55 @@ * the following call traces: * * \code - * TraceRoot::Connect (/nodes/ * /ipv4/interfaces/ * /netdevice/queue/ *); - * resolver = NodeList::CreateTraceResolver (); - * resolver->Connect (/nodes/ * /ipv4/interfaces/ * /netdevice/queue/ *); - * list = CompositeTraceResolver::DoLookup ('nodes'); - * resolver->Connect (/ * /ipv4/interfaces/ * /netdevice/queue/ *); - * list = ArrayTraceResolver::DoLookup ('*'); - * resolver->Connect ('/ipv4/interfaces/ * /netdevice/queue/ *'); - * list = CompositeTraceResolver::DoLookup ('ipv4'); - * resolver->Connect ('/interfaces/ * /netdevice/queue/ *'); - * list = CompositeTraceResolver::DoLookup ('interfaces'); - * resolver->Connect ('/ * /netdevice/queue/ *'); - * list = ArrayTraceResolver::DoLookup ('*'); - * resolver->Connect ('/netdevice/queue/ *'); - * list = CompositeTraceResolver::DoLookup ('netdevice'); - * resolver->Connect ('/queue/ *'); - * list = CompositeTraceResolver::DoLookup ('queue'); - * resolver->Connect ('/ *'); - * list = CompositeTraceResolver::DoLookup ('*'); - * resolver->DoConnect (); + * TraceRoot::Connect ("/nodes/ * /ipv4/interfaces/ * /netdevice/queue/ *", callback); + * traceContext = TraceContext (); + * rootResolver = CompositeTraceResolver (traceContext); + * rootResolver->Connect ("/nodes/ * /ipv4/interfaces/ * /netdevice/queue/ *", callback); + * resolver = CompositeTraceResolver::DoLookup ("nodes"); + * return NodeList::CreateTraceResolver (GetContext ()); + * return ArrayTraceResolver (context); + * resolver->Connect ("/ * /ipv4/interfaces/ * /netdevice/queue/ *", callback); + * ArrayTraceResolver::DoLookup ("*"); + * for (i = 0; i < n_nodes; i++) + * resolver = nodes[i]->CreateTraceResolver (GetContext ()); + * return CompositeTraceResolver (context); + * resolvers.add (resolver); + * return resolvers; + * for resolver in (resolvers) + * resolver->Connect ("/ipv4/interfaces/ * /netdevice/queue/ *", callback); + * CompositeTraceResolver::DoLookup ("ipv4"); + * resolver = ipv4->CreateTraceResolver (GetContext ()); + * return CompositeTraceResolver (context); + * return resolver; + * resolver->Connect ("/interfaces/ * /netdevice/queue/ *", callback); + * CompositeTraceResolver::DoLookup ("interfaces"); + * resolver = ArrayTraceResolver (GetContext ()); + * resolver->Connect ("/ * /netdevice/queue/ *", callback); + * ArrayTraceResolver::DoLookup ("*"); + * for (i = 0; i < n_interfaces; i++) + * resolver = interfaces[i]->CreateTraceResolver (GetContext ()); + * return CompositeTraceResolver () + * resolvers.add (resolver); + * return resolvers; + * resolver->Connect ("/netdevice/queue/ *", callback); + * CompositeTraceResolver::DoLookup ("netdevice"); + * resolver = NetDevice::CreateTraceResolver (GetContext ()); + * return CompositeTraceResolver (); + * return resolver; + * resolver->Connect ("/queue/ *", callback); + * CompositeTraceResolver::DoLookup ("queue"); + * resolver = Queue::CreateTraceResolver (GetContext ()); + * return CompositeTraceResolver (); + * return resolver + * resolver->Connect ("*", callback); + * CompositeTraceResolver::DoLookup ("*"); + * for match in (matches) + * resolver = TerminalTraceResolver ("match"); + * resolvers.add (resolver) + * return resolvers; + * for resolver in (resolvers) + * TerminalTraceResolver->DoConnect (callback); * \endcode - * - * This namespace resolution algorithm makes sure that each subpart of the - * namespace is resolved separately by each component. It allows you to - * never have to know the entire namespace structure to resolve a namespace - * string. All namespace knowledge is local which makes it very easy to plug - * in new components and have them extend the global tracing namespace. - * - * What is central to this namespace parsing and resolution algorithm is the - * construction of an ns3::TraceContext for each trace source during the - * connection process. The root trace context is intialized to be empty and - * TraceResolver::DoLookup method is responsible for incrementally constructing - * the TraceContext assigned to each terminal TraceSource object. */ namespace ns3 { @@ -234,7 +317,7 @@ class CallbackBase; * \brief The main class used to access tracing functionality for * a user. * - * \ingroup tracing + * \ingroup lowleveltracing */ class TraceRoot { diff --git a/src/common/uv-trace-source.h b/src/common/uv-trace-source.h index 45c77da91..b83873580 100644 --- a/src/common/uv-trace-source.h +++ b/src/common/uv-trace-source.h @@ -66,7 +66,7 @@ class SVTraceSource; /** * \brief trace variables of type "unsigned integer" - * \ingroup tracing + * \ingroup lowleveltracing * * This template class implements a POD type: it * behaves like any other variable of type "unsigned integer"