diff options
Diffstat (limited to 'packages/backend/scripts')
| -rw-r--r-- | packages/backend/scripts/check_connect.js | 30 | ||||
| -rw-r--r-- | packages/backend/scripts/generate_api_json.js | 6 | ||||
| -rw-r--r-- | packages/backend/scripts/measure-memory.mjs | 163 | ||||
| -rw-r--r-- | packages/backend/scripts/watch.mjs | 2 |
4 files changed, 138 insertions, 63 deletions
diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js index 96c4549ccb..a1cb839303 100644 --- a/packages/backend/scripts/check_connect.js +++ b/packages/backend/scripts/check_connect.js @@ -4,8 +4,8 @@ */ import Redis from 'ioredis'; -import { loadConfig } from '../built/config.js'; -import { createPostgresDataSource } from '../built/postgres.js'; +import { loadConfig } from '../src-js/config.js'; +import { createPostgresDataSource } from '../src-js/postgres.js'; const config = loadConfig(); @@ -16,26 +16,22 @@ async function connectToPostgres() { } async function connectToRedis(redisOptions) { - return await new Promise(async (resolve, reject) => { - const redis = new Redis({ + let redis; + try { + redis = new Redis({ ...redisOptions, lazyConnect: true, reconnectOnError: false, showFriendlyErrorStack: true, }); - redis.on('error', e => reject(e)); - try { - await redis.connect(); - resolve(); - - } catch (e) { - reject(e); - - } finally { - redis.disconnect(false); - } - }); + await Promise.race([ + new Promise((_, reject) => redis.on('error', e => reject(e))), + redis.connect(), + ]); + } finally { + redis.disconnect(false); + } } // If not all of these are defined, the default one gets reused. @@ -50,7 +46,7 @@ const promises = Array ])) .map(connectToRedis) .concat([ - connectToPostgres() + connectToPostgres(), ]); await Promise.all(promises); diff --git a/packages/backend/scripts/generate_api_json.js b/packages/backend/scripts/generate_api_json.js index 798e243004..237f63a4d3 100644 --- a/packages/backend/scripts/generate_api_json.js +++ b/packages/backend/scripts/generate_api_json.js @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { writeFileSync, existsSync } from 'node:fs'; import { execa } from 'execa'; -import { writeFileSync, existsSync } from "node:fs"; async function main() { if (!process.argv.includes('--no-build')) { @@ -19,10 +19,10 @@ async function main() { } /** @type {import('../src/config.js')} */ - const { loadConfig } = await import('../built/config.js'); + const { loadConfig } = await import('../src-js/config.js'); /** @type {import('../src/server/api/openapi/gen-spec.js')} */ - const { genOpenapiSpec } = await import('../built/server/api/openapi/gen-spec.js'); + const { genOpenapiSpec } = await import('../src-js/server/api/openapi/gen-spec.js'); const config = loadConfig(); const spec = genOpenapiSpec(config, true); 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(), diff --git a/packages/backend/scripts/watch.mjs b/packages/backend/scripts/watch.mjs index a0ccea3b16..9d608b233c 100644 --- a/packages/backend/scripts/watch.mjs +++ b/packages/backend/scripts/watch.mjs @@ -21,7 +21,7 @@ import { execa } from 'execa'; }); }, 3000); - execa('tsc', ['-w', '-p', 'tsconfig.json'], { + execa('tsgo', ['-w', '-p', 'tsconfig.json'], { stdout: process.stdout, stderr: process.stderr, }); |