Files
speckle-server/scratch/monitor-viewer-load.mjs
T

165 lines
5.0 KiB
JavaScript

import fs from 'node:fs/promises'
import path from 'node:path'
import puppeteer from 'puppeteer'
const outDir = path.resolve('scratch', 'viewer-audit')
await fs.mkdir(outDir, { recursive: true })
const browser = await puppeteer.launch({
headless: false,
defaultViewport: { width: 1440, height: 900 },
args: ['--window-size=1440,900']
})
const monitorMs = Number(process.env.MONITOR_MS || 120000)
const page = await browser.newPage()
page.setDefaultTimeout(120000)
await page.evaluateOnNewDocument(() => {
window.localStorage.setItem('AuthToken', 'fake-token')
})
const client = await page.target().createCDPSession()
await client.send('Network.enable')
const requests = new Map()
const timeline = []
const startedAt = Date.now()
const now = () => Date.now() - startedAt
const watched = (url) =>
/\/objects\/|\/api\/getobjects\/|\/api\/stream\/.+\/blob\//.test(url)
client.on('Network.requestWillBeSent', (event) => {
if (!watched(event.request.url)) return
requests.set(event.requestId, {
id: event.requestId,
url: event.request.url,
method: event.request.method,
startMs: now(),
encodedBytes: 0,
decodedBytes: 0,
status: null,
mimeType: null,
done: false
})
timeline.push({ t: now(), event: 'request', url: event.request.url })
console.log(`[${(now() / 1000).toFixed(1)}s] request ${event.request.url}`)
})
client.on('Network.responseReceived', (event) => {
const item = requests.get(event.requestId)
if (!item) return
item.status = event.response.status
item.mimeType = event.response.mimeType
item.responseMs = now()
item.headers = event.response.headers
timeline.push({
t: now(),
event: 'response',
status: item.status,
url: item.url
})
console.log(
`[${(now() / 1000).toFixed(1)}s] response ${item.status} ${item.url}`
)
})
client.on('Network.dataReceived', (event) => {
const item = requests.get(event.requestId)
if (!item) return
item.encodedBytes += event.encodedDataLength || 0
item.decodedBytes += event.dataLength || 0
})
client.on('Network.loadingFinished', (event) => {
const item = requests.get(event.requestId)
if (!item) return
item.endMs = now()
item.done = true
item.encodedBytes = Math.max(item.encodedBytes, event.encodedDataLength || 0)
item.durationMs = item.endMs - item.startMs
item.encodedKbps =
item.durationMs > 0 ? (item.encodedBytes / 1024 / (item.durationMs / 1000)) : 0
timeline.push({
t: now(),
event: 'finished',
encodedBytes: item.encodedBytes,
decodedBytes: item.decodedBytes,
durationMs: item.durationMs,
url: item.url
})
console.log(
`[${(now() / 1000).toFixed(1)}s] finished ${(item.encodedBytes / 1024 / 1024).toFixed(2)} MB in ${(item.durationMs / 1000).toFixed(2)}s = ${(item.encodedKbps / 1024).toFixed(2)} MB/s ${item.url}`
)
})
client.on('Network.loadingFailed', (event) => {
const item = requests.get(event.requestId)
if (!item) return
item.endMs = now()
item.failed = event.errorText
timeline.push({ t: now(), event: 'failed', error: event.errorText, url: item.url })
console.log(`[${(now() / 1000).toFixed(1)}s] failed ${event.errorText} ${item.url}`)
})
page.on('console', (msg) => {
const type = msg.type()
if (type === 'error' || type === 'warning') {
console.log(`[${(now() / 1000).toFixed(1)}s] console ${type}: ${msg.text()}`)
}
})
const waitSettled = async () => {
await page.waitForNetworkIdle({ idleTime: 1000, timeout: 30000 }).catch(() => {})
}
const viewerHref =
'http://localhost:8081/projects/d08738d34e/models/bf3a274337?viewerVerbose=1'
const samples = []
const sampler = setInterval(async () => {
const sample = await page
.evaluate(() => ({
text: document.body.innerText.slice(0, 500),
canvasCount: document.querySelectorAll('canvas').length
}))
.catch(() => null)
if (sample) {
samples.push({ t: now(), ...sample })
console.log(
`[${(now() / 1000).toFixed(1)}s] sample canvas=${sample.canvasCount} text="${sample.text
.replace(/\s+/g, ' ')
.slice(0, 120)}"`
)
}
}, 5000)
try {
console.log(`Opening viewer: ${viewerHref}`)
await page.goto(viewerHref, { waitUntil: 'domcontentloaded' })
await new Promise((resolve) => setTimeout(resolve, monitorMs))
} finally {
clearInterval(sampler)
const summary = [...requests.values()].map((item) => ({
url: item.url,
status: item.status,
startMs: item.startMs,
responseMs: item.responseMs,
endMs: item.endMs,
durationMs: item.durationMs,
encodedBytes: item.encodedBytes,
decodedBytes: item.decodedBytes,
encodedMbps:
item.durationMs > 0
? (item.encodedBytes * 8) / 1000 / 1000 / (item.durationMs / 1000)
: 0,
failed: item.failed
}))
const report = { viewerHref, timeline, requests: summary, samples }
const reportPath = path.join(outDir, 'load-monitor.json')
await fs.writeFile(reportPath, JSON.stringify(report, null, 2))
await page.screenshot({ path: path.join(outDir, 'load-monitor-final.png'), fullPage: true })
console.log(JSON.stringify(report, null, 2))
await browser.close()
}