Question

Get Linux Process Details from a PID in Go

Question

I maintain a list of process IDs (PIDs) for processes currently running on a Linux system. How can I retrieve details about a process from its PID in Go?

I found syscall.Getrusage(), but it does not return the information I need for an arbitrary process. What should I use instead?

Short Answer

By the end of this page, you will understand why syscall.Getrusage is not a general process-inspection API and how to read process information from Linux's /proc filesystem in Go. You will also learn how to handle missing processes, permissions, command-line parsing, and process-lifetime races.

Concept

On Linux, the usual way to inspect a running process is through procfs, a virtual filesystem mounted at /proc.

Each running process has a directory named after its PID:

/proc/<pid>/

For example, a process with PID 1234 may expose files such as:

  • /proc/1234/status — human-readable status data, including name, state, UID, and memory fields.
  • /proc/1234/cmdline — command-line arguments, separated by NUL (\x00) bytes.
  • /proc/1234/exe — a symbolic link to the executable being run.
  • /proc/1234/stat — compact, machine-readable process statistics.
  • /proc/1234/fd — file descriptors currently opened by the process, subject to permissions.

syscall.Getrusage has a different purpose. It reports resource usage for the calling process (RUSAGE_SELF) or its waited-for child processes (RUSAGE_CHILDREN). It does not accept a PID and cannot inspect an arbitrary process.

This distinction matters because process monitoring, diagnostics tools, service managers, and observability agents need information about other processes. On Linux, procfs is the operating system interface designed for that task.

Mental Model

Think of /proc as a live, read-only directory of process report cards.

  • A PID is the process's student number.
  • /proc/<pid> is that student's folder.
  • Files such as status and cmdline are individual report pages.
  • When the process exits, its folder disappears.

Because the process can exit at any moment, you must treat a missing /proc/<pid> directory as a normal possibility rather than an exceptional programming failure.

Take Quiz

Syntax and Examples

Use os.ReadFile to read procfs files and os.Readlink to resolve symbolic links such as /proc/<pid>/exe.

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	pid := 1234
	procDir := filepath.Join("/proc", fmt.Sprint(pid))

	status, err := os.ReadFile(filepath.Join(procDir, "status"))
	if err != nil {
		fmt.Println("could not read process status:", err)
		return
	}

	for _, line := range strings.Split(string(status), "\n") {
		if strings.HasPrefix(line, "Name:") || strings.HasPrefix(line, "State:") {
			fmt.Println(line)
		}
	}
}

The status file contains tab-separated lines similar to:

Name:	python3
State:	S (sleeping)
Pid:	1234
PPid:	1
Uid:	1000	1000	1000	1000

To read the command line, remember that /proc/<pid>/cmdline uses NUL bytes rather than spaces to separate arguments:

raw, err := os.ReadFile("/proc/1234/cmdline")
if err != nil {
	// Handle the error.
}

args := strings.FieldsFunc(string(raw), func(r rune) bool {
	return r == '\x00'
})
fmt.Println(strings.Join(args, " "))

Do not simply print string(raw) if you want a readable command line: the embedded NUL separators will not display as normal spaces.

Step by Step Execution

Consider this small function:

func readCommandLine(pid int) (string, error) {
	path := fmt.Sprintf("/proc/%d/cmdline", pid)
	raw, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}

	args := strings.FieldsFunc(string(raw), func(r rune) bool {
		return r == '\x00'
	})
	return strings.Join(args, " "), nil
}

Step by step:

  1. fmt.Sprintf builds a procfs path such as /proc/1234/cmdline.
  2. os.ReadFile asks the kernel for the process's command-line data.
  3. If the PID no longer exists, or access is denied, os.ReadFile returns an error.
  4. The raw file may look like python3\x00server.py\x00--port\x008080\x00.
  5. strings.FieldsFunc splits that data wherever it finds \x00.
  6. strings.Join produces a display-friendly command line: python3 server.py --port 8080.

The data is a snapshot. The process may change state or exit immediately after it is read.

Real World Use Cases

Reading process details by PID is useful in many Linux programs:

  • Monitoring agents collect process names, states, memory information, and command lines.
  • Service supervisors verify that a saved PID still refers to the expected executable.
  • Diagnostic CLIs show why a worker is consuming resources or whether it is sleeping, running, or stopped.
  • Deployment scripts check whether a server process started successfully.
  • Security and audit tools inspect executable paths and process owners, subject to system permissions.
  • Job runners record the PID of a launched task and later inspect whether it is still running.

For system-wide tools, be careful with PID reuse: after a process exits, Linux may eventually assign the same number to a different process. A PID alone is not a permanent process identity.

Take Quiz

Real Codebase Usage

In production Go code, process inspection is usually wrapped in a small package instead of scattering /proc paths throughout the application.

