bug 954: Changing the simulation time resolution does not work well with attributes
authorPeter D. Barnes, Jr. <barnes26@llnl.gov>
Sat, 08 Dec 2012 21:23:44 -0800
changeset 9183 1a2abe07b53d
parent 9182 da7f1cf69d77
child 9184 065a297f6c9d
bug 954: Changing the simulation time resolution does not work well with attributes
doc/tutorial/source/conceptual-overview.rst
examples/tutorial/first.cc
src/core/model/nstime.h
src/core/model/time.cc
src/core/test/time-test-suite.cc
--- a/doc/tutorial/source/conceptual-overview.rst	Sat Dec 08 10:11:07 2012 -0800
+++ b/doc/tutorial/source/conceptual-overview.rst	Sat Dec 08 21:23:44 2012 -0800
@@ -313,7 +313,7 @@
 ``Debugging`` book and then select the ``Logging`` page.
 
 You should now be looking at the Doxygen documentation for the Logging module.
-In the list of ``#define`` s at the top of the page you will see the entry
+In the list of ``#define``'s at the top of the page you will see the entry
 for ``NS_LOG_COMPONENT_DEFINE``.  Before jumping in, it would probably be 
 good to look for the "Detailed Description" of the logging module to get a 
 feel for the overall operation.  You can either scroll down or select the
@@ -340,6 +340,20 @@
 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);
+
+You can change the resolution exactly once (which must be before
+``Simulator::Run ()`` is called, below).  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:
 
