165 lines
5.0 KiB
JavaScript
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()
|
|
}
|