2024-01-02 21:46:44 +01:00
|
|
|
|
/*
|
|
|
|
|
|
STRESS-TEST-RIG.hpp - setup for stress and performance investigation
|
|
|
|
|
|
|
|
|
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
|
2024, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
**
|
2024-04-07 23:52:56 +02:00
|
|
|
|
** Another, complimentary observation method is to inject a defined and homogeneous
|
|
|
|
|
|
** load peak into the scheduler and then watch the time it takes to process, the
|
|
|
|
|
|
** processing overhead and achieved degree of concurrency. The actual observation
|
|
|
|
|
|
** using this measurement setup attempts to establish a single _control parameter_
|
|
|
|
|
|
** as free variable, allowing to look for correlations and to build a linear
|
|
|
|
|
|
** regression model to characterise a supposed functional dependency. Simply put,
|
|
|
|
|
|
** given a number of fixed sizes jobs (not further correlated) as input, this
|
|
|
|
|
|
** approach yields a »number of jobs per time unit« and »socked overhead« —
|
|
|
|
|
|
** thereby distilling a _behaviour model_ to describe the actual stochastic data.
|
|
|
|
|
|
**
|
2024-01-02 21:46:44 +01:00
|
|
|
|
** ## 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.
|
2024-04-07 23:52:56 +02:00
|
|
|
|
** Moreover, the actual measurement setup requires to perform several test executions,
|
|
|
|
|
|
** controlling some parameters in accordance to the observation scheme. The control
|
|
|
|
|
|
** parameters and the specifics of the actual setup should be clearly visible, while
|
|
|
|
|
|
** hiding the complexities of measurement execution.
|
|
|
|
|
|
**
|
|
|
|
|
|
** This can be achieved by a »Toolbench«, which is a framework with building blocks,
|
|
|
|
|
|
** providing a pre-arranged _measurement rig_ for the various kinds of measurement setup.
|
|
|
|
|
|
** The implementation code is arranged as a »sandwich« structure...
|
|
|
|
|
|
** - StressTestRig, which is also the framework class, acts as _bottom layer_ to
|
|
|
|
|
|
** provide an anchor point, some common definitions implying an invocation scheme
|
|
|
|
|
|
** ** first a TestChainLoad topology is constructed, based on test parameters
|
|
|
|
|
|
** ** this is used to create a TestChainLoad::SchedulerCtx, which is then
|
|
|
|
|
|
** outfitted specifically for each test run
|
|
|
|
|
|
** - the _middle layer_ is a custom `Setup` class, which inherits from the bottom
|
|
|
|
|
|
** layer and fills in the actual topology and configuration for the desired test
|
|
|
|
|
|
** - the test performance is then initiated by layering a specific _test tool_ on
|
|
|
|
|
|
** top of the compound, which in turn picks up the parametrisation from the Setup
|
|
|
|
|
|
** and base configuration, visible as base class (template param) \a CONF
|
|
|
|
|
|
** Together, this leads to the following code scheme, which aims to simplify experimentation:
|
|
|
|
|
|
** \code
|
|
|
|
|
|
** using StressRig = StressTestRig<16>;
|
|
|
|
|
|
**
|
|
|
|
|
|
** struct Setup : StressRig
|
|
|
|
|
|
** {
|
|
|
|
|
|
** uint CONCURRENCY = 4;
|
|
|
|
|
|
** //// more definitions
|
|
|
|
|
|
**
|
|
|
|
|
|
** auto testLoad()
|
|
|
|
|
|
** {....define a Test-Chain-Load topology....}
|
|
|
|
|
|
**
|
|
|
|
|
|
** auto testSetup (TestLoad& testLoad)
|
|
|
|
|
|
** { return StressRig::testSetup(testLoad)
|
|
|
|
|
|
** .withLoadTimeBase(500us)
|
|
|
|
|
|
** // ....more customisation here
|
|
|
|
|
|
** }
|
|
|
|
|
|
** };
|
|
|
|
|
|
**
|
|
|
|
|
|
** auto result = StressRig::with<Setup>()
|
|
|
|
|
|
** .perform<bench::SpecialToolClass>();
|
|
|
|
|
|
** \endcode
|
|
|
|
|
|
**
|
|
|
|
|
|
** ## Breaking Point search
|
|
|
|
|
|
** The bench::BreakingPoint tool typically uses a complex interwoven job plan, which is
|
|
|
|
|
|
** tightened until the timing breaks. The _stressFactor_ of the 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:
|
2024-01-03 21:02:23 +01:00
|
|
|
|
** - 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.
|
2024-01-02 21:46:44 +01:00
|
|
|
|
**
|
2024-04-07 23:52:56 +02:00
|
|
|
|
** ## Parameter Correlation
|
|
|
|
|
|
** As a complement, the bench::ParameterRange tool is provided to run a specific Scheduler setup
|
|
|
|
|
|
** while varying a single control parameter within defined limits. This produces a set of (x,y) data,
|
|
|
|
|
|
** which can be used to search for correlations or build a linear regression model to describe the
|
|
|
|
|
|
** Scheduler's behaviour as function of the control parameter. The typical use case would be to use
|
|
|
|
|
|
** the input length (number of Jobs) as control parameter, leading to a model for Scheduling expense.
|
2024-01-02 21:46:44 +01:00
|
|
|
|
**
|
2024-04-07 23:52:56 +02:00
|
|
|
|
** ## Observation tools
|
|
|
|
|
|
** The TestChainLoad, together with its helpers and framework, already offers some tools to visualise
|
|
|
|
|
|
** the generated topology and to calculate statistics, and to watch an performance with instrumentation.
|
|
|
|
|
|
** In addition, the individual tools provide some debugging output to watch the measurement scheme.
|
|
|
|
|
|
** Result data is either a tuple of values (in case of bench::BreakingPoint), or a table of result
|
|
|
|
|
|
** data as function of the control parameter (for bench::ParameterRange). Result data, when converted
|
|
|
|
|
|
** to CSV, can be visualised as Gnuplot diagram.
|
2024-01-02 21:46:44 +01:00
|
|
|
|
** @see TestChainLoad_test
|
|
|
|
|
|
** @see SchedulerStress_test
|
2024-01-04 02:03:05 +01:00
|
|
|
|
** @see binary-search.hpp
|
2024-04-07 23:52:56 +02:00
|
|
|
|
** @see gnuplot-gen.hpp
|
2024-01-02 21:46:44 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef VAULT_GEAR_TEST_STRESS_TEST_RIG_H
|
|
|
|
|
|
#define VAULT_GEAR_TEST_STRESS_TEST_RIG_H
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-04-08 18:44:46 +02:00
|
|
|
|
#include "test-chain-load.hpp"
|
2024-01-04 02:03:05 +01:00
|
|
|
|
#include "lib/binary-search.hpp"
|
2024-04-08 18:44:46 +02:00
|
|
|
|
#include "lib/test/transiently.hpp"
|
2024-01-02 21:46:44 +01:00
|
|
|
|
|
|
|
|
|
|
#include "vault/gear/scheduler.hpp"
|
|
|
|
|
|
#include "lib/time/timevalue.hpp"
|
2024-01-03 22:48:49 +01:00
|
|
|
|
#include "lib/meta/function.hpp"
|
2024-01-02 21:46:44 +01:00
|
|
|
|
#include "lib/format-string.hpp"
|
2024-04-08 18:44:46 +02:00
|
|
|
|
#include "lib/format-cout.hpp"
|
|
|
|
|
|
#include "lib/gnuplot-gen.hpp"
|
2024-04-09 01:51:03 +02:00
|
|
|
|
#include "lib/stat/statistic.hpp"
|
2024-04-04 00:44:11 +02:00
|
|
|
|
#include "lib/stat/data.hpp"
|
2024-02-24 04:17:05 +01:00
|
|
|
|
#include "lib/util.hpp"
|
2024-01-02 21:46:44 +01:00
|
|
|
|
|
2024-04-08 18:44:46 +02:00
|
|
|
|
#include <algorithm>
|
2024-01-02 23:51:47 +01:00
|
|
|
|
#include <utility>
|
2024-01-04 01:32:11 +01:00
|
|
|
|
#include <vector>
|
2024-01-02 21:46:44 +01:00
|
|
|
|
#include <tuple>
|
2024-01-03 16:27:07 +01:00
|
|
|
|
#include <array>
|
2024-01-02 21:46:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace vault{
|
|
|
|
|
|
namespace gear {
|
|
|
|
|
|
namespace test {
|
|
|
|
|
|
|
|
|
|
|
|
using std::make_tuple;
|
2024-04-07 23:52:56 +02:00
|
|
|
|
using std::forward;
|
2024-02-23 03:04:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
2024-04-07 23:52:56 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Configurable template framework for running Scheduler Stress tests
|
|
|
|
|
|
* Use to build a custom setup class, which is then [injected](\ref StressTestRig::with)
|
|
|
|
|
|
* to [perform](\ref StressTestRig::Launcher::perform) a _specific measurement tool._
|
|
|
|
|
|
* Several tools and detailed customisations are available in `namespace bench`
|
|
|
|
|
|
* - bench::BreakingPoint conducts a binary search to _break a schedule_
|
|
|
|
|
|
* - bench::ParameterRange performs a randomised series of parametrised test runs
|
|
|
|
|
|
*/
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
template<size_t maxFan =DEFAULT_FAN>
|
|
|
|
|
|
class StressTestRig
|
2024-02-23 03:04:24 +01:00
|
|
|
|
: util::NonCopyable
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
using TestLoad = TestChainLoad<maxFan>;
|
|
|
|
|
|
using TestSetup = typename TestLoad::ScheduleCtx;
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-02-23 03:04:24 +01:00
|
|
|
|
/***********************************************************************//**
|
|
|
|
|
|
* Entrance Point: build a stress test measurement setup using a dedicated
|
|
|
|
|
|
* \a TOOL class, takes the configuration \a CONF as template parameter
|
|
|
|
|
|
* and which is assumed to inherit (indirectly) from StressRig.
|
|
|
|
|
|
* @tparam CONF specialised subclass of StressRig with customisation
|
|
|
|
|
|
* @return a builder to configure and then launch the actual test
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class CONF>
|
|
|
|
|
|
static auto
|
|
|
|
|
|
with()
|
|
|
|
|
|
{
|
|
|
|
|
|
return Launcher<CONF>{};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ======= default configuration (inherited) ======= */
|
|
|
|
|
|
|
|
|
|
|
|
uint CONCURRENCY = work::Config::getDefaultComputationCapacity();
|
|
|
|
|
|
bool INSTRUMENTATION = true;
|
|
|
|
|
|
double EPSILON = 0.01; ///< error bound to abort binary search
|
|
|
|
|
|
double UPPER_STRESS = 0.6; ///< starting point for the upper limit, likely to fail
|
|
|
|
|
|
double FAIL_LIMIT = 2.0; ///< delta-limit when to count a run as failure
|
|
|
|
|
|
double TRIGGER_FAIL = 0.55; ///< %-fact: criterion-1 failures above this rate
|
|
|
|
|
|
double TRIGGER_SDEV = FAIL_LIMIT; ///< in ms : criterion-2 standard derivation
|
|
|
|
|
|
double TRIGGER_DELTA = 2*FAIL_LIMIT; ///< in ms : criterion-3 average delta above this limit
|
|
|
|
|
|
bool showRuns = false; ///< print a line for each individual run
|
|
|
|
|
|
bool showStep = true; ///< print a line for each binary search step
|
|
|
|
|
|
bool showRes = true; ///< print result data
|
|
|
|
|
|
bool showRef = true; ///< calculate single threaded reference time
|
|
|
|
|
|
|
|
|
|
|
|
static uint constexpr REPETITIONS{20};
|
|
|
|
|
|
|
|
|
|
|
|
BlockFlowAlloc bFlow{};
|
|
|
|
|
|
EngineObserver watch{};
|
|
|
|
|
|
Scheduler scheduler{bFlow, watch};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
/** Extension point: build the computation topology for this test */
|
|
|
|
|
|
auto
|
2024-02-24 04:17:05 +01:00
|
|
|
|
testLoad(size_t nodes =64)
|
2024-02-23 03:04:24 +01:00
|
|
|
|
{
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
return TestLoad{nodes};
|
2024-02-23 03:04:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
/** (optional) extension point: base configuration of the test ScheduleCtx
|
|
|
|
|
|
* @warning the actual setup \a CONF is layered, beware of shadowing. */
|
2024-02-23 03:04:24 +01:00
|
|
|
|
auto
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
testSetup (TestLoad& testLoad)
|
2024-02-23 03:04:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
return testLoad.setupSchedule(scheduler)
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
.withLevelDuration(200us)
|
2024-02-23 03:04:24 +01:00
|
|
|
|
.withJobDeadline(100ms)
|
|
|
|
|
|
.withUpfrontPlanning();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class CONF>
|
|
|
|
|
|
struct Launcher : CONF
|
|
|
|
|
|
{
|
|
|
|
|
|
template<template<class> class TOOL, typename...ARGS>
|
|
|
|
|
|
auto
|
|
|
|
|
|
perform (ARGS&& ...args)
|
|
|
|
|
|
{
|
|
|
|
|
|
return TOOL<CONF>{}.perform (std::forward<ARGS> (args)...);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace bench { ///< Specialised tools to investigate scheduler performance
|
|
|
|
|
|
|
2024-04-07 23:52:56 +02:00
|
|
|
|
using util::_Fmt;
|
|
|
|
|
|
using util::min;
|
|
|
|
|
|
using util::max;
|
|
|
|
|
|
using std::vector;
|
2024-02-23 03:04:24 +01:00
|
|
|
|
using std::declval;
|
|
|
|
|
|
|
2024-01-02 21:46:44 +01:00
|
|
|
|
|
2024-02-23 03:04:24 +01:00
|
|
|
|
/**************************************************//**
|
2024-01-02 23:51:47 +01:00
|
|
|
|
* Specific stress test scheme to determine the
|
|
|
|
|
|
* »breaking point« where the Scheduler starts to slip
|
|
|
|
|
|
*/
|
2024-01-02 21:46:44 +01:00
|
|
|
|
template<class CONF>
|
2024-02-23 03:04:24 +01:00
|
|
|
|
class BreakingPoint
|
|
|
|
|
|
: public CONF
|
2024-01-02 21:46:44 +01:00
|
|
|
|
{
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
using TestLoad = typename CONF::TestLoad;
|
|
|
|
|
|
using TestSetup = typename TestLoad::ScheduleCtx;
|
2024-01-02 23:51:47 +01:00
|
|
|
|
|
|
|
|
|
|
struct Res
|
|
|
|
|
|
{
|
|
|
|
|
|
double stressFac{0};
|
|
|
|
|
|
double percentOff{0};
|
|
|
|
|
|
double stdDev{0};
|
|
|
|
|
|
double avgDelta{0};
|
|
|
|
|
|
double avgTime{0};
|
2024-01-03 16:27:07 +01:00
|
|
|
|
double expTime{0};
|
2024-01-02 23:51:47 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2024-02-19 15:58:05 +01:00
|
|
|
|
double adjustmentFac{1.0};
|
|
|
|
|
|
|
2024-01-02 23:51:47 +01:00
|
|
|
|
/** prepare the ScheduleCtx for a specifically parametrised test series */
|
|
|
|
|
|
void
|
|
|
|
|
|
configureTest (TestSetup& testSetup, double stressFac)
|
|
|
|
|
|
{
|
Scheduler-test: reorganise test-setup in Stress-test-rig
With the addition of a second tool `bench::ParameterRange`,
the setup of the test-context for measurement became confusing,
since the original scheme was mostly oriented towards the
''breaking point search.''
On close investigation, I discovered several redundancies, and
moreover, it seems questionable to generate an ''adapted-schedule''
for the Parameter-Range measurement method, which aims at overloading
the scheduler and watch the time to resolve such a load peak.
The solution entertained here is to move most of the schedule-ctx setup
into the base implementation, which is typically just inherited by the
actual testcase setup. This allows to leave the decision whether to build
an adapted schedule to the actual tool. So `bench::BreakingPoint` can
always setup the adapted schedule with a specific stress-factor,
while `bench::ParameterRange` by default does nothing in this
respect, and thus the `ScheduleCtx` will provide a default schedule
with the configured level-duration (and the default for this is
lowered to 200µs here).
In a similar vein, calculation of result data points from the raw measurement
is moved over into the actual test setup, thereby gaining flexibility.
2024-04-05 22:50:06 +02:00
|
|
|
|
testSetup.withInstrumentation(CONF::INSTRUMENTATION) // side-effect: clear existing statistics
|
2024-02-19 15:58:05 +01:00
|
|
|
|
.withAdaptedSchedule(stressFac, CONF::CONCURRENCY, adjustmentFac);
|
2024-01-02 23:51:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** perform a repetition of test runs and compute statistics */
|
|
|
|
|
|
Res
|
2024-01-03 16:27:07 +01:00
|
|
|
|
runProbes (TestSetup& testSetup, double stressFac)
|
2024-01-02 23:51:47 +01:00
|
|
|
|
{
|
2024-01-03 16:27:07 +01:00
|
|
|
|
auto sqr = [](auto n){ return n*n; };
|
|
|
|
|
|
Res res;
|
|
|
|
|
|
auto& [sf,pf,sdev,avgD,avgT,expT] = res;
|
2024-01-03 21:02:23 +01:00
|
|
|
|
sf = stressFac;
|
2024-01-03 16:27:07 +01:00
|
|
|
|
std::array<double, CONF::REPETITIONS> runTime;
|
|
|
|
|
|
for (uint i=0; i<CONF::REPETITIONS; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
runTime[i] = testSetup.launch_and_wait() / 1000;
|
|
|
|
|
|
avgT += runTime[i];
|
2024-02-18 18:01:21 +01:00
|
|
|
|
testSetup.adaptEmpirically (stressFac, CONF::CONCURRENCY);
|
2024-02-19 17:36:46 +01:00
|
|
|
|
this->adjustmentFac = 1 / (testSetup.getStressFac() / stressFac);
|
2024-01-03 16:27:07 +01:00
|
|
|
|
}
|
2024-02-18 18:01:21 +01:00
|
|
|
|
expT = testSetup.getExpectedEndTime() / 1000;
|
2024-01-03 16:27:07 +01:00
|
|
|
|
avgT /= CONF::REPETITIONS;
|
2024-01-08 22:58:16 +01:00
|
|
|
|
avgD = (avgT-expT); // can be < 0
|
2024-01-03 16:27:07 +01:00
|
|
|
|
for (uint i=0; i<CONF::REPETITIONS; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
sdev += sqr (runTime[i] - avgT);
|
2024-01-08 22:58:16 +01:00
|
|
|
|
double delta = (runTime[i] - expT);
|
2024-01-03 16:27:07 +01:00
|
|
|
|
bool fail = (delta > CONF::FAIL_LIMIT);
|
|
|
|
|
|
if (fail)
|
|
|
|
|
|
++ pf;
|
|
|
|
|
|
showRun(i, delta, runTime[i], runTime[i] > avgT, fail);
|
|
|
|
|
|
}
|
2024-01-03 23:53:44 +01:00
|
|
|
|
pf /= CONF::REPETITIONS;
|
2024-01-03 16:27:07 +01:00
|
|
|
|
sdev = sqrt (sdev/CONF::REPETITIONS);
|
|
|
|
|
|
showStep(res);
|
2024-01-02 23:51:47 +01:00
|
|
|
|
return res;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** criterion to decide if this test series constitutes a slipped schedule */
|
|
|
|
|
|
bool
|
|
|
|
|
|
decideBreakPoint (Res& res)
|
|
|
|
|
|
{
|
2024-01-03 21:02:23 +01:00
|
|
|
|
return res.percentOff > CONF::TRIGGER_FAIL
|
|
|
|
|
|
and res.stdDev > CONF::TRIGGER_SDEV
|
|
|
|
|
|
and res.avgDelta > CONF::TRIGGER_DELTA;
|
2024-01-02 23:51:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* invoke a binary search to produce a sequence of test series
|
|
|
|
|
|
* with the goal to narrow down the stressFact where the Schedule slips away.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class FUN>
|
|
|
|
|
|
Res
|
2024-01-04 01:32:11 +01:00
|
|
|
|
conductBinarySearch (FUN&& runTestCase, vector<Res> const& results)
|
2024-01-02 23:51:47 +01:00
|
|
|
|
{
|
2024-01-04 02:03:05 +01:00
|
|
|
|
double breakPoint = lib::binarySearch_upper (forward<FUN> (runTestCase)
|
|
|
|
|
|
, 0.0, CONF::UPPER_STRESS
|
|
|
|
|
|
, CONF::EPSILON);
|
2024-01-03 22:48:49 +01:00
|
|
|
|
uint s = results.size();
|
|
|
|
|
|
ENSURE (s >= 2);
|
|
|
|
|
|
Res res;
|
|
|
|
|
|
auto& [sf,pf,sdev,avgD,avgT,expT] = res;
|
|
|
|
|
|
// average data over the last three steps investigated for smoothing
|
|
|
|
|
|
uint points = min (results.size(), 3u);
|
|
|
|
|
|
for (uint i=results.size()-points; i<results.size(); ++i)
|
|
|
|
|
|
{
|
2024-01-04 01:32:11 +01:00
|
|
|
|
Res const& resx = results[i];
|
2024-01-03 22:48:49 +01:00
|
|
|
|
pf += resx.percentOff;
|
|
|
|
|
|
sdev += resx.stdDev;
|
|
|
|
|
|
avgD += resx.avgDelta;
|
|
|
|
|
|
avgT += resx.avgTime;
|
|
|
|
|
|
expT += resx.expTime;
|
|
|
|
|
|
}
|
|
|
|
|
|
pf /= points;
|
|
|
|
|
|
sdev /= points;
|
|
|
|
|
|
avgD /= points;
|
|
|
|
|
|
avgT /= points;
|
|
|
|
|
|
expT /= points;
|
2024-01-04 01:32:11 +01:00
|
|
|
|
sf = breakPoint;
|
2024-01-03 22:48:49 +01:00
|
|
|
|
return res;
|
2024-01-02 23:51:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-03 21:02:23 +01:00
|
|
|
|
|
2024-01-04 01:32:11 +01:00
|
|
|
|
_Fmt fmtRun_ {"....·%-2d: Δ=%4.1f t=%4.1f %s %s"}; // i % Δ % t % t>avg? % fail?
|
2024-02-19 17:36:46 +01:00
|
|
|
|
_Fmt fmtStep_{ "%4.2f| : ∅Δ=%4.1f±%-4.2f ∅t=%4.1f %s %%%-3.0f -- expect:%4.1fms"};// stress % ∅Δ % σ % ∅t % fail % pecentOff % t-expect
|
2024-01-03 16:27:07 +01:00
|
|
|
|
_Fmt fmtResSDv_{"%9s= %5.2f ±%4.2f%s"};
|
2024-01-03 21:02:23 +01:00
|
|
|
|
_Fmt fmtResVal_{"%9s: %5.2f%s"};
|
2024-01-03 16:27:07 +01:00
|
|
|
|
|
|
|
|
|
|
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)
|
2024-01-04 01:32:11 +01:00
|
|
|
|
cout << fmtStep_ % res.stressFac % res.avgDelta % res.stdDev % res.avgTime
|
|
|
|
|
|
% (decideBreakPoint(res)? "—◆—":"—◇—")
|
2024-02-19 17:36:46 +01:00
|
|
|
|
% (100*res.percentOff) % res.expTime
|
2024-01-03 16:27:07 +01:00
|
|
|
|
<< endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
showRes(Res& res)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CONF::showRes)
|
|
|
|
|
|
{
|
|
|
|
|
|
cout << fmtResVal_ % "stresFac" % res.stressFac % "" <<endl;
|
|
|
|
|
|
cout << fmtResVal_ % "fail" %(res.percentOff * 100) % '%' <<endl;
|
2024-01-03 23:53:44 +01:00
|
|
|
|
cout << fmtResSDv_ % "delta" % res.avgDelta % res.stdDev % "ms"<<endl;
|
2024-01-03 16:27:07 +01:00
|
|
|
|
cout << fmtResVal_ % "runTime" % res.avgTime % "ms"<<endl;
|
|
|
|
|
|
cout << fmtResVal_ % "expected" % res.expTime % "ms"<<endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
showRef(TestSetup& testSetup)
|
2024-01-03 16:27:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (CONF::showRef)
|
|
|
|
|
|
cout << fmtResVal_ % "refTime"
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
% (testSetup.calcRuntimeReference() /1000)
|
2024-01-03 16:27:07 +01:00
|
|
|
|
% "ms" << endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-02 23:51:47 +01:00
|
|
|
|
|
2024-01-02 21:46:44 +01:00
|
|
|
|
public:
|
2024-01-02 23:51:47 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Launch a measurement sequence to determine the »breaking point«
|
|
|
|
|
|
* for the configured test load and parametrisation of the Scheduler.
|
|
|
|
|
|
* @return a tuple `[stress-factor, ∅delta, ∅run-time]`
|
|
|
|
|
|
*/
|
2024-01-02 21:46:44 +01:00
|
|
|
|
auto
|
2024-02-23 03:04:24 +01:00
|
|
|
|
perform()
|
2024-01-02 21:46:44 +01:00
|
|
|
|
{
|
2024-01-02 23:51:47 +01:00
|
|
|
|
TRANSIENTLY(work::Config::COMPUTATION_CAPACITY) = CONF::CONCURRENCY;
|
|
|
|
|
|
|
|
|
|
|
|
TestLoad testLoad = CONF::testLoad().buildTopology();
|
|
|
|
|
|
TestSetup testSetup = CONF::testSetup (testLoad);
|
|
|
|
|
|
|
2024-01-04 01:32:11 +01:00
|
|
|
|
vector<Res> observations;
|
2024-01-02 23:51:47 +01:00
|
|
|
|
auto performEvaluation = [&](double stressFac)
|
|
|
|
|
|
{
|
|
|
|
|
|
configureTest (testSetup, stressFac);
|
2024-01-03 16:27:07 +01:00
|
|
|
|
auto res = runProbes (testSetup, stressFac);
|
2024-01-04 01:32:11 +01:00
|
|
|
|
observations.push_back (res);
|
|
|
|
|
|
return decideBreakPoint(res);
|
2024-01-02 23:51:47 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2024-01-04 01:32:11 +01:00
|
|
|
|
Res res = conductBinarySearch (move(performEvaluation), observations);
|
2024-01-03 23:53:44 +01:00
|
|
|
|
showRes (res);
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
showRef (testSetup);
|
2024-01-02 23:51:47 +01:00
|
|
|
|
return make_tuple (res.stressFac, res.avgDelta, res.avgTime);
|
2024-01-02 21:46:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2024-02-23 03:04:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-04-04 00:44:11 +02:00
|
|
|
|
|
2024-02-23 03:04:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**************************************************//**
|
|
|
|
|
|
* Specific test scheme to perform a Scheduler setup
|
|
|
|
|
|
* over a given control parameter range to determine
|
|
|
|
|
|
* correlations
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class CONF>
|
|
|
|
|
|
class ParameterRange
|
|
|
|
|
|
: public CONF
|
|
|
|
|
|
{
|
Scheduler-test: design problems impeding clean test-setup
Encountering ''just some design problems related to the test setup,''
which however turn out hard to overcome. Seems that, in my eagerness
to create a succinct and clear presentation of the test, I went into
danger territory, overstretching the abilities of the C++ language.
After working with a set of tools created step by step over an extended span of time,
''for me'' the machinations of this setup seem to be reduced to flipping a toggle
here and there, and I want to focus these active parts while laying out this test.
''This would require'' to create a system of nested scopes, while getting more and more
specific gradually, and moving to the individual case at question; notably any
clarification and definition within those inner focused contexts would have to be
picked up and linked in dynamically.
Yet the C++ language only allows to be ''either'' open and flexible towards
the actual types, or ''alternatively'' to select dynamically within a fixed
set of (virtual) methods, which then must be determined from the beginning.
It is not possible to tweak and adjust base definitions after the fact,
and it is not possible to fill in constant definitions dynamically
with late binding to some specific implementation type provided only
at current scope.
Seems that I am running against that brick wall over and over again,
piling up complexities driven by an desire for succinctness and clarity.
Now attempting to resolve this quite frustrating situation...
- fix the actual type of the TestChainLoad by a typedef in test context
- avoid the definitions (and thus the danger of shadowing)
and use one `testSetup()` method to place all local adjustments.
2024-04-06 23:21:10 +02:00
|
|
|
|
using TestLoad = typename CONF::TestLoad;
|
|
|
|
|
|
using TestSetup = typename TestLoad::ScheduleCtx;
|
2024-02-23 03:04:24 +01:00
|
|
|
|
|
2024-04-07 23:52:56 +02:00
|
|
|
|
// Type binding for data evaluation
|
|
|
|
|
|
using Param = typename CONF::Param;
|
|
|
|
|
|
using Table = typename CONF::Table;
|
|
|
|
|
|
|
2024-02-23 03:04:24 +01:00
|
|
|
|
|
2024-02-24 04:17:05 +01:00
|
|
|
|
void
|
Scheduler-test: reorganise test-setup in Stress-test-rig
With the addition of a second tool `bench::ParameterRange`,
the setup of the test-context for measurement became confusing,
since the original scheme was mostly oriented towards the
''breaking point search.''
On close investigation, I discovered several redundancies, and
moreover, it seems questionable to generate an ''adapted-schedule''
for the Parameter-Range measurement method, which aims at overloading
the scheduler and watch the time to resolve such a load peak.
The solution entertained here is to move most of the schedule-ctx setup
into the base implementation, which is typically just inherited by the
actual testcase setup. This allows to leave the decision whether to build
an adapted schedule to the actual tool. So `bench::BreakingPoint` can
always setup the adapted schedule with a specific stress-factor,
while `bench::ParameterRange` by default does nothing in this
respect, and thus the `ScheduleCtx` will provide a default schedule
with the configured level-duration (and the default for this is
lowered to 200µs here).
In a similar vein, calculation of result data points from the raw measurement
is moved over into the actual test setup, thereby gaining flexibility.
2024-04-05 22:50:06 +02:00
|
|
|
|
runTest (Param param, Table& data)
|
2024-02-24 04:17:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
TestLoad testLoad = CONF::testLoad(param).buildTopology();
|
|
|
|
|
|
TestSetup testSetup = CONF::testSetup (testLoad)
|
Scheduler-test: reorganise test-setup in Stress-test-rig
With the addition of a second tool `bench::ParameterRange`,
the setup of the test-context for measurement became confusing,
since the original scheme was mostly oriented towards the
''breaking point search.''
On close investigation, I discovered several redundancies, and
moreover, it seems questionable to generate an ''adapted-schedule''
for the Parameter-Range measurement method, which aims at overloading
the scheduler and watch the time to resolve such a load peak.
The solution entertained here is to move most of the schedule-ctx setup
into the base implementation, which is typically just inherited by the
actual testcase setup. This allows to leave the decision whether to build
an adapted schedule to the actual tool. So `bench::BreakingPoint` can
always setup the adapted schedule with a specific stress-factor,
while `bench::ParameterRange` by default does nothing in this
respect, and thus the `ScheduleCtx` will provide a default schedule
with the configured level-duration (and the default for this is
lowered to 200µs here).
In a similar vein, calculation of result data points from the raw measurement
is moved over into the actual test setup, thereby gaining flexibility.
2024-04-05 22:50:06 +02:00
|
|
|
|
.withInstrumentation(); // Note: by default Schedule with CONF::LEVEL_STEP
|
2024-04-04 00:44:11 +02:00
|
|
|
|
double millis = testSetup.launch_and_wait() / 1000;
|
2024-03-07 23:29:36 +01:00
|
|
|
|
auto stat = testSetup.getInvocationStatistic();
|
Scheduler-test: reorganise test-setup in Stress-test-rig
With the addition of a second tool `bench::ParameterRange`,
the setup of the test-context for measurement became confusing,
since the original scheme was mostly oriented towards the
''breaking point search.''
On close investigation, I discovered several redundancies, and
moreover, it seems questionable to generate an ''adapted-schedule''
for the Parameter-Range measurement method, which aims at overloading
the scheduler and watch the time to resolve such a load peak.
The solution entertained here is to move most of the schedule-ctx setup
into the base implementation, which is typically just inherited by the
actual testcase setup. This allows to leave the decision whether to build
an adapted schedule to the actual tool. So `bench::BreakingPoint` can
always setup the adapted schedule with a specific stress-factor,
while `bench::ParameterRange` by default does nothing in this
respect, and thus the `ScheduleCtx` will provide a default schedule
with the configured level-duration (and the default for this is
lowered to 200µs here).
In a similar vein, calculation of result data points from the raw measurement
is moved over into the actual test setup, thereby gaining flexibility.
2024-04-05 22:50:06 +02:00
|
|
|
|
CONF::collectResult (data, param, millis, stat);
|
2024-02-24 04:17:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-23 03:04:24 +01:00
|
|
|
|
public:
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Launch a measurement sequence running the Scheduler with a
|
|
|
|
|
|
* varying parameter value to investigate (x,y) correlations.
|
|
|
|
|
|
* @return ////TODO a tuple `[stress-factor, ∅delta, ∅run-time]`
|
|
|
|
|
|
*/
|
2024-04-04 00:44:11 +02:00
|
|
|
|
Table
|
|
|
|
|
|
perform (Param lower, Param upper)
|
2024-02-23 03:04:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
TRANSIENTLY(work::Config::COMPUTATION_CAPACITY) = CONF::CONCURRENCY;
|
|
|
|
|
|
|
2024-04-04 00:44:11 +02:00
|
|
|
|
Param dist = upper - lower;
|
2024-02-24 04:17:05 +01:00
|
|
|
|
uint cnt = CONF::REPETITIONS;
|
2024-04-04 00:44:11 +02:00
|
|
|
|
vector<Param> points;
|
|
|
|
|
|
points.reserve (cnt);
|
|
|
|
|
|
Param minP{upper}, maxP{lower};
|
2024-02-24 04:17:05 +01:00
|
|
|
|
for (uint i=0; i<cnt; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto random = double(rand())/RAND_MAX;
|
2024-04-04 00:44:11 +02:00
|
|
|
|
Param pos = lower + Param(floor (random*dist + 0.5));
|
|
|
|
|
|
points.push_back(pos);
|
2024-02-24 04:17:05 +01:00
|
|
|
|
minP = min (pos, minP);
|
|
|
|
|
|
maxP = max (pos, maxP);
|
|
|
|
|
|
}
|
|
|
|
|
|
// ensure the bounds participate in test
|
2024-04-04 00:44:11 +02:00
|
|
|
|
if (maxP < upper) points[cnt-2] = upper;
|
|
|
|
|
|
if (minP > lower) points[cnt-1] = lower;
|
2024-02-23 03:04:24 +01:00
|
|
|
|
|
2024-04-04 00:44:11 +02:00
|
|
|
|
Table results;
|
Scheduler-test: reorganise test-setup in Stress-test-rig
With the addition of a second tool `bench::ParameterRange`,
the setup of the test-context for measurement became confusing,
since the original scheme was mostly oriented towards the
''breaking point search.''
On close investigation, I discovered several redundancies, and
moreover, it seems questionable to generate an ''adapted-schedule''
for the Parameter-Range measurement method, which aims at overloading
the scheduler and watch the time to resolve such a load peak.
The solution entertained here is to move most of the schedule-ctx setup
into the base implementation, which is typically just inherited by the
actual testcase setup. This allows to leave the decision whether to build
an adapted schedule to the actual tool. So `bench::BreakingPoint` can
always setup the adapted schedule with a specific stress-factor,
while `bench::ParameterRange` by default does nothing in this
respect, and thus the `ScheduleCtx` will provide a default schedule
with the configured level-duration (and the default for this is
lowered to 200µs here).
In a similar vein, calculation of result data points from the raw measurement
is moved over into the actual test setup, thereby gaining flexibility.
2024-04-05 22:50:06 +02:00
|
|
|
|
for (Param point : points)
|
|
|
|
|
|
runTest (point, results);
|
2024-02-24 04:17:05 +01:00
|
|
|
|
return results;
|
2024-02-23 03:04:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2024-04-07 23:52:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ====== Preconfigured ParamRange-Evaluations ====== */
|
|
|
|
|
|
|
|
|
|
|
|
using lib::stat::Column;
|
|
|
|
|
|
using lib::stat::DataTable;
|
|
|
|
|
|
using lib::stat::CSVData;
|
|
|
|
|
|
using IncidenceStat = lib::IncidenceCount::Statistic;
|
|
|
|
|
|
|
2024-04-09 01:51:03 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Calculate a linear regression model for two table columns
|
|
|
|
|
|
* @return a tuple `(socket,gradient,Vector(predicted),Vector(deltas),correlation,maxDelta,stdev)`
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename F, typename G>
|
|
|
|
|
|
auto
|
|
|
|
|
|
linearRegression (Column<F> const& x, Column<G> const& y)
|
|
|
|
|
|
{
|
|
|
|
|
|
lib::stat::RegressionData points;
|
|
|
|
|
|
size_t cnt = min (x.data.size(), y.data.size());
|
|
|
|
|
|
points.reserve (cnt);
|
|
|
|
|
|
for (size_t i=0; i < cnt; ++i)
|
|
|
|
|
|
points.emplace_back (x.data[i], y.data[i]);
|
|
|
|
|
|
return lib::stat::computeLinearRegression (points);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-07 23:52:56 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Mix-in for setup of a #ParameterRange evaluation to watch
|
|
|
|
|
|
* the processing of a single load peak, using the number of
|
|
|
|
|
|
* added job as independent parameter.
|
|
|
|
|
|
* @remark inject this definition (by inheritance) into the
|
|
|
|
|
|
* Setup, which should then also define a TestChainLoad
|
|
|
|
|
|
* graph with an overall size controlled by the #Param
|
|
|
|
|
|
* @see SchedulerStress_test#watch_expenseFunction()
|
|
|
|
|
|
*/
|
|
|
|
|
|
struct LoadPeak_ParamRange_Evaluation
|
|
|
|
|
|
{
|
|
|
|
|
|
using Param = size_t;
|
|
|
|
|
|
|
|
|
|
|
|
struct DataRow
|
|
|
|
|
|
{
|
|
|
|
|
|
Column<Param> param {"load size"}; // independent variable / control parameter
|
|
|
|
|
|
Column<double> time {"result time"};
|
|
|
|
|
|
Column<double> conc {"concurrency"};
|
|
|
|
|
|
Column<double> jobtime {"avg jobtime"};
|
2024-04-08 18:44:46 +02:00
|
|
|
|
Column<double> impeded {"avg impeded"};
|
2024-04-07 23:52:56 +02:00
|
|
|
|
|
|
|
|
|
|
auto allColumns()
|
|
|
|
|
|
{ return std::tie(param
|
|
|
|
|
|
,time
|
|
|
|
|
|
,conc
|
|
|
|
|
|
,jobtime
|
2024-04-08 18:44:46 +02:00
|
|
|
|
,impeded
|
2024-04-07 23:52:56 +02:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
using Table = DataTable<DataRow>;
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
collectResult(Table& data, Param param, double millis, bench::IncidenceStat const& stat)
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)millis;
|
|
|
|
|
|
data.newRow();
|
|
|
|
|
|
data.param = param;
|
|
|
|
|
|
data.time = stat.coveredTime / 1000;
|
|
|
|
|
|
data.conc = stat.avgConcurrency;
|
2024-04-08 18:44:46 +02:00
|
|
|
|
data.jobtime = stat.activeTime / stat.activationCnt;
|
|
|
|
|
|
data.impeded = (stat.timeAtConc(1) + stat.timeAtConc(0))/stat.activationCnt;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static string
|
|
|
|
|
|
renderGnuplot (Table const& results)
|
|
|
|
|
|
{
|
|
|
|
|
|
using namespace lib::gnuplot_gen;
|
|
|
|
|
|
string csv = results.renderCSV();
|
|
|
|
|
|
Param maxParam = * std::max_element (results.param.data.begin(), results.param.data.end());
|
|
|
|
|
|
Param xtics = maxParam > 500? 50
|
|
|
|
|
|
: maxParam > 200? 20
|
|
|
|
|
|
: maxParam > 100? 10
|
|
|
|
|
|
: 5;
|
|
|
|
|
|
return scatterRegression(
|
|
|
|
|
|
ParamRecord().set (KEY_CSVData, csv)
|
|
|
|
|
|
.set (KEY_TermSize, "600,600")
|
|
|
|
|
|
.set (KEY_Xtics, int64_t(xtics))
|
|
|
|
|
|
.set (KEY_Xlabel, "load size ⟶ number of jobs")
|
|
|
|
|
|
.set (KEY_Ylabel, "active time ⟶ ms")
|
|
|
|
|
|
.set (KEY_Y2label, "concurrent threads ⟶")
|
|
|
|
|
|
.set (KEY_Y3label, "avg job time ⟶ µs")
|
|
|
|
|
|
);
|
2024-04-07 23:52:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2024-02-23 03:04:24 +01:00
|
|
|
|
//
|
|
|
|
|
|
}// namespace bench
|
|
|
|
|
|
}}}// namespace vault::gear::test
|
2024-01-02 21:46:44 +01:00
|
|
|
|
#endif /*VAULT_GEAR_TEST_STRESS_TEST_RIG_H*/
|