From c27e3b46ec0e1f8c530e7847e0fc461dc3ffb140 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 30 Jun 2025 15:51:42 +0300 Subject: [PATCH 1/2] ITS WORKING! --- package.json | 3 +- rsbuild.config.ts | 7 ++++ scripts/requestData.ts | 39 +++++++++++++++++++ scripts/wsServer.ts | 30 ++++++++++++++ src/devtools.ts | 88 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 scripts/requestData.ts create mode 100644 scripts/wsServer.ts diff --git a/package.json b/package.json index 516d57f3..5e6635ab 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "run-playground": "run-p watch-mesher watch-other-workers watch-playground", "run-all": "run-p start run-playground", "build-playground": "rsbuild build --config renderer/rsbuild.config.ts", - "watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts" + "watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts", + "request-data": "tsx scripts/requestData.ts" }, "keywords": [ "prismarine", diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 548be4e2..2c32a416 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -14,6 +14,7 @@ import { appAndRendererSharedConfig } from './renderer/rsbuildSharedConfig' import { genLargeDataAliases } from './scripts/genLargeDataAliases' import sharp from 'sharp' import supportedVersions from './src/supportedVersions.mjs' +import { startWsServer } from './scripts/wsServer' const SINGLE_FILE_BUILD = process.env.SINGLE_FILE_BUILD === 'true' @@ -197,6 +198,12 @@ const appConfig = defineConfig({ await execAsync('pnpm run build-mesher') } fs.writeFileSync('./dist/version.txt', buildingVersion, 'utf-8') + + // Start WebSocket server in development + if (dev && process.env.WS_SERVER === 'true') { + startWsServer() + } + console.timeEnd('total-prep') } if (!dev) { diff --git a/scripts/requestData.ts b/scripts/requestData.ts new file mode 100644 index 00000000..c6c68b20 --- /dev/null +++ b/scripts/requestData.ts @@ -0,0 +1,39 @@ +import WebSocket from 'ws' + +function formatBytes(bytes: number) { + return `${(bytes).toFixed(2)} MB` +} + +function formatTime(ms: number) { + return `${(ms / 1000).toFixed(2)}s` +} + +const ws = new WebSocket('ws://localhost:8081') + +ws.on('open', () => { + console.log('Connected to metrics server, waiting for metrics...') +}) + +ws.on('message', (data) => { + try { + const metrics = JSON.parse(data.toString()) + console.log('\nPerformance Metrics:') + console.log('------------------') + console.log(`Load Time: ${formatTime(metrics.loadTime)}`) + console.log(`Memory Usage: ${formatBytes(metrics.memoryUsage)}`) + console.log(`Timestamp: ${new Date(metrics.timestamp).toLocaleString()}`) + } catch (error) { + console.error('Error parsing metrics:', error) + } +}) + +ws.on('error', (error) => { + console.error('WebSocket error:', error) + process.exit(1) +}) + +// Exit if no metrics received after 5 seconds +setTimeout(() => { + console.error('Timeout waiting for metrics') + process.exit(1) +}, 5000) diff --git a/scripts/wsServer.ts b/scripts/wsServer.ts new file mode 100644 index 00000000..106d117c --- /dev/null +++ b/scripts/wsServer.ts @@ -0,0 +1,30 @@ +import WebSocket from 'ws' + +export function startWsServer(port: number = 8081) { + const wss = new WebSocket.Server({ port }) + + console.log(`WebSocket server started on port ${port}`) + + wss.on('connection', (ws) => { + console.log('Client connected') + + ws.on('message', (message) => { + try { + // Simply relay the message to all connected clients except sender + wss.clients.forEach(client => { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(message.toString()) + } + }) + } catch (error) { + console.error('Error processing message:', error) + } + }) + + ws.on('close', () => { + console.log('Client disconnected') + }) + }) + + return wss +} diff --git a/src/devtools.ts b/src/devtools.ts index b9267127..a842dc2f 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -209,3 +209,91 @@ setInterval(() => { }, 1000) // --- + +// Add type declaration for performance.memory +declare global { + interface Performance { + memory?: { + usedJSHeapSize: number + totalJSHeapSize: number + jsHeapSizeLimit: number + } + } +} + +// Performance metrics WebSocket client +let ws: WebSocket | null = null +let wsReconnectTimeout: NodeJS.Timeout | null = null +let metricsInterval: NodeJS.Timeout | null = null + +// Start collecting metrics immediately +const startTime = performance.now() + +function collectAndSendMetrics () { + if (!ws || ws.readyState !== WebSocket.OPEN) return + + const metrics = { + loadTime: performance.now() - startTime, + memoryUsage: (performance.memory?.usedJSHeapSize ?? 0) / 1024 / 1024, + timestamp: Date.now() + } + + ws.send(JSON.stringify(metrics)) +} + +function connectWebSocket () { + if (ws) return + + ws = new WebSocket('ws://localhost:8081') + + ws.onopen = () => { + console.log('Connected to metrics server') + if (wsReconnectTimeout) { + clearTimeout(wsReconnectTimeout) + wsReconnectTimeout = null + } + + // Start sending metrics immediately after connection + collectAndSendMetrics() + + // Clear existing interval if any + if (metricsInterval) { + clearInterval(metricsInterval) + } + + // Set new interval + metricsInterval = setInterval(collectAndSendMetrics, 500) + } + + ws.onclose = () => { + console.log('Disconnected from metrics server') + ws = null + + // Clear metrics interval + if (metricsInterval) { + clearInterval(metricsInterval) + metricsInterval = null + } + + // Try to reconnect after 3 seconds + wsReconnectTimeout = setTimeout(connectWebSocket, 3000) + } + + ws.onerror = (error) => { + console.error('WebSocket error:', error) + } +} + +// Connect immediately +connectWebSocket() + +// Add command to request current metrics +window.requestMetrics = () => { + const metrics = { + loadTime: performance.now() - startTime, + memoryUsage: (performance.memory?.usedJSHeapSize ?? 0) / 1024 / 1024, + timestamp: Date.now() + } + console.log('Current metrics:', metrics) + return metrics +} From 3345abecf334a4a45116363659b731968daca46e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 9 Jul 2025 16:05:24 +0300 Subject: [PATCH 2/2] add basic metrics server --- package.json | 1 + rsbuild.config.ts | 7 +++-- scripts/requestData.ts | 3 ++ scripts/wsServer.ts | 63 ++++++++++++++++++++++++++---------------- src/devtools.ts | 17 +++++++++++- src/env.d.ts | 1 + 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index b438b2e7..fe4adb16 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev-proxy": "node server.js", "start": "run-p dev-proxy dev-rsbuild watch-mesher", "start2": "run-p dev-rsbuild watch-mesher", + "start-metrics": "ENABLE_METRICS=true rsbuild dev", "build": "pnpm build-other-workers && rsbuild build", "build-analyze": "BUNDLE_ANALYZE=true rsbuild build && pnpm build-other-workers", "build-single-file": "SINGLE_FILE_BUILD=true rsbuild build", diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 5b994ea5..e264f6b7 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -60,6 +60,8 @@ const configSource = (SINGLE_FILE_BUILD ? 'BUNDLED' : (process.env.CONFIG_JSON_S const faviconPath = 'favicon.png' +const enableMetrics = process.env.ENABLE_METRICS === 'true' + // base options are in ./renderer/rsbuildSharedConfig.ts const appConfig = defineConfig({ html: { @@ -160,6 +162,7 @@ const appConfig = defineConfig({ 'process.env.INLINED_APP_CONFIG': JSON.stringify(configSource === 'BUNDLED' ? configJson : null), 'process.env.ENABLE_COOKIE_STORAGE': JSON.stringify(process.env.ENABLE_COOKIE_STORAGE || true), 'process.env.COOKIE_STORAGE_PREFIX': JSON.stringify(process.env.COOKIE_STORAGE_PREFIX || ''), + 'process.env.WS_PORT': JSON.stringify(enableMetrics ? 8081 : false), }, }, server: { @@ -219,8 +222,8 @@ const appConfig = defineConfig({ fs.writeFileSync('./dist/version.txt', buildingVersion, 'utf-8') // Start WebSocket server in development - if (dev && process.env.WS_SERVER === 'true') { - startWsServer() + if (dev && enableMetrics) { + await startWsServer(8081, false) } console.timeEnd('total-prep') diff --git a/scripts/requestData.ts b/scripts/requestData.ts index c6c68b20..dc866a1b 100644 --- a/scripts/requestData.ts +++ b/scripts/requestData.ts @@ -22,6 +22,9 @@ ws.on('message', (data) => { console.log(`Load Time: ${formatTime(metrics.loadTime)}`) console.log(`Memory Usage: ${formatBytes(metrics.memoryUsage)}`) console.log(`Timestamp: ${new Date(metrics.timestamp).toLocaleString()}`) + if (!process.argv.includes('-f')) { // follow mode + process.exit(0) + } } catch (error) { console.error('Error parsing metrics:', error) } diff --git a/scripts/wsServer.ts b/scripts/wsServer.ts index 106d117c..43035f52 100644 --- a/scripts/wsServer.ts +++ b/scripts/wsServer.ts @@ -1,30 +1,45 @@ -import WebSocket from 'ws' +import {WebSocketServer} from 'ws' -export function startWsServer(port: number = 8081) { - const wss = new WebSocket.Server({ port }) - - console.log(`WebSocket server started on port ${port}`) - - wss.on('connection', (ws) => { - console.log('Client connected') - - ws.on('message', (message) => { - try { - // Simply relay the message to all connected clients except sender - wss.clients.forEach(client => { - if (client !== ws && client.readyState === WebSocket.OPEN) { - client.send(message.toString()) +export function startWsServer(port: number = 8081, tryOtherPort: boolean = true): Promise { + return new Promise((resolve, reject) => { + const tryPort = (currentPort: number) => { + const wss = new WebSocketServer({ port: currentPort }) + .on('listening', () => { + console.log(`WebSocket server started on port ${currentPort}`) + resolve(currentPort) + }) + .on('error', (err: any) => { + if (err.code === 'EADDRINUSE' && tryOtherPort) { + console.log(`Port ${currentPort} in use, trying ${currentPort + 1}`) + wss.close() + tryPort(currentPort + 1) + } else { + reject(err) } }) - } catch (error) { - console.error('Error processing message:', error) - } - }) - ws.on('close', () => { - console.log('Client disconnected') - }) + wss.on('connection', (ws) => { + console.log('Client connected') + + ws.on('message', (message) => { + try { + // Simply relay the message to all connected clients except sender + wss.clients.forEach(client => { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(message.toString()) + } + }) + } catch (error) { + console.error('Error processing message:', error) + } + }) + + ws.on('close', () => { + console.log('Client disconnected') + }) + }) + } + + tryPort(port) }) - - return wss } diff --git a/src/devtools.ts b/src/devtools.ts index a842dc2f..547d0571 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -241,10 +241,25 @@ function collectAndSendMetrics () { ws.send(JSON.stringify(metrics)) } +function getWebSocketUrl () { + const wsPort = process.env.WS_SERVER + if (!wsPort) return null + + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + const { hostname } = window.location + return `${protocol}//${hostname}:${wsPort}` +} + function connectWebSocket () { if (ws) return - ws = new WebSocket('ws://localhost:8081') + const wsUrl = getWebSocketUrl() + if (!wsUrl) { + console.log('WebSocket server not configured') + return + } + + ws = new WebSocket(wsUrl) ws.onopen = () => { console.log('Connected to metrics server') diff --git a/src/env.d.ts b/src/env.d.ts index 4cb5bafd..c743d6b4 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -3,6 +3,7 @@ declare namespace NodeJS { // Build configuration NODE_ENV: 'development' | 'production' SINGLE_FILE_BUILD?: string + WS_SERVER?: string DISABLE_SERVICE_WORKER?: string CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE' LOCAL_CONFIG_FILE?: string