Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✅ Add perf test #100

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/build-loadtest-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Docker Image CI

on:
release:
types: [published]

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3)
- name: Check out the repo
uses: actions/checkout@v2
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Push to Docker Hub
uses: docker/build-push-action@v2
with:
context: ./test/performance
push: true
tags: bouyguestelecom/a7-perf:${{ steps.get_version.outputs.VERSION }},bouyguestelecom/a7-perf:latest
platforms: linux/amd64,linux/arm64
27 changes: 27 additions & 0 deletions test/performance/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM golang:1.21

ARG XK6_VERSION=v0.12.2

ENV BASE_URL ""
ENV STAGE_DURATION 30

ENV AVERAGE_LOAD_ITER_PER_SEC 15
ENV AVERAGE_LOAD_ITER_VARIATION 5
ENV AVERAGE_LOAD_DURATION_95 500

ENV AUTOSCALING_MAX_ITER_PER_SEC 20
ENV AUTOSCALING_DURATION_95 500

ENV STRESS_LOAD_MIN_ITER_PER_SEC 10
ENV STRESS_LOAD_MAX_ITER_PER_SEC 50
ENV STRESS_LOAD_DURATION_95 500

WORKDIR /app

RUN go install go.k6.io/xk6/cmd/xk6@${XK6_VERSION}
RUN xk6 build ${XK6_VERSION}
RUN mv /app/k6 /usr/bin

COPY common.js autoscaling.js stressload.js average-load.js /app/

CMD ["k6", "run", "/app/average-load.js"]
36 changes: 36 additions & 0 deletions test/performance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

# Performance Metrics Documentation

## How to use it

The default to use it is by running the docker image `bouyguestelecom/a7-perf`
If no command is provided the average load test will be executed.

There 3 k6 scripts available in the image:

- `average-load.js`
- `stressload.js`
- `autoscaling.js`

to run another script you can use the following command (adjust the env variable for your needs):

```bash
docker run bouyguestelecom/a7-perf k6 run <script_name>
```

## Env variables

| Metric Name | Description | Required | Default |
|--------------------------------------|-----------------------------------------------------------------------------|----------|---------|
| **BASE_URL** | The base URL for the application, used for API calls and resource fetching. | Yes | N/A |
| **STAGE_DURATION** | The total duration of each stage in the performance testing process, measured in seconds. | Yes | N/A |
| **STRESS_LOAD_MIN_ITER_PER_SEC** | The minimum number of iterations per second during stress load testing. | Yes | N/A |
| **STRESS_LOAD_MAX_ITER_PER_SEC** | The maximum number of iterations per second during stress load testing. | Yes | N/A |
| **STRESS_LOAD_DURATION_95** | The 95th percentile duration of stress load tests, indicating the time taken for 95% of the requests. | Yes | N/A |
| **AUTOSCALING_MAX_ITER_PER_SEC** | The maximum iterations per second allowed during autoscaling operations. | Yes | N/A |
| **AUTOSCALING_DURATION_95** | The 95th percentile duration for autoscaling events, reflecting the time taken for 95% of autoscaling actions. | Yes | N/A |
| **AVERAGE_LOAD_ITER_PER_SEC** | The average number of iterations per second during normal load conditions. | Yes | N/A |
| **AVERAGE_LOAD_ITER_VARIATION** | The variation in iterations per second during average load conditions, indicating stability or fluctuations. | Yes | N/A |
| **AVERAGE_LOAD_DURATION_95** | The 95th percentile duration for average load conditions, showing the time taken for 95% of requests under normal load. | Yes | N/A |
| **PREALLOACATED_VUS** | The number of virtual users preallocated for performance testing, used to simulate load conditions. | No | 150 |
| **SKIP_TLS_VERIFY** | A flag to skip TLS verification during API calls, useful for testing in development environments. | No | False |
45 changes: 45 additions & 0 deletions test/performance/autoscaling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { _setup, _main, thresholds, PREALLOACATED_VUS, STAGE_DURATION_WITH_UNIT, commonOptions } from './common.js';

