Skip to content

Commit 826b24d

Browse files
authored
Metrics server (#390)
1 parent 16609aa commit 826b24d

File tree

6 files changed

+204
-1
lines changed

6 files changed

+204
-1
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"dev-proxy": "node server.js",
88
"start": "run-p dev-proxy dev-rsbuild watch-mesher",
99
"start2": "run-p dev-rsbuild watch-mesher",
10+
"start-metrics": "ENABLE_METRICS=true rsbuild dev",
1011
"build": "pnpm build-other-workers && rsbuild build",
1112
"build-analyze": "BUNDLE_ANALYZE=true rsbuild build && pnpm build-other-workers",
1213
"build-single-file": "SINGLE_FILE_BUILD=true rsbuild build",
@@ -32,7 +33,8 @@
3233
"run-all": "run-p start run-playground",
3334
"build-playground": "rsbuild build --config renderer/rsbuild.config.ts",
3435
"watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts",
35-
"update-git-deps": "tsx scripts/updateGitDeps.ts"
36+
"update-git-deps": "tsx scripts/updateGitDeps.ts",
37+
"request-data": "tsx scripts/requestData.ts"
3638
},
3739
"keywords": [
3840
"prismarine",

rsbuild.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { appAndRendererSharedConfig } from './renderer/rsbuildSharedConfig'
1515
import { genLargeDataAliases } from './scripts/genLargeDataAliases'
1616
import sharp from 'sharp'
1717
import supportedVersions from './src/supportedVersions.mjs'
18+
import { startWsServer } from './scripts/wsServer'
1819

1920
const SINGLE_FILE_BUILD = process.env.SINGLE_FILE_BUILD === 'true'
2021

@@ -59,6 +60,8 @@ const configSource = (SINGLE_FILE_BUILD ? 'BUNDLED' : (process.env.CONFIG_JSON_S
5960

6061
const faviconPath = 'favicon.png'
6162

63+
const enableMetrics = process.env.ENABLE_METRICS === 'true'
64+
6265
// base options are in ./renderer/rsbuildSharedConfig.ts
6366
const appConfig = defineConfig({
6467
html: {
@@ -159,6 +162,7 @@ const appConfig = defineConfig({
159162
'process.env.INLINED_APP_CONFIG': JSON.stringify(configSource === 'BUNDLED' ? configJson : null),
160163
'process.env.ENABLE_COOKIE_STORAGE': JSON.stringify(process.env.ENABLE_COOKIE_STORAGE || true),
161164
'process.env.COOKIE_STORAGE_PREFIX': JSON.stringify(process.env.COOKIE_STORAGE_PREFIX || ''),
165+
'process.env.WS_PORT': JSON.stringify(enableMetrics ? 8081 : false),
162166
},
163167
},
164168
server: {
@@ -216,6 +220,12 @@ const appConfig = defineConfig({
216220
await execAsync('pnpm run build-mesher')
217221
}
218222
fs.writeFileSync('./dist/version.txt', buildingVersion, 'utf-8')
223+
224+
// Start WebSocket server in development
225+
if (dev && enableMetrics) {
226+
await startWsServer(8081, false)
227+
}
228+
219229
console.timeEnd('total-prep')
220230
}
221231
if (!dev) {

scripts/requestData.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import WebSocket from 'ws'
2+
3+
function formatBytes(bytes: number) {
4+
return `${(bytes).toFixed(2)} MB`
5+
}
6+
7+
function formatTime(ms: number) {
8+
return `${(ms / 1000).toFixed(2)}s`
9+
}
10+
11+
const ws = new WebSocket('ws://localhost:8081')
12+
13+
ws.on('open', () => {
14+
console.log('Connected to metrics server, waiting for metrics...')
15+
})
16+
17+
ws.on('message', (data) => {
18+
try {
19+
const metrics = JSON.parse(data.toString())
20+
console.log('\nPerformance Metrics:')
21+
console.log('------------------')
22+
console.log(`Load Time: ${formatTime(metrics.loadTime)}`)
23+
console.log(`Memory Usage: ${formatBytes(metrics.memoryUsage)}`)
24+
console.log(`Timestamp: ${new Date(metrics.timestamp).toLocaleString()}`)
25+
if (!process.argv.includes('-f')) { // follow mode
26+
process.exit(0)
27+
}
28+
} catch (error) {
29+
console.error('Error parsing metrics:', error)
30+
}
31+
})
32+
33+
ws.on('error', (error) => {
34+
console.error('WebSocket error:', error)
35+
process.exit(1)
36+
})
37+
38+
// Exit if no metrics received after 5 seconds
39+
setTimeout(() => {
40+
console.error('Timeout waiting for metrics')
41+
process.exit(1)
42+
}, 5000)

scripts/wsServer.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {WebSocketServer} from 'ws'
2+
3+
export function startWsServer(port: number = 8081, tryOtherPort: boolean = true): Promise<number> {
4+
return new Promise((resolve, reject) => {
5+
const tryPort = (currentPort: number) => {
6+
const wss = new WebSocketServer({ port: currentPort })
7+
.on('listening', () => {
8+
console.log(`WebSocket server started on port ${currentPort}`)
9+
resolve(currentPort)
10+
})
11+
.on('error', (err: any) => {
12+
if (err.code === 'EADDRINUSE' && tryOtherPort) {
13+
console.log(`Port ${currentPort} in use, trying ${currentPort + 1}`)
14+
wss.close()
15+
tryPort(currentPort + 1)
16+
} else {
17+
reject(err)
18+
}
19+
})
20+
21+
wss.on('connection', (ws) => {
22+
console.log('Client connected')
23+
24+
ws.on('message', (message) => {
25+
try {
26+
// Simply relay the message to all connected clients except sender
27+
wss.clients.forEach(client => {
28+
if (client !== ws && client.readyState === WebSocket.OPEN) {
29+
client.send(message.toString())
30+
}
31+
})
32+
} catch (error) {
33+
console.error('Error processing message:', error)
34+
}
35+
})
36+
37+
ws.on('close', () => {
38+
console.log('Client disconnected')
39+
})
40+
})
41+
}
42+
43+
tryPort(port)
44+
})
45+
}

src/devtools.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,106 @@ setInterval(() => {
209209
}, 1000)
210210

211211
// ---
212+
213+
// Add type declaration for performance.memory
214+
declare global {
215+
interface Performance {
216+
memory?: {
217+
usedJSHeapSize: number
218+
totalJSHeapSize: number
219+
jsHeapSizeLimit: number
220+
}
221+
}
222+
}
223+
224+
// Performance metrics WebSocket client
225+
let ws: WebSocket | null = null
226+
let wsReconnectTimeout: NodeJS.Timeout | null = null
227+
let metricsInterval: NodeJS.Timeout | null = null
228+
229+
// Start collecting metrics immediately
230+
const startTime = performance.now()
231+
232+
function collectAndSendMetrics () {
233+
if (!ws || ws.readyState !== WebSocket.OPEN) return
234+
235+
const metrics = {
236+
loadTime: performance.now() - startTime,
237+
memoryUsage: (performance.memory?.usedJSHeapSize ?? 0) / 1024 / 1024,
238+
timestamp: Date.now()
239+
}
240+
241+
ws.send(JSON.stringify(metrics))
242+
}
243+
244+
function getWebSocketUrl () {
245+
const wsPort = process.env.WS_SERVER
246+
if (!wsPort) return null
247+
248+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
249+
const { hostname } = window.location
250+
return `${protocol}//${hostname}:${wsPort}`
251+
}
252+
253+
function connectWebSocket () {
254+
if (ws) return
255+
256+
const wsUrl = getWebSocketUrl()
257+
if (!wsUrl) {
258+
console.log('WebSocket server not configured')
259+
return
260+
}
261+
262+
ws = new WebSocket(wsUrl)
263+
264+
ws.onopen = () => {
265+
console.log('Connected to metrics server')
266+
if (wsReconnectTimeout) {
267+
clearTimeout(wsReconnectTimeout)
268+
wsReconnectTimeout = null
269+
}
270+
271+
// Start sending metrics immediately after connection
272+
collectAndSendMetrics()
273+
274+
// Clear existing interval if any
275+
if (metricsInterval) {
276+
clearInterval(metricsInterval)
277+
}
278+
279+
// Set new interval
280+
metricsInterval = setInterval(collectAndSendMetrics, 500)
281+
}
282+
283+
ws.onclose = () => {
284+
console.log('Disconnected from metrics server')
285+
ws = null
286+
287+
// Clear metrics interval
288+
if (metricsInterval) {
289+
clearInterval(metricsInterval)
290+
metricsInterval = null
291+
}
292+
293+
// Try to reconnect after 3 seconds
294+
wsReconnectTimeout = setTimeout(connectWebSocket, 3000)
295+
}
296+
297+
ws.onerror = (error) => {
298+
console.error('WebSocket error:', error)
299+
}
300+
}
301+
302+
// Connect immediately
303+
connectWebSocket()
304+
305+
// Add command to request current metrics
306+
window.requestMetrics = () => {
307+
const metrics = {
308+
loadTime: performance.now() - startTime,
309+
memoryUsage: (performance.memory?.usedJSHeapSize ?? 0) / 1024 / 1024,
310+
timestamp: Date.now()
311+
}
312+
console.log('Current metrics:', metrics)
313+
return metrics
314+
}

src/env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ declare namespace NodeJS {
33
// Build configuration
44
NODE_ENV: 'development' | 'production'
55
SINGLE_FILE_BUILD?: string
6+
WS_SERVER?: string
67
DISABLE_SERVICE_WORKER?: string
78
CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE'
89
LOCAL_CONFIG_FILE?: string

0 commit comments

Comments
 (0)