Add process view
CI / test (push) Successful in 1m1s
Release rolling / release (push) Successful in 7m12s

This commit is contained in:
Raven Scott
2026-07-19 15:41:35 -04:00
parent 2923678354
commit f604d210c3
20 changed files with 2215 additions and 6 deletions
+4
View File
@@ -4,6 +4,10 @@ import { isProcessCollectorEnabled, listProcStats } from '../server/services/col
test('isProcessCollectorEnabled', (t) => {
const prev = process.env.PEARDATA_PROCESSES
delete process.env.PEARDATA_PROCESSES
// Default on for Linux hosts
if (process.platform === 'linux') t.ok(isProcessCollectorEnabled())
else t.absent(isProcessCollectorEnabled())
process.env.PEARDATA_PROCESSES = '0'
t.absent(isProcessCollectorEnabled())
process.env.PEARDATA_PROCESSES = '1'
t.ok(isProcessCollectorEnabled())
+105
View File
@@ -0,0 +1,105 @@
import test from 'brittle'
import {
parseProcStat,
parseProcStatus,
processMatchesQuery,
processMatchesFilter,
sortProcesses,
listProcesses,
_resetProcessMonitorForTests,
} from '../server/services/processes.js'
import { validateMethodArgs } from '../shared/schema.js'
import { isProcessCollectorEnabled } from '../server/services/collectors/processes.js'
test('parseProcStat extracts comm and counters', (t) => {
const raw =
'1 (systemd) S 0 1 1 0 -1 4194560 123 0 0 0 10 20 0 0 20 0 1 0 12345 1000000 100 18446744073709551615 1 1 0 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0'
const st = parseProcStat(raw)
t.ok(st)
t.is(st.name, 'systemd')
t.is(st.state, 'S')
t.is(st.ppid, 0)
t.is(st.utime, 10)
t.is(st.stime, 20)
})
test('parseProcStatus reads uid and threads', (t) => {
const s = parseProcStatus('Name:\tfoo\nUid:\t1000\t1000\t1000\t1000\nThreads:\t4\nVmRSS:\t2048 kB\n')
t.is(s.uid, 1000)
t.is(s.threads, 4)
t.is(s.vmRssKb, 2048)
})
test('process filter + query helpers', (t) => {
const row = {
pid: 42,
name: 'nginx',
cmdline: '/usr/sbin/nginx -g daemon',
user: 'www',
stateLabel: 'running',
cpu: 12,
mem: 200,
io: 3,
kernel: false,
}
t.ok(processMatchesQuery(row, 'nginx'))
t.ok(processMatchesQuery(row, '42'))
t.absent(processMatchesQuery(row, 'postgres'))
t.ok(processMatchesFilter(row, 'running'))
t.ok(processMatchesFilter(row, 'highcpu'))
t.ok(processMatchesFilter(row, 'highmem'))
t.ok(processMatchesFilter(row, 'user'))
t.absent(processMatchesFilter(row, 'zombie'))
})
test('sortProcesses orders by cpu desc', (t) => {
const rows = [
{ pid: 1, name: 'a', cpu: 1, mem: 9 },
{ pid: 2, name: 'b', cpu: 9, mem: 1 },
]
const sorted = sortProcesses(rows, 'cpu', 'desc')
t.is(sorted[0].pid, 2)
t.is(sortProcesses(rows, 'rss', 'desc')[0].pid, 1)
})
test('validateMethodArgs listProcesses', (t) => {
const ok = validateMethodArgs('listProcesses', { sort: 'cpu', limit: 50 })
t.ok(ok.ok)
t.is(ok.args.sort, 'cpu')
const bad = validateMethodArgs('listProcesses', { sort: 'nope' })
t.absent(bad.ok)
})
test('listProcesses returns shape', (t) => {
_resetProcessMonitorForTests()
const res = listProcesses({ limit: 20, sort: 'cpu' })
t.ok(res.ok)
t.ok(res.summary)
t.ok(Array.isArray(res.processes))
if (process.platform === 'linux') {
t.ok(res.supported)
t.ok(res.summary.total > 0)
// Second call should populate CPU rates after prev sample
_resetProcessMonitorForTests()
listProcesses({ limit: 5 })
const again = listProcesses({ limit: 5 })
t.ok(again.processes.length > 0)
t.ok(again.processes[0].pid > 0)
t.ok(again.processes[0].name)
} else {
t.absent(res.supported)
}
})
test('isProcessCollectorEnabled defaults on linux', (t) => {
const prev = process.env.PEARDATA_PROCESSES
delete process.env.PEARDATA_PROCESSES
if (process.platform === 'linux') t.ok(isProcessCollectorEnabled())
else t.absent(isProcessCollectorEnabled())
process.env.PEARDATA_PROCESSES = '0'
t.absent(isProcessCollectorEnabled())
process.env.PEARDATA_PROCESSES = '1'
t.ok(isProcessCollectorEnabled())
if (prev == null) delete process.env.PEARDATA_PROCESSES
else process.env.PEARDATA_PROCESSES = prev
})
+1
View File
@@ -41,6 +41,7 @@ test('method roles cover monitoring surface', (t) => {
'listChildPeers',
'getWeights',
'queryLogs',
'listProcesses',
'getStorageInfo',
'getRetentionConfig',
'setRetentionConfig',