bug 954: Changing the simulation time resolution does not work well with attributes

v.3 Responding to code review comments

https://codereview.appspot.com/6821106/
This commit is contained in:
Peter D. Barnes, Jr.
2013-08-14 16:52:06 -07:00
parent d1ce1beb67
commit 85e9ba3b48
7 changed files with 415 additions and 65 deletions

View File

@@ -340,6 +340,21 @@ Just as in any C++ program, you need to define a main function that will be
the first function run. There is nothing at all special here. Your
|ns3| script is just a C++ program.
The next line sets the time resolution to one nanosecond, which happens
to be the default value:
::
Time::SetResolution (Time::NS);
The resolution is the smallest time value that can be represented (as well as
the smallest representable difference between two time values).
You can change the resolution exactly once. The mechanism enabling this
flexibility is somewhat memory hungry, so once the resolution has been
set explicitly we release the memory, preventing further updates. (If
you don't set the resolution explicitly, it will default to one nanosecond,
and the memory will be released when the simulation starts.)
The next two lines of the script are used to enable two logging components that
are built into the Echo Client and Echo Server applications:

View File

@@ -27,6 +27,7 @@ NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
int
main (int argc, char *argv[])
{
Time::SetResolution (Time::NS);
LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);

View File

@@ -24,10 +24,12 @@
#include "attribute.h"
#include "attribute-helper.h"
#include "int64x64.h"
#include "unused.h"
#include <stdint.h>
#include <limits>
#include <cmath>
#include <ostream>
#include <set>
namespace ns3 {
@@ -106,12 +108,12 @@ public:
*/
enum Unit
{
S = 0,
MS = 1,
US = 2,
NS = 3,
PS = 4,
FS = 5,
S = 0, //!< second
MS = 1, //!< millisecond
US = 2, //!< microsecond
NS = 3, //!< nanosecond
PS = 4, //!< picosecond
FS = 5, //!< femtosecond
LAST = 6
};
@@ -122,42 +124,86 @@ public:
}
inline Time ()
: m_data ()
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
inline Time(const Time &o)
: m_data (o.m_data)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (double v)
: m_data (lround (v))
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (int v)
: m_data (v)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (long int v)
: m_data (v)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (long long int v)
: m_data (v)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (unsigned int v)
: m_data (v)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (unsigned long int v)
: m_data (v)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
explicit inline Time (unsigned long long int v)
: m_data (v)
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
/**
* \brief String constructor
* Construct Time object from common time expressions like "
* 1ms" or "10s". Supported units include:
* - s (seconds)
* - ms (milliseconds)
* - us (microseconds)
* - ns (nanoseconds)
* - ps (picoseconds)
* - fs (femtoseconds)
* \brief Construct Time object from common time expressions like "1ms"
*
* Supported units include:
* - `s` (seconds)
* - `ms` (milliseconds)
* - `us` (microseconds)
* - `ns` (nanoseconds)
* - `ps` (picoseconds)
* - `fs` (femtoseconds)
*
* There can be no white space between the numerical portion
* and the units. Any otherwise malformed string causes a fatal error to
@@ -181,6 +227,17 @@ public:
return Time (std::numeric_limits<int64_t>::max ());
}
/**
* Destructor
*/
~Time ()
{
if (g_markingTimes)
{
Clear (this);
}
}
/**
* \return true if the time is zero, false otherwise.
*/
@@ -216,7 +273,9 @@ public:
{
return m_data > 0;
}
/**
* \return -1,0,+1 if `this < o`, `this == o`, or `this > o`
*/
inline int Compare (const Time &o) const
{
return (m_data < o.m_data) ? -1 : (m_data == o.m_data) ? 0 : 1;
@@ -272,8 +331,7 @@ public:
return ToInteger (Time::FS);
}
/**
* \returns an approximation of the time stored in this
* instance in the units specified in m_tsPrecision.
* \returns the raw time value, in the current units
*/
inline int64_t GetTimeStep (void) const
{
@@ -403,13 +461,21 @@ public:
}
explicit inline Time (const int64x64_t &value)
: m_data (value.GetHigh ())
{}
{
if (g_markingTimes)
{
Mark (this);
}
}
inline static Time From (const int64x64_t &value)
{
return Time (value);
}
private:
/**
* How to convert between other units and the current unit
*/
struct Information
{
bool toMul;
@@ -418,15 +484,18 @@ private:
int64x64_t timeTo;
int64x64_t timeFrom;
};
/**
* Current time unit, and conversion info.
*/
struct Resolution
{
struct Information info[LAST];
enum Time::Unit unit;
struct Information info[LAST]; //!< Conversion info from current unit
enum Time::Unit unit; //!< Current time unit
};
static inline struct Resolution *PeekResolution (void)
{
static struct Time::Resolution resolution = GetNsResolution ();
static struct Time::Resolution resolution = SetDefaultNsResolution ();
return &resolution;
}
static inline struct Information *PeekInformation (enum Unit timeUnit)
@@ -434,8 +503,75 @@ private:
return &(PeekResolution ()->info[timeUnit]);
}
static struct Resolution GetNsResolution (void);
static void SetResolution (enum Unit unit, struct Resolution *resolution);
static struct Resolution SetDefaultNsResolution (void);
static void SetResolution (enum Unit unit, struct Resolution *resolution,
const bool convert = true);
/**
* Record all instances of Time, so we can rescale them when
* the resolution changes.
*
* \intern
*
* We use a std::set so we can remove the record easily when
* ~Time() is called.
*
* We don't use Ptr<Time>, because we would have to bloat every Time
* instance with SimpleRefCount<Time>.
*
* Seems like this should be std::set< Time * const >, but
* [Stack Overflow](http://stackoverflow.com/questions/5526019/compile-errors-stdset-with-const-members)
* says otherwise, quoting the standard:
*
* > &sect;23.1/3 states that std::set key types must be assignable
* > and copy constructable; clearly a const type will not be assignable.
*/
typedef std::set< Time * > MarkedTimes;
/**
* Record of outstanding Time objects which will need conversion
* when the resolution is set.
*
* \intern
*
* Use a classic static variable so we can check in Time ctors
* without a function call.
*
* We'd really like to initialize this here, but we don't want to require
* C++0x, so we init in time.cc. To ensure that happens before first use,
* we add a call to StaticInit (below) to every compilation unit which
* includes nstime.h.
*/
static MarkedTimes * g_markingTimes;
public:
/**
* Function to force static initialization of Time
*/
static bool StaticInit ();
private:
/* Friend the Simulator class so it can call the private function
ClearMarkedTimes ()
*/
friend class Simulator;
/**
* Remove all MarkedTimes.
*
* \intern
* Has to be visible to the Simulator class, hence the friending.
*/
static void ClearMarkedTimes ();
/**
* Record a Time instance with the MarkedTimes
*/
static void Mark (Time * const time);
/**
* Remove a Time instance from the MarkedTimes, called by ~Time()
*/
static void Clear (Time * const time);
/**
* Convert existing Times to the new unit.
*/
static void ConvertTimes (const enum Unit unit);
friend bool operator == (const Time &lhs, const Time &rhs);
friend bool operator != (const Time &lhs, const Time &rhs);
@@ -451,8 +587,14 @@ private:
friend Time Max (const Time &ta, const Time &tb);
friend Time Min (const Time &ta, const Time &tb);
int64_t m_data;
};
int64_t m_data; //!< Virtual time value, in the current unit.
}; // class Time
// Force static initialization of Time
static bool NS_UNUSED_GLOBAL (g_TimeStaticInit) = Time::StaticInit ();
inline bool
operator == (const Time &lhs, const Time &rhs)
@@ -506,6 +648,7 @@ inline Time &operator -= (Time &lhs, const Time &rhs)
/**
* \anchor ns3-Time-Abs
* \relates ns3::TimeUnit
* Absolute value function for Time
* \param time the input value
* \returns the absolute value of the input value.
*/
@@ -525,8 +668,6 @@ inline Time Max (const Time &ta, const Time &tb)
return Time ((ta.m_data < tb.m_data) ? tb : ta);
}
/**
* \anchor ns3-Time-Min
* \relates ns3::TimeUnit
* \param ta the first value
* \param tb the seconds value
* \returns the min of the two input values.
@@ -537,7 +678,19 @@ inline Time Min (const Time &ta, const Time &tb)
}
/**
* \brief Time output streamer.
*
* Generates output such as "3.96ns"
* \relates ns3::Time
*/
std::ostream& operator<< (std::ostream& os, const Time & time);
/**
* \brief Time input streamer
*
* Uses the Time::Time (const std::string &) constructor
* \relates ns3::Time
*/
std::istream& operator>> (std::istream& is, Time & time);
/**
@@ -549,6 +702,7 @@ std::istream& operator>> (std::istream& is, Time & time);
* Simulator::Schedule (Seconds (5.0), ...);
* \endcode
* \param seconds seconds value
* \relates ns3::Time
*/
inline Time Seconds (double seconds)
{
@@ -564,6 +718,7 @@ inline Time Seconds (double seconds)
* Simulator::Schedule (MilliSeconds (5), ...);
* \endcode
* \param ms milliseconds value
* \relates ns3::Time
*/
inline Time MilliSeconds (uint64_t ms)
{
@@ -578,6 +733,7 @@ inline Time MilliSeconds (uint64_t ms)
* Simulator::Schedule (MicroSeconds (5), ...);
* \endcode
* \param us microseconds value
* \relates ns3::Time
*/
inline Time MicroSeconds (uint64_t us)
{
@@ -592,6 +748,7 @@ inline Time MicroSeconds (uint64_t us)
* Simulator::Schedule (NanoSeconds (5), ...);
* \endcode
* \param ns nanoseconds value
* \relates ns3::Time
*/
inline Time NanoSeconds (uint64_t ns)
{
@@ -606,6 +763,7 @@ inline Time NanoSeconds (uint64_t ns)
* Simulator::Schedule (PicoSeconds (5), ...);
* \endcode
* \param ps picoseconds value
* \relates ns3::Time
*/
inline Time PicoSeconds (uint64_t ps)
{
@@ -620,6 +778,7 @@ inline Time PicoSeconds (uint64_t ps)
* Simulator::Schedule (FemtoSeconds (5), ...);
* \endcode
* \param fs femtoseconds value
* \relates ns3::Time
*/
inline Time FemtoSeconds (uint64_t fs)
{
@@ -627,26 +786,50 @@ inline Time FemtoSeconds (uint64_t fs)
}
/**
* \see Seconds(double)
* \relates ns3::Time
*/
inline Time Seconds (int64x64_t seconds)
{
return Time::From (seconds, Time::S);
}
/**
* \see MilliSeconds(uint64_t)
* \relates ns3::Time
*/
inline Time MilliSeconds (int64x64_t ms)
{
return Time::From (ms, Time::MS);
}
/**
* \see MicroSeconds(uint64_t)
* \relates ns3::Time
*/
inline Time MicroSeconds (int64x64_t us)
{
return Time::From (us, Time::US);
}
/**
* \see NanoSeconds(uint64_t)
* \relates ns3::Time
*/
inline Time NanoSeconds (int64x64_t ns)
{
return Time::From (ns, Time::NS);
}
/**
* \see PicoSeconds(uint64_t)
* \relates ns3::Time
*/
inline Time PicoSeconds (int64x64_t ps)
{
return Time::From (ps, Time::PS);
}
/**
* \see FemtoSeconds(uint64_t)
* \relates ns3::Time
*/
inline Time FemtoSeconds (int64x64_t fs)
{
return Time::From (fs, Time::FS);

View File

@@ -157,6 +157,7 @@ void
Simulator::Run (void)
{
NS_LOG_FUNCTION_NOARGS ();
Time::ClearMarkedTimes ();
GetImpl ()->Run ();
}

View File

@@ -30,9 +30,41 @@
#include <cmath>
#include <sstream>
NS_LOG_COMPONENT_DEFINE ("Time");
namespace ns3 {
NS_LOG_COMPONENT_DEFINE ("Time");
// The set of marked times
// static
Time::MarkedTimes * Time::g_markingTimes = 0;
// Function called to force static initialization
// static
bool Time::StaticInit ()
{
static bool firstTime = true;
if (firstTime)
{
if (! g_markingTimes)
{
g_markingTimes = new Time::MarkedTimes;
}
// Schedule the cleanup.
// We'd really like:
// NS_LOG_LOGIC ("scheduling ClearMarkedTimes()");
// Simulator::Schedule ( Seconds (0), & ClearMarkedTimes);
// [or even better: Simulator::AtStart ( & ClearMarkedTimes ); ]
// But this triggers a static initialization order error,
// since the Simulator static initialization may not have occurred.
// Instead, we call ClearMarkedTimes directly from Simulator::Run ()
firstTime = false;
}
return firstTime;
}
Time::Time (const std::string& s)
{
@@ -48,36 +80,34 @@ Time::Time (const std::string& s)
if (trailer == std::string ("s"))
{
*this = Time::FromDouble (r, Time::S);
return;
}
if (trailer == std::string ("ms"))
else if (trailer == std::string ("ms"))
{
*this = Time::FromDouble (r, Time::MS);
return;
}
if (trailer == std::string ("us"))
else if (trailer == std::string ("us"))
{
*this = Time::FromDouble (r, Time::US);
return;
}
if (trailer == std::string ("ns"))
else if (trailer == std::string ("ns"))
{
*this = Time::FromDouble (r, Time::NS);
return;
}
if (trailer == std::string ("ps"))
else if (trailer == std::string ("ps"))
{
*this = Time::FromDouble (r, Time::PS);
return;
}
if (trailer == std::string ("fs"))
else if (trailer == std::string ("fs"))
{
*this = Time::FromDouble (r, Time::FS);
return;
}
else
{
NS_ABORT_MSG ("Can't Parse Time " << s);
}
// else
}
else
{
// they didn't provide units, assume seconds
std::istringstream iss;
iss.str (s);
@@ -86,24 +116,42 @@ Time::Time (const std::string& s)
*this = Time::FromDouble (v, Time::S);
}
if (g_markingTimes)
{
Mark (this);
}
}
// static
struct Time::Resolution
Time::GetNsResolution (void)
Time::SetDefaultNsResolution (void)
{
NS_LOG_FUNCTION_NOARGS ();
struct Resolution resolution;
SetResolution (Time::NS, &resolution);
SetResolution (Time::NS, &resolution, false);
return resolution;
}
// static
void
Time::SetResolution (enum Unit resolution)
{
NS_LOG_FUNCTION (resolution);
SetResolution (resolution, PeekResolution ());
}
// static
void
Time::SetResolution (enum Unit unit, struct Resolution *resolution)
Time::SetResolution (enum Unit unit, struct Resolution *resolution,
const bool convert /* = true */)
{
NS_LOG_FUNCTION (unit << resolution);
NS_LOG_FUNCTION (resolution);
if (convert)
{ // We have to convert old values
ConvertTimes (unit);
}
int8_t power [LAST] = { 15, 12, 9, 6, 3, 0};
for (int i = 0; i < Time::LAST; i++)
{
@@ -136,6 +184,97 @@ Time::SetResolution (enum Unit unit, struct Resolution *resolution)
}
resolution->unit = unit;
}
// static
void
Time::ClearMarkedTimes ()
{
NS_LOG_FUNCTION_NOARGS ();
if (g_markingTimes)
{
NS_LOG_LOGIC ("clearing MarkedTimes");
delete g_markingTimes;
g_markingTimes = 0;
}
} // Time::ClearMarkedTimes
// static
void
Time::Mark (Time * const time)
{
NS_LOG_FUNCTION (time);
NS_ASSERT (time != 0);
if (g_markingTimes)
{
std::pair< MarkedTimes::iterator, bool> ret;
ret = g_markingTimes->insert ( time);
NS_LOG_LOGIC ("\t[" << g_markingTimes->size () << "] recording " << time);
if (ret.second == false)
{
NS_LOG_WARN ("already recorded " << time << "!");
}
}
} // Time::Mark ()
// static
void
Time::Clear (Time * const time)
{
NS_LOG_FUNCTION (time);
NS_ASSERT (time != 0);
if (g_markingTimes)
{
NS_ASSERT_MSG (g_markingTimes->count (time) == 1,
"Time object " << time <<
" registered " << g_markingTimes->count (time) <<
" times (should be 1)." );
MarkedTimes::size_type num = g_markingTimes->erase (time);
if (num != 1)
{
NS_LOG_WARN ("unexpected result erasing " << time << "!");
NS_LOG_WARN ("got " << num << ", expected 1");
}
else
{
NS_LOG_LOGIC ("\t[" << g_markingTimes->size () << "] removing " << time);
}
}
} // Time::Clear ()
// static
void
Time::ConvertTimes (const enum Unit unit)
{
NS_LOG_FUNCTION_NOARGS();
NS_ASSERT_MSG (g_markingTimes != 0,
"No MarkedTimes registry. "
"Time::SetResolution () called more than once?");
for ( MarkedTimes::iterator it = g_markingTimes->begin();
it != g_markingTimes->end();
it++ )
{
Time * const tp = *it;
tp->m_data = tp->ToInteger (unit);
}
NS_LOG_LOGIC ("logged " << g_markingTimes->size () << " Time objects.");
ClearMarkedTimes ();
} // Time::ConvertTimes ()
// static
enum Time::Unit
Time::GetResolution (void)
{

View File

@@ -5,4 +5,14 @@
# define NS_UNUSED(x) ((void)(x))
#endif
#ifndef NS_UNUSED_GLOBAL
#if defined(__GNUC__)
# define NS_UNUSED_GLOBAL(x) x __attribute__((unused))
#elif defined(__LCLINT__)
# define NS_UNUSED_GLOBAL(x) /*@unused@*/ x
#else
# define NS_UNUSED_GLOBAL(x) x
#endif
#endif
#endif /* UNUSED_H */

View File

@@ -27,31 +27,26 @@ using namespace ns3;
class TimeSimpleTestCase : public TestCase
{
public:
TimeSimpleTestCase (enum Time::Unit resolution);
TimeSimpleTestCase ();
private:
virtual void DoSetup (void);
virtual void DoRun (void);
virtual void DoTeardown (void);
enum Time::Unit m_originalResolution;
enum Time::Unit m_resolution;
};
TimeSimpleTestCase::TimeSimpleTestCase (enum Time::Unit resolution)
: TestCase ("Sanity check of common time operations"),
m_resolution (resolution)
TimeSimpleTestCase::TimeSimpleTestCase ()
: TestCase ("Sanity check of common time operations")
{
}
void
TimeSimpleTestCase::DoSetup (void)
{
m_originalResolution = Time::GetResolution ();
}
void
TimeSimpleTestCase::DoRun (void)
{
Time::SetResolution (m_resolution);
NS_TEST_ASSERT_MSG_EQ_TOL (Seconds (1.0).GetSeconds (), 1.0, TimeStep (1).GetSeconds (),
"is 1 really 1 ?");
NS_TEST_ASSERT_MSG_EQ_TOL (Seconds (10.0).GetSeconds (), 10.0, TimeStep (1).GetSeconds (),
@@ -70,12 +65,18 @@ TimeSimpleTestCase::DoRun (void)
NS_TEST_ASSERT_MSG_EQ (FemtoSeconds (1).GetFemtoSeconds (), 1,
"is 1fs really 1fs ?");
#endif
Time ten = NanoSeconds (10);
int64_t tenValue = ten.GetInteger ();
Time::SetResolution (Time::PS);
int64_t tenKValue = ten.GetInteger ();
NS_TEST_ASSERT_MSG_EQ (tenValue * 1000, tenKValue,
"change resolution to PS");
}
void
TimeSimpleTestCase::DoTeardown (void)
{
Time::SetResolution (m_originalResolution);
}
class TimesWithSignsTestCase : public TestCase
@@ -139,7 +140,7 @@ public:
TimeTestSuite ()
: TestSuite ("time", UNIT)
{
AddTestCase (new TimeSimpleTestCase (Time::US), TestCase::QUICK);
AddTestCase (new TimeSimpleTestCase (), TestCase::QUICK);
AddTestCase (new TimesWithSignsTestCase (), TestCase::QUICK);
}
} g_timeTestSuite;