Rename the "stats" CLI command, tool app, and all internal references to "metrics". Add file size tracking from each app's DATA_DIR as a new metric, shown in both the CLI table and web UI. https://claude.ai/code/session_013agP8J1cCfrWZkueZ33jQB
116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import color from 'kleur'
|
|
import { get } from '../http'
|
|
import { resolveAppName } from '../name'
|
|
|
|
interface AppMetrics {
|
|
name: string
|
|
state: string
|
|
port?: number
|
|
pid?: number
|
|
cpu?: number
|
|
memory?: number
|
|
rss?: number
|
|
dataSize?: number
|
|
tool?: boolean
|
|
}
|
|
|
|
function formatBytes(bytes?: number): string {
|
|
if (bytes === undefined) return '-'
|
|
if (bytes < 1024) return `${bytes} B`
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`
|
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`
|
|
}
|
|
|
|
function formatRss(kb?: number): string {
|
|
if (kb === undefined) return '-'
|
|
if (kb < 1024) return `${kb} KB`
|
|
if (kb < 1024 * 1024) return `${(kb / 1024).toFixed(1)} MB`
|
|
return `${(kb / 1024 / 1024).toFixed(2)} GB`
|
|
}
|
|
|
|
function formatPercent(value?: number): string {
|
|
if (value === undefined) return '-'
|
|
return `${value.toFixed(1)}%`
|
|
}
|
|
|
|
function pad(str: string, len: number, right = false): string {
|
|
if (right) return str.padStart(len)
|
|
return str.padEnd(len)
|
|
}
|
|
|
|
export async function metricsApp(arg?: string) {
|
|
// If arg is provided, show metrics for that app only
|
|
if (arg) {
|
|
const name = resolveAppName(arg)
|
|
if (!name) return
|
|
|
|
const metrics: AppMetrics | undefined = await get(`/api/tools/metrics/api/metrics/${name}`)
|
|
if (!metrics) {
|
|
console.error(`App not found: ${name}`)
|
|
return
|
|
}
|
|
|
|
console.log(`${color.bold(metrics.name)} ${metrics.tool ? color.gray('[tool]') : ''}`)
|
|
console.log(` State: ${metrics.state}`)
|
|
if (metrics.pid) console.log(` PID: ${metrics.pid}`)
|
|
if (metrics.port) console.log(` Port: ${metrics.port}`)
|
|
if (metrics.cpu !== undefined) console.log(` CPU: ${formatPercent(metrics.cpu)}`)
|
|
if (metrics.memory !== undefined) console.log(` Memory: ${formatPercent(metrics.memory)}`)
|
|
if (metrics.rss !== undefined) console.log(` RSS: ${formatRss(metrics.rss)}`)
|
|
console.log(` Data: ${formatBytes(metrics.dataSize)}`)
|
|
return
|
|
}
|
|
|
|
// Show metrics for all apps
|
|
const metrics: AppMetrics[] | undefined = await get('/api/tools/metrics/api/metrics')
|
|
if (!metrics || metrics.length === 0) {
|
|
console.log('No apps found')
|
|
return
|
|
}
|
|
|
|
// Sort: running first, then by name
|
|
metrics.sort((a, b) => {
|
|
if (a.state === 'running' && b.state !== 'running') return -1
|
|
if (a.state !== 'running' && b.state === 'running') return 1
|
|
return a.name.localeCompare(b.name)
|
|
})
|
|
|
|
// Calculate column widths
|
|
const nameWidth = Math.max(4, ...metrics.map(s => s.name.length + (s.tool ? 7 : 0)))
|
|
const stateWidth = Math.max(5, ...metrics.map(s => s.state.length))
|
|
|
|
// Header
|
|
console.log(
|
|
color.gray(
|
|
`${pad('NAME', nameWidth)} ${pad('STATE', stateWidth)} ${pad('PID', 7, true)} ${pad('CPU', 7, true)} ${pad('MEM', 7, true)} ${pad('RSS', 10, true)} ${pad('DATA', 10, true)}`
|
|
)
|
|
)
|
|
|
|
// Rows
|
|
for (const s of metrics) {
|
|
const name = s.tool ? `${s.name} ${color.gray('[tool]')}` : s.name
|
|
const stateColor = s.state === 'running' ? color.green : s.state === 'invalid' ? color.red : color.gray
|
|
const state = stateColor(s.state)
|
|
|
|
const pid = s.pid ? String(s.pid) : '-'
|
|
const cpu = formatPercent(s.cpu)
|
|
const mem = formatPercent(s.memory)
|
|
const rss = formatRss(s.rss)
|
|
const data = formatBytes(s.dataSize)
|
|
|
|
console.log(
|
|
`${pad(name, nameWidth)} ${pad(state, stateWidth)} ${pad(pid, 7, true)} ${pad(cpu, 7, true)} ${pad(mem, 7, true)} ${pad(rss, 10, true)} ${pad(data, 10, true)}`
|
|
)
|
|
}
|
|
|
|
// Summary
|
|
const running = metrics.filter(s => s.state === 'running')
|
|
const totalCpu = running.reduce((sum, s) => sum + (s.cpu ?? 0), 0)
|
|
const totalRss = running.reduce((sum, s) => sum + (s.rss ?? 0), 0)
|
|
const totalData = metrics.reduce((sum, s) => sum + (s.dataSize ?? 0), 0)
|
|
|
|
console.log()
|
|
console.log(color.gray(`${running.length} running, ${formatPercent(totalCpu)} CPU, ${formatRss(totalRss)} RSS, ${formatBytes(totalData)} data`))
|
|
}
|