_checkRequiredEnvVars([
'BASE_URL',
'STAGE_DURATION',
'AUTOSCALING_MAX_ITER_PER_SEC',
'AUTOSCALING_DURATION_95'
])

const AUTOSCALING_MAX_ITER_PER_SEC = __ENV.AUTOSCALING_MAX_ITER_PER_SEC
const AUTOSCALING_DURATION_95 = __ENV.AUTOSCALING_DURATION_95

const minTarget = Math.round(AUTOSCALING_MAX_ITER_PER_SEC / 5)
const midTarget = Math.round(AUTOSCALING_MAX_ITER_PER_SEC / 2)

const autoscalingStages = [
{ duration: STAGE_DURATION_WITH_UNIT, target: minTarget },
{ duration: STAGE_DURATION_WITH_UNIT, target: midTarget },
{ duration: STAGE_DURATION_WITH_UNIT, target: AUTOSCALING_MAX_ITER_PER_SEC },
{ duration: STAGE_DURATION_WITH_UNIT, target: midTarget },
{ duration: STAGE_DURATION_WITH_UNIT, target: minTarget },

]

export const options = {
...commonOptions,
scenarios: {
autoscaling: {
exec: 'main',
executor: 'ramping-arrival-rate',
preAllocatedVUs: PREALLOACATED_VUS,
gracefulStop: "0s",
stages: autoscalingStages,
tags: { test_type: 'autoscaling' },
},
},
thresholds: {
...thresholds,
'http_req_duration{test_type:autoscaling}': [`p(95)< ${AUTOSCALING_DURATION_95}`], // 95% of requests should be below 200ms
}
}

export const setup = _setup

export const main = _main
48 changes: 48 additions & 0 deletions test/performance/average-load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { _setup, _main, commonOptions, thresholds, PREALLOACATED_VUS, STAGE_DURATION_WITH_UNIT, _checkRequiredEnvVars } from './common.js';


_checkRequiredEnvVars([
'BASE_URL',
'STAGE_DURATION',
'AVERAGE_LOAD_ITER_PER_SEC',
'AVERAGE_LOAD_ITER_VARIATION',
'AVERAGE_LOAD_DURATION_95'
])

const AVERAGE_LOAD_ITER_PER_SEC = __ENV.AVERAGE_LOAD_ITER_PER_SEC
const AVERAGE_LOAD_ITER_VARIATION = __ENV.AVERAGE_LOAD_ITER_VARIATION
const AVERAGE_LOAD_DURATION_95 = __ENV.AVERAGE_LOAD_DURATION_95

const averageLoadStages = [
// With this executor it will increase from 0 to `AVERAGE_LOAD_ITER_PER_SEC` iterations/s within the first `STAGE_DURATION_WITH_UNIT`
// Then will go from AVERAGE_LOAD_ITER_VARIATION iter/s to AVERAGE_LOAD_ITER_VARIATION + AVERAGE_LOAD_ITER_PER_SEC iter/s within the next 30secs
// then will stay at 100 iter/s for 30secs
// finally will go from 100 iter/s to 50 iter/s within the next 30secs
// it only reaches the target at the end of the duration
{ duration: STAGE_DURATION_WITH_UNIT, target: AVERAGE_LOAD_ITER_PER_SEC },
{ duration: STAGE_DURATION_WITH_UNIT, target: Number(AVERAGE_LOAD_ITER_VARIATION) + Number(AVERAGE_LOAD_ITER_PER_SEC) },
{ duration: STAGE_DURATION_WITH_UNIT, target: Number(AVERAGE_LOAD_ITER_PER_SEC) - Number(AVERAGE_LOAD_ITER_VARIATION) },
{ duration: STAGE_DURATION_WITH_UNIT, target: AVERAGE_LOAD_ITER_PER_SEC },
]

export const options = {
...commonOptions,
scenarios: {
averageLoad: {
exec: 'main',
executor: 'ramping-arrival-rate',
preAllocatedVUs: PREALLOACATED_VUS,
gracefulStop: "0s",
stages: averageLoadStages,
tags: { test_type: 'averageLoad' },
},
},
thresholds: {
...thresholds,
'http_req_duration{test_type:averageLoad}': [`p(95)< ${AVERAGE_LOAD_DURATION_95}`], // 95% of requests should be below 200ms
}
}

