Skip to content

System Monitoring

@termuijs/data exposes six system data providers and a file-tailing function. Each provider is a plain object with getter properties or methods. Accessing a property samples the value at that moment, so call it inside a render loop or a polling hook to get live readings.

Install the package:

·CODE
npm install @termuijs/data

cpu

The cpu object reads from Node's built-in os module and computes a delta between two samples. Readings within 200ms of each other share the same sample, so multiple properties in one render cycle stay consistent.

Properties

PropertyTypeDescription
cpu.percentnumberOverall CPU usage, 0–100
cpu.perCorenumber[]Per-core usage array, 0–100 each
cpu.loadAvgnumber[]Load averages [1min, 5min, 15min]
cpu.modelstringCPU model name
cpu.countnumberNumber of logical cores
cpu.speednumberClock speed in MHz

Example

·CODE
import { usePolling } from '@termuijs/data'
import { cpu } from '@termuijs/data'

function CpuPanel() {
    const { data } = usePolling(() => Promise.resolve({
        percent: cpu.percent,
        perCore: cpu.perCore,
        load:    cpu.loadAvg,
    }), 1000)

    return (
        <col>
            <Text>CPU: {data?.percent ?? 0}%</Text>
            <Text dim>Load: {data?.load.join(' / ')}</Text>
            <Text dim>Cores: {data?.perCore.map(p => `${p}%`).join('  ')}</Text>
        </col>
    )
}

memory

The memory object reads from os.totalmem() and os.freemem(). Human-readable properties format bytes automatically. Use memory.raw when you need byte counts for arithmetic.

Properties

PropertyTypeDescription
memory.percentnumberUsed memory as a percentage, 0–100
memory.usedstringUsed memory, formatted (e.g. "3.2 GB")
memory.freestringFree memory, formatted
memory.totalstringTotal memory, formatted
memory.raw{ used: number; free: number; total: number }Raw byte counts

Example

·CODE
import { memory } from '@termuijs/data'

function MemoryBar() {
    const { data } = usePolling(() => Promise.resolve(memory.raw), 2000)

    const pct = data ? Math.round((data.used / data.total) * 100) : 0

    return (
        <col>
            <Text>Memory: {pct}%</Text>
            <Text dim>{memory.used} / {memory.total}</Text>
        </col>
    )
}

disk

The disk object runs df -h and caches the result for 5 seconds. It parses output on macOS and Linux and skips devfs entries.

Properties

PropertyTypeDescription
disk.percentnumberRoot partition (/) usage, 0–100
disk.mainDiskPartition | nullFull info for the root partition
disk.partitionsDiskPartition[]All mounted partitions

DiskPartition

·CODE
interface DiskPartition {
    filesystem: string;
    size:       string;
    used:       string;
    available:  string;
    percent:    number;
    mountpoint: string;
}

Example

·CODE
import { disk } from '@termuijs/data'

function DiskPanel() {
    const { data } = usePolling(() => Promise.resolve(disk.partitions), 10_000)

    return (
        <col>
            {(data ?? []).map(p => (
                <row key={p.mountpoint}>
                    <Text width={20}>{p.mountpoint}</Text>
                    <Text>{p.used} / {p.size} ({p.percent}%)</Text>
                </row>
            ))}
        </col>
    )
}

network

The network object reads from os.networkInterfaces(). It returns only external IPv4 interfaces.

Properties

PropertyTypeDescription
network.interfacesNetworkInterface[]All active external IPv4 interfaces
network.ipstringPrimary IP address, falls back to "127.0.0.1"
network.hostnamestringSystem hostname

NetworkInterface

·CODE
interface NetworkInterface {
    name:     string;
    address:  string;
    family:   string;
    mac:      string;
    internal: boolean;
}

Example

·CODE
import { network } from '@termuijs/data'

function NetworkPanel() {
    return (
        <col>
            <Text>Host: {network.hostname}</Text>
            <Text>IP:   {network.ip}</Text>
            {network.interfaces.map(i => (
                <Text key={i.name} dim>{i.name}: {i.address} ({i.mac})</Text>
            ))}
        </col>
    )
}

processes

The processes object runs ps aux and caches the result for 2 seconds. It returns up to 50 processes sorted by CPU usage.

API

MemberSignatureDescription
processes.top(n?: number) => ProcessInfo[]Top N processes by CPU. Default: 10
processes.listProcessInfo[]Full list, up to 50 entries
processes.countnumberTotal number of processes in the list

ProcessInfo

·CODE
interface ProcessInfo {
    pid:  number;
    name: string;
    cpu:  number;   // percentage
    mem:  number;   // percentage
    user: string;
}

Example

·CODE
import { processes } from '@termuijs/data'

function ProcessTable() {
    const { data } = usePolling(() => Promise.resolve(processes.top(8)), 2000)

    return (
        <col>
            <row>
                <Text width={8} bold>PID</Text>
                <Text width={20} bold>NAME</Text>
                <Text width={8} bold>CPU</Text>
                <Text width={8} bold>MEM</Text>
            </row>
            {(data ?? []).map(p => (
                <row key={p.pid}>
                    <Text width={8}>{p.pid}</Text>
                    <Text width={20}>{p.name}</Text>
                    <Text width={8}>{p.cpu}%</Text>
                    <Text width={8}>{p.mem}%</Text>
                </row>
            ))}
        </col>
    )
}