Common patterns include:

  • Validate input first. Reject pid <= 0; PID 0 and negative values have special meanings in some system APIs.
  • Use guard clauses. Return immediately when a procfs file cannot be read.
  • Treat disappearance as expected. A process may exit between listing PIDs and reading its files.
  • Read only required fields. Reading status and cmdline is often enough; avoid collecting every procfs file without a need.
  • Handle partial access. You may read status but be unable to inspect fd or another sensitive path for a process owned by a different user.
  • Keep Linux-specific code isolated. /proc layout is Linux-specific. Put it in a file such as process_linux.go, potentially with a //go:build linux constraint when your project supports multiple operating systems.
  • Use a library when breadth matters. A library such as github.com/shirou/gopsutil/v3/process can provide a higher-level API, but direct procfs access is lightweight when you need only a few Linux fields.

A useful verification pattern is to store both the PID and a process start-time value from /proc/<pid>/stat. Later, compare both values to reduce the risk of confusing a reused PID with the original process.

Common Mistakes

Expecting Getrusage to accept a PID

This is not how Getrusage works:

// There is no arbitrary PID argument here.
err := syscall.Getrusage(1234, &usage)

The first argument is a selector such as syscall.RUSAGE_SELF, not a process ID. Use /proc/<pid> for another process's details.

Assuming a PID still exists

This can fail even if you just saw the PID in a previous list:

raw, err := os.ReadFile("/proc/1234/status")

The process could have exited in the meantime. Check err and handle os.IsNotExist(err) as a normal outcome.

Parsing cmdline with spaces

This is incorrect:

args := strings.Split(string(raw), " ")

Arguments are NUL-separated, and an individual argument can itself contain spaces. Split on \x00 instead.

Building paths from untrusted strings

Avoid concatenating arbitrary input directly into a path:

path := "/proc/" + userInput + "/status"

Parse the input as an integer, require a positive value, then construct the path from that integer.

Treating an empty command line as an error

Kernel threads may have an empty cmdline file. An empty command line can be valid; use the process name from status as a fallback display name.

Assuming every procfs field is portable

/proc and the exact contents of its files are Linux interfaces. Code that reads them will not work unchanged on Windows or most non-Linux systems.

Comparisons

NeedBest optionWhy
Resource usage of the current Go programsyscall.Getrusage(syscall.RUSAGE_SELF, ...)Reports usage for the calling process.
Resource usage of waited-for child processessyscall.Getrusage(syscall.RUSAGE_CHILDREN, ...)Aggregates completed child-process usage.
Details for an arbitrary running Linux PID/proc/<pid>/...Exposes kernel-maintained information for that process.
Start, signal, or wait for a process created by your Go programos/exec and os.ProcessGo manages the process you launched.
Cross-platform process inspectionA library such as gopsutilProvides a common API, with platform-specific implementation underneath.

os.Process is not a full inspection API. It is mainly useful for interacting with a process, such as sending it a signal. On Unix, constructing an os.Process from a PID does not prove that the PID is still the same process you intended to inspect.

Take Quiz

Cheat Sheet

// Read a text-like procfs file.
raw, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid))

// Read a process command line (NUL-separated arguments).
raw, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
args := strings.FieldsFunc(string(raw), func(r rune) bool {
	return r == '\x00'
})
command := strings.Join(args, " ")

// Find the executable path.
exe, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))

Key rules:

  • Require pid > 0 before building a procfs path.
  • Use /proc/<pid>/status for fields such as Name, State, PPid, and Uid.
  • Split cmdline on NUL (\x00), not on spaces.
  • Check every I/O error: processes can exit while being inspected.
  • Expect permission errors for processes owned by other users or protected by system security settings.
  • An empty cmdline may be valid, particularly for kernel threads.
  • A PID can eventually be reused; do not rely on a PID as a permanent identity.
  • Getrusage is for the current process or child-process accounting, not arbitrary PID lookup.

FAQ

Can Go get details for any Linux PID?

Go can read many details through /proc/<pid>, but permissions and Linux security settings can restrict what a user may inspect for another user's process.

Why does /proc/<pid> sometimes disappear?

The process exited. Procfs entries exist only while their associated process exists.

What does an empty /proc/<pid>/cmdline mean?

It can mean the process has no user-space command line, which is common for kernel threads. It does not always mean reading failed.

Is /proc/<pid>/status stable to parse?

It is intended for human-readable process status and is commonly parsed line by line. Parse field names rather than relying on a fixed line number.

Can I use syscall.Getrusage for another process's PID?

No. Its selector refers to the current process or child-process usage categories, not an arbitrary PID.

How do I find a process's executable path?

Use os.Readlink on /proc/<pid>/exe. This can fail if the process exits or access is restricted.

Is procfs available on macOS or Windows?

No. The /proc/<pid> layout discussed here is Linux-specific. Use platform-specific APIs or a cross-platform library for other operating systems.

Take Quiz

Mini Project

Description

Build a Linux command-line tool that accepts a PID and prints a small process report. It demonstrates practical procfs reading, command-line parsing, basic status-field parsing, and reliable error handling when a process has already exited.

Goal

Create a Go program that prints a process's PID, name, state, parent PID, user ID, executable path, and command line.

Requirements

Accept exactly one PID argument from the command line.|Reject invalid or non-positive PID values.|Read process fields from /proc/<pid>/status.|Read and format /proc/<pid>/cmdline correctly using NUL separators.|Attempt to display the executable path from /proc/<pid>/exe.|Print a clear error if the process cannot be inspected.

Take Quiz

Keep learning

Related questions

View more