export const setup = _setup

export const main = _main
42 changes: 42 additions & 0 deletions test/performance/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import http from 'k6/http';
import { check } from 'k6';

export const STAGE_DURATION_WITH_UNIT = __ENV.STAGE_DURATION + 's';
export const PREALLOACATED_VUS = __ENV.PREALLOACATED_VUS ?? 150;
let URL = `${__ENV.BASE_URL}/ACO/[email protected]/build/micro-front-main.js`;
const URL_CATALOG = `${__ENV.BASE_URL}/?catalog`

// console.log("--------------------")
// console.log({ __ENV })
// console.log("--------------------")



export const commonOptions = {
insecureSkipTLSVerify: __ENV.SKIP_TLS_VERIFY ?? false,
}

export const thresholds = {
http_req_failed: ['rate<0.0001'], // http errors should be less than 1%
}

export const _checkRequiredEnvVars = (requiredEnvVars) => {
for (const envVar of requiredEnvVars) {
if (!__ENV[envVar]) {
throw new Error(`Environment variable ${envVar} is missing`);
}
}
}

export const _setup = () => {
const res = http.get(URL_CATALOG);
check(res, { 'status was 200': (r) => r.status == 200 });
// make sure catalog fetch takes less than 10s
check(res, { 'status was 200': (r) => r.timings.duration < 10000 });

}

export const _main = () => {
const res = http.get(URL);
check(res, { 'status was 200': (r) => r.status == 200 });
}
131 changes: 131 additions & 0 deletions test/performance/loadtesting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Discover how the system functions with sudden and massive increases in traffic.
Copy link
Collaborator Author

@Quentin1006 Quentin1006 Oct 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retirer le fichier


import http from 'k6/http';
import { check } from 'k6';

/********************
* Environment variables
*
* @env BASE_URL
* @env STAGE_DURATION
* @env AVERAGE_LOAD_ITER_PER_SEC
* @env AVERAGE_LOAD_ITER_VARIATION
* @env AUTOSCALING_ITER_PER_SEC
* @env STRESS_LOAD_MIN_ITER_PER_SEC
* @env STRESS_LOAD_MAX_ITER_PER_SEC
*/


// const URL_CATALOG = "https://swap-dev.int.nbyt.fr/?catalog"

// const BASE_URL = __ENV.BASE_URL
// const STAGE_DURATION = __ENV.STAGE_DURATION
// const AVERAGE_LOAD_ITER_PER_SEC = __ENV.AVERAGE_LOAD_ITER_PER_SEC
// const AVERAGE_LOAD_ITER_VARIATION = __ENV.AVERAGE_LOAD_ITER_VARIATION
// const AUTOSCALING_ITER_PER_SEC = __ENV.AUTOSCALING_ITER_PER_SEC
// const STRESS_LOAD_MIN_ITER_PER_SEC = __ENV.STRESS_LOAD_MIN_ITER_PER_SEC
// const STRESS_LOAD_MAX_ITER_PER_SEC = __ENV.STRESS_LOAD_MAX_ITER_PER_SEC


/** TO SKIP SETTING ALL ENV VARIABLES */
const BASE_URL = __ENV.BASE_URL ?? 'http://localhost:45537'
const STAGE_DURATION = __ENV.STAGE_DURATION ?? 30;
const AVERAGE_LOAD_ITER_PER_SEC = __ENV.AVERAGE_LOAD_ITER_PER_SEC ?? 10;
const AVERAGE_LOAD_ITER_VARIATION = __ENV.AVERAGE_LOAD_ITER_VARIATION ?? 10;
const AUTOSCALING_ITER_PER_SEC = __ENV.AUTOSCALING_ITER_PER_SEC ?? 10;
const STRESS_LOAD_MIN_ITER_PER_SEC = __ENV.STRESS_LOAD_MIN_ITER_PER_SEC ?? 10;
const STRESS_LOAD_MAX_ITER_PER_SEC = __ENV.STRESS_LOAD_MAX_ITER_PER_SEC ?? 20;
/** END TO SKIP SETTING ALL ENV VARIABLES */