tail

tail watches a file and streams new lines as they are appended. It reads the last N lines on startup and then watches the file with fs.watchFile at a 500ms interval. When the file is truncated, it re-reads from the start.

Signature

·CODE
function tail(filePath: string, opts?: TailOptions): TailStream

TailOptions

·CODE
interface TailOptions {
    initialLines?: number;   // Lines to read on startup. Default: 20
    maxLines?:     number;   // Max lines kept in buffer. Default: 1000
}

TailStream

·CODE
interface TailStream {
    lines:  string[];   // Current buffer
    active: boolean;    // Whether the watcher is running
    stop(): void;       // Stop watching and release the file handle
}

Call stop() when you no longer need the stream. The watcher is not cleaned up automatically.

Example

·CODE
import { tail } from '@termuijs/data'

function LogViewer({ path }: { path: string }) {
    const [lines, setLines] = useState<string[]>([])

    useEffect(() => {
        const stream = tail(path, { initialLines: 30, maxLines: 500 })

        const timer = setInterval(() => {
            setLines([...stream.lines])
        }, 500)

        return () => {
            clearInterval(timer)
            stream.stop()
        }
    }, [path])

    return (
        <col>
            {lines.slice(-20).map((line, i) => (
                <Text key={i} dim={i < lines.length - 5}>{line}</Text>
            ))}
        </col>
    )
}

services

The services object checks process supervisor status for a list of service names. It tries systemd first (Linux only), falls back to PM2, then falls back to process name matching via ps aux.

API

MemberSignatureDescription
services.list(names: string[]) => ServiceInfo[]Status for each named service

ServiceInfo

·CODE
interface ServiceInfo {
    name:          string;
    active:        boolean;
    status:        string;       // substate: "running", "stopped", "online", etc.
    uptime:        string;       // Human-readable uptime, e.g. "3d 2h"
    uptimeSeconds: number;
    restarts:      number;
    cpu:           number;       // percentage
    mem:           number;       // percentage (MB for PM2)
    pid:           number;
    description:   string;
}

Example

·CODE
import { services } from '@termuijs/data'

const WATCHED = ['nginx', 'postgres', 'redis']

function ServicesPanel() {
    const { data } = usePolling(
        () => Promise.resolve(services.list(WATCHED)),
        5000
    )

    return (
        <col>
            {(data ?? []).map(s => (
                <row key={s.name}>
                    <Text width={16}>{s.name}</Text>
                    <Text color={s.active ? 'green' : 'red'}>
                        {s.active ? 'up' : 'down'}
                    </Text>
                    <Text dim width={12}>{s.uptime}</Text>
                    <Text dim>CPU {s.cpu}%  MEM {s.mem}%</Text>
                </row>
            ))}
        </col>
    )
}

Live system dashboard

Combine all providers to build a full system dashboard. The example below polls each provider at a sensible interval and lays out the results in a grid.

·CODE
import { cpu, memory, disk, network, processes, services } from '@termuijs/data'
import { usePolling } from '@termuijs/data'

function SystemDashboard() {
    const { data: sys } = usePolling(() => Promise.resolve({
        cpuPct:  cpu.percent,
        loadAvg: cpu.loadAvg,
        memPct:  memory.percent,
        memUsed: memory.used,
        memTotal: memory.total,
        diskPct: disk.percent,
        diskMain: disk.main,
        ip:      network.ip,
        host:    network.hostname,
    }), 1000)

    const { data: procs } = usePolling(
        () => Promise.resolve(processes.top(5)),
        2000
    )

    const { data: svcs } = usePolling(
        () => Promise.resolve(services.list(['nginx', 'postgres'])),
        5000
    )

    return (
        <col padding={1}>
            <Text bold>System — {sys?.host}</Text>

            <row gap={4}>
                <col>
                    <Text bold>CPU</Text>
                    <Text>{sys?.cpuPct ?? 0}%</Text>
                    <Text dim>Load {sys?.loadAvg.join(' / ')}</Text>
                </col>
                <col>
                    <Text bold>Memory</Text>
                    <Text>{sys?.memPct ?? 0}%</Text>
                    <Text dim>{sys?.memUsed} / {sys?.memTotal}</Text>
                </col>
                <col>
                    <Text bold>Disk /</Text>
                    <Text>{sys?.diskPct ?? 0}%</Text>
                    <Text dim>{sys?.diskMain?.used} / {sys?.diskMain?.size}</Text>
                </col>
                <col>
                    <Text bold>Network</Text>
                    <Text>{sys?.ip}</Text>
                </col>
            </row>

            <Text bold marginTop={1}>Top Processes</Text>
            {(procs ?? []).map(p => (
                <row key={p.pid}>
                    <Text width={24}>{p.name}</Text>
                    <Text>CPU {p.cpu}%  MEM {p.mem}%</Text>
                </row>
            ))}

            <Text bold marginTop={1}>Services</Text>
            {(svcs ?? []).map(s => (
                <row key={s.name}>
                    <Text width={16}>{s.name}</Text>
                    <Text color={s.active ? 'green' : 'red'}>
                        {s.status}
                    </Text>
                    <Text dim>{s.uptime}</Text>
                </row>
            ))}
        </col>
    )
}

See also

  • Hooks, reactive hooks for fetching and streaming
  • Docker, container list and live stats
  • Database, connection health and pool metrics