Add k6 tests
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,3 +41,6 @@ out/
|
|||||||
|
|
||||||
### Local postgresql database data ###
|
### Local postgresql database data ###
|
||||||
/local/postgresql/pgdata
|
/local/postgresql/pgdata
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
|
**/dist
|
||||||
21
k6-example/LICENSE
Normal file
21
k6-example/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Sebastian Southern
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
40
k6-example/README.md
Normal file
40
k6-example/README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# K6 Example
|
||||||
|
|
||||||
|
This repository contains a starting point for running k6 tests in typescript. It uses a publicly hosted endpoint so that anyone with internet access can test out the API themselves and then use this repo as a starting point to continue building their own tests.
|
||||||
|
|
||||||
|
## Pre-Reqs
|
||||||
|
|
||||||
|
- Download `k6` from here https://k6.io/docs/get-started/installation/
|
||||||
|
- Install dependencies `yarn install`
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
### Installing xk6-dashboard with Docker
|
||||||
|
|
||||||
|
[xk6-dashboard](https://github.com/grafana/xk6-dashboard) is a k6 extension that can be used to visualise your performance test in real time.
|
||||||
|
|
||||||
|
To run the tests with monitoring with xk6-dashboard extension, we need to install it. The simplest way to install is via docker and can be done via
|
||||||
|
|
||||||
|
`docker pull ghcr.io/grafana/xk6-dashboard:0.6.1`
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### reqres
|
||||||
|
|
||||||
|
We use the [reqres](https://reqres.in/) publicly hosted REST API to showcase the testing with k6
|
||||||
|
|
||||||
|
To execute the first sample test that showcases how `per-vu-iterations` works, you can run:
|
||||||
|
|
||||||
|
`yarn test:demo`
|
||||||
|
|
||||||
|
To test with monitoring in place, run:
|
||||||
|
|
||||||
|
`yarn test-with-monitoring:demo`
|
||||||
|
|
||||||
|
To execute the second sample test that showcases how to use `stages`, you can run:
|
||||||
|
|
||||||
|
`yarn test:demo-stages`
|
||||||
|
|
||||||
|
To test with monitoring in place, run:
|
||||||
|
|
||||||
|
`yarn test-with-monitoring:demo-stages`
|
||||||
|
|
||||||
3
k6-example/configuration.yml
Normal file
3
k6-example/configuration.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
target:
|
||||||
|
url: http://localhost
|
||||||
|
port: 51001
|
||||||
2208
k6-example/package-lock.json
generated
Normal file
2208
k6-example/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
k6-example/package.json
Normal file
29
k6-example/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "k6-example",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"test:demo": "yarn build && k6 run dist/tests/load-tests.cjs",
|
||||||
|
"test:demo-stages": "yarn build && k6 run dist/tests/reqres-stages.cjs",
|
||||||
|
"test-with-monitoring:demo": "yarn build && docker run --platform linux/amd64 -it -p 5665:5665 -v $(pwd)/dist/:/src ghcr.io/grafana/xk6-dashboard:0.6.1 run --out 'dashboard=period=2s' /src/tests/reqres.cjs",
|
||||||
|
"test-with-monitoring:demo-stages": "yarn build && docker run --platform linux/amd64 -it -p 5665:5665 -v $(pwd)/dist/:/src ghcr.io/grafana/xk6-dashboard:0.6.1 run --out 'dashboard=period=2s' /src/tests/reqres-stages.cjs"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "7.23.3",
|
||||||
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
"@types/k6": "~0.47.3",
|
||||||
|
"@types/node": "^24.5.2",
|
||||||
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
|
"typescript": "5.3.2",
|
||||||
|
"vite": "^5.0.0"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^4.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
k6-example/src/apis/marketplace-apis.ts
Normal file
36
k6-example/src/apis/marketplace-apis.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {Marketplace} from "../data/marketplace";
|
||||||
|
import http from "k6/http";
|
||||||
|
import {logWaitingTime} from "../utils/logger";
|
||||||
|
import {Trend} from "k6/metrics";
|
||||||
|
import {check} from "k6";
|
||||||
|
import {Response} from "../data/common";
|
||||||
|
|
||||||
|
const marketplaceUrl = 'http://localhost:51001'
|
||||||
|
|
||||||
|
// Metrics that we want to track
|
||||||
|
const metrics = {
|
||||||
|
getMarketplace: new Trend("get_marketplace_time", true),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMarketplace = (): Response<Marketplace> => {
|
||||||
|
const url = `${marketplaceUrl}/api/marketplace`;
|
||||||
|
|
||||||
|
const response = http.get(url);
|
||||||
|
|
||||||
|
// console.log('response:', response);
|
||||||
|
|
||||||
|
logWaitingTime({
|
||||||
|
metric: metrics.getMarketplace,
|
||||||
|
response: response,
|
||||||
|
messageType: 'Get Marketplace',
|
||||||
|
});
|
||||||
|
|
||||||
|
check(response, {
|
||||||
|
'Get Marketplace: is 200': r => r.status === 200
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {} as Marketplace,
|
||||||
|
statusCode: response.status
|
||||||
|
}
|
||||||
|
}
|
||||||
113
k6-example/src/apis/reqres.ts
Normal file
113
k6-example/src/apis/reqres.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { check } from "k6";
|
||||||
|
import http, { StructuredRequestBody } from "k6/http";
|
||||||
|
import { Trend } from "k6/metrics";
|
||||||
|
import { logWaitingTime } from "../utils/logger";
|
||||||
|
import { Response } from '../data/common';
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
id: number;
|
||||||
|
email: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Metrics that we want to track
|
||||||
|
const metrics = {
|
||||||
|
getUserResponseTime: new Trend("get_user_response_time", true),
|
||||||
|
updateUserResponseTime: new Trend("update_user_response_time", true),
|
||||||
|
deleteUserResponseTime: new Trend("delete_user_response_time", true),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Endpoint that we test against
|
||||||
|
const reqResUrl = "https://reqres.in/api";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getUser makes a GET request to the /users/:id endpoint and asserts
|
||||||
|
* that the response is 200 and that the user id and email match the
|
||||||
|
* user object that was passed in.
|
||||||
|
* @param user
|
||||||
|
* @returns Response<User>
|
||||||
|
*/
|
||||||
|
export const getUser = (user: User): Response<User> => {
|
||||||
|
const url = reqResUrl;
|
||||||
|
const params = {
|
||||||
|
headers: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = http.get(`${url}/users/${user.id}`, params);
|
||||||
|
const jsonRes = res.json() as { data: User };
|
||||||
|
logWaitingTime({
|
||||||
|
metric: metrics.getUserResponseTime,
|
||||||
|
response: res,
|
||||||
|
messageType: `Get User`,
|
||||||
|
});
|
||||||
|
|
||||||
|
check(res, {
|
||||||
|
"Get User By Id: is 200": (r) => r.status === 200,
|
||||||
|
"Get User By Id: has correct id": (_) => jsonRes.data.id === user.id,
|
||||||
|
"Get User By Id: has correct email": (_) =>
|
||||||
|
jsonRes.data.email === user.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: jsonRes.data,
|
||||||
|
statusCode: res.status,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateUserResponse = {
|
||||||
|
updatedAt: string;
|
||||||
|
job: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateUser = (user: User): Response<UpdateUserResponse> => {
|
||||||
|
const url = reqResUrl;
|
||||||
|
const params = {
|
||||||
|
headers: {},
|
||||||
|
};
|
||||||
|
const jobTitle = "QA Engineer";
|
||||||
|
const payload: StructuredRequestBody = {
|
||||||
|
job: jobTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const res = http.patch(`${url}/users/${user.id}`, payload, params);
|
||||||
|
const jsonRes = res.json() as UpdateUserResponse
|
||||||
|
logWaitingTime({
|
||||||
|
metric: metrics.updateUserResponseTime,
|
||||||
|
response: res,
|
||||||
|
messageType: `Update User`,
|
||||||
|
});
|
||||||
|
|
||||||
|
check(res, {
|
||||||
|
"Update User By Id: is 200": (r) => r.status === 200,
|
||||||
|
"Update User By Id: has correct updatedAt": (_) => jsonRes.updatedAt !== "",
|
||||||
|
"Update User By Id: has correct job title": (_) => jsonRes.job === jobTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: jsonRes,
|
||||||
|
statusCode: res.status,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteUser = (userId: number): Response<{}> => {
|
||||||
|
const url = reqResUrl;
|
||||||
|
|
||||||
|
const res = http.del(`${url}/users/${userId}`);
|
||||||
|
logWaitingTime({
|
||||||
|
metric: metrics.deleteUserResponseTime,
|
||||||
|
response: res,
|
||||||
|
messageType: `Delete User`,
|
||||||
|
});
|
||||||
|
|
||||||
|
check(res, {
|
||||||
|
"Delete User By Id: is 204": (r) => r.status === 204,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {},
|
||||||
|
statusCode: res.status,
|
||||||
|
};
|
||||||
|
};
|
||||||
4
k6-example/src/data/common.ts
Normal file
4
k6-example/src/data/common.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export type Response<T> = {
|
||||||
|
data: T;
|
||||||
|
statusCode: number;
|
||||||
|
};
|
||||||
15
k6-example/src/data/marketplace.ts
Normal file
15
k6-example/src/data/marketplace.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export interface Catalog {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isShared: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Marketplace {
|
||||||
|
catalogs: Catalog[];
|
||||||
|
sharedItems: Item[]
|
||||||
|
}
|
||||||
86
k6-example/src/data/users.ts
Normal file
86
k6-example/src/data/users.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
export const users = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
email: "george.bluth@reqres.in",
|
||||||
|
first_name: "George",
|
||||||
|
last_name: "Bluth",
|
||||||
|
avatar: "https://reqres.in/img/faces/1-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
email: "janet.weaver@reqres.in",
|
||||||
|
first_name: "Janet",
|
||||||
|
last_name: "Weaver",
|
||||||
|
avatar: "https://reqres.in/img/faces/2-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
email: "emma.wong@reqres.in",
|
||||||
|
first_name: "Emma",
|
||||||
|
last_name: "Wong",
|
||||||
|
avatar: "https://reqres.in/img/faces/3-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
email: "eve.holt@reqres.in",
|
||||||
|
first_name: "Eve",
|
||||||
|
last_name: "Holt",
|
||||||
|
avatar: "https://reqres.in/img/faces/4-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
email: "charles.morris@reqres.in",
|
||||||
|
first_name: "Charles",
|
||||||
|
last_name: "Morris",
|
||||||
|
avatar: "https://reqres.in/img/faces/5-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
email: "tracey.ramos@reqres.in",
|
||||||
|
first_name: "Tracey",
|
||||||
|
last_name: "Ramos",
|
||||||
|
avatar: "https://reqres.in/img/faces/6-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
email: "michael.lawson@reqres.in",
|
||||||
|
first_name: "Michael",
|
||||||
|
last_name: "Lawson",
|
||||||
|
avatar: "https://reqres.in/img/faces/7-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
email: "lindsay.ferguson@reqres.in",
|
||||||
|
first_name: "Lindsay",
|
||||||
|
last_name: "Ferguson",
|
||||||
|
avatar: "https://reqres.in/img/faces/8-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
email: "tobias.funke@reqres.in",
|
||||||
|
first_name: "Tobias",
|
||||||
|
last_name: "Funke",
|
||||||
|
avatar: "https://reqres.in/img/faces/9-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
email: "byron.fields@reqres.in",
|
||||||
|
first_name: "Byron",
|
||||||
|
last_name: "Fields",
|
||||||
|
avatar: "https://reqres.in/img/faces/10-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
email: "george.edwards@reqres.in",
|
||||||
|
first_name: "George",
|
||||||
|
last_name: "Edwards",
|
||||||
|
avatar: "https://reqres.in/img/faces/11-image.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
email: "rachel.howell@reqres.in",
|
||||||
|
first_name: "Rachel",
|
||||||
|
last_name: "Howell",
|
||||||
|
avatar: "https://reqres.in/img/faces/12-image.jpg",
|
||||||
|
},
|
||||||
|
];
|
||||||
3
k6-example/src/tests/config.ts
Normal file
3
k6-example/src/tests/config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const VUs = 1
|
||||||
22
k6-example/src/tests/load-tests.ts
Normal file
22
k6-example/src/tests/load-tests.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { vu } from "k6/execution";
|
||||||
|
import { Options } from "k6/options";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import {getMarketplace} from "../apis/marketplace-apis";
|
||||||
|
|
||||||
|
export const options: Options = {
|
||||||
|
scenarios: {
|
||||||
|
login: {
|
||||||
|
executor: 'per-vu-iterations',
|
||||||
|
vus: 50,
|
||||||
|
iterations: 10,
|
||||||
|
maxDuration: '1h30m',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function test () {
|
||||||
|
logger.info(`Running iteration ${vu.iterationInInstance}`);
|
||||||
|
|
||||||
|
getMarketplace();
|
||||||
|
}
|
||||||
34
k6-example/src/tests/reqres-stages.ts
Normal file
34
k6-example/src/tests/reqres-stages.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { SharedArray } from "k6/data";
|
||||||
|
import { vu } from "k6/execution";
|
||||||
|
import { Options } from "k6/options";
|
||||||
|
import { deleteUser, getUser, updateUser } from "../apis/reqres";
|
||||||
|
import { users } from "../data/users";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
const data = new SharedArray("users", function () {
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const options: Options = {
|
||||||
|
stages: [
|
||||||
|
// Ramp up to 12 users over 30 seconds
|
||||||
|
{ duration: "1m", target: 12 },
|
||||||
|
// Maintain steady state of 12 users over the next two minutes
|
||||||
|
{ duration: "2m", target: 12 },
|
||||||
|
// Ramp down to 0 users over the next 30 seconds
|
||||||
|
{ duration: "30s", target: 0 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function test() {
|
||||||
|
// Get a random user from data that isn't currently being tested
|
||||||
|
const user = data[vu.idInTest - 1];
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Running iteration ${vu.iterationInInstance} for user id ${user.id} with name ${user.first_name} ${user.last_name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
getUser(user);
|
||||||
|
updateUser(user);
|
||||||
|
deleteUser(user.id);
|
||||||
|
}
|
||||||
35
k6-example/src/tests/reqres.ts
Normal file
35
k6-example/src/tests/reqres.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { SharedArray } from "k6/data";
|
||||||
|
// @ts-ignore
|
||||||
|
import { vu } from "k6/execution";
|
||||||
|
import { Options } from "k6/options";
|
||||||
|
import { deleteUser, getUser, updateUser } from "../apis/reqres";
|
||||||
|
import { users } from "../data/users";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
const data = new SharedArray("users", function () {
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const options: Options = {
|
||||||
|
scenarios: {
|
||||||
|
login: {
|
||||||
|
executor: 'per-vu-iterations',
|
||||||
|
vus: 5,
|
||||||
|
iterations: 10,
|
||||||
|
maxDuration: '1h30m',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function test () {
|
||||||
|
// Get a random user from data that isn't currently being tested
|
||||||
|
const user = data[vu.idInTest - 1];
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Running iteration ${vu.iterationInInstance} for user id ${user.id} with name ${user.first_name} ${user.last_name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
getUser(user);
|
||||||
|
updateUser(user);
|
||||||
|
deleteUser(user.id);
|
||||||
|
}
|
||||||
39
k6-example/src/utils/configuration.ts
Normal file
39
k6-example/src/utils/configuration.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const FILE_ENCODING = 'utf-8';
|
||||||
|
const YAML_FILE_PATH = '../configuration.yml';
|
||||||
|
|
||||||
|
export interface Configuration {
|
||||||
|
target: TargetConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TargetConfiguration {
|
||||||
|
url: string;
|
||||||
|
port: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function loadConfiguration(): Configuration {
|
||||||
|
try {
|
||||||
|
const fileContent = fs.readFileSync(path.resolve(__dirname, YAML_FILE_PATH), FILE_ENCODING);
|
||||||
|
const data = yaml.load(fileContent);
|
||||||
|
|
||||||
|
return {
|
||||||
|
target: {
|
||||||
|
// @ts-ignore
|
||||||
|
url: data['target']['url'],
|
||||||
|
// @ts-ignore
|
||||||
|
port: data['target']['port']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
let error = {
|
||||||
|
message: 'Error while loading the configuration.',
|
||||||
|
cause: e
|
||||||
|
};
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
k6-example/src/utils/logger.ts
Normal file
49
k6-example/src/utils/logger.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
export const getTimestamp = (): string => {
|
||||||
|
const date = new Date();
|
||||||
|
const hours = date.getHours().toString().padStart(2, "0");
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||||
|
const seconds = date.getSeconds().toString().padStart(2, "0");
|
||||||
|
const milliseconds = date.getMilliseconds().toString().padStart(3, "0");
|
||||||
|
|
||||||
|
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logger = {
|
||||||
|
info(...val: any): void {
|
||||||
|
console.log(getTimestamp(), ...val);
|
||||||
|
},
|
||||||
|
warn(...val: any): void {
|
||||||
|
console.warn(getTimestamp(), ...val);
|
||||||
|
},
|
||||||
|
error(...val: any): void {
|
||||||
|
console.error(getTimestamp(), ...val);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logWaitingTime = ({
|
||||||
|
metric,
|
||||||
|
response,
|
||||||
|
messageType,
|
||||||
|
}: {
|
||||||
|
metric: any;
|
||||||
|
response: any;
|
||||||
|
messageType: string;
|
||||||
|
}): void => {
|
||||||
|
const responseTimeThreshold = 5000;
|
||||||
|
let correlationId = "";
|
||||||
|
let responseTime = response.timings.waiting;
|
||||||
|
try {
|
||||||
|
let json = response.json();
|
||||||
|
correlationId = json.correlationId;
|
||||||
|
} catch (err) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log any responses that far longer than expected so we can troubleshoot those particular queries
|
||||||
|
if (responseTime > responseTimeThreshold) {
|
||||||
|
logger.warn(
|
||||||
|
`${messageType} with correlationId '${correlationId}' took longer than ${responseTimeThreshold}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
metric.add(responseTime);
|
||||||
|
};
|
||||||
26
k6-example/tsconfig.json
Normal file
26
k6-example/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "commonjs",
|
||||||
|
"noEmit": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"removeComments": false,
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
|
||||||
|
"skipLibCheck": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
67
k6-example/vite.config.js
Normal file
67
k6-example/vite.config.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { babel } from '@rollup/plugin-babel';
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
|
import copy from 'rollup-plugin-copy';
|
||||||
|
import fg from 'fast-glob';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the entrypoints of files within a set of given paths (i.e src/tests).
|
||||||
|
* This is useful as when we execute k6, we run it against individual test files
|
||||||
|
* @returns an object of [fileName]: absolute file path
|
||||||
|
*/
|
||||||
|
const getEntryPoints = (entryPoints) => {
|
||||||
|
// Searches for files that match the patterns defined in the array of input points.
|
||||||
|
// Returns an array of absolute file paths.
|
||||||
|
const files = fg.sync(entryPoints, { absolute: true });
|
||||||
|
|
||||||
|
// Maps the file paths in the "files" array to an array of key-value pair.
|
||||||
|
const entities = files.map((file) => {
|
||||||
|
// Extract the part of the file path after the "src" folder and before the file extension.
|
||||||
|
const [key] = file.match(/(?<=src\/).*$/) || [];
|
||||||
|
|
||||||
|
// Remove the file extension from the key.
|
||||||
|
const keyWithoutExt = key.replace(/\.[^.]*$/, '');
|
||||||
|
|
||||||
|
return [keyWithoutExt, file];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert the array of key-value pairs to an object using the Object.fromEntries() method.
|
||||||
|
// Returns an object where each key is the file name without the extension and the value is the absolute file path.
|
||||||
|
return Object.fromEntries(entities);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
mode: 'production',
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: getEntryPoints(['./src/tests/*.ts']),
|
||||||
|
fileName: '[name]',
|
||||||
|
formats: ['cjs'],
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
outDir: 'dist',
|
||||||
|
minify: false, // Don't minimize, as it's not used in the browser
|
||||||
|
rollupOptions: {
|
||||||
|
external: [new RegExp(/^(k6|https?\:\/\/)(\/.*)?/)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
copy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: 'assets/**/*',
|
||||||
|
dest: 'dist',
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
babel({
|
||||||
|
babelHelpers: 'bundled',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
}),
|
||||||
|
nodeResolve(),
|
||||||
|
],
|
||||||
|
});
|
||||||
1160
k6-example/yarn.lock
Normal file
1160
k6-example/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
0
load-tests/configuration.yml
Normal file
0
load-tests/configuration.yml
Normal file
18
load-tests/package.json
Normal file
18
load-tests/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "yarn build && node dist/api-test.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"k6": "^0.0.0"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
"@types/k6": "^1.2.0",
|
||||||
|
"@types/node": "^24.5.2",
|
||||||
|
"tsx": "^4.20.5",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
load-tests/src/api-test.ts
Normal file
9
load-tests/src/api-test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
|
||||||
|
const
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
|
||||||
|
}
|
||||||
0
load-tests/src/configuration.ts
Normal file
0
load-tests/src/configuration.ts
Normal file
0
load-tests/tsconfig.json
Normal file
0
load-tests/tsconfig.json
Normal file
236
load-tests/yarn.lock
Normal file
236
load-tests/yarn.lock
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz#ee6b7163a13528e099ecf562b972f2bcebe0aa97"
|
||||||
|
integrity sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==
|
||||||
|
|
||||||
|
"@esbuild/android-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz#115fc76631e82dd06811bfaf2db0d4979c16e2cb"
|
||||||
|
integrity sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==
|
||||||
|
|
||||||
|
"@esbuild/android-arm@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.10.tgz#8d5811912da77f615398611e5bbc1333fe321aa9"
|
||||||
|
integrity sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==
|
||||||
|
|
||||||
|
"@esbuild/android-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.10.tgz#e3e96516b2d50d74105bb92594c473e30ddc16b1"
|
||||||
|
integrity sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz#6af6bb1d05887dac515de1b162b59dc71212ed76"
|
||||||
|
integrity sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz#99ae82347fbd336fc2d28ffd4f05694e6e5b723d"
|
||||||
|
integrity sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz#0c6d5558a6322b0bdb17f7025c19bd7d2359437d"
|
||||||
|
integrity sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz#8c35873fab8c0857a75300a3dcce4324ca0b9844"
|
||||||
|
integrity sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz#3edc2f87b889a15b4cedaf65f498c2bed7b16b90"
|
||||||
|
integrity sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz#86501cfdfb3d110176d80c41b27ed4611471cde7"
|
||||||
|
integrity sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz#e6589877876142537c6864680cd5d26a622b9d97"
|
||||||
|
integrity sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz#11119e18781f136d8083ea10eb6be73db7532de8"
|
||||||
|
integrity sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz#3052f5436b0c0c67a25658d5fc87f045e7def9e6"
|
||||||
|
integrity sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz#2f098920ee5be2ce799f35e367b28709925a8744"
|
||||||
|
integrity sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz#fa51d7fd0a22a62b51b4b94b405a3198cf7405dd"
|
||||||
|
integrity sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz#a27642e36fc282748fdb38954bd3ef4f85791e8a"
|
||||||
|
integrity sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==
|
||||||
|
|
||||||
|
"@esbuild/linux-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz#9d9b09c0033d17529570ced6d813f98315dfe4e9"
|
||||||
|
integrity sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz#25c09a659c97e8af19e3f2afd1c9190435802151"
|
||||||
|
integrity sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz#7fa5f6ffc19be3a0f6f5fd32c90df3dc2506937a"
|
||||||
|
integrity sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz#8faa6aa1afca0c6d024398321d6cb1c18e72a1c3"
|
||||||
|
integrity sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz#a42979b016f29559a8453d32440d3c8cd420af5e"
|
||||||
|
integrity sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz#fd87bfeadd7eeb3aa384bbba907459ffa3197cb1"
|
||||||
|
integrity sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz#3a18f590e36cb78ae7397976b760b2b8c74407f4"
|
||||||
|
integrity sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz#e71741a251e3fd971408827a529d2325551f530c"
|
||||||
|
integrity sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz#c6f010b5d3b943d8901a0c87ea55f93b8b54bf94"
|
||||||
|
integrity sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==
|
||||||
|
|
||||||
|
"@esbuild/win32-x64@0.25.10":
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz#e4b3e255a1b4aea84f6e1d2ae0b73f826c3785bd"
|
||||||
|
integrity sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==
|
||||||
|
|
||||||
|
"@types/js-yaml@^4.0.9":
|
||||||
|
version "4.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2"
|
||||||
|
integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==
|
||||||
|
|
||||||
|
"@types/k6@^1.2.0":
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/k6/-/k6-1.2.0.tgz#c27231f6311b48cb4c553bcf3d8d4bc036d00ab8"
|
||||||
|
integrity sha512-7F/vjekOUCzFi5AY+yYBOL38iEtN9EXufdKOAMFkJsdLryJ3/bB/W9jppED1FO5o7zeaPf8M46ardYPD8xgy/w==
|
||||||
|
|
||||||
|
"@types/node@^24.5.2":
|
||||||
|
version "24.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446"
|
||||||
|
integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==
|
||||||
|
dependencies:
|
||||||
|
undici-types "~7.12.0"
|
||||||
|
|
||||||
|
argparse@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||||
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
|
esbuild@~0.25.0:
|
||||||
|
version "0.25.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.10.tgz#37f5aa5cd14500f141be121c01b096ca83ac34a9"
|
||||||
|
integrity sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==
|
||||||
|
optionalDependencies:
|
||||||
|
"@esbuild/aix-ppc64" "0.25.10"
|
||||||
|
"@esbuild/android-arm" "0.25.10"
|
||||||
|
"@esbuild/android-arm64" "0.25.10"
|
||||||
|
"@esbuild/android-x64" "0.25.10"
|
||||||
|
"@esbuild/darwin-arm64" "0.25.10"
|
||||||
|
"@esbuild/darwin-x64" "0.25.10"
|
||||||
|
"@esbuild/freebsd-arm64" "0.25.10"
|
||||||
|
"@esbuild/freebsd-x64" "0.25.10"
|
||||||
|
"@esbuild/linux-arm" "0.25.10"
|
||||||
|
"@esbuild/linux-arm64" "0.25.10"
|
||||||
|
"@esbuild/linux-ia32" "0.25.10"
|
||||||
|
"@esbuild/linux-loong64" "0.25.10"
|
||||||
|
"@esbuild/linux-mips64el" "0.25.10"
|
||||||
|
"@esbuild/linux-ppc64" "0.25.10"
|
||||||
|
"@esbuild/linux-riscv64" "0.25.10"
|
||||||
|
"@esbuild/linux-s390x" "0.25.10"
|
||||||
|
"@esbuild/linux-x64" "0.25.10"
|
||||||
|
"@esbuild/netbsd-arm64" "0.25.10"
|
||||||
|
"@esbuild/netbsd-x64" "0.25.10"
|
||||||
|
"@esbuild/openbsd-arm64" "0.25.10"
|
||||||
|
"@esbuild/openbsd-x64" "0.25.10"
|
||||||
|
"@esbuild/openharmony-arm64" "0.25.10"
|
||||||
|
"@esbuild/sunos-x64" "0.25.10"
|
||||||
|
"@esbuild/win32-arm64" "0.25.10"
|
||||||
|
"@esbuild/win32-ia32" "0.25.10"
|
||||||
|
"@esbuild/win32-x64" "0.25.10"
|
||||||
|
|
||||||
|
fsevents@~2.3.3:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
|
||||||
|
get-tsconfig@^4.7.5:
|
||||||
|
version "4.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.1.tgz#d34c1c01f47d65a606c37aa7a177bc3e56ab4b2e"
|
||||||
|
integrity sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==
|
||||||
|
dependencies:
|
||||||
|
resolve-pkg-maps "^1.0.0"
|
||||||
|
|
||||||
|
js-yaml@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||||
|
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||||
|
dependencies:
|
||||||
|
argparse "^2.0.1"
|
||||||
|
|
||||||
|
k6@^0.0.0:
|
||||||
|
version "0.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/k6/-/k6-0.0.0.tgz#8c923200be0a68c578e8f5a32be96b1d8065cc3b"
|
||||||
|
integrity sha512-GAQSWayS2+LjbH5bkRi+pMPYyP1JSp7o+4j58ANZ762N/RH/SdlAT3CHHztnn8s/xgg8kYNM24Gd2IPo9b5W+g==
|
||||||
|
|
||||||
|
resolve-pkg-maps@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
|
||||||
|
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
|
||||||
|
|
||||||
|
tsx@^4.20.5:
|
||||||
|
version "4.20.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.20.5.tgz#856c8b2f114c50a9f4ae108126967a167f240dc7"
|
||||||
|
integrity sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==
|
||||||
|
dependencies:
|
||||||
|
esbuild "~0.25.0"
|
||||||
|
get-tsconfig "^4.7.5"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.3"
|
||||||
|
|
||||||
|
typescript@^5.9.2:
|
||||||
|
version "5.9.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"
|
||||||
|
integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
|
||||||
|
|
||||||
|
undici-types@~7.12.0:
|
||||||
|
version "7.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb"
|
||||||
|
integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: Get thread name
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{url}}/thread/name
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import com.zeenea.experiments.virtualthreads.virtualthreadsapp.domain.marketplac
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class MarketplaceService {
|
public class MarketplaceService {
|
||||||
@@ -21,9 +21,12 @@ public class MarketplaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Marketplace getMarketplace() {
|
public Marketplace getMarketplace() {
|
||||||
return getMarketplaceInParallel();
|
return getMarketplaceInParallel2();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method uses classic threads while it is using `CompletableFuture`s. This way should be avoided.
|
||||||
|
*/
|
||||||
private Marketplace getMarketplaceInParallel() {
|
private Marketplace getMarketplaceInParallel() {
|
||||||
CompletableFuture<List<Catalog>> getCatalogsFuture = CompletableFuture.supplyAsync(catalogService::getAll);
|
CompletableFuture<List<Catalog>> getCatalogsFuture = CompletableFuture.supplyAsync(catalogService::getAll);
|
||||||
CompletableFuture<List<Item>> getAllItemsFuture = CompletableFuture.supplyAsync(itemService::getAllItems);
|
CompletableFuture<List<Item>> getAllItemsFuture = CompletableFuture.supplyAsync(itemService::getAllItems);
|
||||||
@@ -38,6 +41,23 @@ public class MarketplaceService {
|
|||||||
return new Marketplace(catalogs, sharedItems);
|
return new Marketplace(catalogs, sharedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Marketplace getMarketplaceInParallel2() {
|
||||||
|
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||||
|
Future<List<Catalog>> getCatalogsFuture = executorService.submit(catalogService::getAll);
|
||||||
|
Future<List<Item>> getItemsFuture = executorService.submit(itemService::getAllItems);
|
||||||
|
|
||||||
|
List<Catalog> catalogs = getCatalogsFuture.get();
|
||||||
|
List<Item> items = getItemsFuture.get();
|
||||||
|
|
||||||
|
var sharedItems = items.stream()
|
||||||
|
.filter(Item::isShared)
|
||||||
|
.toList();
|
||||||
|
return new Marketplace(catalogs, sharedItems);
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Marketplace getMarketplaceSequentially() {
|
private Marketplace getMarketplaceSequentially() {
|
||||||
return new Marketplace(
|
return new Marketplace(
|
||||||
catalogService.getAll(),
|
catalogService.getAll(),
|
||||||
|
|||||||
Reference in New Issue
Block a user