summaryrefslogtreecommitdiff
path: root/packages/backend/scripts/measure-memory.mjs
diff options
context:
space:
mode:
authormisskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com>2026-03-05 10:56:50 +0000
committerGitHub <noreply@github.com>2026-03-05 10:56:50 +0000
commitfe3dd8edb5f30104cd0a7ed755eb254feda2922d (patch)
treeaf6cf5fa4ca75302ac2de5db742cead00bc13d21 /packages/backend/scripts/measure-memory.mjs
parentMerge pull request #16998 from misskey-dev/develop (diff)
parentRelease: 2026.3.0 (diff)
downloadmisskey-fe3dd8edb5f30104cd0a7ed755eb254feda2922d.tar.gz
misskey-fe3dd8edb5f30104cd0a7ed755eb254feda2922d.tar.bz2
misskey-fe3dd8edb5f30104cd0a7ed755eb254feda2922d.zip
Merge pull request #17217 from misskey-dev/develop
Release: 2026.3.0
Diffstat (limited to 'packages/backend/scripts/measure-memory.mjs')
-rw-r--r--packages/backend/scripts/measure-memory.mjs163
1 files changed, 121 insertions, 42 deletions
diff --git a/packages/backend/scripts/measure-memory.mjs b/packages/backend/scripts/measure-memory.mjs
index 017252d7ec..3f30e24fb4 100644
--- a/packages/backend/scripts/measure-memory.mjs
+++ b/packages/backend/scripts/measure-memory.mjs
@@ -14,24 +14,56 @@ import { fork } from 'node:child_process';
import { setTimeout } from 'node:timers/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
+import * as http from 'node:http';
+import * as fs from 'node:fs/promises';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
+const SAMPLE_COUNT = 3; // Number of samples to measure
const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup
const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle
-async function measureMemory() {
- const startTime = Date.now();
+const keys = {
+ VmPeak: 0,
+ VmSize: 0,
+ VmHWM: 0,
+ VmRSS: 0,
+ VmData: 0,
+ VmStk: 0,
+ VmExe: 0,
+ VmLib: 0,
+ VmPTE: 0,
+ VmSwap: 0,
+};
+
+async function getMemoryUsage(pid) {
+ const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
+
+ const result = {};
+ for (const key of Object.keys(keys)) {
+ const match = status.match(new RegExp(`${key}:\\s+(\\d+)\\s+kB`));
+ if (match) {
+ result[key] = parseInt(match[1], 10);
+ } else {
+ throw new Error(`Failed to parse ${key} from /proc/${pid}/status`);
+ }
+ }
+ return result;
+}
+
+async function measureMemory() {
// Start the Misskey backend server using fork to enable IPC
- const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), [], {
+ const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), ['expose-gc'], {
cwd: join(__dirname, '..'),
env: {
...process.env,
- NODE_ENV: 'test',
+ NODE_ENV: 'production',
+ MK_DISABLE_CLUSTERING: '1',
},
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
+ execArgv: [...process.execArgv, '--expose-gc'],
});
let serverReady = false;
@@ -57,6 +89,40 @@ async function measureMemory() {
process.stderr.write(`[server error] ${err}\n`);
});
+ async function triggerGc() {
+ const ok = new Promise((resolve) => {
+ serverProcess.once('message', (message) => {
+ if (message === 'gc ok') resolve();
+ });
+ });
+
+ serverProcess.send('gc');
+
+ await ok;
+
+ await setTimeout(1000);
+ }
+
+ function createRequest() {
+ return new Promise((resolve, reject) => {
+ const req = http.request({
+ host: 'localhost',
+ port: 61812,
+ path: '/api/meta',
+ method: 'POST',
+ }, (res) => {
+ res.on('data', () => { });
+ res.on('end', () => {
+ resolve();
+ });
+ });
+ req.on('error', (err) => {
+ reject(err);
+ });
+ req.end();
+ });
+ }
+
// Wait for server to be ready or timeout
const startupStartTime = Date.now();
while (!serverReady) {
@@ -73,46 +139,23 @@ async function measureMemory() {
// Wait for memory to settle
await setTimeout(MEMORY_SETTLE_TIME);
- // Get memory usage from the server process via /proc
const pid = serverProcess.pid;
- let memoryInfo;
- try {
- const fs = await import('node:fs/promises');
+ const beforeGc = await getMemoryUsage(pid);
- // Read /proc/[pid]/status for detailed memory info
- const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
- const vmRssMatch = status.match(/VmRSS:\s+(\d+)\s+kB/);
- const vmDataMatch = status.match(/VmData:\s+(\d+)\s+kB/);
- const vmSizeMatch = status.match(/VmSize:\s+(\d+)\s+kB/);
+ await triggerGc();
- memoryInfo = {
- rss: vmRssMatch ? parseInt(vmRssMatch[1], 10) * 1024 : null,
- heapUsed: vmDataMatch ? parseInt(vmDataMatch[1], 10) * 1024 : null,
- vmSize: vmSizeMatch ? parseInt(vmSizeMatch[1], 10) * 1024 : null,
- };
- } catch (err) {
- // Fallback: use ps command
- process.stderr.write(`Warning: Could not read /proc/${pid}/status: ${err}\n`);
+ const afterGc = await getMemoryUsage(pid);
- const { execSync } = await import('node:child_process');
- try {
- const ps = execSync(`ps -o rss= -p ${pid}`, { encoding: 'utf-8' });
- const rssKb = parseInt(ps.trim(), 10);
- memoryInfo = {
- rss: rssKb * 1024,
- heapUsed: null,
- vmSize: null,
- };
- } catch {
- memoryInfo = {
- rss: null,
- heapUsed: null,
- vmSize: null,
- error: 'Could not measure memory',
- };
- }
- }
+ // create some http requests to simulate load
+ const REQUEST_COUNT = 10;
+ await Promise.all(
+ Array.from({ length: REQUEST_COUNT }).map(() => createRequest()),
+ );
+
+ await triggerGc();
+
+ const afterRequest = await getMemoryUsage(pid);
// Stop the server
serverProcess.kill('SIGTERM');
@@ -135,15 +178,51 @@ async function measureMemory() {
const result = {
timestamp: new Date().toISOString(),
- startupTimeMs: startupTime,
- memory: memoryInfo,
+ beforeGc,
+ afterGc,
+ afterRequest,
+ };
+
+ return result;
+}
+
+async function main() {
+ // 直列の方が時間的に分散されて正確そうだから直列でやる
+ const results = [];
+ for (let i = 0; i < SAMPLE_COUNT; i++) {
+ const res = await measureMemory();
+ results.push(res);
+ }
+
+ // Calculate averages
+ const beforeGc = structuredClone(keys);
+ const afterGc = structuredClone(keys);
+ const afterRequest = structuredClone(keys);
+ for (const res of results) {
+ for (const key of Object.keys(keys)) {
+ beforeGc[key] += res.beforeGc[key];
+ afterGc[key] += res.afterGc[key];
+ afterRequest[key] += res.afterRequest[key];
+ }
+ }
+ for (const key of Object.keys(keys)) {
+ beforeGc[key] = Math.round(beforeGc[key] / SAMPLE_COUNT);
+ afterGc[key] = Math.round(afterGc[key] / SAMPLE_COUNT);
+ afterRequest[key] = Math.round(afterRequest[key] / SAMPLE_COUNT);
+ }
+
+ const result = {
+ timestamp: new Date().toISOString(),
+ beforeGc,
+ afterGc,
+ afterRequest,
};
// Output as JSON to stdout
console.log(JSON.stringify(result, null, 2));
}
-measureMemory().catch((err) => {
+main().catch((err) => {
console.error(JSON.stringify({
error: err.message,
timestamp: new Date().toISOString(),