/* STRESS-TEST-RIG.hpp - setup for stress and performance investigation Copyright (C) Lumiera.org 2024, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file stress-test-rig.hpp ** A test bench to conduct performance measurement series. Outfitted especially ** to determine runtime behaviour of the Scheduler and associated parts of the ** Lumiera Engine through systematic execution of load scenarios. ** ** # Scheduler Stress Testing ** ** The point of departure for any stress testing is to show that the subject will ** break in controlled ways only. For the Scheduler this can easily be achieved by ** overloading until job deadlines are broken. Much more challenging however is the ** task to find out about the boundary of regular scheduler operation. This realm ** can be defined by the ability of the scheduler to follow and conform to the ** timings set out explicitly in the schedule. Obviously, short and localised ** load peaks can be accommodated, yet once a persistent backlog builds up, ** the schedule starts to slip and the calculation process will flounder. ** ** A method to determine such a _»breaking point«_ in a systematic way is based on ** building a [synthetic calculation load](\ref test-chain-load.hpp) and establish ** the timings of a test schedule based on a simplified model of expected computation ** expense. By scaling and condensing these schedule timings, a loss of control can ** be provoked, and determined by statistical observation: since the process of ** scheduling contains an essentially random component, persistent overload will be ** indicated by an increasing variance of the overall runtime, and a departure from ** the nominal runtime of the executed schedule. ** ** ## Setup ** To perform this test scheme, an operational Scheduler is required, and an instance ** of the TestChainLoad must be provided, configured with desired load properties. ** The _stressFactor_ of the corresponding generated schedule will be the active parameter ** of this test, performing a binary search for the _breaking point._ The Measurement ** attempts to narrow down to the point of massive failure, when the ability to somehow ** cope with the schedule completely break down. Based on watching the Scheduler in ** operation, the detection was linked to three conditions, which typically will ** be triggered together, and within a narrow and reproducible parameter range: ** - an individual run counts as _accidentally failed_ when the execution slips ** away by more than 2ms with respect to the defined overall schedule. When more ** than 55% of all observed runs are considered as failed, the first condition is met ** - moreover, the observed ''standard derivation'' must also surpass the same limit ** of > 2ms, which indicates that the Scheduling mechanism is under substantial ** strain; in regular operation, the slip is rather ~ 200µs. ** - the third condition is that the ''averaged delta'' has surpassed 4ms, ** which is 2 times the basic failure indicator. ** ** ## Observation tools ** ** @see TestChainLoad_test ** @see SchedulerStress_test */ #ifndef VAULT_GEAR_TEST_STRESS_TEST_RIG_H #define VAULT_GEAR_TEST_STRESS_TEST_RIG_H #include "vault/common.hpp" //#include "test-chain-load.hpp" //#include "lib/test/transiently.hpp" #include "vault/gear/scheduler.hpp" #include "lib/time/timevalue.hpp" //#include "lib/iter-explorer.hpp" #include "lib/meta/function.hpp" #include "lib/format-string.hpp" #include "lib/format-cout.hpp"//////////////////////////TODO RLY? //#include "lib/util.hpp" //#include #include //#include //#include //#include #include #include namespace vault{ namespace gear { namespace test { using util::_Fmt; using util::min; using util::max; // using util::isnil; // using util::limited; // using util::unConst; // using util::toString; // using util::isLimited; // using lib::time::Time; // using lib::time::TimeValue; // using lib::time::FrameRate; // using lib::time::Duration; // using lib::test::Transiently; // using lib::meta::_FunRet; // using std::string; // using std::function; using std::make_pair; using std::make_tuple; // using std::forward; // using std::string; // using std::swap; using std::move; namespace err = lumiera::error; //////////////////////////TODO RLY? namespace { // Default definitions .... } namespace stress_test_rig { template struct _ValidateBinarySearchFun { static_assert (not sizeof(P), "Functor unsuitable for binary search. " "Expected signature pair(PAR)" ); }; template struct _ValidateBinarySearchFun(PAR), PAR> { using Result = RES; }; template inline auto make_binarySearchResults() { using Sig = typename lib::meta::_Fun::Sig; using Res = typename _ValidateBinarySearchFun::Result; using Data = std::vector>; return Data{}; } template inline auto binarySearch_impl (FUN&& fun, CON results, PAR lower, PAR upper, PAR epsilon) { REQUIRE (lower <= upper); while ((upper-lower) >= epsilon) { PAR div = lower + (upper-lower) / 2; results.emplace_back (fun(div)); bool hit = results.back().first; if (hit) upper = div; else lower = div; cout<<"##################LOOP lower="<avg? % fail? _Fmt fmtStep_{ "%4.2f| : ∅Δ=%4.1f±%-4.2f ∅t=%4.1f %%%3.1f -- expect:%4.1fms"}; // stress % ∅Δ % σ % ∅t % fail % t-expect _Fmt fmtResSDv_{"%9s= %5.2f ±%4.2f%s"}; _Fmt fmtResVal_{"%9s: %5.2f%s"}; void showRun(uint i, double delta, double t, bool over, bool fail) { if (CONF::showRuns) cout << fmtRun_ % i % delta % t % (over? "+":"-") % (fail? "●":"○") << endl; } void showStep(Res& res) { if (CONF::showStep) cout << fmtStep_ % res.stressFac % res.avgDelta % res.stdDev % res.avgTime % res.percentOff % res.expTime << endl; } void showRes(Res& res) { if (CONF::showRes) { cout << fmtResVal_ % "stresFac" % res.stressFac % "" <{64}; } /** (optional) extension point: base configuration of the test ScheduleCtx */ template auto testSetup (TL& testLoad) { return testLoad.setupSchedule(scheduler) .withJobDeadline(100ms) .withUpfrontPlanning(); } /** * Entrance Point: build a stress test measurement setup * to determine the »breaking point« where the Scheduler is unable * to keep up with the defined schedule. * @tparam CONF specialised subclass of StressRig with customisation * @return a builder to configure and then launch the actual test */ template static auto with() { return stress_test_rig::BreakingPointBench{}; } }; }}} // namespace vault::gear::test #endif /*VAULT_GEAR_TEST_STRESS_TEST_RIG_H*/