controlloop − RWP*Load Simulator control loop
controlloop ::= for controloption { controloption } loop statementlist end [ loop ] controloption ::= count expression | start expression | stop expression | [ queue | noqueue ] every expression | wait expression
The control loop is the primary mechanism to drive repeated execution in RWP*Load Simulator, i.e. to simulate actual workload. The control of the loop includes time to start and stop, number of executions and wait time between each execution or frequency of execution. Either stop or count must be provided, the rest are optional.
In rwloadsim, time is always measured in seconds and on most Operating System, the resolution is 1ms or better. You should always use variables or expressions of type double to represent time.
All control loops (in all threads) have their timings coordinated at (approximately) the same time, and unless the start time is explicitly set to non-zero, this means starting at the same time. The actual time with reference to this common start time is returned by the function call runseconds(). In order to ensure reasonable ramp-up to e.g. start database connections in session pools, or to actually start operating system threads, rwloadsim has a built-in delay from the clock time it is called until this 0s time when control loops really start processing; the default is 5s. If you use call runseconds() before this common control loop start time, it will return a negative value.
There is no requirement to use a control loop, but if you do not, execution in threads will start as soon as the thread is created. To execute something just once at a controlled time, you therefore need to use a control loop with count set to one.
Multiple control loops can very well be executed (by the same thread) after each other, but they cannot be nested, neither directly nor indirectly.
When a pooled database is in use, a session will be acquired from the pool before each execute of a procedure (or function) doing database work, and it will be returned to the pool when the procedure (or function) exits. Control loops are normally part of a thread execution, but can also be declared and used outside.
The control loop is controlled via one or more options that appear in any sequence after the initial for keyword. The list of options is terminated by the loop keyword and then followed by the statement list to be executed.
The available options are:
start t
Start execution at the time-stamp provided by the double expression, t. The default is 0s and negative values are allowed in which case the thread will start before the normal thread start time as explained above.
stop t
Stop execution at the time-stamp, t, which is a double; an exact stop time cannot be guaranteed. Either count or stop must be provided. Note that the stop time is calculated when the loop starts and not recalculated during loop execution. You can use the break statement to finish a control loop before the stop time expires.
count t
Execute the loop after t executions; only the integer part of the expression, t, is used. Either count or stop must be provided.
wait t
Wait after each execution by a time provided as the double expression t. The expression is (as opposed to the stop time) calculated after each loop so a random wait time can be provided as e.g. uniform(0.5,1.5) which would mean a random wait time between 0.5s and 1.5s. You cannot specify both wait and every.
[ queue ] every e
Attempt execution every so often where the double expression, e, is the time in seconds. This is different from a wait time, as every sets the time between each start of the execution, so every simulates an expected arrival rate in a queuing system. Before an execution, the point in time to start the next execution will be calculated and saved in the everyuntil variable, either relatively to start time of the current loop or relatively to the original common start time. The latter implements a backlog and is achieved using the queue keyword. If a planned start time is surpassed, the execution will start immediately. To simulate queuing systems, use queue every with an argument of erlang2(1/x), where x is the expected arrival rate per second.
The keyword can be prefixed with either "queue" or "noqueue" which will enable or disable queuing simulation using a backlog. Either of these overwrites the setting done via the -Q or -N options to rwloadsim or set via the $queue directive.
You can overwrite the everyuntil variable if your programming has a requirement to use a different expected time to start the next loop.
This first example shows a very typical actual simulation of a workload.
for queue every erlang2(0.1) stop 300 loop doit() at mydb; end loop;
Execute the procedure doit() repeatedly at random times on average once every 0.1s for a period of 5 minutes. If the doit() procedures does database work and the mydb database is using a session pool or drcp, a session will be acquired at the beginning and released at the end of each call of the procedure. If the normal execution time for the call to doit() - including time to potentially wait for a database session - is much less than 0.1s, there will normally be actual idle time between each execution. Conversely, if the execution time of doit() is larger than 0.1s, it is possible that there will be no idle time between each execution.
The use of Erlang distribution with k=2 is very common when simulating queuing behavior. See https://en.wikipedia.org/wiki/Erlang_distribution.
This second example shows how the control loop can be used to execute a procedure at regular intervals.
integer x := 0; for start 10 every 10 stop 120 loop save_data(x, runseconds); x += 1; end loop;
Starting 10s after the common start time and subsequently every 10s call the procedure save_data() with two parameters, the last of which will be the time in seconds from the common start time. Due to the omission of the queue keyword, if any call to save_data() takes longer than 10s, all subsequent calls will be delayed although still 10s between each.
This third example shows how a procedure marked with the statisticsonly attribute can be used to count failures when simulating a real queuing system with a time out.
procedure doit_failed() statisticsonly # do nothing except counting executions null; end; for queue every erlang2(0.1) stop 300 loop if runseconds() > everyuntil+1.0 then # if we are more than 1 second late, this is a failure doit_failed(); else # only do the actual work when there is no time out doit() at mydb; end if; end loop;
The doit_failed() procedure will not attempt getting a database session and will not actually do any work, so when the time out happens (current time more than one second after the expected start time), rather than calling the real procedure, which would both need to get a database session and to execute some actual SQL, a simple count of the time outs will happen.
Copyright
© 2023 Oracle Corporation
Licensed under the Universal Permissive License v 1.0 as
shown at https://oss.oracle.com/licenses/upl
threadexecution(1rwl), statement(1rwl), expression(1rwl), controlloop(1rwl)