Linux / Bash

Linux Process Using 100% CPU, How to Trace High System Load With htop/perf

A process pinned at 100% CPU tells you something is consuming a full core continuously, but identifying htop's culprit process is only the first step — finding out exactly what that process is doing that's so expensive requires a few more targeted diagnostic tools.

The Problem

System load is unusually high, and a specific process is clearly the cause:

htop
  PID  USER  %CPU  %MEM  COMMAND
 4521  app    99.8   2.1  node server.js

Knowing which process is responsible is useful, but doesn't yet explain why — the same command name could be stuck in an infinite loop, processing a genuinely large workload, or blocked in a busy-wait pattern, each requiring a different fix.

Why It Happens

Sustained 100% CPU usage on a single process generally falls into one of a few categories:

  • A genuine infinite loop or logic bug causing the process to spin without ever completing or yielding, common after a code change introduced unintended looping behavior
  • A legitimately CPU-intensive operation (data processing, image manipulation, complex computation) that's simply taking a long time, which may be entirely expected behavior for the workload rather than a bug
  • Excessive garbage collection pressure in a managed-memory language runtime, where the process spends most of its CPU time on GC rather than actual application work, often as a symptom of a separate memory-related issue
  • A busy-wait or polling pattern implemented inefficiently, checking a condition in a tight loop rather than using a proper blocking or event-driven wait mechanism

The Fix

Start with htop to confirm the specific process and get a first impression — press F2 to check display options, and consider enabling per-thread view (H in htop) if the process is multi-threaded, since 100% CPU might be concentrated in a single specific thread rather than spread evenly:

htop
# press 'H' to toggle showing individual threads

For a snapshot of exactly what a process is doing at the code level, use perf top, which shows which functions are consuming CPU time in real time, across both your application code and system libraries it calls into:

sudo perf top -p 4521

This surfaces the specific function names consuming the most CPU cycles, which is often immediately informative — a function name related to JSON parsing, regex matching, or a specific library call points directly at what part of the code is actually expensive, rather than just knowing the process overall is busy.

For a more detailed, recorded profile you can analyze afterward rather than only watching live, record a sample and generate a flame graph or report:

sudo perf record -p 4521 -g -- sleep 30
sudo perf report

The -g flag captures call graphs, showing not just which function is hot but the chain of calls that led there, which is significantly more useful for understanding root cause than a flat list of function names alone.

For scripting languages or specific runtimes, use language-specific profiling tools rather than relying purely on system-level tools, since these often provide more directly actionable, code-level detail:

# Node.js example
node --prof server.js
node --prof-process isolate-*.log > profile.txt

If you suspect an infinite loop or stuck logic rather than genuinely expensive computation, use strace to see what system calls the process is actually making — a process making the exact same system call repeatedly in a tight loop is a strong signal of a busy-wait or stuck logic pattern, versus a process making varied, progressing system calls, which suggests genuine ongoing work rather than a stuck loop:

sudo strace -p 4521 -c
# run for a few seconds, then Ctrl+C to see a summary of syscall frequency

Still Not Working?

If the profiling tools show CPU time spread broadly across many different functions rather than concentrated in one obvious hot spot, and the workload genuinely seems to be doing real, varied work rather than being stuck, the process might simply be legitimately CPU-bound for the task at hand — in that case, the appropriate response shifts from "find and fix a bug" to either optimizing the actual algorithm being used (if it's less efficient than it could be), or providing more CPU resources/parallelism for what is genuinely a compute-intensive, expected workload rather than a malfunction.