How witr Detects Processes Managed by PM2 and Supervisor
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.
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. This static catalogue maps executable basenames to canonical supervisor labels, enabling the system to recognize a wide variety of process managers.
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. 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:
// 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, which coordinates multiple detection strategies. When a supervisor is identified, the resulting model.Source struct—defined in 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:
witrmaintains a definitive map of supervisor executables ininternal/source/supervisor.goto recognize PM2, supervisord, and other managers. - Ancestry walking: The
detectSupervisorfunction 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 viamatchCmdlineTokens. - Structured output: Successful detection returns a
model.SourcewithType: 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. 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. 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, ultimately rendering in CLI output or JSON exports to indicate process management lineage.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →