Skip to main content

Overview

psys monitors network processes and connections in real-time using Linux’s ss command-line utility. Data is automatically refreshed every 5 seconds to provide live updates.

Data Collection

psys uses two ss commands to gather network information:

Listening Ports (ss -tlnp)

let ssListen: string;
try {
  ssListen = execSync("ss -tlnp 2>/dev/null", { encoding: "utf8" });
} catch {
  return { listeners: [], connections: [] };
}
The -tlnp flags stand for: tcp, listening sockets, numeric addresses (no DNS lookup), process information.

Established Connections (ss -tnp)

let ssEstab: string;
try {
  ssEstab = execSync("ss -tnp 2>/dev/null", { encoding: "utf8" });
} catch {
  return { listeners, connections };
}
The -tnp flags capture: tcp, numeric addresses, process information for established connections.

Parsing ss Output

psys uses regular expressions to parse the ss command output:

Listener Regex

const SS_LISTEN_RE = /LISTEN\s+\d+\s+\d+\s+(\S+)\s+\S+(?:\s+users:\(\("([^"]+)",pid=(\d+),fd=\d+\)\))?/;
This captures:
  • Local address and port
  • Process name
  • PID (process ID)

Established Connection Regex

const SS_ESTAB_RE = /ESTAB\s+\d+\s+\d+\s+(\S+)\s+(\S+)\s+users:\(\("([^"]+)",pid=(\d+),fd=\d+\)\)/;
This captures:
  • Local address and port
  • Remote (peer) address and port
  • Process name
  • PID

Data Structure

The collected data is structured into two main types:

Listener Type

export type Listener = {
  pid: number;
  processName: string;
  serviceLabel?: string;        // Display name (e.g., "Redis" or container name)
  containerName?: string;        // Set when Docker container
  processIconType?: string;      // Icon type: node, redis, mongo, etc.
  address: string;
  addressDescription?: string;   // Human-readable address description
  port: number;
  cmd?: string;                  // Full command line
};

Connection Type

export type Connection = {
  fromPid: number;
  fromProcessName: string;
  fromAddress: string;
  fromPort: number;
  toAddress: string;
  toPort: number;
  toLabel?: string;  // Friendly name for the target
};

Process Information Retrieval

psys reads additional process information from the /proc filesystem:

Process Name

function getProcessName(pid: number): string {
  try {
    const commPath = join("/proc", String(pid), "comm");
    if (existsSync(commPath)) {
      return readFileSync(commPath, "utf8").trim();
    }
  } catch {
    // ignore
  }
  return `pid:${pid}`;
}

Command Line

function getProcessCmd(pid: number): string | undefined {
  try {
    const path = join("/proc", String(pid), "cmdline");
    if (existsSync(path)) {
      const raw = readFileSync(path, "utf8");
      return raw.replace(/\0/g, " ").trim().slice(0, 80);
    }
  } catch {
    // ignore
  }
  return undefined;
}
Reading from /proc requires appropriate permissions. psys gracefully handles permission errors.

Process Icon Detection

psys automatically detects the type of process and assigns appropriate icons:
function getProcessIconType(
  processName: string,
  containerName: string | undefined,
  port: number
): string | undefined {
  const name = processName.toLowerCase();
  const container = (containerName ?? "").toLowerCase();
  
  if (name.includes("node") || name === "mainthread") return "node";
  if (name.includes("next")) return "next";
  if (name.includes("redis")) return "redis";
  if (name.includes("mongo")) return "mongo";
  if (name.includes("postgres") || name.includes("psql")) return "postgres";
  if (name.includes("mysql") || name.includes("mariadb")) return "mysql";
  if (name.includes("apache") || name.includes("httpd")) return "apache";
  if (name.includes("ssh") || name.includes("sshd")) return "ssh";
  
  // Check container name
  if (container.includes("redis")) return "redis";
  if (container.includes("mongo")) return "mongo";
  if (container.includes("postgres")) return "postgres";
  if (container.includes("mysql")) return "mysql";
  
  // Fall back to port-based detection
  return PORT_ICON_TYPE[port] ?? "generic";
}

Supported Icon Types

Node.js

node, mainthread processes

Next.js

next processes

Redis

redis processes or port 6379

MongoDB

mongo processes or port 27017

PostgreSQL

postgres, psql processes or port 5432

MySQL

mysql, mariadb processes or port 3306

Apache

apache, httpd processes or port 80

SSH

ssh, sshd processes or port 22

Generic

All other processes

Known Service Detection

When the process name is unknown ("?"), psys falls back to port-based service detection:
function knownListenerService(port: number): string | undefined {
  const known: Record<number, string> = {
    22: "SSH",
    53: "DNS (systemd-resolved)",
    80: "Apache / HTTP",
    443: "HTTPS",
    3000: "Node/Express",
    3001: "Node/Express",
    3002: "psys (this app)",
    631: "CUPS (printing)",
    6379: "Redis",
    27017: "MongoDB",
    5432: "PostgreSQL",
    3306: "MySQL",
    5672: "RabbitMQ",
    9200: "Elasticsearch",
  };
  return known[port];
}

Address Interpretation

psys provides human-readable descriptions for listening addresses:
function getAddressDescription(addr: string): string {
  const a = addr.trim();
  if (a === "0.0.0.0") return "Listening on all IPv4 interfaces";
  if (a === "127.0.0.1") return "Localhost only (IPv4)";
  if (a === "[::1]" || a === "::1" || a === "::") return "Localhost or all IPv6";
  if (a === "127.0.0.53") return "systemd-resolved (local DNS)";
  if (a.startsWith("127.") || a.startsWith("10.") || 
      a.startsWith("192.168.") || a.startsWith("172.")) 
    return "Local network (IPv4)";
  if (a.startsWith("[") && a.includes("]")) return "IPv6 address";
  return "Other interface";
}

Auto-Refresh Polling

The dashboard automatically refreshes data every 5 seconds:
useEffect(() => {
  fetchData();
  const t = setInterval(fetchData, 5000);
  return () => clearInterval(t);
}, [fetchData]);
You can manually trigger a refresh at any time using the Refresh button in the header.