Skip to content

Commit 39d3683

Browse files
committed
Expose native service resource telemetry
1 parent cc3d7ac commit 39d3683

13 files changed

Lines changed: 641 additions & 1 deletion

File tree

‎apps/web/src/features/system-info/api.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { api } from '@/lib/api'
2121
import type {
2222
SystemInstanceDeleteResponse,
2323
SystemInstanceListResponse,
24+
SystemResourceSnapshot,
2425
} from './types'
2526

2627
export async function listSystemInstances() {
@@ -30,6 +31,13 @@ export async function listSystemInstances() {
3031
return res.data
3132
}
3233

34+
export async function getNativeSystemResources() {
35+
const res = await api.get<SystemResourceSnapshot>(
36+
'/core/v1/admin/system-resources'
37+
)
38+
return res.data
39+
}
40+
3341
export async function deleteStaleSystemInstances() {
3442
const res = await api.delete<SystemInstanceDeleteResponse>(
3543
'/api/system-info/stale-instances'

‎apps/web/src/features/system-info/components/system-instances-panel.tsx‎

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,15 @@ import { cn } from '@/lib/utils'
6969
import {
7070
deleteStaleSystemInstance,
7171
deleteStaleSystemInstances,
72+
getNativeSystemResources,
7273
listSystemInstances,
7374
} from '../api'
74-
import type { SystemInstance, SystemInstanceStatus } from '../types'
75+
import type {
76+
SystemInstance,
77+
SystemInstanceStatus,
78+
SystemProcessInfo,
79+
SystemResourceSnapshot,
80+
} from '../types'
7581

7682
const INSTANCE_POLL_INTERVAL_MS = 30_000
7783
const INSTANCE_SKELETON_KEYS = [
@@ -394,6 +400,7 @@ function ResourceCell(props: ResourceCellProps) {
394400

395401
type SystemInstancesTableProps = {
396402
instances: SystemInstance[]
403+
native?: SystemResourceSnapshot
397404
deletingReporterId: string | null
398405
isDeletingInstance: boolean
399406
onDeleteStaleInstance: (instance: SystemInstance) => void
@@ -556,6 +563,188 @@ function StorageCell(props: { instance: SystemInstance }) {
556563
)
557564
}
558565

566+
function processInfo(
567+
instance: SystemInstance,
568+
name: 'core' | 'extensions'
569+
): SystemProcessInfo | undefined {
570+
return instance.info?.resources?.processes?.[name]
571+
}
572+
573+
function RuntimeMetric(props: {
574+
label: string
575+
value?: number
576+
format?: (value: number) => string
577+
}) {
578+
const { t } = useTranslation()
579+
return (
580+
<div className='flex items-center justify-between gap-3 text-xs'>
581+
<span className='text-muted-foreground'>{props.label}</span>
582+
<span className='font-mono tabular-nums'>
583+
{typeof props.value === 'number'
584+
? (props.format?.(props.value) ?? String(props.value))
585+
: t('Not sampled')}
586+
</span>
587+
</div>
588+
)
589+
}
590+
591+
function RuntimeResourceCard(props: {
592+
label: string
593+
version?: string
594+
pid?: number
595+
cpuPercent?: number
596+
memoryBytes?: number
597+
memoryBytesLabel?: string
598+
memoryTotalBytes?: number
599+
memoryPercent?: number
600+
}) {
601+
const { t } = useTranslation()
602+
return (
603+
<div className='bg-card rounded-md border p-3'>
604+
<div className='flex items-center justify-between gap-2'>
605+
<span className='text-sm font-medium'>{props.label}</span>
606+
{props.version ? (
607+
<Badge variant='outline' className='font-mono text-[10px]'>
608+
{props.version}
609+
</Badge>
610+
) : null}
611+
</div>
612+
<div className='mt-2 space-y-1.5'>
613+
<RuntimeMetric
614+
label={t('CPU')}
615+
value={props.cpuPercent}
616+
format={formatPercent}
617+
/>
618+
<RuntimeMetric
619+
label={t('Memory')}
620+
value={props.memoryPercent}
621+
format={formatPercent}
622+
/>
623+
{typeof props.memoryBytes === 'number' ? (
624+
<RuntimeMetric
625+
label={props.memoryBytesLabel ?? t('RSS')}
626+
value={props.memoryBytes}
627+
format={formatBytes}
628+
/>
629+
) : null}
630+
{typeof props.memoryTotalBytes === 'number' ? (
631+
<RuntimeMetric
632+
label={t('Total')}
633+
value={props.memoryTotalBytes}
634+
format={formatBytes}
635+
/>
636+
) : null}
637+
<div className='flex items-center justify-between gap-3 text-xs'>
638+
<span className='text-muted-foreground'>{t('PID')}</span>
639+
<span className='font-mono tabular-nums'>
640+
{typeof props.pid === 'number' ? props.pid : t('Not reported')}
641+
</span>
642+
</div>
643+
</div>
644+
</div>
645+
)
646+
}
647+
648+
type ResourceBreakdownRow = {
649+
instance: SystemInstance | null
650+
system?: NonNullable<
651+
NonNullable<NonNullable<SystemInstance['info']>['resources']>['system']
652+
>
653+
core?: SystemProcessInfo
654+
extensions?: SystemProcessInfo
655+
}
656+
657+
/**
658+
* Shows host-wide samples separately from the two service processes. The
659+
* backend may add these fields independently; absent samples stay explicit.
660+
*/
661+
function RuntimeResourceBreakdown(props: {
662+
instances: SystemInstance[]
663+
native?: SystemResourceSnapshot
664+
}) {
665+
const { t } = useTranslation()
666+
const rows: ResourceBreakdownRow[] = props.instances.flatMap((instance) => {
667+
const resources = instance.info?.resources
668+
const system = resources?.system
669+
const core = processInfo(instance, 'core')
670+
const extensions = processInfo(instance, 'extensions')
671+
if (!system && !core && !extensions) return []
672+
return [{ instance, system, core, extensions }]
673+
})
674+
if (props.native) {
675+
rows.push({
676+
instance: null,
677+
system: {
678+
cpu: { usage_percent: props.native.system.cpu_usage_percent },
679+
memory: {
680+
usage_percent: props.native.system.memory_usage_percent,
681+
used_bytes: props.native.system.memory_used_bytes,
682+
free_bytes: props.native.system.memory_available_bytes,
683+
total_bytes: props.native.system.memory_total_bytes,
684+
},
685+
},
686+
core: props.native.processes.core,
687+
extensions: props.native.processes.extensions,
688+
})
689+
}
690+
if (rows.length === 0) return null
691+
692+
return (
693+
<section className='mt-4 space-y-3 border-t pt-4'>
694+
<div>
695+
<h4 className='text-sm font-semibold'>{t('Service resource breakdown')}</h4>
696+
<p className='text-muted-foreground mt-0.5 text-xs'>
697+
{t(
698+
'System samples describe the host. Core and Extensions values describe their own processes.'
699+
)}
700+
</p>
701+
</div>
702+
<div className='grid gap-3 lg:grid-cols-3'>
703+
{rows.map(({ instance, system, core, extensions }) => {
704+
const key = instance ? getReporterId(instance) : 'native-services'
705+
return (
706+
<div key={key} className='space-y-2'>
707+
<div className='text-muted-foreground truncate font-mono text-[11px]'>
708+
{instance ? getInstanceDisplayName(instance) : t('Native services')}
709+
</div>
710+
{system ? (
711+
<RuntimeResourceCard
712+
label={t('System')}
713+
cpuPercent={system.cpu?.usage_percent}
714+
memoryBytes={system.memory?.used_bytes}
715+
memoryBytesLabel={t('Used')}
716+
memoryTotalBytes={system.memory?.total_bytes}
717+
memoryPercent={system.memory?.usage_percent}
718+
/>
719+
) : null}
720+
{core ? (
721+
<RuntimeResourceCard
722+
label={t('Core process')}
723+
version={core.version}
724+
pid={core.pid}
725+
cpuPercent={core.cpu_percent}
726+
memoryBytes={core.memory_bytes}
727+
memoryPercent={core.memory_percent}
728+
/>
729+
) : null}
730+
{extensions ? (
731+
<RuntimeResourceCard
732+
label={t('Extensions process')}
733+
version={extensions.version}
734+
pid={extensions.pid}
735+
cpuPercent={extensions.cpu_percent}
736+
memoryBytes={extensions.memory_bytes}
737+
memoryPercent={extensions.memory_percent}
738+
/>
739+
) : null}
740+
</div>
741+
)
742+
})}
743+
</div>
744+
</section>
745+
)
746+
}
747+
559748
/**
560749
* Destructive row action. Kept as a real text button at `h-9` so the touch
561750
* target stays usable on phones, where the icon-only variant was too small.
@@ -802,6 +991,10 @@ function SystemInstancesList(props: SystemInstancesTableProps) {
802991
</TableBody>
803992
</Table>
804993
</div>
994+
<RuntimeResourceBreakdown
995+
instances={props.instances}
996+
native={props.native}
997+
/>
805998
</>
806999
)
8071000
}
@@ -827,6 +1020,13 @@ export function SystemInstancesPanel() {
8271020
retry: false,
8281021
refetchInterval: INSTANCE_POLL_INTERVAL_MS,
8291022
})
1023+
const nativeResourcesQuery = useQuery({
1024+
queryKey: ['system-info', 'native-resources'],
1025+
queryFn: getNativeSystemResources,
1026+
staleTime: 30 * 1000,
1027+
retry: false,
1028+
refetchInterval: INSTANCE_POLL_INTERVAL_MS,
1029+
})
8301030

8311031
const instances = instancesQuery.data ?? []
8321032
const staleInstances = instances.filter(
@@ -948,6 +1148,7 @@ export function SystemInstancesPanel() {
9481148
<div className='p-4 sm:p-5'>
9491149
<SystemInstancesList
9501150
instances={instances}
1151+
native={nativeResourcesQuery.data}
9511152
deletingReporterId={deletingReporterId}
9521153
isDeletingInstance={
9531154
isMutatingInstance || deleteStaleInstancesMutation.isPending

‎apps/web/src/features/system-info/types.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ For commercial licensing, please contact support@quantumnous.com
1818
*/
1919
export type SystemInstanceStatus = 'online' | 'stale'
2020

21+
/** Version and resource facts for one split native service process. */
22+
export type SystemProcessInfo = {
23+
version?: string
24+
pid?: number
25+
started_at?: number
26+
cpu_percent?: number
27+
memory_bytes?: number
28+
memory_percent?: number
29+
threads?: number
30+
[key: string]: unknown
31+
}
32+
2133
export type SystemInstanceInfo = {
2234
schema_version?: number
2335
reporter?: {
@@ -57,6 +69,31 @@ export type SystemInstanceInfo = {
5769
usage_percent?: number
5870
[key: string]: unknown
5971
}
72+
/** Host-wide sample, distinct from process-level samples below. */
73+
system?: {
74+
cpu?: {
75+
usage_percent?: number
76+
cores?: number
77+
load_1?: number
78+
load_5?: number
79+
load_15?: number
80+
[key: string]: unknown
81+
}
82+
memory?: {
83+
total_bytes?: number
84+
used_bytes?: number
85+
free_bytes?: number
86+
usage_percent?: number
87+
[key: string]: unknown
88+
}
89+
[key: string]: unknown
90+
}
91+
/** Core and Extensions process samples, keyed by service name. */
92+
processes?: {
93+
core?: SystemProcessInfo
94+
extensions?: SystemProcessInfo
95+
[key: string]: SystemProcessInfo | undefined
96+
}
6097
storage?: {
6198
total_bytes?: number
6299
used_bytes?: number
@@ -69,6 +106,30 @@ export type SystemInstanceInfo = {
69106
[key: string]: unknown
70107
}
71108

109+
export type SystemResourceSnapshot = {
110+
schema_version: number
111+
observed_at_unix_ms: number
112+
sample_window_ms: number
113+
system: {
114+
logical_cpus: number
115+
load_average: {
116+
one: number
117+
five: number
118+
fifteen: number
119+
}
120+
cpu_usage_percent: number
121+
memory_total_bytes: number
122+
memory_available_bytes: number
123+
memory_used_bytes: number
124+
memory_usage_percent: number
125+
}
126+
processes: {
127+
core?: SystemProcessInfo
128+
extensions?: SystemProcessInfo
129+
[key: string]: SystemProcessInfo | undefined
130+
}
131+
}
132+
72133
export type SystemInstance = {
73134
// node_name is the legacy reporter key; reporter_id is its explicit alias.
74135
node_name: string

0 commit comments

Comments
 (0)