How witr Detects Complex Supervisor Chains Like PM2 and systemd

witr detects complex supervisor chains by building a process ancestry tree and running a layered source-detection pipeline that identifies systemd via cgroups and D-Bus, then scans ancestors for known supervisor executables like PM2 using basename and token matching.

The witr open-source tool analyzes where processes originate, including nested deployment scenarios where multiple process managers stack on top of each other. Understanding how witr detects complex supervisor chains reveals how it correctly identifies a Node.js application running under PM2, which itself runs as a systemd service.

The Core Detection Pipeline

witr's detection starts in internal/source/detect.go, where it sequentially applies specialized detectors to a built process ancestry list. The ancestry is constructed from /proc data, tracing parent processes up to PID 1. The detection order matters: more specific sources are checked before generic fallbacks.

The fixed sequence is:

if src := detectContainer(ancestry); src != nil { … }
if src := detectSSH(ancestry);     src != nil { … }
if src := detectShell(ancestry);   src != nil { … }
if src := detectSystemd(ancestry); src != nil { … }   // systemd before supervisor
…
if src := detectSupervisor(ancestry); src != nil { … }

This ordering ensures systemd is identified before generic supervisor detection, enabling proper chain reporting.

Detecting systemd as the Foundation

The detectSystemd function resides in internal/source/systemd_linux.go and is guarded by //go:build linux. It performs three critical checks:

  1. Verify systemd is the init system via IsSystemdRunning, which confirms /run/systemd/system exists
  2. Confirm PID 1 appears in the ancestry, establishing systemd's role in the process tree
  3. Extract the unit name from the process's cgroup using getUnitNameFromCgroup

Once identified, witr enriches the source via the systemd D-Bus API (enrichFromSystemd), producing a model.Source with type SourceSystemd and the service unit name (e.g., pm2-deploy.service).

Detecting Intermediate Supervisors Like PM2

After systemd detection, detectSupervisor in internal/source/supervisor.go scans the same ancestry for known process managers. The function uses a two-phase matching strategy:

Phase 1: Basename matching against the knownSupervisors map:

// Excerpt from knownSupervisors map in internal/source/supervisor.go
var knownSupervisors = map[string]string{
    "pm2":         "pm2",
    "systemd":     "systemd service",
    "supervisord": "supervisord",
    "gunicorn":    "gunicorn",
    // … additional entries
}

The function checks filepath.Base(p.Command) for each ancestor. If the basename matches a key, it returns SourceSupervisor with the corresponding label.

Phase 2: Token matching via matchCmdlineTokens if basename matching fails. This extracts non-flag, non-environment-assignment tokens from the full command line and looks them up in knownSupervisors.

Before either phase, detectSupervisor checks for shell presence in the ancestry to avoid misidentifying interactive shells as supervisors.

How the PM2 → systemd Chain Is Resolved

Consider a typical deployment: systemd launches PM2 v5.3.1: God, which spawns a node application. witr resolves this as follows:

Layer Detection Method Result
systemd detectSystemd → cgroup unit name pm2-deploy.service
PM2 detectSupervisor → basename match on "PM2 v5.3.1: God" pm2
node Leaf process, no further supervisor (application)

The CLI displays this as:


systemd (pid 1) → PM2 v5.3.1: God (pid 5034) → node (pid 14233)

This exact chain appears in test fixtures at docs/fixtures/port5000_short.txt and node_verbose.txt.

Practical Usage and Code Integration

To programmatically detect supervisor chains in your own Go applications using witr's logic:

package main

import (
    "fmt"
    "github.com/pranshuparmar/witr/internal/source"
    "github.com/pranshuparmar/witr/pkg/model"
)

func main() {
    // ancestry is []model.Process built from /proc by witr internals
    var ancestry []model.Process
    
    src := source.Detect(ancestry)
    fmt.Printf("Type: %s, Name: %s\n", src.Type, src.Name)
    
    switch src.Type {
    case model.SourceSystemd:
        // src.Name contains unit like "pm2-deploy.service"
        fmt.Println("Managed by systemd unit:", src.Name)
    case model.SourceSupervisor:
        // src.Name contains label like "pm2", "supervisord"
        fmt.Println("Managed by supervisor:", src.Name)
    }
}

For the specific systemd → PM2 → node scenario:

// Ancestry: PID 1 (systemd) → PID 5034 (PM2 God) → PID 14233 (node)
src := source.Detect(ancestry)

// First call returns SourceSystemd for the immediate environment
// Additional ancestors can be inspected for layered detection

Key Implementation Files

Summary

  • witr builds process ancestry from /proc to trace parent relationships to PID 1
  • Detection order places systemd before generic supervisor matching, enabling proper chain resolution
  • systemd detection uses cgroup unit names and D-Bus API enrichment via internal/source/systemd_linux.go
  • Supervisor detection matches known executables by basename and command-line tokens using the knownSupervisors map in internal/source/supervisor.go
  • Complex chains like systemd → PM2 → node are displayed with each layer identified by its appropriate detector

Frequently Asked Questions

How does witr distinguish PM2 from a regular shell process?

detectSupervisor first checks for shell presence in the ancestry to exclude interactive sessions, then matches against the knownSupervisors map using executable basenames and command-line tokens. The PM2 "God" process has a distinctive command line (PM2 v5.3.1: God) that matches the "pm2" key.

Can witr detect multiple levels of supervision simultaneously?

witr's sequential detector design identifies the most specific source for each level. While source.Detect returns a single primary source, the full ancestry inspection allows layered reporting. The CLI renders the complete chain: systemd → PM2 → node.

Why does systemd detection run before supervisor detection?

The fixed order in internal/source/detect.go places detectSystemd before detectSupervisor because systemd often acts as the parent of other supervisors. Reversing the order would incorrectly report systemd-launched PM2 instances as standalone PM2 without the systemd context.

Does witr support non-Linux systems for supervisor detection?

The detectSystemd function is Linux-only (//go:build linux), but detectSupervisor works cross-platform. On macOS or other Unix systems, witr can still identify PM2, supervisord, and other supervisors via basename and token matching, though systemd-specific features are unavailable.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →