KEYCLOAK-9000 Update stress-testing script

This commit is contained in:
Tomas Kyjovsky 2018-12-03 15:15:14 +01:00 committed by Marek Posolda
parent 9b1ab0f992
commit 195aeaca68
3 changed files with 177 additions and 126 deletions

View file

@ -1,68 +1,79 @@
# Stress Testing # Keycloak Performance Testsuite - Stress Testing
Stress testing is a type of performance testing focused on *finding the maximum performance* of the system for a specific scenario.
There are various strategies but in general the stress test is a cycle of individual tests runs.
After each run the performance assertions are evaluated before deciding if/how the loop should continue.
The [test assertions](https://gatling.io/docs/2.3/general/assertions/) are constructed as boolean expressions on top of computed performance metrics, such as mean response time, percentage of failed requests, etc.
## Requirements ## Requirements
- `bc` tool for floating-point arithmetic - Bash
- `bc`: Arbitrary precision calculator.
## Stress Test
The performance testsuite contains a stress-testing script: `stress-test.sh`.
The stress test is implemented as a loop of individual performance test runs.
The script supports two algorithms:
- incremental (default)
- bisection
The *incremental algorithm* loop starts from a base load and then increases the load by a specified amount in each iteration.
The loop ends when a performance test fails, or when the maximum number of iterations is reached.
The *bisection algorithm* loop has a lower and an upper bound, and a resolution parameter.
In each iteration the middle of the interval is used as a value for the performance test load.
Depending on whether the test passes or fails the lower or upper half of the interval is used for the next iteration.
The loop ends if size of the interval is lower than the specified resolution, or when the maximum number of iterations is reached.
## Usage ## Usage
`./stress-test.sh [ADDITIONAL_TEST_PARAMS]` ```
export PARAMETER1=value1
export PARAMETER2=value2
...
stress-test.sh [-DadditionalTestsuiteParam1=value1 -DadditionalTestsuiteParam2=value2 ...]
```
Parameters of the stress test are loaded from `stress-test-config.sh`. ## Parameters
Additional `PROVISIONING_PARAMETERS` can be set via environment variable. ### Script Execution Parameters
## Common Parameters | Variable | Description | Default Value |
| Environment Variable | Description | Default Value |
| --- | --- | --- | | --- | --- | --- |
| `algorithm` | Stress test loop algorithm. Available values: `incremental`, `bisection`. | `incremental` | | `MVN` | The base Maven command to be used. | `mvn` |
| `provisioning` | When `true` (enabled), the `provision` and `import-dump` operations are run before, and the `teardown` operation is run after test in each iteration. Warm-up is applied in all iterations. When `false` (disabled), there is no provisioning or teardown, and the warm-up is only applied in the first iteration. | `true` (enabled) | | `KEYCLOAK_PROJECT_HOME` | Root directory of the Keycloak project. | Root directory relative to the location of the `stress-test.sh` script. |
| `PROVISIONING_PARAMETERS` | Additional set of parameters passed to the provisioning command. | | | `DRY_RUN` | Don't execute performance tests. Only print out execution information for each iteration. | `false` |
| `maxIterations` | Maximum number of iterations of the stress test loop. | `10` iterations |
| `dataset` | Dataset to be used. | `100u2c` |
| `warmUpPeriod` | Sets value of `warmUpPeriod` parameter. If `provisioning` is disabled the warm-up is only done in the first iteration. | `120` seconds |
| `sequentialUsersFrom` | Value for the `sequentialUsersFrom` test parameter. If provisioning is disabled the value passed to the test command will be multiplied with each iteration. To be used with registration test scenario. | `-1` (random user iteration) |
### Performance Testuite Parameters
## Incremental Method | Variable | Description | Default Value |
Incremental stress test is a loop with gradually increasing load being put on the system.
The cycle breaks with the first loop that fails the performance assertions, or after a maximum number of iterations
It is useful for testing how various performance metrics evolve dependning on linear increments of load.
### Parameters of Incremental Stress Test
| Environment Variable | Description | Default Value |
| --- | --- | --- | | --- | --- | --- |
| `usersPerSec0` | Value of `usersPerSec` parameter for the first iteration. | `5` user per second | | `DATASET` | Dataset to be used. | `1r_10c_100u` |
| `incrementFactor` | Factor of increment of `usersPerSec` with each subsequent iteration. The `usersPerSec` for iteration `i` (counted from 0) is computed as `usersPerSec0 + i * incrementFactor`. | `1` | | `WARMUP_PERIOD` | Value of `warmUpPeriod` testsuite parameter. | `120` seconds |
| `RAMPUP_PERIOD` | Value of `rampUpPeriod` testsuite parameter. | `60` seconds |
| `MEASUREMENT_PERIOD` | Value of `measurementPeriod` testsuite parameter. | `120` seconds |
| `FILTER_RESULTS` | Value of `filterResults` testsuite parameter. Should be enabled. | `true` |
| `@` | Any parameters provided to the `stress-test.sh` script will be passed to the performance testsuite. Optional. | |
### Stress Test Parameters
## Bisection Method | Variable | Description | Default Value |
This method (also called interval halving method) halves an interval defined by the lowest and highest expected value.
The test is performed with a load value from the middle of the specified interval and depending on the result either the lower or the upper half is used in the next iteration.
The cycle breaks when the interval gets smaller than a specified tolerance value, or after a maximum number of iterations.
If set up properly the bisection algorithm is typically faster and more precise than the incremental method.
However it doesn't show metrics evolving with the linear progression of load.
### Parameters of Bisection Stress Test
| Environment Variable | Description | Default Value |
| --- | --- | --- | | --- | --- | --- |
| `lowPoint` | The lower bound of the halved interval. Should be set to the lowest reasonably expected value of maximum performance. | `0` users per second | | `STRESS_TEST_ALGORITHM` | Stress test loop algorithm: `incremental` or `bisection`. | `incremental` |
| `highPoint` | The upper bound of the halved interval. | `10` users per second | | `STRESS_TEST_MAX_ITERATIONS` | Maximum number of stress test loop iterations. | `10` iterations |
| `tolerance` | Indicates the precision of measurement. The stress test loop stops when the size of the halved interval is lower than this value. | `1` users per second | | `STRESS_TEST_PROVISIONING` | Should the system be re-provisioned in each iteration? If enabled the dataset DB dump is re-imported and the warmup is run in each iteration. | `false` |
| `STRESS_TEST_PROVISIONING_GENERATE_DATASET` | Should the dataset be generated, instead of imported from DB dump? | `false` |
| `STRESS_TEST_PROVISIONING_PARAMETERS` | Additional parameters for the provisioning command. Optional. | |
#### Incremental Algorithm
| Variable | Description | Default Value |
| --- | --- | --- |
| `STRESS_TEST_UPS_FIRST` | Value of `usersPerSec` parameter in the first iteration. | `1.000` users per second |
| `STRESS_TEST_UPS_INCREMENT` | Increment of `usersPerSec` parameter for each subsequent iteration. | `1.000` users per second |
#### Bisection Algorithm
| Variable | Description | Default Value |
| --- | --- | --- |
| `STRESS_TEST_UPS_LOWER_BOUND` | Lower bound of `usersPerSec` parameter. | `0.000` users per second |
| `STRESS_TEST_UPS_UPPER_BOUND` | Upper bound of `usersPerSec` parameter. | `10.000` users per second |
| `STRESS_TEST_UPS_RESOLUTION` | Required resolution of the bisection algorithm. | `1.000` users per second |

View file

@ -1,22 +0,0 @@
#!/bin/bash
# common settings
export algorithm=incremental
export provisioning=false
export maxIterations=10
export dataset=100u2c
export warmUpPeriod=120
export sequentialUsersFrom=-1
# incremental
export usersPerSec0=5
export incrementFactor=1
# bisection
export lowPoint=0.000
export highPoint=10.000
export tolerance=1.000
# other
export debug=false

View file

@ -1,78 +1,123 @@
#!/bin/bash #!/bin/bash
BASEDIR=$(cd "$(dirname "$0")"; pwd) # Execution Parameters
cd $BASEDIR MVN="${MVN:-mvn}"
KEYCLOAK_PROJECT_HOME=${KEYCLOAK_PROJECT_HOME:-$(cd "$(dirname "$0")/../.."; pwd)}
DRY_RUN=${DRY_RUN:-false}
. ./stress-test-config.sh # Performance Testsuite Parameters
DATASET=${DATASET:-1r_10c_100u}
WARMUP_PERIOD=${WARMUP_PERIOD:-120}
RAMPUP_PERIOD=${RAMPUP_PERIOD:-60}
MEASUREMENT_PERIOD=${MEASUREMENT_PERIOD:-120}
FILTER_RESULTS=${FILTER_RESULTS:-true}
MVN=${MVN:-mvn} # Stress Test Parameters
PROVISIONING_PARAMETERS=${PROVISIONING_PARAMETERS:-} STRESS_TEST_ALGORITHM=${STRESS_TEST_ALGORITHM:-incremental}
PROVISION_COMMAND="$MVN verify -P provision,import-dump $PROVISIONING_PARAMETERS -Ddataset=$dataset" STRESS_TEST_MAX_ITERATIONS=${STRESS_TEST_MAX_ITERATIONS:-10}
TEARDOWN_COMMAND="$MVN verify -P teardown" STRESS_TEST_PROVISIONING=${STRESS_TEST_PROVISIONING:-false}
STRESS_TEST_PROVISIONING_GENERATE_DATASET=${STRESS_TEST_PROVISIONING_GENERATE_DATASET:-false}
STRESS_TEST_PROVISIONING_PARAMETERS=${STRESS_TEST_PROVISIONING_PARAMETERS:-}
function runCommand { # Stress Test - Incremental Algorithm Parameters
STRESS_TEST_UPS_FIRST=${STRESS_TEST_UPS_FIRST:-1.000}
STRESS_TEST_UPS_INCREMENT=${STRESS_TEST_UPS_INCREMENT:-1.000}
# Stress Test - Bisection Algorithm Parameters
lower_bound=${STRESS_TEST_UPS_LOWER_BOUND:-0.000}
upper_bound=${STRESS_TEST_UPS_UPPER_BOUND:-10.000}
STRESS_TEST_UPS_RESOLUTION=${STRESS_TEST_UPS_RESOLUTION:-1.000}
if $STRESS_TEST_PROVISIONING_GENERATE_DATASET; then DATASET_PROFILE="generate-data"; else DATASET_PROFILE="import-dump"; fi
PROVISIONING_COMMAND="$MVN -f $KEYCLOAK_PROJECT_HOME/testsuite/performance/pom.xml verify -P provision,$DATASET_PROFILE $STRESS_TEST_PROVISIONING_PARAMETERS -Ddataset=$DATASET"
TEARDOWN_COMMAND="$MVN -f $KEYCLOAK_PROJECT_HOME/testsuite/performance/pom.xml verify -P teardown"
function run_command {
echo " $1" echo " $1"
echo echo
if ! $debug; then eval "$1"; fi if ! $DRY_RUN; then eval "$1"; fi
} }
function runTest { function run_test {
# use specified warmUpPeriod only in the first iteration, or if provisioning is enabled if [[ $i == 0 || $STRESS_TEST_PROVISIONING == true ]]; then # use specified WARMUP_PERIOD only in the first iteration, or if STRESS_TEST_PROVISIONING is enabled
if [[ $i == 0 || $provisioning == true ]]; then WARMUP_PARAMETER="-DwarmUpPeriod=$WARMUP_PERIOD ";
warmUpParameter="-DwarmUpPeriod=$warmUpPeriod ";
else else
warmUpParameter="-DwarmUpPeriod=0 "; WARMUP_PARAMETER="-DwarmUpPeriod=0 ";
fi
if [[ $sequentialUsersFrom == -1 || $provisioning == true ]]; then
sequentialUsers=$sequentialUsersFrom
else
sequentialUsers=`echo "$sequentialUsersFrom * ( $i + 1 )" | bc`
fi fi
TEST_COMMAND="$MVN verify -Ptest $@ -Ddataset=$dataset $warmUpParameter -DfilterResults=true -DsequentialUsersFrom=$sequentialUsers -DusersPerSec=$usersPerSec" test_command="$MVN -f $KEYCLOAK_PROJECT_HOME/testsuite/performance/tests/pom.xml verify -Ptest $@ -Ddataset=$DATASET $WARMUP_PARAMETER -DrampUpPeriod=$RAMPUP_PERIOD -DmeasurementPeriod=$MEASUREMENT_PERIOD -DfilterResults=$FILTER_RESULTS -DusersPerSec=$users_per_sec"
echo "ITERATION: $(( i+1 )) / $maxIterations $ITERATION_INFO" if $STRESS_TEST_PROVISIONING; then
echo run_command "$PROVISIONING_COMMAND"
if $provisioning; then
runCommand "$PROVISION_COMMAND"
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo "Provisioning failed." echo "Provisioning failed."
runCommand "$TEARDOWN_COMMAND" || break run_command "$TEARDOWN_COMMAND" || break
break break
fi fi
runCommand "$TEST_COMMAND" run_command "$test_command"
export testResult=$? export test_result=$?
runCommand "$TEARDOWN_COMMAND" || exit 1 run_command "$TEARDOWN_COMMAND" || exit 1
else else
runCommand "$TEST_COMMAND" run_command "$test_command"
export testResult=$? export test_result=$?
fi fi
[[ $testResult != 0 ]] && echo "Test exit code: $testResult" [[ $test_result != 0 ]] && echo "Test exit code: $test_result"
} }
cat <<EOM
Stress Test Summary:
Script Execution Parameters:
MVN: $MVN
KEYCLOAK_PROJECT_HOME: $KEYCLOAK_PROJECT_HOME
DRY_RUN: $DRY_RUN
echo "Starting ${algorithm} stress test" Performance Testsuite Parameters:
echo DATASET: $DATASET
WARMUP_PERIOD: $WARMUP_PERIOD seconds
RAMPUP_PERIOD: $RAMPUP_PERIOD seconds
MEASUREMENT_PERIOD: $MEASUREMENT_PERIOD seconds
FILTER_RESULTS: $FILTER_RESULTS
usersPerSecTop=0 Stress Test Parameters:
STRESS_TEST_ALGORITHM: $STRESS_TEST_ALGORITHM
STRESS_TEST_MAX_ITERATIONS: $STRESS_TEST_MAX_ITERATIONS
STRESS_TEST_PROVISIONING: $STRESS_TEST_PROVISIONING
EOM
if $STRESS_TEST_PROVISIONING; then cat <<EOM
STRESS_TEST_PROVISIONING_GENERATE_DATASET: $STRESS_TEST_PROVISIONING_GENERATE_DATASET (MVN -P $DATASET_PROFILE)
STRESS_TEST_PROVISIONING_PARAMETERS: $STRESS_TEST_PROVISIONING_PARAMETERS
EOM
fi
case "${algorithm}" in users_per_sec_max=0
case "${STRESS_TEST_ALGORITHM}" in
incremental) incremental)
for (( i=0; i < $maxIterations; i++)); do cat <<EOM
usersPerSec=`echo "$usersPerSec0 + $i * $incrementFactor" | bc` Incremental Stress Test Parameters:
STRESS_TEST_UPS_FIRST: $STRESS_TEST_UPS_FIRST users per second
STRESS_TEST_UPS_INCREMENT: $STRESS_TEST_UPS_INCREMENT users per second
EOM
runTest $@ for (( i=0; i < $STRESS_TEST_MAX_ITERATIONS; i++)); do
if [[ $testResult == 0 ]]; then users_per_sec=`bc <<<"scale=10; $STRESS_TEST_UPS_FIRST + $i * $STRESS_TEST_UPS_INCREMENT"`
usersPerSecTop=$usersPerSec
echo
echo "STRESS TEST ITERATION: $(( i+1 )) / $STRESS_TEST_MAX_ITERATIONS Load: $users_per_sec users per second"
echo
run_test $@
if [[ $test_result == 0 ]]; then
users_per_sec_max=$users_per_sec
else else
echo "INFO: Last iteration failed. Stopping the loop." echo "INFO: Last iteration failed. Stopping the loop."
break break
@ -84,23 +129,35 @@ case "${algorithm}" in
bisection) bisection)
for (( i=0; i < $maxIterations; i++)); do cat <<EOM
intervalSize=`echo "$highPoint - $lowPoint" | bc` Bisection Stress Test Parameters:
usersPerSec=`echo "$lowPoint + $intervalSize * 0.5" | bc` STRESS_TEST_UPS_LOWER_BOUND: $lower_bound users per second
if [[ `echo "$intervalSize < $tolerance" | bc` == 1 ]]; then echo "INFO: intervalSize < tolerance. Stopping the loop."; break; fi STRESS_TEST_UPS_UPPER_BOUND: $upper_bound users per second
if [[ `echo "$intervalSize < 0" | bc` == 1 ]]; then echo "ERROR: Invalid state: lowPoint > highPoint. Stopping the loop."; exit 1; fi STRESS_TEST_UPS_RESOLUTION: $STRESS_TEST_UPS_RESOLUTION users per second
ITERATION_INFO="L: $lowPoint H: $highPoint intervalSize: $intervalSize tolerance: $tolerance" EOM
runTest $@ for (( i=0; i < $STRESS_TEST_MAX_ITERATIONS; i++)); do
if [[ $testResult == 0 ]]; then interval_size=`bc<<<"scale=10; $upper_bound - $lower_bound"`
usersPerSecTop=$usersPerSec users_per_sec=`bc<<<"scale=10; $lower_bound + $interval_size * 0.5"`
echo
echo "STRESS TEST ITERATION: $(( i+1 )) / $STRESS_TEST_MAX_ITERATIONS Bisection interval: [$lower_bound, $upper_bound], interval_size: $interval_size, STRESS_TEST_UPS_RESOLUTION: $STRESS_TEST_UPS_RESOLUTION, Load: $users_per_sec users per second"
echo
if [[ `bc<<<"scale=10; $interval_size < $STRESS_TEST_UPS_RESOLUTION"` == 1 ]]; then echo "INFO: interval_size < STRESS_TEST_UPS_RESOLUTION. Stopping the loop."; break; fi
if [[ `bc<<<"scale=10; $interval_size < 0"` == 1 ]]; then echo "ERROR: Invalid state: lower_bound > upper_bound. Stopping the loop."; exit 1; fi
run_test $@
if [[ $test_result == 0 ]]; then
users_per_sec_max=$users_per_sec
echo "INFO: Last iteration succeeded. Continuing with the upper half of the interval." echo "INFO: Last iteration succeeded. Continuing with the upper half of the interval."
lowPoint=$usersPerSec lower_bound=$users_per_sec
else else
echo "INFO: Last iteration failed. Continuing with the lower half of the interval." echo "INFO: Last iteration failed. Continuing with the lower half of the interval."
highPoint=$usersPerSec upper_bound=$users_per_sec
fi fi
done done
@ -108,10 +165,15 @@ case "${algorithm}" in
;; ;;
*) *)
echo "Algorithm '${algorithm}' not supported." echo "Algorithm '${STRESS_TEST_ALGORITHM}' not supported."
exit 1 exit 1
;; ;;
esac esac
echo "Highest load with passing test: $usersPerSecTop users per second" echo "Maximal load with passing test: $users_per_sec_max users per second"
if ! $DRY_RUN; then # Generate a Jenkins Plot Plugin-compatible data file
mkdir -p "$KEYCLOAK_PROJECT_HOME/testsuite/performance/tests/target"
echo "YVALUE=$users_per_sec_max" > "$KEYCLOAK_PROJECT_HOME/testsuite/performance/tests/target/stress-test-result.properties"
fi