--- a/examples/tutorial/first.cc	Sat Dec 08 10:11:07 2012 -0800
+++ b/examples/tutorial/first.cc	Sat Dec 08 21:23:44 2012 -0800
@@ -27,6 +27,7 @@
 int
 main (int argc, char *argv[])
 {
+  Time::SetResolution (Time::NS);
   LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
   LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
 
--- a/src/core/model/nstime.h	Sat Dec 08 10:11:07 2012 -0800
+++ b/src/core/model/nstime.h	Sat Dec 08 21:23:44 2012 -0800
@@ -26,6 +26,7 @@
 #include "int64x64.h"
 #include <stdint.h>
 #include <cmath>
+#include <set>
 #include <ostream>
 
 namespace ns3 {
@@ -36,115 +37,48 @@
  */
 /**
  * \ingroup time
- * \brief keep track of time unit.
- *
- * This template class is used to keep track of the value
- * of a specific time unit: the type TimeUnit<1> is used to
- * keep track of seconds, the type TimeUnit<2> is used to keep
- * track of seconds squared, the type TimeUnit<-1> is used to
- * keep track of 1/seconds, etc.
- *
- * This base class defines all the functionality shared by all
- * these time unit objects: it defines all the classic arithmetic
- * operators +, -, *, /, and all the classic comparison operators:
- * ==, !=, <, >, <=, >=. It is thus easy to add, substract, or
- * multiply multiple TimeUnit objects. The return type of any such
- * arithmetic expression is always a TimeUnit object.
- *
- * The ns3::uint64_t, ns3::Time, ns3::TimeSquare, and ns3::TimeInvert classes
- * are aliases for the TimeUnit<0>, TimeUnit<1>, TimeUnit<2> and TimeUnit<-1>
- * types respectively.
+ * \brief Keep track of time values and allow control of global simulation resolution.
  *
- * For example:
- * \code
- * Time<1> t1 = Seconds (10.0);
- * Time<1> t2 = Seconds (10.0);
- * Time<2> t3 = t1 * t2;
- * Time<0> t4 = t1 / t2;
- * Time<3> t5 = t3 * t1;
- * Time<-2> t6 = t1 / t5;
- * TimeSquare t7 = t3;
- * uint64_t s = t4;
- * \endcode
- *
- * If you try to assign the result of an expression which does not
- * match the type of the variable it is assigned to, you will get a
- * compiler error. For example, the following will not compile:
- * \code
- * Time<1> = Seconds (10.0) * Seconds (1.5);
- * \endcode
- *
- * You can also use the following non-member functions to manipulate
- * any of these ns3::TimeUnit object:
- *  - \ref ns3-Time-Abs ns3::Abs
- *  - \ref ns3-Time-Max ns3::Max
- *  - \ref ns3-Time-Min ns3::Min
- */
-/**
- * \ingroup time
- * \brief keep track of time values and allow control of global simulation resolution
- *
- * This class defines all the classic C++ arithmetic
- * operators +, -, *, /, and all the classic comparison operators:
+ * This class defines the classic addition/subtraction C++ arithmetic
+ * operators +, -, +=, -=, and all the classic comparison operators:
  * ==, !=, <, >, <=, >=. It is thus easy to add, substract, or
- * multiply multiple Time objects.
- *
- * The ns3::uint64_t, ns3::TimeSquare, and ns3::TimeInvert classes
- * are backward-compatibility aliases for ns3::Time.
+ * compare Time objects.
  *
  * For example:
  * \code
  * Time t1 = Seconds (10.0);
  * Time t2 = Seconds (10.0);
- * Time t3 = t1 * t2;
- * Time t4 = t1 / t2;
- * Time t5 = t3 * t1;
- * Time t6 = t1 / t5;
- * Time t7 = t3;
+ * Time t3 = t1;
+ * t3 += t2;
  * \endcode
  *
  * You can also use the following non-member functions to manipulate
  * any of these ns3::Time object:
- *  - \ref ns3-Time-Abs ns3::Abs
- *  - \ref ns3-Time-Max ns3::Max
- *  - \ref ns3-Time-Min ns3::Min
+ *  - \ref Abs()
+ *  - \ref Max()
+ *  - \ref Min()
  *
- * This class also controls
- * the resolution of the underlying time value . The default resolution
- * is nanoseconds. That is, TimeStep (1).GetNanoSeconds () will return
- * 1. It is possible to either increase or decrease the resolution and the
- * code tries really hard to make this easy.
+ * This class also controls the resolution of the underlying time value.
+ * The resolution is the smallest representable time interval.
+ * The default resolution is nanoseconds.  
  *
- * If your resolution is X (say, nanoseconds) and if you create Time objects 
- * with a lower resolution (say, picoseconds), don't expect that this 
- * code will return 1: PicoSeconds (1).GetPicoSeconds (). It will most 
- * likely return 0 because the Time object has only 64 bits of fractional 
- * precision which means that PicoSeconds (1) is stored as a 64-bit aproximation
- * of 1/1000 in the Time object. If you later multiply it again by the exact 
- * value 1000, the result is unlikely to be 1 exactly. It will be close to
- * 1 but not exactly 1.
- * 
- * In general, it is thus a really bad idea to try to use time objects of a
- * resolution higher than the global resolution controlled through 
- * Time::SetResolution. If you do need to use picoseconds, it's thus best
- * to switch the global resolution to picoseconds to avoid nasty surprises.
+ * To change the resolution, use SetResolution().  All Time objects created
+ * before the call to SetResolution() will be updated to the new resolution.
+ * This can only be done once!  (Tracking each Time object uses 4 pointers.
+ * For speed, once we convert the existing instances we discard the recording
+ * data structure and stop tracking new instances, so we have no way
+ * to do a second conversion.)
  *
- * Another important issue to keep in mind is that if you increase the
- * global resolution, you also implicitely decrease the range of your simulation.
- * i.e., the global simulation time is stored in a 64 bit integer whose interpretation
- * will depend on the global resolution so, 2^64 picoseconds which is the maximum
- * duration of your simulation if the global resolution is picoseconds 
- * is smaller than 2^64 nanoseconds which is the maximum duration of your simulation
- * if the global resolution is nanoseconds.
+ * Because of the memory (and modest construction cost) of tracking Time
+ * objects, simulations should explicitly choose a resolution before
+ * calling Simulator::Run ().
  *
- * Finally, don't even think about ever changing the global resolution after
- * creating Time objects: all Time objects created before the call to SetResolution
- * will contain values which are not updated to the new resolution. In practice,
- * the default value for the attributes of many models is indeed calculated
- * before the main function of the main program enters. Because of this, if you
- * use one of these models (and it's likely), it's going to be hard to change
- * the global simulation resolution in a way which gives reasonable results. This
- * issue has been filed as bug 954 in the ns-3 bugzilla installation.
+ * If you increase the global resolution, you also implicitly decrease
+ * the range of your simulation.  The global simulation time is stored
+ * in a 64 bit integer, whose interpretation will depend on the global
+ * resolution.  Therefore the maximum duration of your simulation,
+ * if you use picoseconds, is 2^64 ps = 2^24 s = 7 months, whereas,
+ * had you used nanoseconds, you could have run for 584 years.
  */
 class Time
 {
@@ -154,12 +88,12 @@
    */
   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
   };
 
@@ -168,44 +102,66 @@
     m_data = o.m_data;
     return *this;
   }
+  inline Time &operator = (const int64_t &value)
+  {
+    m_data = value;
+    return *this;
+  }
   inline Time ()
     : m_data ()
-  {}
+  {
+    TimeSet (this);
+  }
   inline Time(const Time &o)
     : m_data (o.m_data)
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (double v)
     : m_data (lround (v))
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (int v)
     : m_data (v)
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (long int v)
     : m_data (v)
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (long long int v)
     : m_data (v)
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (unsigned int v)
     : m_data (v)
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (unsigned long int v)
     : m_data (v)
-  {}
+  {
+    TimeSet (this);
+  }
   explicit inline Time (unsigned long long int v)
     : m_data (v)
-  {}
-
+  {
+    TimeSet (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
@@ -215,6 +171,14 @@
   explicit Time (const std::string & s);
 
   /**
+   *  Destructor
+   */
+  ~Time ()
+  {
+    TimeUnset (this);
+  }
+  
+  /**
    * \return true if the time is zero, false otherwise.
    */
   inline bool IsZero (void) const
@@ -249,7 +213,9 @@
   {
     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;
@@ -305,8 +271,7 @@
     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
   {
@@ -436,30 +401,38 @@
   }
   explicit inline Time (const int64x64_t &value)
     : m_data (value.GetHigh ())
-  {}
+  {
+    TimeSet (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;
-    bool fromMul;
-    uint64_t factor;
-    int64x64_t timeTo;
-    int64x64_t timeFrom;
+    bool toMul;                     //!< Multiply when converting To, otherwise divide
+    bool fromMul;                   //!< Multiple when converting From, otherwise divide
+    uint64_t factor;                //!< Ratio of this unit / current unit
+    int64x64_t timeTo;              //!< Multiplier to convert to this unit
+    int64x64_t timeFrom;            //!< Multiplier to convert from this unit
   };
+  /**
+   * 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)
@@ -467,8 +440,51 @@
     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.
+   *
+   * \internal
+   *
+   * 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
+   * http://stackoverflow.com/questions/5526019/compile-errors-stdset-with-const-members
+   * (and gcc 4.2) say no.
+   */
+  typedef std::set< Time * > TimesSet;
+  /**
+   * Get the TimesSet instance.
+   *
+   * \param [in] deleteMe If true delete the TimesSet, so that it returns a null pointer ever after
+   */
+  static TimesSet * GetTimesSet ( const bool deleteMe = false );
+  /**
+   * Helper to clean up at Simulator::Run
+   */
+  static void DeleteTimesSet ();
+  /**
+   * Record a Time instance with the TimesSet
+   */
+  static void TimeSet (Time * const time);
+  /**
+   * Remove a Time instance from the TimesSet, called by ~Time()
+   */
+  static void TimeUnset (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);
@@ -537,8 +553,8 @@
 }
 
 /**
- * \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.
  */
@@ -547,8 +563,6 @@
   return Time ((time.m_data < 0) ? -time.m_data : time.m_data);
 }
 /**
- * \anchor ns3-Time-Max
- * \relates ns3::TimeUnit
  * \param ta the first value
  * \param tb the seconds value
  * \returns the max of the two input values.
@@ -558,8 +572,6 @@
   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.
@@ -570,7 +582,19 @@
 }
 
 
+/**
+ * \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 (std::string) constructor
+ * \relates ns3::Time
+ */
 std::istream& operator>> (std::istream& is, Time & time);
 
 /**
@@ -582,6 +606,7 @@
  * Simulator::Schedule (Seconds (5.0), ...);
  * \endcode
  * \param seconds seconds value
+ * \relates ns3::Time
  */
 inline Time Seconds (double seconds)
 {
@@ -597,6 +622,7 @@
  * Simulator::Schedule (MilliSeconds (5), ...);
  * \endcode
  * \param ms milliseconds value
+ * \relates ns3::Time
  */
 inline Time MilliSeconds (uint64_t ms)
 {
@@ -611,6 +637,7 @@
  * Simulator::Schedule (MicroSeconds (5), ...);
  * \endcode
  * \param us microseconds value
+ * \relates ns3::Time
  */
 inline Time MicroSeconds (uint64_t us)
 {
@@ -625,6 +652,7 @@
  * Simulator::Schedule (NanoSeconds (5), ...);
  * \endcode
  * \param ns nanoseconds value
+ * \relates ns3::Time
  */
 inline Time NanoSeconds (uint64_t ns)
 {
@@ -639,6 +667,7 @@
  * Simulator::Schedule (PicoSeconds (5), ...);
  * \endcode
  * \param ps picoseconds value
+ * \relates ns3::Time
  */
 inline Time PicoSeconds (uint64_t ps)
 {
@@ -653,6 +682,7 @@
  * Simulator::Schedule (FemtoSeconds (5), ...);
  * \endcode
  * \param fs femtoseconds value
+ * \relates ns3::Time
  */
 inline Time FemtoSeconds (uint64_t fs)
 {
@@ -660,26 +690,50 @@
 }
 
 
+/**
+ * \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);
--- a/src/core/model/time.cc	Sat Dec 08 10:11:07 2012 -0800
+++ b/src/core/model/time.cc	Sat Dec 08 21:23:44 2012 -0800
@@ -21,22 +21,22 @@
  */
 #include "nstime.h"
 #include "abort.h"
+#include "log.h"
 #include "global-value.h"
 #include "enum.h"
 #include "string.h"
 #include "object.h"
 #include "config.h"
-#include "log.h"
+#include "simulator.h"
 #include <cmath>
 #include <sstream>
 
+NS_LOG_COMPONENT_DEFINE ("Time");
+
 namespace ns3 {
 
-NS_LOG_COMPONENT_DEFINE ("Time");
-
 Time::Time (const std::string& s)
 {
-  NS_LOG_FUNCTION (this << &s);
   std::string::size_type n = s.find_first_not_of ("+-0123456789.");
   if (n != std::string::npos)
     { // Found non-numeric
@@ -48,62 +48,82 @@
       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;
         }
-      NS_ABORT_MSG ("Can't Parse Time " << s);
+      else
+        {
+          NS_ABORT_MSG ("Can't Parse Time " << s);
+        }
     }
-  // else
-  // they didn't provide units, assume seconds
-  std::istringstream iss;
-  iss.str (s);
-  double v;
-  iss >> v;
-  *this = Time::FromDouble (v, Time::S);
+  else
+    {
+      // they didn't provide units, assume seconds
+      std::istringstream iss;
+      iss.str (s);
+      double v;
+      iss >> v;
+      *this = Time::FromDouble (v, Time::S);
+    }
+  
+  TimeSet (this);
 }
 
+// static
 struct Time::Resolution
-Time::GetNsResolution (void)
+Time::SetDefaultNsResolution (void)
 {
-  NS_LOG_FUNCTION_NOARGS ();
+  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
+enum Time::Unit
+Time::GetResolution (void)
+{
+  NS_LOG_FUNCTION_NOARGS();
+  return PeekResolution ()->unit;
+}
+
+// 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 (unit << resolution << convert);
+  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,18 +156,115 @@
     }
   resolution->unit = unit;
 }
-enum Time::Unit
-Time::GetResolution (void)
+
+
+// static
+Time::TimesSet *
+Time::GetTimesSet ( const bool deleteMe /* = false */ )
 {
-  NS_LOG_FUNCTION_NOARGS ();
-  return PeekResolution ()->unit;
+  static TimesSet * times = new TimesSet;  
+
+  if (deleteMe)
+    {
+      NS_LOG_LOGIC ("deleting TimesSet");
+      if (times)
+        {
+          delete times;
+        }
+      times = 0;
+    }
+
+  return times;
+}
+
+// static
+void
+Time::DeleteTimesSet ()
+{
+  NS_LOG_FUNCTION_NOARGS();
+  Time::GetTimesSet (true);
 }
 
+// static
+void
+Time::TimeSet (Time * const time)
+{
+  NS_ASSERT (time != 0);
+
+  TimesSet * times = GetTimesSet();
+  if (times)
+    {
+      std::pair< TimesSet::iterator, bool> ret;
+      ret = times->insert ( time);
+      NS_LOG_LOGIC ("\t[" << times->size () << "] recording " << time);
+  
+      if (ret.second == false)
+        {
+          NS_LOG_WARN ("already recorded " << time << "!");
+        }
+      // If this is the first Time, schedule the cleanup.
+      if (times->size () == 1)
+        {
+          // We schedule here, after the first event has been added,
+          // rather than in GetTimesSet when the set is empty.
+          // Scheduling there creates another Time, which
+          // finds an empty set and schedules an event . . .
+          // Doing it here, the schedule creates the second Time,
+          // which doesn't recurse.
+          NS_LOG_LOGIC ("scheduling DeleteTimesSet()");
+          Simulator::Schedule ( Seconds (0), & DeleteTimesSet);
+        }
+    }
+}
+
+// static
+void
+Time::TimeUnset (Time * const time)
+{
+  NS_ASSERT (time != 0);
+  TimesSet * times = GetTimesSet ();
+  if (times)
+    {
+      NS_ASSERT_MSG (times->count (time) == 1,
+		     "Time object " << time << " registered "
+		     << times->count (time) << " times (should be 1)." );
+
+      TimesSet::size_type num = times->erase (time);
+      if (num != 1)
+        {
+          NS_LOG_WARN ("unexpected result erasing " << time << "!");
+          NS_LOG_WARN ("got " << num << ", expected 1");
+        }
+      else
+        {
+          NS_LOG_LOGIC ("\t[" << times->size () << "] removing  " << time);
+        }
+    }
+}
+
+// static
+void
+Time::ConvertTimes (const enum Unit unit)
+{
+  NS_LOG_FUNCTION_NOARGS();
+  TimesSet * times = GetTimesSet ();
+  NS_ASSERT_MSG (times != 0, "No Time registry. Time::SetResolution () called mare than once?");
+  
+  for ( TimesSet::iterator it = times->begin();
+        it != times->end();
+        it++ )
+    {
+      Time * const tp = *it;
+      (*tp) = tp->ToInteger (unit);
+    }
+
+  NS_LOG_LOGIC ("logged " << GetTimesSet ()->size () << " Time objects.");
+  GetTimesSet (true);
+}
 
 std::ostream&
 operator<< (std::ostream& os, const Time & time)
 {
-  NS_LOG_FUNCTION (&os << time);
   std::string unit;
   switch (Time::GetResolution ())
     {
@@ -180,7 +297,6 @@
 }
 std::istream& operator>> (std::istream& is, Time & time)
 {
-  NS_LOG_FUNCTION (&is << time);
   std::string value;
   is >> value;
   time = Time (value);
--- a/src/core/test/time-test-suite.cc	Sat Dec 08 10:11:07 2012 -0800
+++ b/src/core/test/time-test-suite.cc	Sat Dec 08 21:23:44 2012 -0800
@@ -27,31 +27,26 @@
 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 @@
   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 @@
   TimeTestSuite ()
     : TestSuite ("time", UNIT)
   {
-    AddTestCase (new TimeSimpleTestCase (Time::US));
+    AddTestCase (new TimeSimpleTestCase ());
     AddTestCase (new TimesWithSignsTestCase ());
   }
 } g_timeTestSuite;