Skip to content

Commit

Permalink
test: add load tests (#93)
Browse files Browse the repository at this point in the history
* setup env for k6

* test: load tests to package.json

* test: add tests

* test: add load test

* test: granualize metrics

* test: have a standard workload

* test: load 50 users 1 request/sec/user

* test: log the response failures

* test: randomize the iteration interval

* test: adjust the load scenario

* test: doc update

* test: move globals to shared esconfig
  • Loading branch information
shibbas authored Oct 27, 2023
1 parent 6e3f8e9 commit eeb8fa1
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"version": "latest",
"installBicep": true
},
"ghcr.io/azure/azure-dev/azd:latest": {}
"ghcr.io/azure/azure-dev/azd:latest": {},
"ghcr.io/devcontainers-contrib/features/k6:1": {}
},

// Configure tool-specific properties.
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"start:indexer": "npm run dev --workspace=indexer",
"test": "npm run test -ws --if-present",
"test:playwright": "npx playwright test",
"test:load": "k6 run tests/load/index.js",
"build": "npm run build -ws --if-present",
"clean": "npm run clean -ws --if-present",
"docker:build": "npm run docker:build -ws --if-present",
Expand All @@ -33,6 +34,7 @@
"@playwright/test": "^1.39.0",
"@tapjs/nock": "^3.1.13",
"@types/node": "^18.15.3",
"@types/k6": "^0.47.1",
"concurrently": "^8.2.1",
"eslint-config-shared": "^1.0.0",
"lint-staged": "^14.0.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-config/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
globals: {
__ENV: 'readonly',
},
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
Expand Down
22 changes: 22 additions & 0 deletions tests/load/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The tests use [k6](https://k6.io/) to perform load testing.

# Install k6

k6 is already included in the dev container, so no further installation is required.

For manual installation, refer to [k6 installation docs](https://k6.io/docs/get-started/installation/).

# To run the test

Set the following environment variables to point to the deployment.

```
export WEBAPP_URI=<webapp_uri>
export SEARCH_API_URI=<search_api_uri>
```

Once set, you can now run load tests using the following command:

```
npm run test:load
```
66 changes: 66 additions & 0 deletions tests/load/chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import http from 'k6/http';
import { Trend } from 'k6/metrics';
import { group, sleep } from 'k6';

const chatStreamLatency = new Trend('chat_stream_duration');
const chatNoStreamLatency = new Trend('chat_nostream_duration');

function between(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
}

function choose(list) {
return list[between(0, list.length)];
}

export function chat(baseUrl, stream = true) {
group('Chat flow', function () {
const defaultPrompts = [
'How to search and book rentals?',
'What is the refund policy?',
'How to contact a representative?',
];

const payload = JSON.stringify({
messages: [{ content: choose(defaultPrompts), role: 'user' }],
context: {
retrieval_mode: 'hybrid',
semantic_ranker: true,
semantic_captions: false,
suggest_followup_questions: true,
retrievalMode: 'hybrid',
top: 3,
useSemanticRanker: true,
useSemanticCaptions: false,
excludeCategory: '',
promptTemplate: '',
promptTemplatePrefix: '',
promptTemplateSuffix: '',
suggestFollowupQuestions: true,
approach: 'rrr',
},
stream,
});

const parameters = {
headers: {
'Content-Type': 'application/json',
},
tags: { type: 'API' },
};

const response = http.post(`${baseUrl}/chat`, payload, parameters);

if (response.status !== 200) {
console.log(`Response: ${response.status} ${response.body}`);
}

// add duration property to metric
const latencyMetric = stream ? chatStreamLatency : chatNoStreamLatency;
latencyMetric.add(response.timings.duration, { type: 'API' });

sleep(between(5, 20)); // wait between 5 and 20 seconds between each user iteration
});
}
16 changes: 16 additions & 0 deletions tests/load/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const thresholdsSettings = {
'http_req_failed{type:API}': [{ threshold: 'rate<0.01' }], // less than 1% failed requests
'http_req_failed{type:content}': [{ threshold: 'rate<0.01' }], // less than 1% failed requests
'http_req_duration{type:API}': ['p(90)<40000'], // 90% of the API requests must complete below 40s
'http_req_duration{type:content}': ['p(99)<200'], // 99% of the content requests must complete below 200ms
};

// 5.00 iterations/s for 1m0s (maxVUs: 100-200, gracefulStop: 30s)
export const standardWorkload = {
executor: 'constant-arrival-rate',
rate: 5,
timeUnit: '1s',
duration: '1m',
preAllocatedVUs: 100,
maxVUs: 200,
};
19 changes: 19 additions & 0 deletions tests/load/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { mainpage } from './mainpage.js';
import { chat } from './chat.js';
import { thresholdsSettings, standardWorkload } from './config.js';

export const options = {
scenarios: {
staged: standardWorkload,
},
thresholds: thresholdsSettings,
};

const webappUrl = __ENV.WEBAPP_URI;
const searchUrl = __ENV.SEARCH_API_URI;

export default function () {
mainpage(webappUrl);
chat(searchUrl, true);
//chat(searchUrl, false);
}
15 changes: 15 additions & 0 deletions tests/load/mainpage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import http from 'k6/http';
import { Trend } from 'k6/metrics';
import { group, sleep } from 'k6';

const mainpageLatency = new Trend('mainpage_duration');

export function mainpage(baseUrl) {
group('Mainpage', function () {
// save response as variable
const response = http.get(`${baseUrl}`, { tags: { type: 'content' } });
// add duration property to metric
mainpageLatency.add(response.timings.duration, { type: 'content' });
sleep(1);
});
}

0 comments on commit eeb8fa1

Please sign in to comment.