Files
plane/loadtests/script_100.js
T
pratapalakshmiandGitHub 807f9d83ea [INFRA-309] chore: add loadtests on plane (#5768)
* feat: add load testing framework and initial reports

- Introduced k6-based load tests for the Plane API, simulating mixed read/write traffic.
- Created comprehensive documentation including a load test report, usage instructions, and configuration details.
- Added a load test script to facilitate performance testing with defined thresholds and operation mixes.
- Established clear SLO definitions and scaling recommendations based on test results.

This addition aims to enhance performance validation and ensure the API can handle expected user loads effectively.

* feat: enhance database connection management with environment variables

- Added configurable connection management settings for the database, allowing customization of connection max age and health checks via environment variables.
- Applied these settings to both the default database and read replica configurations to ensure consistent connection management across the application.

This update aims to improve database performance and reliability by enabling better control over connection parameters.

* feat: add comprehensive load testing scripts for API performance

- Introduced three new load testing scripts using k6: script_100.js, script_500.js, and script_parallel.js, designed to simulate varying levels of user traffic and operations on the Plane API.
- Each script includes detailed configurations for request thresholds, timeout settings, and scenarios for both read and write operations.
- Removed the outdated script.js to streamline the load testing framework.

This enhancement aims to improve performance validation and ensure the API can handle diverse user loads effectively.

* refactor: streamline database connection management and enhance load testing scripts

- Removed configurable connection management settings from the database configuration in common.py, simplifying the connection setup.
- Updated load testing script_parllel.js to separate read and write API endpoints, improving clarity and organization of the load testing framework.
- Introduced new constants for read and write project IDs, enhancing the structure of the load testing scripts.

This refactor aims to improve maintainability and clarity in both database settings and load testing configurations.

* docs: update load test report and usage documentation

- Revised the load test report to focus on production infrastructure sizing and deployment guidance, emphasizing measured outcomes and configuration boundaries.
- Updated the load testing scripts documentation to clarify the parallel read and write scenarios, including new recommended configurations and usage examples.
- Adjusted thresholds in the load testing script to reflect updated performance expectations.

These changes aim to enhance clarity and provide better guidance for running load tests in production environments.

* docs: update load test report with revised performance metrics

- Adjusted load test report to reflect updated performance metrics, including improved response times and success rates for read and write operations.
- Revised thresholds and total results to provide a clearer picture of system performance under load.
- Enhanced clarity in the report layout for better readability and understanding of results.

These updates aim to ensure accurate documentation of load testing outcomes and facilitate better performance analysis.
2026-02-11 13:36:10 +05:30

167 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '30s', target: 75 },
{ duration: '30s', target: 100 },
{ duration: '3m', target: 100 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_failed: ['rate<0.02'],
http_req_duration: ['p(95)<15000'], // 15s to allow for slow responses under load
},
};
// Longer timeout so requests complete under load; increase sleep to reduce burst rate.
const REQ_TIMEOUT = '15s';
const SLEEP_BETWEEN_REQUESTS = 1.5;
const COOKIE =
'session-id=aiva7cr9c6rs4zqvc35zrcy1yjttli0c0k8y8w9hb884rzmf75m1euqk1zko7h8od5mlqsfcfl9zsvdj0bp2z600v8niyr4rnnmagv5d3ltjcuxcbxr5sjjs5owxpizj';
const API_BASE =
'https://commercial.loadtest.plane.town/api/workspaces/plane/projects';
// Match browser request (e.g. GET .../projects/ with same headers as curl).
const COMMON_HEADERS = {
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9,te;q=0.8',
Cookie: COOKIE,
Referer: 'https://commercial.loadtest.plane.town/plane/projects/',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
};
const POST_HEADERS = {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Cookie: COOKIE,
Referer: 'https://commercial.loadtest.plane.town/plane/projects/',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
};
// Only include projects the load-test user has access to (Member/Admin). Remove any that return 403.
const PROJECT_IDS = [
'106035e0-0be5-4a27-85eb-9f47663d4d7b',
'9d282c44-c53a-47e9-b5e8-199ef72d1b4b',
'f7aef638-beba-4711-8887-1a7ce61178cc'
];
// ---------- helpers ----------
function randomString(len) {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length: len }, () =>
chars[Math.floor(Math.random() * chars.length)]
).join('');
}
function issuePayload(projectId) {
return JSON.stringify({
project_id: projectId,
name: `Issue ${randomString(6)}`,
description_html: `<p>${randomString(12)}</p>`,
priority: 'none',
assignee_ids: [],
label_ids: [],
});
}
// Log failure with response body to debug 403 etc. (body truncated to avoid huge logs).
function logFailed(opLabel, projectId, res, extra = '') {
const body = res.body && res.body.length > 0 ? res.body : '(empty)';
const bodyPreview = body.length > 300 ? body.slice(0, 300) + '...' : body;
console.log(
`[FAILED] ${opLabel} project=${projectId ?? 'n/a'} status=${res.status} ${extra} body=${bodyPreview}`
);
}
// ---------- main ----------
export default function () {
// Evenly distribute projects by VU
const projectId =
PROJECT_IDS[(__VU - 1) % PROJECT_IDS.length];
// Evenly distribute operations by iteration
const op = (__ITER % 10);
// 01 → 20% GET projects
if (op < 2) {
const res = http.get(`${API_BASE}/`, {
headers: COMMON_HEADERS,
timeout: REQ_TIMEOUT,
});
if (res.status === 0 || res.status >= 400) {
logFailed('GET projects', null, res, '(0=timeout, 4xx/5xx=error)');
}
}
// 23 → 20% GET cycles (GET .../projects/:id/cycles/ with session-id cookie; returns JSON array of cycles)
else if (op < 4) {
const res = http.get(
`${API_BASE}/${projectId}/cycles/`,
{ headers: COMMON_HEADERS, timeout: REQ_TIMEOUT }
);
if (res.status === 0 || res.status >= 400) {
logFailed('GET cycles', projectId, res);
}
}
// 45 → 20% GET pages
else if (op < 6) {
const res = http.get(
`${API_BASE}/${projectId}/pages/?search=&type=public`,
{ headers: COMMON_HEADERS, timeout: REQ_TIMEOUT }
);
if (res.status === 0 || res.status >= 400) {
logFailed('GET pages', projectId, res);
}
}
// 68 → 30% POST create issue
else if (op < 9) {
const res = http.post(
`${API_BASE}/${projectId}/issues/`,
issuePayload(projectId),
{ headers: POST_HEADERS, timeout: REQ_TIMEOUT }
);
if (res.status === 0 || res.status >= 400) {
logFailed('POST create', projectId, res);
}
}
// 9 → 10% POST + DELETE
else {
const res = http.post(
`${API_BASE}/${projectId}/issues/`,
issuePayload(projectId),
{ headers: POST_HEADERS, timeout: REQ_TIMEOUT }
);
if (res.status === 0 || res.status >= 400) {
logFailed('POST create (before delete)', projectId, res);
} else if (res.status === 200 || res.status === 201) {
try {
const issue = JSON.parse(res.body);
if (issue.id) {
const delRes = http.del(
`${API_BASE}/${projectId}/issues/${issue.id}/`,
null,
{ headers: COMMON_HEADERS, timeout: REQ_TIMEOUT }
);
if (delRes.status === 0 || delRes.status >= 400) {
logFailed('DELETE', projectId, delRes, `issueId=${issue.id}`);
}
}
} catch (_) {}
}
}
sleep(SLEEP_BETWEEN_REQUESTS);
}