Skip to content

Commit 15b523d

Browse files
jirevweDotunj
andauthored
Add load test scripts (frain-dev#997)
* chore: add load test scripts * fix: update load test script for outgoing events (frain-dev#1000) * add workflow for running load tests in CI (frain-dev#1025) * update loadtest script * feat: add workflow for load test * feat: add schedule for workflow * fix: update secrets name * chore: add trigger for workflow * fix: update flag name * fix: update file path * chore: remove pull request trigger for workflow Co-authored-by: Dotun Jolaoso <dotunjolaosho@gmail.com>
1 parent 4a25bbc commit 15b523d

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

.github/workflows/load-test.yml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Run Load Test
2+
3+
on:
4+
schedule:
5+
- cron: '30 14 * * *'
6+
- cron: '30 18 * * *'
7+
- cron: '30 21 * * *'
8+
9+
workflow_dispatch:
10+
inputs:
11+
name:
12+
description: "Manual workflow name"
13+
required: true
14+
15+
16+
jobs:
17+
load_test:
18+
name: K6 Load Test
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v1
24+
25+
- name: Run Local K6 Test
26+
uses: grafana/k6-action@v0.2.0
27+
with:
28+
filename: loadtest/convoy.js
29+
flags: --out influxdb=${{ secrets.INFLUXDB_BASE_URL }}
30+
env:
31+
BASE_URL: ${{ secrets.CONVOY_BASE_URL }}
32+
API_KEY: ${{ secrets.CONVOY_API_KEY }}
33+
APP_ID: ${{ secrets.CONVOY_APP_ID }}

loadtest/convoy.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import http from "k6/http";
2+
import { check, sleep } from "k6";
3+
import { Trend, Rate } from "k6/metrics";
4+
import { randomItem } from "https://jslib.k6.io/k6-utils/1.2.0/index.js";
5+
6+
const baseUrl = `${__ENV.BASE_URL}/api/v1`;
7+
const apiKey = __ENV.API_KEY;
8+
const appId = __ENV.APP_ID;
9+
const params = {
10+
headers: {
11+
"Content-Type": "application/json",
12+
Authorization: `Bearer ${apiKey}`,
13+
},
14+
};
15+
16+
const listEventsErrorRate = new Rate("List_Events_errors");
17+
const createEventErrorRate = new Rate("Create_Event_error");
18+
const ListEventsTrend = new Trend("List_Events");
19+
const createEventsTrend = new Trend("Create_Events");
20+
21+
const names = ["John", "Jane", "Bert", "Ed"];
22+
const emails = [
23+
"John@gmail.com",
24+
"Jane@amazon.com",
25+
"Bert@yahoo.com",
26+
"Ed@hotmail.com",
27+
];
28+
29+
export const generateEventPayload = (appId) => ({
30+
app_id: appId,
31+
data: {
32+
player_name: randomItem(names),
33+
email: randomItem(emails),
34+
},
35+
event_type: `${randomItem(names)}.${randomItem(names)}`.toLowerCase(),
36+
});
37+
38+
export let options = {
39+
noConnectionReuse: true,
40+
stages: [
41+
{ duration: "60s", target: 20 }, // simulate ramp-up of traffic from 1 to 20 users over 60s
42+
{ duration: "60s", target: 20 }, // stay at 20 users for 60s
43+
],
44+
thresholds: {
45+
"List_Events": ["p(95) < 3000"], //95% of requests must complete below 3s
46+
"Create_Events": ["p(99) < 3000"], //99% of requests must complete below 3s
47+
"List_Events_errors": ["rate<0.1"], // error rate must be less than 10%
48+
"Create_Event_error": ["rate<0.1"], // error rate must be less than 10%
49+
http_req_duration: ["p(99)<6000"], // 99% of requests must complete below 6s
50+
},
51+
};
52+
53+
export default function () {
54+
let eventBody = JSON.stringify(generateEventPayload(appId));
55+
const listEventsUrl = `${baseUrl}/events?appId=${appId}`;
56+
const createEventUrl = `${baseUrl}/events`;
57+
58+
const requests = {
59+
"List_Events": {
60+
method: "GET",
61+
url: listEventsUrl,
62+
params: params,
63+
},
64+
"Create_Events": {
65+
method: "POST",
66+
url: createEventUrl,
67+
params: params,
68+
body: eventBody,
69+
},
70+
};
71+
72+
const responses = http.batch(requests)
73+
const listResp = responses['List_Events'];
74+
const createResp = responses['Create_Events'];
75+
76+
check(listResp, {
77+
'list_events': (r) => r.status === 200,
78+
}) || listEventsErrorRate.add(1);
79+
80+
ListEventsTrend.add(listResp.timings.duration)
81+
82+
check(createResp, {
83+
'event_created': (r) => r.status === 201,
84+
}) || createEventErrorRate.add(1)
85+
86+
createEventsTrend.add(createResp.timings.duration)
87+
88+
sleep(1);
89+
}

loadtest/setup.sh

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
3+
# https://linuxtect.com/make-bash-shell-safe-with-set-euxo-pipefail/
4+
# https://gist.github.com/vncsna/64825d5609c146e80de8b1fd623011ca
5+
set -euo pipefail
6+
7+
8+
HOST=$1
9+
ENDPOINT=$2
10+
BASE_URL=$HOST/api/v1
11+
12+
have_jq=$(which jq)
13+
14+
# create the test group
15+
curl --location --request POST $BASE_URL/groups \
16+
--header 'Content-Type: application/json' \
17+
--data-raw '{
18+
"config": {
19+
"disableEndpoint": false,
20+
"signature": {
21+
"hash": "SHA256",
22+
"header": "X-Test-Signature"
23+
},
24+
"strategy": {
25+
"default": {
26+
"intervalSeconds": 60,
27+
"retryLimit": 10
28+
},
29+
"type": "default"
30+
}
31+
},
32+
"rate_limit": 5000,
33+
"rate_limit_duration": "1m",
34+
"name": "load-test-group"
35+
}'
36+
37+
# get groups
38+
groups=$(curl --location --request GET "$BASE_URL/groups")
39+
40+
# get group for test
41+
groupId=$(echo $groups | jq --raw-output .data[0].uid)
42+
43+
curl --location --request POST "$BASE_URL/applications?groupId=$groupId" \
44+
--header 'Content-Type: application/json' \
45+
--data-raw '{
46+
"name": "staging",
47+
"support_email": "getconvoy@gmail.com"
48+
}'
49+
50+
# get apps
51+
apps=$(curl --location --request GET "$BASE_URL/applications?groupId=$groupId")
52+
53+
# get group for test
54+
appId=$(echo $apps | jq --raw-output .data.content[-1:][0].uid)
55+
56+
curl --location --request POST "$BASE_URL/applications/$appId/endpoints?groupId=$groupId" \
57+
--header 'Content-Type: application/json' \
58+
--data-raw "{ \"url\": \"$ENDPOINT\", \"description\": \"John wants to wick\", \"secret\": \"john-wicks-dog\" }"
59+
60+
echo "group id: $groupId, app id $appId"

0 commit comments

Comments
 (0)