A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
wall-clock-synchronizer.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2008 University of Washington
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 */
17
19
20#include "log.h"
21
22#include <chrono>
23#include <condition_variable>
24#include <ctime> // clock_t
25#include <mutex>
26
27/**
28 * \file
29 * \ingroup realtime
30 * ns3::WallClockSynchronizer implementation.
31 */
32
33namespace ns3
34{
35
36NS_LOG_COMPONENT_DEFINE("WallClockSynchronizer");
37
38NS_OBJECT_ENSURE_REGISTERED(WallClockSynchronizer);
39
40TypeId
42{
43 static TypeId tid =
44 TypeId("ns3::WallClockSynchronizer").SetParent<Synchronizer>().SetGroupName("Core");
45 return tid;
46}
47
49{
50 NS_LOG_FUNCTION(this);
51 //
52 // In Linux, the basic timekeeping unit is derived from a variable called HZ
53 // HZ is the frequency in hertz of the system timer. The system timer fires
54 // every 1/HZ seconds and a counter, called the jiffies counter is incremented
55 // at each tick. The time between ticks is called a jiffy (American slang for
56 // a short period of time). The ticking of the jiffies counter is how the
57 // the kernel tells time.
58 //
59 // Now, the shortest time the kernel can sleep is one jiffy since a timer
60 // has to be set to expire and trigger the process to be made ready. The
61 // Posix clock CLOCK_REALTIME is defined as a 1/HZ clock, so by doing a
62 // clock_getres () on the realtime clock we can infer the scheduler quantum
63 // and the minimimum sleep time for the system. This is most certainly NOT
64 // going to be one nanosecond even though clock_nanosleep () pretends it is.
65 //
66 // The reason this number is important is that we are going to schedule lots
67 // of waits for less time than a jiffy. The clock_nanosleep function is
68 // going to guarantee that it will sleep for AT LEAST the time specified.
69 // The least time that it will sleep is a jiffy.
70 //
71 // In order to deal with this, we are going to do a spin-wait if the simulator
72 // requires a delay less than a jiffy. This is on the order of one millisecond
73 // (999848 ns) on the ns-regression machine.
74 //
75 // If the underlying OS does not support posix clocks, we'll just assume a
76 // one millisecond quantum and deal with this as best we can
77
78 m_jiffy = std::chrono::system_clock::period::num * std::nano::den /
79 std::chrono::system_clock::period::den;
80 NS_LOG_INFO("Jiffy is " << m_jiffy << " ns");
81}
82
84{
85 NS_LOG_FUNCTION(this);
86}
87
88bool
90{
91 NS_LOG_FUNCTION(this);
92 return true;
93}
94
95uint64_t
97{
98 NS_LOG_FUNCTION(this);
99 return GetNormalizedRealtime();
100}
101
102void
104{
105 NS_LOG_FUNCTION(this << ns);
106 //
107 // In order to make sure we're really locking the simulation time to some
108 // wall-clock time, we need to be able to compare that simulation time to
109 // that wall-clock time. The wall clock will have been running for some
110 // long time and will probably have a huge count of nanoseconds in it. We
111 // save the real time away so we can subtract it from "now" later and get
112 // a count of nanoseconds in real time since the simulation started.
113 //
115 NS_LOG_INFO("origin = " << m_realtimeOriginNano);
116}
117
118int64_t
120{
121 NS_LOG_FUNCTION(this << ns);
122 //
123 // In order to make sure we're really locking the simulation time to some
124 // wall-clock time, we need to be able to compare that simulation time to
125 // that wall-clock time. In DoSetOrigin we saved the real time at the start
126 // of the simulation away. This is the place where we subtract it from "now"
127 // to a count of nanoseconds in real time since the simulation started. We
128 // then subtract the current real time in normalized nanoseconds we just got
129 // from the normalized simulation time in nanoseconds that is passed in as
130 // the parameter ns. We return an integer difference, but in reality all of
131 // the mechanisms that cause wall-clock to simulator time drift cause events
132 // to be late. That means that the wall-clock will be higher than the
133 // simulation time and drift will be positive. I would be astonished to
134 // see a negative drift, but the possibility is admitted for other
135 // implementations; and we'll use the ability to report an early result in
136 // DoSynchronize below.
137 //
138 uint64_t nsNow = GetNormalizedRealtime();
139
140 if (nsNow > ns)
141 {
142 //
143 // Real time (nsNow) is larger/later than the simulator time (ns). We are
144 // behind real time and the difference (drift) is positive.
145 //
146 return (int64_t)(nsNow - ns);
147 }
148 else
149 {
150 //
151 // Real time (nsNow) is smaller/earlier than the simulator time (ns). We are
152 // ahead of real time and the difference (drift) is negative.
153 //
154 return -(int64_t)(ns - nsNow);
155 }
156}
157
158bool
159WallClockSynchronizer::DoSynchronize(uint64_t nsCurrent, uint64_t nsDelay)
160{
161 NS_LOG_FUNCTION(this << nsCurrent << nsDelay);
162 //
163 // This is the belly of the beast. We have received two parameters from the
164 // simulator proper -- a current simulation time (nsCurrent) and a simulation
165 // time to delay which identifies the time the next event is supposed to fire.
166 //
167 // The first thing we need to do is to (try and) correct for any realtime
168 // drift that has happened in the system. In this implementation, we realize
169 // that all mechanisms for drift will cause the drift to be such that the
170 // realtime is greater than the simulation time. This typically happens when
171 // our process is put to sleep for a given time, but actually sleeps for
172 // longer. So, what we want to do is to "catch up" to realtime and delay for
173 // less time than we are actually asked to delay. DriftCorrect will return a
174 // number from 0 to nsDelay corresponding to the amount of catching-up we
175 // need to do. If we are more than nsDelay behind, we do not wait at all.
176 //
177 // Note that it will be impossible to catch up if the amount of drift is
178 // cumulatively greater than the amount of delay between events. The method
179 // GetDrift () is available to clients of the syncrhonizer to keep track of
180 // the cumulative drift. The client can assert if the drift gets out of
181 // hand, print warning messages, or just ignore the situation and hope it will
182 // go away.
183 //
184 uint64_t ns = DriftCorrect(nsCurrent, nsDelay);
185 NS_LOG_INFO("Synchronize ns = " << ns);
186 //
187 // Once we've decided on how long we need to delay, we need to split this
188 // time into sleep waits and busy waits. The reason for this is described
189 // in the comments for the constructor where jiffies and jiffy resolution is
190 // explained.
191 //
192 // Here, I'll just say that we need that the jiffy is the minimum resolution
193 // of the system clock. It can only sleep in blocks of time equal to a jiffy.
194 // If we want to be more accurate than a jiffy (we do) then we need to sleep
195 // for some number of jiffies and then busy wait for any leftover time.
196 //
197 uint64_t numberJiffies = ns / m_jiffy;
198 NS_LOG_INFO("Synchronize numberJiffies = " << numberJiffies);
199 //
200 // This is where the real world interjects its very ugly head. The code
201 // immediately below reflects the fact that a sleep is actually quite probably
202 // going to end up sleeping for some number of jiffies longer than you wanted.
203 // This is because your system is going to be off doing other unimportant
204 // stuff during that extra time like running file systems and networks. What
205 // we want to do is to ask the system to sleep enough less than the requested
206 // delay so that it comes back early most of the time (coming back early is
207 // fine, coming back late is bad). If we can convince the system to come back
208 // early (most of the time), then we can busy-wait until the requested
209 // completion time actually comes around (most of the time).
210 //
211 // The tradeoff here is, of course, that the less time we spend sleeping, the
212 // more accurately we will sync up; but the more CPU time we will spend busy
213 // waiting (doing nothing).
214 //
215 // I'm not really sure about this number -- a boss of mine once said, "pick
216 // a number and it'll be wrong." But this works for now.
217 //
218 // \todo Hardcoded tunable parameter below.
219 //
220 if (numberJiffies > 3)
221 {
222 NS_LOG_INFO("SleepWait for " << numberJiffies * m_jiffy << " ns");
223 NS_LOG_INFO("SleepWait until " << nsCurrent + numberJiffies * m_jiffy << " ns");
224 //
225 // SleepWait is interruptible. If it returns true it meant that the sleep
226 // went until the end. If it returns false, it means that the sleep was
227 // interrupted by a Signal. In this case, we need to return and let the
228 // simulator re-evaluate what to do.
229 //
230 if (!SleepWait((numberJiffies - 3) * m_jiffy))
231 {
232 NS_LOG_INFO("SleepWait interrupted");
233 return false;
234 }
235 }
236 NS_LOG_INFO("Done with SleepWait");
237 //
238 // We asked the system to sleep for some number of jiffies, but that doesn't
239 // mean we actually did. Let's re-evaluate what we need to do here. Maybe
240 // we're already late. Probably the "real" delay time left has little to do
241 // with what we would calculate it to be naively.
242 //
243 // We are now at some Realtime. The important question now is not, "what
244 // would we calculate in a mathematicians paradise," it is, "how many
245 // nanoseconds do we need to busy-wait until we get to the Realtime that
246 // corresponds to nsCurrent + nsDelay (in simulation time). We have a handy
247 // function to do just that -- we ask for the time the realtime clock has
248 // drifted away from the simulation clock. That's our answer. If the drift
249 // is negative, we're early and we need to busy wait for that number of
250 // nanoseconds. The place were we want to be is described by the parameters
251 // we were passed by the simulator.
252 //
253 int64_t nsDrift = DoGetDrift(nsCurrent + nsDelay);
254 //
255 // If the drift is positive, we are already late and we need to just bail out
256 // of here as fast as we can. Return true to indicate that the requested time
257 // has, in fact, passed.
258 //
259 if (nsDrift >= 0)
260 {
261 NS_LOG_INFO("Back from SleepWait: IML8 " << nsDrift);
262 return true;
263 }
264 //
265 // There are some number of nanoseconds left over and we need to wait until
266 // the time defined by nsDrift. We'll do a SpinWait since the usual case
267 // will be that we are doing this Spinwait after we've gotten a rough delay
268 // using the SleepWait above. If SpinWait completes to the end, it will
269 // return true; if it is interrupted by a signal it will return false.
270 //
271 NS_LOG_INFO("SpinWait until " << nsCurrent + nsDelay);
272 return SpinWait(nsCurrent + nsDelay);
273}
274
275void
277{
278 NS_LOG_FUNCTION(this);
279
280 std::unique_lock<std::mutex> lock(m_mutex);
281 m_condition = true;
282
283 // Manual unlocking is done before notifying, to avoid waking up
284 // the waiting thread only to block again (see notify_one for details).
285 // Reference: https://en.cppreference.com/w/cpp/thread/condition_variable
286 lock.unlock();
287 m_conditionVariable.notify_one();
288}
289
290void
292{
293 NS_LOG_FUNCTION(this << cond);
294 m_condition = cond;
295}
296
297void
299{
300 NS_LOG_FUNCTION(this);
302}
303
304uint64_t
306{
307 NS_LOG_FUNCTION(this);
309}
310
311bool
313{
314 NS_LOG_FUNCTION(this << ns);
315 // We just sit here and spin, wasting CPU cycles until we get to the right
316 // time or are told to leave.
317 for (;;)
318 {
319 if (GetNormalizedRealtime() >= ns)
320 {
321 return true;
322 }
323 if (m_condition)
324 {
325 return false;
326 }
327 }
328 // Quiet compiler
329 return true;
330}
331
332bool
334{
335 NS_LOG_FUNCTION(this << ns);
336
337 std::unique_lock<std::mutex> lock(m_mutex);
338 bool finishedWaiting =
339 m_conditionVariable.wait_for(lock,
340 std::chrono::nanoseconds(ns), // Timeout
341 [this]() { return m_condition; }); // Wait condition
342
343 return finishedWaiting;
344}
345
346uint64_t
347WallClockSynchronizer::DriftCorrect(uint64_t nsNow, uint64_t nsDelay)
348{
349 NS_LOG_FUNCTION(this << nsNow << nsDelay);
350 int64_t drift = DoGetDrift(nsNow);
351 //
352 // If we're running late, drift will be positive and we need to correct by
353 // delaying for less time. If we're early for some bizarre reason, we don't
354 // do anything since we'll almost instantly self-correct.
355 //
356 if (drift < 0)
357 {
358 return nsDelay;
359 }
360 //
361 // If we've drifted out of sync by less than the requested delay, then just
362 // subtract the drift from the delay and fix up the drift in one go. If we
363 // have more drift than delay, then we just play catch up as fast as possible
364 // by not delaying at all.
365 //
366 auto correction = (uint64_t)drift;
367 if (correction <= nsDelay)
368 {
369 return nsDelay - correction;
370 }
371 else
372 {
373 return 0;
374 }
375}
376
377uint64_t
379{
380 NS_LOG_FUNCTION(this);
381 auto now = std::chrono::system_clock::now().time_since_epoch();
382 return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
383}
384
385uint64_t
387{
388 NS_LOG_FUNCTION(this);
390}
391
392} // namespace ns3
Base class used for synchronizing the simulation events to some real time "wall clock....
Definition: synchronizer.h:53
uint64_t m_realtimeOriginNano
The real time, in ns, when SetOrigin was called.
Definition: synchronizer.h:318
a unique identifier for an interface.
Definition: type-id.h:59
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition: type-id.cc:932
uint64_t GetNormalizedRealtime()
Get the current normalized real time, in ns.
int64_t DoGetDrift(uint64_t ns) override
Get the drift between the real time clock used to synchronize the simulation and the current simulati...
bool SleepWait(uint64_t ns)
Put our process to sleep for some number of nanoseconds.
void DoEventStart() override
Record the normalized real time at which the current event is starting execution.
bool DoSynchronize(uint64_t nsCurrent, uint64_t nsDelay) override
Wait until the real time is in sync with the specified simulation time.
void DoSetOrigin(uint64_t ns) override
Establish a correspondence between a simulation time and a wall-clock (real) time.
uint64_t m_jiffy
Size of the system clock tick, as reported by clock_getres, in ns.
void DoSignal() override
Tell a possible simulator thread waiting in the DoSynchronize method that an event has happened which...
uint64_t DoGetCurrentRealtime() override
Retrieve the value of the origin of the underlying normalized wall clock time in Time resolution unit...
static TypeId GetTypeId()
Get the registered TypeId for this class.
bool m_condition
The condition state.
void DoSetCondition(bool cond) override
Set the condition variable to tell a possible simulator thread waiting in the Synchronize method that...
uint64_t m_nsEventStart
Time recorded by DoEventStart.
bool SpinWait(uint64_t ns)
Do a busy-wait until the normalized realtime equals the argument or the condition variable becomes tr...
std::condition_variable m_conditionVariable
Condition variable for thread synchronizer.
uint64_t GetRealtime()
Get the current absolute real time (in ns since the epoch).
~WallClockSynchronizer() override
Destructor.
bool DoRealtime() override
Return true if this synchronizer is actually synchronizing to a realtime clock.
uint64_t DoEventEnd() override
Return the amount of real time elapsed since the last call to EventStart.
uint64_t DriftCorrect(uint64_t nsNow, uint64_t nsDelay)
Compute a correction to the nominal delay to account for realtime drift since the last DoSynchronize.
std::mutex m_mutex
Mutex controlling access to the condition variable.
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition: object-base.h:46
Debug message logging.
Every class exported by the ns3 library is enclosed in the ns3 namespace.
ns3::WallClockSynchronizer declaration.