Lumiera  0.pre.03
»edit your freedom«
scheduler-load-control-test.cpp
Go to the documentation of this file.
1 /*
2  SchedulerLoadControl(Test) - verify scheduler load management facility
3 
4  Copyright (C) Lumiera.org
5  2023, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
28 #include "lib/test/run.hpp"
30 #include "vault/real-clock.hpp"
31 
32 #include <chrono>
33 
34 using test::Test;
35 
36 
37 namespace vault{
38 namespace gear {
39 namespace test {
40 
41  using std::move;
42  using std::chrono::microseconds;
43 
44  using Capacity = LoadController::Capacity;
45  using Wiring = LoadController::Wiring;
46 
47 
48 
49 
50 
51  /*************************************************************************/
58  {
59 
60  virtual void
61  run (Arg)
62  {
63  simpleUsage();
69  }
70 
71 
75  void
77  {
78  LoadController ctrl;
80  }
81 
82 
83 
92  void
94  {
95  Time next{0,10};
96 
97  Time ut{1,0};
98  Time t1{0,9};
99  Time t2{next - SLEEP_HORIZON};
100  Time t21{t2 + ut};
101  Time t3{next - WORK_HORIZON};
102  Time t31{t3 + ut};
103  Time t4{next - NEAR_HORIZON};
104 
105  CHECK (Capacity::IDLEWAIT == LoadController::classifyTimeHorizon (Offset{next - ut }));
106  CHECK (Capacity::IDLEWAIT == LoadController::classifyTimeHorizon (Offset{next - t1 }));
107  CHECK (Capacity::WORKTIME == LoadController::classifyTimeHorizon (Offset{next - t2 }));
108  CHECK (Capacity::WORKTIME == LoadController::classifyTimeHorizon (Offset{next - t21}));
109  CHECK (Capacity::NEARTIME == LoadController::classifyTimeHorizon (Offset{next - t3 }));
110  CHECK (Capacity::NEARTIME == LoadController::classifyTimeHorizon (Offset{next - t31}));
111  CHECK (Capacity::SPINTIME == LoadController::classifyTimeHorizon (Offset{next - t4 }));
112 
113  CHECK (Capacity::DISPATCH == LoadController::classifyTimeHorizon (Offset::ZERO ));
114  CHECK (Capacity::DISPATCH == LoadController::classifyTimeHorizon (Offset{t4 - next }));
115  }
116 
117 
118 
121  void
123  {
124  LoadController lctrl;
125 
126  Time t1{1,0};
127  Time t2{2,0};
128  Time t3{3,0};
129 
130  CHECK (not lctrl.tendedNext (t2));
131 
132  lctrl.tendNext (t2);
133  CHECK ( lctrl.tendedNext (t2));
134  CHECK (not lctrl.tendedNext (t3));
135 
136  lctrl.tendNext (t3);
137  CHECK ( lctrl.tendedNext (t3));
138 
139  // However — this is not a history memory...
140  CHECK (not lctrl.tendedNext (t1));
141  CHECK (not lctrl.tendedNext (t2));
142  CHECK ( lctrl.tendedNext (t3));
143 
144  lctrl.tendNext (t1);
145  CHECK ( lctrl.tendedNext (t1));
146  CHECK (not lctrl.tendedNext (t2));
147  CHECK (not lctrl.tendedNext (t3));
148 
149  lctrl.tendNext (t2);
150  CHECK (not lctrl.tendedNext (t1));
151  CHECK ( lctrl.tendedNext (t2));
152  CHECK (not lctrl.tendedNext (t3));
153  }
154 
155 
156 
165  void
167  {
168  LoadController lctrl;
169 
170  Time next{0,10};
171  Time nil{Time::NEVER};
172 
173  Time mt{1,0};
174  Time t1{0,9};
175  Time t2{next - SLEEP_HORIZON};
176  Time t3{next - WORK_HORIZON};
177  Time t4{next - NEAR_HORIZON};
178  Time t5{next + mt}; // ╭────────────── next Activity at scheduler head
179  // │ ╭──────── current time of evaluation
180  // Time `next` has not been tended yet... // ▼ ▼
181  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, mt ));
182 
183  // but after marking `next` as tended, capacity can be directed elsewhere
184  lctrl.tendNext (next);
185  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity (next, mt ));
186 
187  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity ( nil, mt ));
188  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity (next, t1 ));
189  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity (next, t2 ));
190  CHECK (Capacity::NEARTIME == lctrl.markOutgoingCapacity (next, t3 ));
191  CHECK (Capacity::SPINTIME == lctrl.markOutgoingCapacity (next, t4 ));
192 
193  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next,next));
194  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next, t5 ));
195 
196  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity ( nil, mt ));
197  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t1 ));
198  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t2 ));
199  CHECK (Capacity::NEARTIME == lctrl.markIncomingCapacity (next, t3 ));
200  CHECK (Capacity::SPINTIME == lctrl.markIncomingCapacity (next, t4 ));
201 
202  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next,next));
203  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next, t5 ));
204 
205  // tend-next works in limited ways also on incoming capacity
206  lctrl.tendNext (Time::NEVER); // mark as not yet tended...
207  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity ( nil, mt ));
208  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t1 ));
209  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t2 ));
210  CHECK (Capacity::TENDNEXT == lctrl.markIncomingCapacity (next, t3 ));
211  CHECK (Capacity::SPINTIME == lctrl.markIncomingCapacity (next, t4 ));
212 
213  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next,next));
214  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next, t5 ));
215 
216  // while being used rather generously on outgoing capacity
217  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity ( nil, mt )); // re-randomisation before long-term sleep
218  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, t1 ));
219  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, t2 ));
220  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, t3 ));
221  CHECK (Capacity::SPINTIME == lctrl.markOutgoingCapacity (next, t4 ));
222 
223  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next,next));
224  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next, t5 ));
225  }
226 
227 
228 
243  void
245  {
246  auto is_between = [](auto lo, auto hi, auto val)
247  {
248  return lo <= val and val < hi;
249  };
250 
251  LoadController lctrl;
252 
253  TimeVar now = RealClock::now();
254  Offset ten{FSecs(10)};
255  Time next{now + ten};
256  lctrl.tendNext(next);
257 
258  CHECK (Time::ZERO == lctrl.scatteredDelayTime (now, Capacity::DISPATCH) );
259  CHECK (Time::ZERO == lctrl.scatteredDelayTime (now, Capacity::SPINTIME) );
260  CHECK ( ten == lctrl.scatteredDelayTime (now, Capacity::TENDNEXT) );
261  CHECK (is_between ( ten, ten+ WORK_HORIZON, lctrl.scatteredDelayTime (now, Capacity::NEARTIME)));
262  CHECK (is_between ( ten, ten+SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::WORKTIME)));
263  CHECK (is_between ( ten, ten+SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::IDLEWAIT)));
264 
265  lctrl.tendNext(Time::ANYTIME); // reset to ensure we get no base offset
266 
267  // Offset is randomised based on the current time
268  // Verify this yields an even distribution
269  double avg{0};
270  const size_t REPETITIONS = 1e6;
271  for (size_t i=0; i< REPETITIONS; ++i)
272  avg += _raw(lctrl.scatteredDelayTime (RealClock::now(), Capacity::IDLEWAIT));
273  avg /= REPETITIONS;
274 
275  auto expect = _raw(SLEEP_HORIZON)/2;
276  auto error = fabs(avg/expect - 1);
277  CHECK (0.002 > error); // observing a quite stable skew ~ 0.8‰ on my system
278  } // let's see if this error bound triggers eventually...
279 
280 
281 
282 
296  void
298  {
299  uint maxThreads = 10;
300  uint currThreads = 0;
301 
303  setup.maxCapacity = [&]{ return maxThreads; };
304  setup.currWorkForceSize = [&]{ return currThreads; };
305  // rigged setup to verify calculated load indicator
306  LoadController lctrl{move(setup)};
307 
308  CHECK (0 == lctrl.averageLag());
309  CHECK (0 == lctrl.effectiveLoad());
310 
311  // Manipulate the sampled average lag (in µs)
312  lctrl.setCurrentAverageLag (200);
313  // Scheduling 200µs behind nominal start time -> 100% schedule pressure
314 
315  currThreads = 5;
316  CHECK (0.5 == lctrl.effectiveLoad());
317  currThreads = 8;
318  CHECK (0.8 == lctrl.effectiveLoad());
319  currThreads = 10;
320  CHECK (1.0 == lctrl.effectiveLoad());
321 
322  // congestion +500µs -> 200% schedule pressure
323  lctrl.setCurrentAverageLag (200+500);
324  CHECK (2.0 == lctrl.effectiveLoad());
325 
326  lctrl.setCurrentAverageLag (200+500+500);
327  CHECK (3.0 == lctrl.effectiveLoad()); // -> 300%
328 
329  // if average headroom 500µs -> 50% load
330  lctrl.setCurrentAverageLag (200-500);
331  CHECK (0.5 == lctrl.effectiveLoad());
332  CHECK (-300 == lctrl.averageLag());
333 
334  lctrl.setCurrentAverageLag (200-500-500-500);
335  CHECK (0.25 == lctrl.effectiveLoad());
336  CHECK (-1300 == lctrl.averageLag());
337 
338  // load indicator is always modulated by concurrency level
339  currThreads = 2;
340  CHECK (0.05 == lctrl.effectiveLoad());
341 
342  // average lag is sampled from the situation when workers call in
343  Time head = Time::ZERO;
344  TimeVar curr = Time{1,0};
345  lctrl.markIncomingCapacity (head,curr);
346  CHECK (-882 == lctrl.averageLag());
347 
348  lctrl.markIncomingCapacity (head,curr);
349  CHECK (-540 == lctrl.averageLag());
350 
351  curr = Time{0,1};
352  lctrl.markIncomingCapacity (head,curr);
353  lctrl.markIncomingCapacity (head,curr);
354  CHECK (1291 == lctrl.averageLag());
355 
356  curr = head - Time{0,2};
357  lctrl.markIncomingCapacity (head,curr);
358  CHECK (-2581 == lctrl.averageLag());
359  }
360  };
361 
362 
364  LAUNCHER (SchedulerLoadControl_test, "unit engine");
365 
366 
367 
368 }}} // namespace vault::gear::test
static const Time ANYTIME
border condition marker value. ANYTIME <= any time value
Definition: timevalue.hpp:322
bool tendedNext(Time nextHead) const
did we already tend for the indicated next relevant head time?
int64_t setCurrentAverageLag(int64_t lag)
a mutable time value, behaving like a plain number, allowing copy and re-accessing ...
Definition: timevalue.hpp:241
Duration NEAR_HORIZON
what counts as "imminent" (e.g. for spin-waiting)
Scheduler resource usage coordination.
Definition: run.hpp:49
Capacity markIncomingCapacity(Time head, Time now)
decide how this thread&#39;s capacity shall be used when returning from idle wait and asking for work ...
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:308
Controller to coordinate resource usage related to the Scheduler.
Abstract Base Class for all testcases.
Definition: run.hpp:62
Simple test class runner.
Offset scatteredDelayTime(Time now, Capacity capacity)
Generate a time offset to relocate currently unused capacity to a time range where it&#39;s likely to be ...
void tendNext(Time nextHead)
Mark the indicated next head time as tended.
Duration SLEEP_HORIZON
schedules beyond that horizon justify going idle
static const Time NEVER
border condition marker value. NEVER >= any time value
Definition: timevalue.hpp:323
Offset measures a distance in time.
Definition: timevalue.hpp:367
auto setup(FUN &&workFun)
Helper: setup a Worker-Pool configuration for the test.
static Capacity classifyTimeHorizon(Offset off)
classification of time horizon for scheduling
Capacity markOutgoingCapacity(Time head, Time now)
decide how this thread&#39;s capacity shall be used after it returned from being actively employed ...
Duration WORK_HORIZON
the scope of activity currently in the works
Front-end for simplified access to the current wall clock time.
Capacity
Allocation of capacity to time horizon of expected work.
Vault-Layer implementation namespace root.