mirror of
https://github.com/stablyai/orca.git
synced 2026-11-05 19:47:27 +00:00
The resource panel summed RSS. On macOS that counts the Electron framework's shared pages once per helper process, so main and renderer read 2-8x too high, while the GPU process read 3-5x too low because RSS excludes graphics memory. The detached terminal daemon was also missing from the app total. On macOS, sample per-process phys_footprint from top (the figure Activity Monitor shows) at most every 10 s while the panel is open, and fall back to RSS when top is unavailable. Count the local terminal daemon in the app's 'other' bucket; its PTY children stay attributed to their worktrees. Linux and Windows keep RSS/working set. Co-authored-by: m4air <m4air@Mac.localdomain>
206 lines
7.8 KiB
TypeScript
206 lines
7.8 KiB
TypeScript
import type { PublicKnownRuntimeEnvironment } from '../shared/runtime-environments'
|
|
import type {
|
|
RuntimeRepoList,
|
|
RuntimeRepoSearchRefs,
|
|
RuntimeWorktreeListResult,
|
|
RuntimeWorktreePsResult,
|
|
RuntimeWorktreeRecord
|
|
} from '../shared/runtime-types'
|
|
import type { MemorySnapshot, WorktreeMemory } from '../shared/process-stats-types'
|
|
import { formatListingHostScope, type WithAnnotatedHostScope } from './omitted-host-scope-selectors'
|
|
import { formatWorktreePsTerminalFields } from './worktree-ps-terminal-verdict'
|
|
import { formatWorktreePsProgramStatus } from './worktree-ps-program-status'
|
|
|
|
export function formatMemorySnapshot(snapshot: MemorySnapshot): string {
|
|
const topWorktrees = [...snapshot.worktrees].sort((a, b) => b.memory - a.memory).slice(0, 10)
|
|
const hostAvailable = snapshot.host.availableMemory ?? snapshot.host.freeMemory
|
|
const hostAvailableSource = snapshot.host.availableMemorySource ?? 'free-memory'
|
|
const lines = [
|
|
`collectedAt: ${new Date(snapshot.collectedAt).toISOString()}`,
|
|
`totalMemory: ${formatByteCount(snapshot.totalMemory)}`,
|
|
`processMemoryMetric: ${formatProcessMemoryMetric(snapshot.processMemoryMetric)}`,
|
|
...formatCommitLines(snapshot),
|
|
`totalCpu: ${formatCpu(snapshot.totalCpu)}`,
|
|
[
|
|
`hostUsed: ${formatByteCount(snapshot.host.usedMemory)}`,
|
|
`/ ${formatByteCount(snapshot.host.totalMemory)}`,
|
|
`(${snapshot.host.memoryUsagePercent.toFixed(1)}%)`
|
|
].join(' '),
|
|
[`hostAvailable: ${formatByteCount(hostAvailable)}`, `(${hostAvailableSource})`].join(' '),
|
|
[
|
|
`app: ${formatByteCount(snapshot.app.memory)}`,
|
|
`(main ${formatByteCount(snapshot.app.main.memory)},`,
|
|
`renderer ${formatByteCount(snapshot.app.renderer.memory)},`,
|
|
`other ${formatByteCount(snapshot.app.other.memory)})`
|
|
].join(' '),
|
|
`worktrees: ${snapshot.worktrees.length}`
|
|
]
|
|
|
|
if (topWorktrees.length === 0) {
|
|
lines.push('topWorktrees: none')
|
|
return lines.join('\n')
|
|
}
|
|
|
|
lines.push('', 'Top worktrees:')
|
|
for (const worktree of topWorktrees) {
|
|
lines.push(formatWorktreeMemoryLine(worktree))
|
|
}
|
|
if (snapshot.worktrees.length > topWorktrees.length) {
|
|
lines.push(`... ${snapshot.worktrees.length - topWorktrees.length} more worktrees`)
|
|
}
|
|
return lines.join('\n')
|
|
}
|
|
|
|
function formatWorktreeMemoryLine(worktree: WorktreeMemory): string {
|
|
return [
|
|
`- ${worktree.worktreeName}`,
|
|
`${formatByteCount(worktree.memory)}`,
|
|
...(worktree.privateMemory === undefined
|
|
? []
|
|
: [`${formatByteCount(worktree.privateMemory)} committed`]),
|
|
`${formatCpu(worktree.cpu)}`,
|
|
`${worktree.sessions.length} session${worktree.sessions.length === 1 ? '' : 's'}`
|
|
].join(' ')
|
|
}
|
|
|
|
// Missing commit fields mean unknown usage, not zero.
|
|
function formatCommitLines(snapshot: MemorySnapshot): string[] {
|
|
if (typeof snapshot.totalPrivateMemory !== 'number') {
|
|
return []
|
|
}
|
|
return [
|
|
`totalPrivateMemory: ${formatByteCount(snapshot.totalPrivateMemory)}`,
|
|
`processCommitMetric: ${formatProcessCommitMetric(snapshot.processCommitMetric)}`
|
|
]
|
|
}
|
|
|
|
function formatProcessCommitMetric(metric: MemorySnapshot['processCommitMetric']): string {
|
|
return metric === 'private-bytes'
|
|
? 'summed private bytes; committed memory, counted whether resident or paged out'
|
|
: `summed ${metric ?? 'unknown'}`
|
|
}
|
|
|
|
function formatCpu(cpu: number): string {
|
|
return `${cpu.toFixed(1)}%`
|
|
}
|
|
|
|
function formatProcessMemoryMetric(metric: MemorySnapshot['processMemoryMetric']): string {
|
|
if (metric === 'footprint') {
|
|
return 'summed memory footprint (Activity Monitor); includes graphics memory'
|
|
}
|
|
return metric === 'working-set'
|
|
? 'summed working set; shared pages may repeat'
|
|
: 'summed RSS; shared or aliased pages may repeat'
|
|
}
|
|
|
|
function formatByteCount(bytes: number): string {
|
|
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
return '0 B'
|
|
}
|
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
let value = bytes
|
|
let unitIndex = 0
|
|
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
value /= 1024
|
|
unitIndex += 1
|
|
}
|
|
const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)
|
|
return `${formatted} ${units[unitIndex]}`
|
|
}
|
|
|
|
export function formatEnvironmentList(result: {
|
|
environments: PublicKnownRuntimeEnvironment[]
|
|
}): string {
|
|
if (result.environments.length === 0) {
|
|
return 'No saved environments.'
|
|
}
|
|
return result.environments
|
|
.map(
|
|
(environment) =>
|
|
`${environment.id} ${environment.name} ${environment.endpoints[0]?.endpoint ?? 'no-endpoint'}`
|
|
)
|
|
.join('\n')
|
|
}
|
|
|
|
export function formatEnvironment(environment: PublicKnownRuntimeEnvironment): string {
|
|
return [
|
|
`id: ${environment.id}`,
|
|
`name: ${environment.name}`,
|
|
`runtimeId: ${environment.runtimeId ?? 'unknown'}`,
|
|
`lastUsedAt: ${environment.lastUsedAt ?? 'never'}`,
|
|
`preferredEndpointId: ${environment.preferredEndpointId}`,
|
|
...environment.endpoints.map(
|
|
(endpoint) => `endpoint: ${endpoint.id} ${endpoint.kind} ${endpoint.endpoint}`
|
|
)
|
|
].join('\n')
|
|
}
|
|
|
|
export function formatWorktreePs(result: WithAnnotatedHostScope<RuntimeWorktreePsResult>): string {
|
|
const scope = formatListingHostScope(result.hostScope)
|
|
if (result.worktrees.length === 0) {
|
|
return `No worktrees found.\n${scope}`
|
|
}
|
|
const body = result.worktrees
|
|
.map(
|
|
(worktree) =>
|
|
`${worktree.repo} ${worktree.branch} host=${worktree.hostId ?? 'unverifiable'} ${formatWorktreePsTerminalFields(worktree)} unread:${worktree.unread ? 'yes' : 'no'}${formatWorktreePsProgramStatus(worktree.agents)}\n${worktree.path}${worktree.preview ? `\npreview: ${worktree.preview}` : ''}`
|
|
)
|
|
.join('\n\n')
|
|
const bodyWithScope = `${body}\n\n${scope}`
|
|
return result.truncated
|
|
? `${bodyWithScope}\ntruncated: showing ${result.worktrees.length} of ${result.totalCount}`
|
|
: bodyWithScope
|
|
}
|
|
|
|
export function formatRepoList(result: RuntimeRepoList): string {
|
|
if (result.repos.length === 0) {
|
|
return 'No repos found.'
|
|
}
|
|
return result.repos.map((repo) => `${repo.id} ${repo.displayName} ${repo.path}`).join('\n')
|
|
}
|
|
|
|
export function formatRepoShow(result: { repo: Record<string, unknown> }): string {
|
|
return Object.entries(result.repo)
|
|
.map(
|
|
([key, value]) =>
|
|
`${key}: ${typeof value === 'object' ? JSON.stringify(value) : String(value)}`
|
|
)
|
|
.join('\n')
|
|
}
|
|
|
|
export function formatRepoRefs(result: RuntimeRepoSearchRefs): string {
|
|
if (result.refs.length === 0) {
|
|
return 'No refs found.'
|
|
}
|
|
return result.truncated ? `${result.refs.join('\n')}\n\ntruncated: yes` : result.refs.join('\n')
|
|
}
|
|
|
|
export function formatWorktreeList(
|
|
result: WithAnnotatedHostScope<RuntimeWorktreeListResult>
|
|
): string {
|
|
const scope = formatListingHostScope(result.hostScope)
|
|
if (result.worktrees.length === 0) {
|
|
return `No worktrees found.\n${scope}`
|
|
}
|
|
const body = result.worktrees
|
|
.map((worktree) => {
|
|
const childCount = worktree.childWorktreeIds?.length ?? 0
|
|
return `${String(worktree.id)} ${String(worktree.branch)} host=${String(worktree.hostId ?? 'unverifiable')} ${String(worktree.path)}\ndisplayName: ${String(worktree.displayName ?? '')}\nparentWorktreeId: ${String(worktree.parentWorktreeId ?? 'null')}\nchildWorktreeIds: ${childCount > 0 ? worktree.childWorktreeIds.join(',') : '[]'}\nlinkedIssue: ${String(worktree.linkedIssue ?? 'null')}\ncomment: ${String(worktree.comment ?? '')}`
|
|
})
|
|
.join('\n\n')
|
|
const bodyWithScope = `${body}\n\n${scope}`
|
|
return result.truncated
|
|
? `${bodyWithScope}\ntruncated: showing ${result.worktrees.length} of ${result.totalCount}`
|
|
: bodyWithScope
|
|
}
|
|
|
|
export function formatWorktreeShow(result: { worktree: RuntimeWorktreeRecord }): string {
|
|
const worktree = result.worktree
|
|
return Object.entries(worktree)
|
|
.map(
|
|
([key, value]) =>
|
|
`${key}: ${typeof value === 'object' ? JSON.stringify(value) : String(value)}`
|
|
)
|
|
.join('\n')
|
|
}
|