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)
5  2023, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
19 #include "lib/test/run.hpp"
21 #include "vault/real-clock.hpp"
22 
23 #include <chrono>
24 
25 using test::Test;
26 
27 
28 namespace vault{
29 namespace gear {
30 namespace test {
31 
32  using std::move;
33  using std::chrono::microseconds;
34 
35  using Capacity = LoadController::Capacity;
36  using Wiring = LoadController::Wiring;
37 
38 
39 
40 
41 
42  /*************************************************************************/
49  {
50 
51  virtual void
52  run (Arg)
53  {
54  simpleUsage();
60  }
61 
62 
66  void
68  {
69  LoadController ctrl;
71  }
72 
73 
74 
83  void
85  {
86  Time next{0,10};
87 
88  Time ut{1,0};
89  Time t1{0,9};
90  Time t2{next - SLEEP_HORIZON};
91  Time t21{t2 + ut};
92  Time t3{next - WORK_HORIZON};
93  Time t31{t3 + ut};
94  Time t4{next - NEAR_HORIZON};
95 
96  CHECK (Capacity::IDLEWAIT == LoadController::classifyTimeHorizon (Offset{next - ut }));
97  CHECK (Capacity::IDLEWAIT == LoadController::classifyTimeHorizon (Offset{next - t1 }));
98  CHECK (Capacity::WORKTIME == LoadController::classifyTimeHorizon (Offset{next - t2 }));
99  CHECK (Capacity::WORKTIME == LoadController::classifyTimeHorizon (Offset{next - t21}));
100  CHECK (Capacity::NEARTIME == LoadController::classifyTimeHorizon (Offset{next - t3 }));
101  CHECK (Capacity::NEARTIME == LoadController::classifyTimeHorizon (Offset{next - t31}));
102  CHECK (Capacity::SPINTIME == LoadController::classifyTimeHorizon (Offset{next - t4 }));
103 
104  CHECK (Capacity::DISPATCH == LoadController::classifyTimeHorizon (Offset::ZERO ));
105  CHECK (Capacity::DISPATCH == LoadController::classifyTimeHorizon (Offset{t4 - next }));
106  }
107 
108 
109 
112  void
114  {
115  LoadController lctrl;
116 
117  Time t1{1,0};
118  Time t2{2,0};
119  Time t3{3,0};
120 
121  CHECK (not lctrl.tendedNext (t2));
122 
123  lctrl.tendNext (t2);
124  CHECK ( lctrl.tendedNext (t2));
125  CHECK (not lctrl.tendedNext (t3));
126 
127  lctrl.tendNext (t3);
128  CHECK ( lctrl.tendedNext (t3));
129 
130  // However — this is not a history memory...
131  CHECK (not lctrl.tendedNext (t1));
132  CHECK (not lctrl.tendedNext (t2));
133  CHECK ( lctrl.tendedNext (t3));
134 
135  lctrl.tendNext (t1);
136  CHECK ( lctrl.tendedNext (t1));
137  CHECK (not lctrl.tendedNext (t2));
138  CHECK (not lctrl.tendedNext (t3));
139 
140  lctrl.tendNext (t2);
141  CHECK (not lctrl.tendedNext (t1));
142  CHECK ( lctrl.tendedNext (t2));
143  CHECK (not lctrl.tendedNext (t3));
144  }
145 
146 
147 
156  void
158  {
159  LoadController lctrl;
160 
161  Time next{0,10};
162  Time nil{Time::NEVER};
163 
164  Time mt{1,0};
165  Time t1{0,9};
166  Time t2{next - SLEEP_HORIZON};
167  Time t3{next - WORK_HORIZON};
168  Time t4{next - NEAR_HORIZON};
169  Time t5{next + mt}; // ╭────────────── next Activity at scheduler head
170  // │ ╭──────── current time of evaluation
171  // Time `next` has not been tended yet... // ▼ ▼
172  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, mt ));
173 
174  // but after marking `next` as tended, capacity can be directed elsewhere
175  lctrl.tendNext (next);
176  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity (next, mt ));
177 
178  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity ( nil, mt ));
179  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity (next, t1 ));
180  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity (next, t2 ));
181  CHECK (Capacity::NEARTIME == lctrl.markOutgoingCapacity (next, t3 ));
182  CHECK (Capacity::SPINTIME == lctrl.markOutgoingCapacity (next, t4 ));
183 
184  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next,next));
185  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next, t5 ));
186 
187  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity ( nil, mt ));
188  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t1 ));
189  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t2 ));
190  CHECK (Capacity::NEARTIME == lctrl.markIncomingCapacity (next, t3 ));
191  CHECK (Capacity::SPINTIME == lctrl.markIncomingCapacity (next, t4 ));
192 
193  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next,next));
194  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next, t5 ));
195 
196  // tend-next works in limited ways also on incoming capacity
197  lctrl.tendNext (Time::NEVER); // mark as not yet tended...
198  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity ( nil, mt ));
199  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t1 ));
200  CHECK (Capacity::IDLEWAIT == lctrl.markIncomingCapacity (next, t2 ));
201  CHECK (Capacity::TENDNEXT == lctrl.markIncomingCapacity (next, t3 ));
202  CHECK (Capacity::SPINTIME == lctrl.markIncomingCapacity (next, t4 ));
203 
204  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next,next));
205  CHECK (Capacity::DISPATCH == lctrl.markIncomingCapacity (next, t5 ));
206 
207  // while being used rather generously on outgoing capacity
208  CHECK (Capacity::WORKTIME == lctrl.markOutgoingCapacity ( nil, mt )); // re-randomisation before long-term sleep
209  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, t1 ));
210  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, t2 ));
211  CHECK (Capacity::TENDNEXT == lctrl.markOutgoingCapacity (next, t3 ));
212  CHECK (Capacity::SPINTIME == lctrl.markOutgoingCapacity (next, t4 ));
213 
214  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next,next));
215  CHECK (Capacity::DISPATCH == lctrl.markOutgoingCapacity (next, t5 ));
216  }
217 
218 
219 
234  void
236  {
237  auto is_between = [](auto lo, auto hi, auto val)
238  {
239  return lo <= val and val < hi;
240  };
241 
242  LoadController lctrl;
243 
244  TimeVar now = RealClock::now();
245  Offset ten{FSecs(10)};
246  Time next{now + ten};
247  lctrl.tendNext(next);
248 
249  CHECK (Time::ZERO == lctrl.scatteredDelayTime (now, Capacity::DISPATCH) );
250  CHECK (Time::ZERO == lctrl.scatteredDelayTime (now, Capacity::SPINTIME) );
251  CHECK ( ten == lctrl.scatteredDelayTime (now, Capacity::TENDNEXT) );
252  CHECK (is_between ( ten, ten+ WORK_HORIZON, lctrl.scatteredDelayTime (now, Capacity::NEARTIME)));
253  CHECK (is_between ( ten, ten+SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::WORKTIME)));
254  CHECK (is_between ( ten, ten+SLEEP_HORIZON, lctrl.scatteredDelayTime (now, Capacity::IDLEWAIT)));
255 
256  lctrl.tendNext(Time::ANYTIME); // reset to ensure we get no base offset
257 
258  // Offset is randomised based on the current time
259  // Verify this yields an even distribution
260  double avg{0};
261  const size_t REPETITIONS = 1e6;
262  for (size_t i=0; i< REPETITIONS; ++i)
263  avg += _raw(lctrl.scatteredDelayTime (RealClock::now(), Capacity::IDLEWAIT));
264  avg /= REPETITIONS;
265 
266  auto expect = _raw(SLEEP_HORIZON)/2;
267  auto error = fabs(avg/expect - 1);
268  CHECK (0.002 > error); // observing a quite stable skew ~ 0.8‰ on my system
269  } // let's see if this error bound triggers eventually...
270 
271 
272 
273 
287  void
289  {
290  uint maxThreads = 10;
291  uint currThreads = 0;
292 
294  setup.maxCapacity = [&]{ return maxThreads; };
295  setup.currWorkForceSize = [&]{ return currThreads; };
296  // rigged setup to verify calculated load indicator
297  LoadController lctrl{move(setup)};
298 
299  CHECK (0 == lctrl.averageLag());
300  CHECK (0 == lctrl.effectiveLoad());
301 
302  // Manipulate the sampled average lag (in µs)
303  lctrl.setCurrentAverageLag (200);
304  // Scheduling 200µs behind nominal start time -> 100% schedule pressure
305 
306  currThreads = 5;
307  CHECK (0.5 == lctrl.effectiveLoad());
308  currThreads = 8;
309  CHECK (0.8 == lctrl.effectiveLoad());
310  currThreads = 10;
311  CHECK (1.0 == lctrl.effectiveLoad());
312 
313  // congestion +500µs -> 200% schedule pressure
314  lctrl.setCurrentAverageLag (200+500);
315  CHECK (2.0 == lctrl.effectiveLoad());
316 
317  lctrl.setCurrentAverageLag (200+500+500);
318  CHECK (3.0 == lctrl.effectiveLoad()); // -> 300%
319 
320  // if average headroom 500µs -> 50% load
321  lctrl.setCurrentAverageLag (200-500);
322  CHECK (0.5 == lctrl.effectiveLoad());
323  CHECK (-300 == lctrl.averageLag());
324 
325  lctrl.setCurrentAverageLag (200-500-500-500);
326  CHECK (0.25 == lctrl.effectiveLoad());
327  CHECK (-1300 == lctrl.averageLag());
328 
329  // load indicator is always modulated by concurrency level
330  currThreads = 2;
331  CHECK (0.05 == lctrl.effectiveLoad());
332 
333  // average lag is sampled from the situation when workers call in
334  Time head = Time::ZERO;
335  TimeVar curr = Time{1,0};
336  lctrl.markIncomingCapacity (head,curr);
337  CHECK (-882 == lctrl.averageLag());
338 
339  lctrl.markIncomingCapacity (head,curr);
340  CHECK (-540 == lctrl.averageLag());
341 
342  curr = Time{0,1};
343  lctrl.markIncomingCapacity (head,curr);
344  lctrl.markIncomingCapacity (head,curr);
345  CHECK (1291 == lctrl.averageLag());
346 
347  curr = head - Time{0,2};
348  lctrl.markIncomingCapacity (head,curr);
349  CHECK (-2581 == lctrl.averageLag());
350  }
351  };
352 
353 
355  LAUNCHER (SchedulerLoadControl_test, "unit engine");
356 
357 
358 
359 }}} // namespace vault::gear::test
static const Time ANYTIME
border condition marker value. ANYTIME <= any time value
Definition: timevalue.hpp:313
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:232
Duration NEAR_HORIZON
what counts as "imminent" (e.g. for spin-waiting)
Scheduler resource usage coordination.
Definition: run.hpp:40
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:299
Controller to coordinate resource usage related to the Scheduler.
Abstract Base Class for all testcases.
Definition: run.hpp:53
Simplistic 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:314
Offset measures a distance in time.
Definition: timevalue.hpp:358
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.