summaryrefslogtreecommitdiff
path: root/packages/backend/scripts/measure-memory.mjs
blob: 017252d7ecbf47d2d5584140bb5cf1a9543c05a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * SPDX-FileCopyrightText: syuilo and misskey-project
 * SPDX-License-Identifier: AGPL-3.0-only
 */

/**
 * This script starts the Misskey backend server, waits for it to be ready,
 * measures memory usage, and outputs the result as JSON.
 *
 * Usage: node scripts/measure-memory.mjs
 */

import { fork } from 'node:child_process';
import { setTimeout } from 'node:timers/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

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();

	// Start the Misskey backend server using fork to enable IPC
	const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), [], {
		cwd: join(__dirname, '..'),
		env: {
			...process.env,
			NODE_ENV: 'test',
		},
		stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
	});

	let serverReady = false;

	// Listen for the 'ok' message from the server indicating it's ready
	serverProcess.on('message', (message) => {
		if (message === 'ok') {
			serverReady = true;
		}
	});

	// Handle server output
	serverProcess.stdout?.on('data', (data) => {
		process.stderr.write(`[server stdout] ${data}`);
	});

	serverProcess.stderr?.on('data', (data) => {
		process.stderr.write(`[server stderr] ${data}`);
	});

	// Handle server error
	serverProcess.on('error', (err) => {
		process.stderr.write(`[server error] ${err}\n`);
	});

	// Wait for server to be ready or timeout
	const startupStartTime = Date.now();
	while (!serverReady) {
		if (Date.now() - startupStartTime > STARTUP_TIMEOUT) {
			serverProcess.kill('SIGTERM');
			throw new Error('Server startup timeout');
		}
		await setTimeout(100);
	}

	const startupTime = Date.now() - startupStartTime;
	process.stderr.write(`Server started in ${startupTime}ms\n`);

	// 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');

		// 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/);

		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 { 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',
			};
		}
	}

	// Stop the server
	serverProcess.kill('SIGTERM');

	// Wait for process to exit
	let exited = false;
	await new Promise((resolve) => {
		serverProcess.on('exit', () => {
			exited = true;
			resolve(undefined);
		});
		// Force kill after 10 seconds if not exited
		setTimeout(10000).then(() => {
			if (!exited) {
				serverProcess.kill('SIGKILL');
			}
			resolve(undefined);
		});
	});

	const result = {
		timestamp: new Date().toISOString(),
		startupTimeMs: startupTime,
		memory: memoryInfo,
	};

	// Output as JSON to stdout
	console.log(JSON.stringify(result, null, 2));
}

measureMemory().catch((err) => {
	console.error(JSON.stringify({
		error: err.message,
		timestamp: new Date().toISOString(),
	}));
	process.exit(1);
});