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