Files
IOT_SIM/server/test/load.ts
T

104 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 并发压力测试:同时运行 N 个学生程序(教材项目 2 任务 1),测量内存、启动时间和操作往返延迟。
// 用法:bun test/load.ts [N],默认 100。运行前先编译运行时:bun run build:runtime
import { readFileSync } from 'node:fs'
import { cp, mkdir, rm } from 'node:fs/promises'
import { resolve } from 'node:path'
import type { ControlNode, RunnerMessage } from '@iot/shared'
import { compileService } from '../src/compile-service'
import { config, ROOT } from '../src/config'
import { readLines } from '../src/runner-process'
const COUNT = Number(process.argv[2] ?? 100)
const dir = resolve(ROOT, '.runs/load-test')
await rm(dir, { recursive: true, force: true })
await mkdir(dir, { recursive: true })
await cp(resolve(ROOT, 'samples/p2-t1-LED显示屏'), dir, { recursive: true })
const compiled = await compileService.compile(dir)
compileService.stop()
if (!compiled.ok) throw new Error('示例编译失败')
interface Instance {
proc: Bun.Subprocess<'pipe', 'pipe', 'inherit'>
form?: ControlNode
onMessage?: (m: RunnerMessage) => void
}
function findId(node: ControlNode, name: string): string | undefined {
if (node.name === name) return node.id
for (const child of node.children) {
const id = findId(child, name)
if (id) return id
}
}
function send(instance: Instance, message: object) {
instance.proc.stdin.write(`${JSON.stringify(message)}\n`)
instance.proc.stdin.flush()
}
/** 读取 /proc 里的内存数据(MB)。Rss 会把多个进程共享的 .NET 运行库重复计算,Pss 按进程数平摊,合计值更接近真实占用 */
function memoryMb(pid: number, field: 'Rss' | 'Pss') {
const rollup = readFileSync(`/proc/${pid}/smaps_rollup`, 'utf8')
return Number(new RegExp(`^${field}:\\s+(\\d+)`, 'm').exec(rollup)![1]) / 1024
}
console.log(`启动 ${COUNT} 个学生程序…`)
const startAt = performance.now()
const instances: Instance[] = []
const shown: Promise<void>[] = []
for (let i = 0; i < COUNT; i++) {
const proc = Bun.spawn(['dotnet', config.runnerDll, 'run', dir], { stdin: 'pipe', stdout: 'pipe', stderr: 'inherit' })
const instance: Instance = { proc }
instances.push(instance)
shown.push(
new Promise((resolveShown) => {
void readLines(proc.stdout, (line) => {
const message = JSON.parse(line) as RunnerMessage
if (message.type === 'formShown') {
instance.form = message.form
resolveShown()
}
instance.onMessage?.(message)
})
}),
)
}
await Promise.all(shown)
console.log(`全部窗体显示完成:${Math.round(performance.now() - startAt)} ms`)
await Bun.sleep(3000)
for (const field of ['Rss', 'Pss'] as const) {
const values = instances.map((i) => memoryMb(i.proc.pid, field))
const total = values.reduce((a, b) => a + b, 0)
console.log(`内存 ${field}:合计 ${Math.round(total)} MB,平均 ${Math.round(total / COUNT)} MB`)
}
// 每个程序:选 COM5 → 打开 → 输入文字 → 推送,测量从发出"推送"到收到 LED 变化的往返时间
const latencies = await Promise.all(
instances.map(async (instance) => {
const form = instance.form!
const id = (name: string) => findId(form, name)!
send(instance, { type: 'action', id: id('com_Port'), action: 'dropDown' })
send(instance, { type: 'action', id: id('com_Port'), action: 'select', index: 3 })
send(instance, { type: 'action', id: id('btn_Open'), action: 'click' })
send(instance, { type: 'action', id: id('txt_SendText'), action: 'text', value: '压力测试' })
await Bun.sleep(200)
const led = id('led')
const sentAt = performance.now()
const done = new Promise<number>((resolveDone) => {
instance.onMessage = (m) => {
if (m.type === 'patch' && m.changes.some((c) => c.id === led && c.prop === 'LedText')) resolveDone(performance.now() - sentAt)
}
})
send(instance, { type: 'action', id: id('btn_Send'), action: 'click' })
return done
}),
)
latencies.sort((a, b) => a - b)
const pct = (p: number) => Math.round(latencies[Math.min(latencies.length - 1, Math.floor((latencies.length * p) / 100))])
console.log(`操作往返(${COUNT} 个程序同时点击):中位数 ${pct(50)} ms,P95 ${pct(95)} ms,最大 ${pct(100)} ms`)
for (const instance of instances) instance.proc.kill()
await rm(dir, { recursive: true, force: true })