# How witr Detects Complex Supervisor Chains Like PM2 and systemd

> Learn how witr detects complex supervisor chains like PM2 and systemd. It builds a process ancestry tree and uses a layered pipeline to identify systemd and scan ancestors for supervisors.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: deep-dive
- Published: 2026-08-10

---

**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`](https://github.com/pranshuparmar/witr/blob/main/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:

```go
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`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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:

```go
// 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`](https://github.com/pranshuparmar/witr/blob/main/docs/fixtures/port5000_short.txt) and [`node_verbose.txt`](https://github.com/pranshuparmar/witr/blob/main/node_verbose.txt).

## Practical Usage and Code Integration

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

```go
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:

```go
// 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

- **[`internal/source/detect.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/detect.go)** — Orchestrates detector execution order; defines `Detect(ancestry)` entry point
- **[`internal/source/supervisor.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/supervisor.go)** — Contains `knownSupervisors` map, `detectSupervisor` function, and `matchCmdlineTokens` tokenizer
- **[`internal/source/systemd_linux.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/systemd_linux.go)** — Linux-specific systemd detection with cgroup parsing and D-Bus enrichment
- **[`pkg/model/source.go`](https://github.com/pranshuparmar/witr/blob/main/pkg/model/source.go)** — Defines `Source` struct and `SourceType` constants (`SourceSystemd`, `SourceSupervisor`)

## 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`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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`](https://github.com/pranshuparmar/witr/blob/main/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.