forked from defunkt/toes
Convert getAppMetrics to async and replace spawnSync with async Bun.spawn
spawnSync blocks the event loop while waiting for ps and du, which stalls the SSE metrics stream and other requests. Running these concurrently with async spawn (and Promise.all for du) keeps the server responsive under load.
This commit is contained in:
parent
926e57e34e
commit
195be426f1
|
|
@ -103,7 +103,7 @@ let _appDiskCache: Record<string, number> = {}
|
|||
let _appDiskLastUpdate = 0
|
||||
const DISK_CACHE_TTL = 30000
|
||||
|
||||
function getAppMetrics(): Record<string, AppMetrics> {
|
||||
async function getAppMetrics(): Promise<Record<string, AppMetrics>> {
|
||||
const apps = allApps()
|
||||
const running = apps.filter(a => a.proc?.pid)
|
||||
const result: Record<string, AppMetrics> = {}
|
||||
|
|
@ -117,8 +117,10 @@ function getAppMetrics(): Record<string, AppMetrics> {
|
|||
if (pidToName.size > 0) {
|
||||
try {
|
||||
const pids = [...pidToName.keys()].join(',')
|
||||
const ps = Bun.spawnSync(['ps', '-o', 'pid=,%cpu=,rss=', '-p', pids])
|
||||
for (const line of ps.stdout.toString().split('\n')) {
|
||||
const proc = Bun.spawn(['ps', '-o', 'pid=,%cpu=,rss=', '-p', pids], { stdout: 'pipe', stderr: 'ignore' })
|
||||
const output = await new Response(proc.stdout).text()
|
||||
await proc.exited
|
||||
for (const line of output.split('\n')) {
|
||||
const parts = line.trim().split(/\s+/)
|
||||
if (parts.length < 3) continue
|
||||
const pid = parseInt(parts[0]!, 10)
|
||||
|
|
@ -135,12 +137,21 @@ function getAppMetrics(): Record<string, AppMetrics> {
|
|||
if (now - _appDiskLastUpdate > DISK_CACHE_TTL) {
|
||||
_appDiskLastUpdate = now
|
||||
_appDiskCache = {}
|
||||
for (const app of apps) {
|
||||
try {
|
||||
const du = Bun.spawnSync(['du', '-sk', join(APPS_DIR, app.name)])
|
||||
const kb = parseInt(du.stdout.toString().trim().split('\t')[0]!, 10)
|
||||
if (kb) _appDiskCache[app.name] = kb * 1024
|
||||
} catch {}
|
||||
const duResults = await Promise.all(
|
||||
apps.map(async app => {
|
||||
try {
|
||||
const proc = Bun.spawn(['du', '-sk', join(APPS_DIR, app.name)], { stdout: 'pipe', stderr: 'ignore' })
|
||||
const output = await new Response(proc.stdout).text()
|
||||
await proc.exited
|
||||
const kb = parseInt(output.trim().split('\t')[0]!, 10)
|
||||
return { name: app.name, bytes: kb ? kb * 1024 : 0 }
|
||||
} catch {
|
||||
return { name: app.name, bytes: 0 }
|
||||
}
|
||||
})
|
||||
)
|
||||
for (const { name, bytes } of duResults) {
|
||||
if (bytes) _appDiskCache[name] = bytes
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,26 +165,30 @@ function getAppMetrics(): Record<string, AppMetrics> {
|
|||
}
|
||||
|
||||
// Get current system metrics
|
||||
router.get('/metrics', c => {
|
||||
router.get('/metrics', async c => {
|
||||
const metrics: SystemMetrics = {
|
||||
cpu: getCpuUsage(),
|
||||
ram: getMemoryUsage(),
|
||||
disk: getDiskUsage(),
|
||||
apps: getAppMetrics(),
|
||||
apps: await getAppMetrics(),
|
||||
}
|
||||
return c.json(metrics)
|
||||
})
|
||||
|
||||
// SSE stream for real-time metrics (updates every 2s)
|
||||
router.sse('/metrics/stream', (send) => {
|
||||
let queue = Promise.resolve()
|
||||
|
||||
const sendMetrics = () => {
|
||||
const metrics: SystemMetrics = {
|
||||
cpu: getCpuUsage(),
|
||||
ram: getMemoryUsage(),
|
||||
disk: getDiskUsage(),
|
||||
apps: getAppMetrics(),
|
||||
}
|
||||
send(metrics)
|
||||
queue = queue.then(async () => {
|
||||
const metrics: SystemMetrics = {
|
||||
cpu: getCpuUsage(),
|
||||
ram: getMemoryUsage(),
|
||||
disk: getDiskUsage(),
|
||||
apps: await getAppMetrics(),
|
||||
}
|
||||
await send(metrics)
|
||||
})
|
||||
}
|
||||
|
||||
// Initial send
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user