Platform-Specific Process Discovery in witr: /proc, ps, and Win32 API Differences
witr uses direct /proc filesystem reads on Linux, invokes the ps command on macOS and BSD systems, and calls Win32 APIs on Windows to gather process information, with each implementation populating the same internal model.Process structure.
The witr repository by pranshuparmar implements portable process inspection across Unix-like and Windows systems. Because operating systems expose process metadata through fundamentally different interfaces, the codebase maintains three distinct platform-specific implementations. Understanding these platform-specific differences in witr's use of /proc, ps, and Win32 API reveals how the tool achieves consistent cross-platform functionality while respecting each OS's native capabilities.
Linux: Direct /proc Filesystem Access
On Linux systems, witr reads the kernel's pseudo-filesystem directly rather than spawning external utilities. This approach eliminates subprocess overhead and provides deterministic access to process state.
Implementation in process_linux.go
The ReadProcess function in internal/proc/process_linux.go opens and parses virtual files under /proc/<pid>/ to construct a model.Process instance. It reads stat for process state and metrics, environ for environment variables, cwd for the current working directory via symlink resolution, and cgroup for container detection.
// internal/proc/process_linux.go – ReadProcess
stat, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil { … } // process vanished while reading
envBytes, _ := os.ReadFile(fmt.Sprintf("/proc/%d/environ", pid))
cwd, _ := os.Readlink(fmt.Sprintf("/proc/%d/cwd", pid))
cgroup, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid))
// … parse `stat` fields, detect container, health, etc.
This implementation checks for zombie processes by examining the state field (state == "Z") and accesses file descriptor listings via /proc/<pid>/fd for complete process inspection. The method captures per-process limits and exact command-line arguments without race conditions, provided the initial /proc/<pid> directory existence check succeeds.
macOS and BSD: Parsing ps Output
On Darwin (macOS) and BSD variants, witr invokes the system ps command and parses its textual output. This fallback mechanism accommodates platforms that lack a standardized procfs implementation.
Implementation Details
The readPIDCommMap function in internal/proc/psenv_unix.go executes platform-specific ps commands. On macOS, it runs ps -axo pid=,comm=,args=, while FreeBSD uses ps -axww -o pid -o comm -o args. The code scans the output line-by-line to map PIDs to command information.
// internal/proc/psenv_unix.go – readPIDCommMap
cmd := exec.Command("ps", "-axo", "pid=,comm=")
out, _ := cmd.Output()
scanner := bufio.NewScanner(bytes.NewReader(out))
for scanner.Scan() {
// each line: "<pid> <comm>"
parts := strings.Fields(scanner.Text())
pid, _ := strconv.Atoi(parts[0])
pidCommMap[pid] = strings.Join(parts[1:], " ")
}
This approach relies on parsing text output, making it vulnerable to format changes in future OS updates, though it provides consistent PID, executable name, and argument retrieval across BSD-family systems. Container detection falls back to environment variable inspection (e.g., checking for SNAP_NAME) rather than cgroup analysis.
Windows: Win32 API System Calls
On Windows, witr bypasses both filesystem abstraction and external processes, instead calling the native Win32 API directly through system DLLs including kernel32.dll, psapi.dll, and ntdll.dll.
Low-Level Process Inspection
The process_windows.go file implements GetProcessDetailedInfo to wrap Windows-specific calls. It uses OpenProcess to obtain a handle with PROCESS_QUERY_LIMITED_INFORMATION access, QueryFullProcessImageNameW for the executable path, and GetProcessTimes for CPU accounting. The implementation can also read the Process Environment Block (PEB) via ReadProcessMemory for additional metadata.
// internal/proc/process_windows.go – GetProcessDetailedInfo
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil { return … }
defer windows.CloseHandle(handle)
var exePath [windows.MAX_PATH]uint16
size := uint32(len(exePath))
err = windows.QueryFullProcessImageName(handle, 0, &exePath[0], &size)
if err != nil { return … }
imageName := windows.UTF16ToString(exePath[:size])
// … retrieve environment, start time, CPU usage via GetProcessTimes, etc.
The Windows implementation handles Unicode string translation and interprets structures like PROCESS_BASIC_INFORMATION and IO_COUNTERS. It requires Windows 10 or later for full API availability and returns "process does not exist" errors when calls fail due to permission or timing issues.
Performance and Reliability Comparison
Each platform implementation trades different characteristics for portability:
-
Linux (/proc): Offers the lowest overhead through direct kernel interface reads without subprocess creation. The approach eliminates race conditions with disappearing processes if the initial
/proc/<pid>existence check succeeds, and captures comprehensive data including cgroups for container detection. -
macOS/BSD (ps): Incurs higher latency due to subprocess spawning and text parsing overhead. The method depends on consistent
psoutput formatting and provides limited container detection (falling back to environment variables likeSNAP_NAME), but requires no kernel modules or elevated privileges beyond standard process visibility. -
Windows (Win32 API): Delivers performance comparable to Linux's /proc approach through direct system calls. The implementation depends on specific API availability (requiring Windows 10 or later for full functionality) and handles structure marshaling for
PEBandWin32ProcessInfoto populate the generic model.
Summary
- Linux implementation reads
/proc/<pid>/stat,environ,cwd, andcgroupfiles directly ininternal/proc/process_linux.gofor minimal overhead and rich metadata extraction. - macOS and BSD systems execute
pscommands viainternal/proc/psenv_unix.goand parse textual output to obtain PID and command information. - Windows platform calls Win32 APIs including
OpenProcess,QueryFullProcessImageNameW, andGetProcessTimesininternal/proc/process_windows.goto translate native structures into themodel.Processformat. - All three implementations populate the same
model.Processstructure defined inpkg/model/process.go, ensuring consistent data models despite divergent data collection mechanisms. - Container detection varies by platform: Linux uses cgroup analysis, while other platforms rely on environment variable inspection.
Frequently Asked Questions
Why does witr use different process discovery methods across platforms?
Operating systems expose process information through incompatible interfaces. Linux provides the /proc pseudo-filesystem as a standard kernel feature, BSD systems traditionally expose limited process data requiring userland utilities like ps, and Windows maintains proprietary internal structures accessible only through documented Win32 APIs. witr adapts to each platform's native mechanism to maximize compatibility and data completeness without requiring kernel extensions.
How does witr handle process enumeration on macOS without /proc?
macOS does not mount a procfs by default, so witr falls back to executing the ps command with specific output flags (-axo pid=,comm=,args=) and parsing the resulting text. This occurs in internal/proc/psenv_unix.go, where the readPIDCommMap function builds a PID-to-command mapping from the utility's output, providing equivalent functionality to Linux's direct file reads through a subprocess interface.
What Win32 API functions does witr use for Windows process inspection?
On Windows, witr calls OpenProcess to obtain process handles, QueryFullProcessImageNameW for executable paths, GetProcessTimes for CPU accounting, and can access ReadProcessMemory for PEB (Process Environment Block) inspection. These functions from kernel32.dll and psapi.dll are wrapped in internal/proc/process_windows.go to populate the same model.Process fields that /proc provides on Linux.
Are there performance differences between the Linux and Windows implementations?
Both Linux and Windows implementations provide comparable low-latency process discovery because both use direct system interfaces without spawning subprocesses. The Linux /proc approach requires only open and read syscalls, while Windows uses equivalent lightweight Win32 calls. In contrast, the macOS/BSD ps approach introduces measurable overhead due to process creation and text parsing, making it slower for high-frequency process enumeration.
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 →