Add CLI perf command to toggle request timing on proxied apps

This commit is contained in:
Chris Wanstrath 2026-03-19 10:50:23 -07:00
parent 1e4d66cbe4
commit c3ad78f1be
5 changed files with 60 additions and 1 deletions

View File

@ -16,3 +16,4 @@ export {
unshareApp, unshareApp,
} from './manage' } from './manage'
export { metricsApp } from './metrics' export { metricsApp } from './metrics'
export { perfToggle } from './perf'

30
src/cli/commands/perf.ts Normal file
View File

@ -0,0 +1,30 @@
import color from 'ansis'
import { get, handleError, post } from '../http'
interface PerfState {
perfTiming: boolean
}
export async function perfToggle(onOff?: string) {
try {
if (onOff === undefined) {
// Toggle
const res = await post<PerfState>('/api/system/perf')
if (res) console.log(`Perf timing ${res.perfTiming ? color.green('on') : color.red('off')}`)
} else if (onOff === 'on') {
const res = await post<PerfState>('/api/system/perf', { on: true })
if (res) console.log(`Perf timing ${color.green('on')}`)
} else if (onOff === 'off') {
const res = await post<PerfState>('/api/system/perf', { on: false })
if (res) console.log(`Perf timing ${color.red('off')}`)
} else if (onOff === 'status') {
const res = await get<PerfState>('/api/system/perf')
if (res) console.log(`Perf timing is ${res.perfTiming ? color.green('on') : color.red('off')}`)
} else {
console.error('Usage: toes perf [on|off|status]')
process.exit(1)
}
} catch (error) {
handleError(error)
}
}

View File

@ -24,6 +24,7 @@ import {
shareApp, shareApp,
startApp, startApp,
metricsApp, metricsApp,
perfToggle,
stopApp, stopApp,
unshareApp, unshareApp,
} from './commands' } from './commands'
@ -232,6 +233,13 @@ env
.option('-g, --global', 'remove a global variable') .option('-g, --global', 'remove a global variable')
.action(envRm) .action(envRm)
program
.command('perf')
.helpGroup('Config:')
.description('Toggle request timing for proxied app requests')
.argument('[on|off|status]', 'enable, disable, or check status (toggles if omitted)')
.action(perfToggle)
// Shell // Shell
program program

View File

@ -1,4 +1,5 @@
import { allApps, APPS_DIR, onChange } from '$apps' import { allApps, APPS_DIR, onChange } from '$apps'
import { perfTiming, setPerfTiming } from '../proxy'
import { onHostLog } from '../tui' import { onHostLog } from '../tui'
import { Hype } from '@because/hype' import { Hype } from '@because/hype'
import { cpus, freemem, platform, totalmem } from 'os' import { cpus, freemem, platform, totalmem } from 'os'
@ -283,6 +284,16 @@ onChange(collectLogs)
// Subscribe to host-level log messages // Subscribe to host-level log messages
onHostLog(pushHostLog) onHostLog(pushHostLog)
// Perf timing toggle
router.get('/perf', c => c.json({ perfTiming }))
router.post('/perf', async c => {
const body = await c.req.json<{ on?: boolean }>()
const on = body.on ?? !perfTiming
setPerfTiming(on)
console.log(`[perf] timing ${on ? 'enabled' : 'disabled'}`)
return c.json({ perfTiming: on })
})
// Restart server (systemd brings it back) // Restart server (systemd brings it back)
router.post('/restart', c => { router.post('/restart', c => {
setTimeout(() => process.exit(0), 100) setTimeout(() => process.exit(0), 100)

View File

@ -3,6 +3,9 @@ import { getAppBySubdomain } from '$apps'
export type { WsData } export type { WsData }
export let perfTiming = false
export const setPerfTiming = (on: boolean) => { perfTiming = on }
const pendingMessages = new Map<ServerWebSocket<WsData>, (string | ArrayBuffer | Uint8Array)[]>() const pendingMessages = new Map<ServerWebSocket<WsData>, (string | ArrayBuffer | Uint8Array)[]>()
const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>() const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>()
@ -53,12 +56,18 @@ export async function proxySubdomain(subdomain: string, req: Request): Promise<R
headers.delete('transfer-encoding') headers.delete('transfer-encoding')
try { try {
return await fetch(target, { const start = perfTiming ? performance.now() : 0
const res = await fetch(target, {
method: req.method, method: req.method,
headers, headers,
body, body,
redirect: 'manual', redirect: 'manual',
}) })
if (perfTiming) {
const ms = (performance.now() - start).toFixed(1)
console.log(`[perf] ${req.method} ${subdomain}${url.pathname}${res.status} in ${ms}ms`)
}
return res
} catch (e) { } catch (e) {
console.error(`Proxy error for ${subdomain}:`, e) console.error(`Proxy error for ${subdomain}:`, e)
return new Response(`App "${subdomain}" is not responding`, { status: 502 }) return new Response(`App "${subdomain}" is not responding`, { status: 502 })