if (!__ENV.BASE_URL || !__ENV.STAGE_DURATION || !__ENV.AVERAGE_LOAD_ITER_PER_SEC || !__ENV.AVERAGE_LOAD_ITER_VARIATION || !__ENV.AUTOSCALING_ITER_PER_SEC || !__ENV.STRESS_LOAD_MIN_ITER_PER_SEC || !__ENV.STRESS_LOAD_MAX_ITER_PER_SEC) {
throw new Error('One or more environment variables are not specified');
}

const STAGE_DURATION_WITH_UNIT = STAGE_DURATION + 's';



const averageLoadStages = [
// With this executor it will increase from 0 to `AVERAGE_LOAD_ITER_PER_SEC` iterations/s within the first `STAGE_DURATION_WITH_UNIT`
// Then will go from AVERAGE_LOAD_ITER_VARIATION iter/s to AVERAGE_LOAD_ITER_VARIATION + AVERAGE_LOAD_ITER_PER_SEC iter/s within the next 30secs
// then will stay at 100 iter/s for 30secs
// finally will go from 100 iter/s to 50 iter/s within the next 30secs
// it only reaches the target at the end of the duration
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: __ENV.AVERAGE_LOAD_ITER_PER_SEC },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: __ENV.AVERAGE_LOAD_ITER_VARIATION + AVERAGE_LOAD_ITER_PER_SEC },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: __ENV.AVERAGE_LOAD_ITER_PER_SEC - AVERAGE_LOAD_ITER_VARIATION },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: __ENV.AVERAGE_LOAD_ITER_PER_SEC },
]

const autoscalingStages = [
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 20 },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 50 },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 100 },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 50 },
{ duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 20 },

]

const stressLoadStages = new Array(Math.round((STRESS_LOAD_MAX_ITER_PER_SEC - STRESS_LOAD_MIN_ITER_PER_SEC)) / 10).fill(0).map((_, i) => {
return { duration: STAGE_DURATION_WITH_UNIT, target: STRESS_LOAD_MIN_ITER_PER_SEC + i * 10 }
})

console.log({ stressLoadStages })

// const stressLoadStages = [
// { duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 20 },
// { duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 30 },
// { duration: __ENV.STAGE_DURATION_WITH_UNIT, target: 40 },
// ]

export const options = {
scenarios: {
// averageLoad: {
// exec: 'main',
// executor: 'ramping-arrival-rate',
// preAllocatedVUs: 100,
// startTime: '0s',
// gracefulStop: "0s",
// stages: averageLoadStages,
// tags: { test_type: 'averageLoad' },
// },
// autoscaling: {
// exec: 'main',
// executor: 'ramping-arrival-rate',
// preAllocatedVUs: 150,
// // only way to run scenarios sequentially
// startTime: STAGE_DURATION * averageLoadStages.length + 's',
// gracefulStop: "0s",
// stages: autoscalingStages,
// tags: { test_type: 'autoscaling' },
// },
stressLoad: {
executor: 'ramping-arrival-rate',
exec: 'main',
// startTime: STAGE_DURATION * (averageLoadStages.length + autoscalingStages.length) + 's',
preAllocatedVUs: 150,
gracefulStop: "0s",
stages: stressLoadStages,
tags: { test_type: 'stressLoad' },
},

},
thresholds: {
http_req_failed: ['rate<0.0001'], // http errors should be less than 1%
http_req_duration: ['p(95)<200'], // 95% of requests should be below 200ms
'http_req_duration{test_type:stressLoad}': ['p(95)<1000'], // 95% of requests should be below 200ms
},
};

export function _setup() {
const res = http.get(URL_CATALOG);
check(res, { 'status was 200': (r) => r.status == 200 });
// make sure catalog fetch takes less than 10s
check(res, { 'status was 200': (r) => r.timings.duration < 10000 });

}

export function _main() {
const res = http.get(URL);
check(res, { 'status was 200': (r) => r.status == 200 });
}
Loading
Loading