When native macOS software experiences heavy CPU usage, spinning beachballs, or background processing freezes, diagnosing the root cause traditionally requires attaching heavy GUI profiling tools like Xcode Instruments. However, on remote servers, continuous integration build machines, or user workstations where installing Xcode is impractical, developers need a lightweight command-line tool that can capture accurate execution call stacks instantly.
macOS includes the native sample command-line utility located at /usr/bin/sample. Built directly into the operating system, sample halts the target process periodically for microseconds, records the exact program counter instruction of every thread, symbolicates function addresses against system dSYMs, and outputs a formatted call graph detailing where the application spends its execution time. This guide covers how to execute and analyze sampling sessions.
Basic Syntax and Running a Sample Session
Because inspecting process memory allocations requires Mach task port debugging entitlements, executing sample typically requires administrator privileges:
sudo sample <Target_Process> <Duration_In_Seconds> [Sample_Interval_In_Milliseconds] -f <Output_File>
1. Profiling an Unresponsive Application by Name
To record a 10-second sample of an active process (sampling every 1 millisecond by default):
sudo sample Safari 10 -f /tmp/safari_sample.txt
2. Targeting a Background Process by PID
To profile a headless daemon or terminal process:
# Identify PID
pgrep -x node
# Sample PID for 5 seconds
sudo sample 14820 5 -f /tmp/node_sample.txt
Reading and Analyzing the Symbolicated Report
Open the generated text report in Terminal using less or your code editor:
less /tmp/safari_sample.txt
The report contains three critical sections:
- Process Header: Details process architecture (arm64e or x86_64), parent process, Mach thread counts, and total CPU time consumed during the sample interval.
- Call Graph (Call Tree): The central diagnostic element. It displays an indented call tree where each row represents a nested function call. Numbers beside each function indicate how many sample ticks occurred inside that function:
- High sample counts in leaf nodes indicate hot CPU calculation loops.
- Sample counts grouped inside
mach_msg_traporsemaphore_wait_trapindicate thread idle or lock-waiting states.
- Binary Images: Lists all loaded dynamic libraries (dylibs) and frameworks alongside their memory addresses and UUIDs.
Comparison: sample vs spindump vs leaks
| macOS Diagnostic Tool | Primary Scope | Best Diagnostic Use Case |
|---|---|---|
| sample | Single specific target process | Profiling CPU bottlenecks and slow algorithms |
| spindump | Entire system (All running processes) | Diagnosing system-wide freezes and kernel deadlocks |
| leaks | Process heap memory | Detecting allocated memory blocks without valid retainers |