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 8 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ etc/nginx/edge/*

test-output.json
node_modules

zip

.npmrc

3 changes: 1 addition & 2 deletions test/integration/run.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/bin/sh

npx [email protected] ./tests/*.http --silent --json --all -e dev > ./test-output.json
npx [email protected] ./tests/*.http --silent --json --all -e dev > ./test-output.json
cat ./test-output.json | jq -r '.summary | "Total: \(.totalTests), Passed: \(.successTests), Failed: \(.failedTests)"'
echo "Tests results details will be available under artifact 'http-yac-test-results'"

Expand Down
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 });
}
39 changes: 39 additions & 0 deletions test/performance/stressload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { _setup, _main, commonOptions, thresholds, PREALLOACATED_VUS, STAGE_DURATION_WITH_UNIT, _checkRequiredEnvVars, commonOptions } from './common.js';

_checkRequiredEnvVars([
"BASE_URL",
"STAGE_DURATION",
"STRESS_LOAD_MIN_ITER_PER_SEC",
"STRESS_LOAD_MAX_ITER_PER_SEC",
"STRESS_LOAD_DURATION_95"
])

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
const STRESS_LOAD_DURATION_95 = __ENV.STRESS_LOAD_DURATION_95

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 }
})

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

export const setup = _setup

export const main = _main
Loading