# How witr Detects Processes Managed by PM2 and Supervisor

> Learn how witr detects processes managed by PM2 and supervisor by traversing the process ancestry chain and matching executable names. Understand the detection mechanism.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: how-to-guide
- Published: 2026-08-09

---

**witr identifies PM2, supervisord, and other process managers by traversing the process ancestry chain and matching executable names against a static catalogue defined in [`internal/source/supervisor.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/supervisor.go).**

When analyzing process trees in production environments, accurately identifying which supervisor launched a given process is critical for root cause analysis. The open-source tool `witr` (from the repository `pranshuparmar/witr`) implements a robust detection mechanism to determine if a process was spawned by PM2, supervisord, or similar init systems. This article examines exactly how `witr` detects processes managed by PM2 and supervisor through ancestry walking and executable fingerprinting.

## The Supervisor Catalogue in internal/source/supervisor.go

The foundation of detection rests in the `knownSupervisors` map located at lines 10-14 of [`internal/source/supervisor.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/supervisor.go). This static catalogue maps executable basenames to canonical supervisor labels, enabling the system to recognize a wide variety of process managers.

```go
var knownSupervisors = map[string]string{
    "pm2":          "pm2",
    "supervisord":  "supervisord",
    "supervisor":   "supervisord",
    // … other supervisors such as gunicorn, runit, systemd, … 
}

```

The entry `"pm2": "pm2"` explicitly instructs the detector to treat any process whose executable basename is **pm2** as a PM2 supervisor instance. Similarly, `supervisord` entries map both the binary name and common aliases to the canonical label.

## Ancestry Traversal via detectSupervisor

The core logic resides in the `detectSupervisor` function spanning lines 41-78 of [`internal/source/supervisor.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/supervisor.go). This function accepts a slice of `model.Process` representing the process ancestry (ordered from immediate parent to system init) and iterates through each ancestor to identify supervisory relationships.

For each process in the ancestry chain, the function performs the following steps:

- Extracts the command basename using `filepath.Base(p.Command)`
- Checks whether that basename exists as a key in `knownSupervisors` (line 62)
- Falls back to token-wise matching of the full command line via `matchCmdlineTokens` (lines 82-99) if the basename check fails

When a match is found, the function returns a `model.Source` object with `Type: SourceSupervisor` and `Name` set to the mapped label (e.g., `"pm2"` or `"supervisord"`).

### Handling Edge Cases and init Systems

To prevent false positives, the detection logic includes a specific guard at lines 63-65. If the matched label is `"init"` and a shell process is present in the ancestry, the function skips the detection. This avoids incorrectly attributing processes to the system init when they were actually launched interactively through a shell.

## Code Example: Detecting PM2-Managed Processes

The following example demonstrates how `witr` identifies a Node.js process managed by PM2 by analyzing its ancestry chain:

```go
// Example: Detecting a PM2‑managed node process
ancestry := []model.Process{
    {Command: "/sbin/init", Cmdline: "init"},
    {Command: "/home/deploy/.pm2/pm2", Cmdline: "PM2 v5.3.1: God"},
    {Command: "/usr/local/bin/node", Cmdline: "node server.js"},
}
src := detectSupervisor(ancestry)
// src.Type == model.SourceSupervisor && src.Name == "pm2"

```

In this scenario, the function iterates through the ancestry, identifies the `pm2` executable at index 1, and returns a source indicating the process tree is managed by PM2.

## Integration with the Source Detection Pipeline

The `detectSupervisor` function is orchestrated by [`internal/source/detect.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/detect.go), which coordinates multiple detection strategies. When a supervisor is identified, the resulting `model.Source` struct—defined in [`pkg/model/source.go`](https://github.com/pranshuparmar/witr/blob/main/pkg/model/source.go)—propagates through the system to annotate process tree output. This allows `witr` to display readable causal chains such as **"PM2 v5.3.1: God"** in JSON or terminal output, providing immediate context about process provenance.

## Summary

- **Static catalogue**: `witr` maintains a definitive map of supervisor executables in [`internal/source/supervisor.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/supervisor.go) to recognize PM2, supervisord, and other managers.
- **Ancestry walking**: The `detectSupervisor` function traverses the process parent chain from child to root system init.
- **Two-stage matching**: Detection first attempts basename matching against `knownSupervisors`, then falls back to token-wise command line analysis via `matchCmdlineTokens`.
- **Structured output**: Successful detection returns a `model.Source` with `Type: SourceSupervisor`, enabling downstream formatting of supervisor metadata.
- **False positive prevention**: The algorithm explicitly skips matches against `"init"` when a shell is present in the ancestry to avoid misattribution.

## Frequently Asked Questions

### How does witr distinguish between PM2 and other process managers?

`witr` distinguishes supervisors by comparing process executable basenames against the `knownSupervisors` map in [`internal/source/supervisor.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/supervisor.go). Each supervisor has a unique entry mapping its binary name to a canonical label, allowing the `detectSupervisor` function to differentiate PM2 (`"pm2"`) from supervisord (`"supervisord"`) or systemd even when they appear in similar ancestry positions.

### What happens if a process is launched by a shell script rather than directly by PM2?

If a shell script intermediary exists between PM2 and the target process, the ancestry chain will include the shell process. The `detectSupervisor` function continues walking upward through the ancestry until it either finds a matching supervisor executable or exhausts the list. As long as PM2 appears somewhere in the parent chain, the detection will succeed and attribute the process to PM2.

### Can witr detect custom or renamed supervisor binaries?

By default, `witr` only detects supervisors explicitly listed in the `knownSupervisors` map. If a supervisor binary has been renamed or is a custom implementation not included in the catalogue, the basename check will fail. However, the fallback `matchCmdlineTokens` function (lines 82-99) performs token-wise matching of the command line, which may still catch renamed binaries if they contain recognizable substrings, though exact matches against the map are preferred for reliability.

### Where does witr store the detection results for downstream use?

Detection results are stored in the `model.Source` struct defined in [`pkg/model/source.go`](https://github.com/pranshuparmar/witr/blob/main/pkg/model/source.go). When `detectSupervisor` identifies a managed process, it populates this struct with `Type: SourceSupervisor` and the supervisor name (e.g., `"pm2"`). This structure is then passed through the detection pipeline orchestrated by [`internal/source/detect.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/detect.go), ultimately rendering in CLI output or JSON exports to indicate process management lineage.