Configure perf tools and FlameGraph toolkit to capture detailed CPU profiling data and generate interactive flame graphs for Linux performance analysis. Monitor application hotspots, CPU usage patterns, and identify performance bottlenecks in production environments.
Prerequisites
- Root or sudo access
- Active Linux system with kernel version 3.0+
- At least 2GB free disk space for profiling data
- Basic familiarity with command line
What this solves
Linux perf tools provide detailed CPU profiling and performance analysis capabilities for applications and system processes. This tutorial helps you set up comprehensive performance monitoring with flame graph visualization to identify CPU hotspots, analyze function call patterns, and optimize application performance in production environments.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of perf tools and dependencies.
sudo apt update && sudo apt upgrade -y
Install perf tools and dependencies
Install the perf profiling tools, debug symbol packages, and essential build tools for flame graph generation.
sudo apt install -y linux-tools-common linux-tools-generic linux-tools-$(uname -r) \
libc6-dbg build-essential git perl
Configure kernel symbol access
Configure the system to allow perf access to kernel symbols and enable proper symbol resolution for detailed profiling.
echo 'kernel.perf_event_paranoid = 1' | sudo tee -a /etc/sysctl.conf
echo 'kernel.kptr_restrict = 0' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Install debug information packages
Install debug symbols for better function name resolution in flame graphs and performance reports.
sudo apt install -y libc6-dbg
echo 'deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse' | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
echo 'deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse' | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 428D7C01 C8CAB6595FDFF622
sudo apt update
Clone FlameGraph toolkit
Download the FlameGraph toolkit from GitHub for generating interactive SVG flame graphs from perf data.
cd /opt
sudo git clone https://github.com/brendangregg/FlameGraph.git
sudo chown -R $USER:$USER /opt/FlameGraph
echo 'export PATH="/opt/FlameGraph:$PATH"' >> ~/.bashrc
source ~/.bashrc
Configure perf data collection settings
Set up optimal perf configuration for comprehensive CPU profiling and stack trace collection.
# Add perf settings for all users
* soft nofile 65536
* hard nofile 65536
* soft nproc 32768
* hard nproc 32768
sudo mkdir -p /tmp/perf-data
sudo chmod 755 /tmp/perf-data
Test basic perf functionality
Verify that perf tools are working correctly by running a simple CPU profiling test.
perf stat -e cycles,instructions,cache-references,cache-misses sleep 3
Configure advanced profiling setup
Create perf data collection script
Create a reusable script for capturing detailed performance data with optimal settings for flame graph generation.
#!/bin/bash
Perf profiling script with flame graph generation
Usage: ./perf-profile.sh [duration] [process_name_or_pid]
DURATION=${1:-30}
TARGET=${2:-""}
OUTPUT_DIR="/tmp/perf-data/$(date +%Y%m%d_%H%M%S)"
DATA_FILE="$OUTPUT_DIR/perf.data"
FLAMEGRAPH_FILE="$OUTPUT_DIR/flamegraph.svg"
mkdir -p "$OUTPUT_DIR"
echo "Starting perf recording for $DURATION seconds..."
echo "Output directory: $OUTPUT_DIR"
if [ -n "$TARGET" ]; then
# Profile specific process
if [[ "$TARGET" =~ ^[0-9]+$ ]]; then
# PID provided
perf record -F 997 -g --call-graph=dwarf -p "$TARGET" \
-o "$DATA_FILE" sleep "$DURATION"
else
# Process name provided
PID=$(pgrep -f "$TARGET" | head -1)
if [ -z "$PID" ]; then
echo "Error: Process '$TARGET' not found"
exit 1
fi
perf record -F 997 -g --call-graph=dwarf -p "$PID" \
-o "$DATA_FILE" sleep "$DURATION"
fi
else
# System-wide profiling
perf record -F 997 -g --call-graph=dwarf -a \
-o "$DATA_FILE" sleep "$DURATION"
fi
echo "Generating perf report..."
perf report -i "$DATA_FILE" --stdio > "$OUTPUT_DIR/perf_report.txt"
echo "Generating flame graph..."
perf script -i "$DATA_FILE" | stackcollapse-perf.pl | flamegraph.pl > "$FLAMEGRAPH_FILE"
echo "Profiling complete!"
echo "Flame graph: $FLAMEGRAPH_FILE"
echo "Perf report: $OUTPUT_DIR/perf_report.txt"
echo "Raw data: $DATA_FILE"
sudo chmod +x /usr/local/bin/perf-profile.sh
Configure automatic symbol server setup
Set up automatic symbol download and caching for better flame graph accuracy.
[buildid-dir]
dir = /home/$USER/.debug
[annotate]
hide_src_code = false
use_offset = true
[report]
group = true
sort_order = comm,dso,symbol
demangle = true
[record]
call-graph = dwarf
[top]
sort_order = dso,symbol
Create monitoring service configuration
Set up a systemd service for continuous background performance monitoring when needed.
[Unit]
Description=Continuous Performance Monitoring
After=multi-user.target
[Service]
Type=simple
User=root
ExecStart=/bin/bash -c 'while true; do perf record -F 99 -g --call-graph=dwarf -a -o /tmp/perf-data/continuous_$(date +%%s).data sleep 300; done'
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Generate your first flame graph
Capture sample performance data
Run a performance capture session to generate sample data for flame graph creation.
# System-wide profiling for 30 seconds
sudo perf record -F 997 -g --call-graph=dwarf -a sleep 30
Generate interactive flame graph
Convert the perf data into an interactive SVG flame graph for analysis.
perf script | stackcollapse-perf.pl | flamegraph.pl > /tmp/flamegraph_$(date +%Y%m%d_%H%M%S).svg
echo "Flame graph generated: /tmp/flamegraph_$(date +%Y%m%d_%H%M%S).svg"
echo "Open this file in a web browser to view the interactive flame graph"
Profile specific application
Example of profiling a specific running application using the custom script.
# Profile nginx process for 60 seconds
sudo /usr/local/bin/perf-profile.sh 60 nginx
Profile specific PID
sudo /usr/local/bin/perf-profile.sh 30 1234
System-wide profiling
sudo /usr/local/bin/perf-profile.sh 45
Advanced flame graph analysis
Configure differential flame graphs
Set up scripts for comparing performance between different time periods or configurations.
#!/bin/bash
Generate differential flame graphs
Usage: ./perf-diff.sh [baseline_perf_data] [current_perf_data]
BASELINE=$1
CURRENT=$2
if [ ! -f "$BASELINE" ] || [ ! -f "$CURRENT" ]; then
echo "Usage: $0 baseline.data current.data"
exit 1
fi
OUTPUT_DIR="/tmp/perf-diff-$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUTPUT_DIR"
echo "Generating differential analysis..."
Generate folded stacks for both datasets
perf script -i "$BASELINE" | stackcollapse-perf.pl > "$OUTPUT_DIR/baseline.folded"
perf script -i "$CURRENT" | stackcollapse-perf.pl > "$OUTPUT_DIR/current.folded"
Create differential flame graph
difffolded.pl "$OUTPUT_DIR/baseline.folded" "$OUTPUT_DIR/current.folded" | \
flamegraph.pl > "$OUTPUT_DIR/diff_flamegraph.svg"
echo "Differential flame graph: $OUTPUT_DIR/diff_flamegraph.svg"
sudo chmod +x /usr/local/bin/perf-diff.sh
Configure memory profiling integration
Add memory allocation tracking to complement CPU profiling for comprehensive performance analysis.
# Install additional tools for memory profiling
sudo apt install -y valgrind massif-visualizer || sudo dnf install -y valgrind
Create memory+CPU profiling script
sudo tee /usr/local/bin/perf-memory.sh > /dev/null << 'EOF'
#!/bin/bash
DURATION=${1:-30}
TARGET=${2:-""}
OUTPUT_DIR="/tmp/perf-memory-$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUTPUT_DIR"
if [ -n "$TARGET" ]; then
PID=$(pgrep -f "$TARGET" | head -1)
if [ -z "$PID" ]; then
echo "Process not found: $TARGET"
exit 1
fi
# CPU profiling
perf record -F 997 -g --call-graph=dwarf -p "$PID" \
-o "$OUTPUT_DIR/cpu.data" sleep "$DURATION" &
# Memory events
perf record -e cache-misses,page-faults -p "$PID" \
-o "$OUTPUT_DIR/memory.data" sleep "$DURATION" &
wait
else
echo "System-wide memory+CPU profiling"
perf record -F 997 -g -e cycles,cache-misses,page-faults -a \
-o "$OUTPUT_DIR/combined.data" sleep "$DURATION"
fi
echo "Memory and CPU profiling complete: $OUTPUT_DIR"
EOF
sudo chmod +x /usr/local/bin/perf-memory.sh
Verify your setup
# Verify perf installation
perf --version
perf list | grep -E "cpu|cache|memory" | head -10
Check FlameGraph tools
ls -la /opt/FlameGraph/*.pl | head -5
Verify kernel settings
sysctl kernel.perf_event_paranoid
sysctl kernel.kptr_restrict
Test basic profiling
sudo perf stat -e cycles,instructions ls /tmp
Verify scripts are executable
ls -la /usr/local/bin/perf-*
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| "perf: No such file or directory" | Wrong kernel tools package | Install linux-tools-$(uname -r) matching your kernel |
| "Permission denied" when running perf | Restrictive paranoia settings | Set kernel.perf_event_paranoid = 1 in sysctl |
| Flame graphs show only hex addresses | Missing debug symbols | Install debug packages and configure symbol paths |
| "Failed to open [kernel.kallsyms]" | Kernel symbol access restricted | Set kernel.kptr_restrict = 0 temporarily for profiling |
| Empty or minimal flame graphs | Low sampling frequency or short duration | Increase frequency with -F 997 and capture for longer periods |
| "stackcollapse-perf.pl: command not found" | FlameGraph tools not in PATH | Verify PATH includes /opt/FlameGraph or use full paths |
Next steps
- Configure comprehensive system monitoring with sar and sysstat
- Optimize system performance using the insights from flame graphs
- Set up Prometheus and Grafana for continuous performance monitoring
- Advanced application profiling with BPF integration
- Automate performance regression testing in CI/CD pipelines
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Linux Performance Monitoring with perf tools and flame graphs installer
# Supports Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, RHEL, and Amazon Linux
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
INSTALL_DIR="/opt/FlameGraph"
PERF_DATA_DIR="/tmp/perf-data"
SCRIPT_NAME="perf-profile.sh"
cleanup() {
echo -e "${RED}[ERROR]${NC} Installation failed. Cleaning up..."
sudo rm -rf "$INSTALL_DIR" 2>/dev/null || true
sudo rm -rf "$PERF_DATA_DIR" 2>/dev/null || true
sudo rm -f "/usr/local/bin/$SCRIPT_NAME" 2>/dev/null || true
}
trap cleanup ERR
usage() {
echo "Usage: $0"
echo "Install Linux performance monitoring with perf tools and flame graphs"
exit 1
}
log_step() {
echo -e "${GREEN}[$1]${NC} $2"
}
log_warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if running as root or with sudo
if [[ $EUID -eq 0 ]]; then
log_error "Please run this script as a regular user with sudo privileges, not as root"
exit 1
fi
if ! sudo -n true 2>/dev/null; then
log_error "This script requires sudo privileges"
exit 1
fi
# Detect distribution
if [ ! -f /etc/os-release ]; then
log_error "Cannot detect Linux distribution"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
PERF_PACKAGES="linux-tools-common linux-tools-generic linux-tools-$(uname -r) libc6-dbg build-essential git perl"
DEBUG_PACKAGES="libc6-dbg"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PERF_PACKAGES="perf kernel-debuginfo-$(uname -r) glibc-debuginfo make gcc git perl which"
DEBUG_PACKAGES="glibc-debuginfo kernel-debuginfo"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
PERF_PACKAGES="perf glibc-debuginfo make gcc git perl which"
DEBUG_PACKAGES="glibc-debuginfo"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_step "1/8" "Updating system packages"
sudo $PKG_UPDATE
log_step "2/8" "Installing perf tools and dependencies"
if ! sudo $PKG_INSTALL $PERF_PACKAGES; then
log_warn "Some perf packages may not be available, continuing with available ones"
fi
log_step "3/8" "Configuring kernel symbol access"
echo 'kernel.perf_event_paranoid = 1' | sudo tee -a /etc/sysctl.conf >/dev/null
echo 'kernel.kptr_restrict = 0' | sudo tee -a /etc/sysctl.conf >/dev/null
sudo sysctl -p >/dev/null
log_step "4/8" "Installing debug information packages"
case "$ID" in
ubuntu|debian)
if [ "$ID" = "ubuntu" ]; then
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse" | \
sudo tee /etc/apt/sources.list.d/ddebs.list >/dev/null
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list >/dev/null
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 428D7C01 C8CAB6595FDFF622 >/dev/null 2>&1 || true
sudo apt update >/dev/null 2>&1 || true
fi
;;
*)
if command -v dnf >/dev/null 2>&1; then
sudo dnf debuginfo-install -y kernel glibc >/dev/null 2>&1 || log_warn "Debug info packages may not be available"
fi
;;
esac
log_step "5/8" "Cloning FlameGraph toolkit"
sudo rm -rf "$INSTALL_DIR"
sudo git clone https://github.com/brendangregg/FlameGraph.git "$INSTALL_DIR" >/dev/null 2>&1
sudo chown -R root:root "$INSTALL_DIR"
sudo chmod -R 755 "$INSTALL_DIR"
log_step "6/8" "Creating perf data directory"
sudo mkdir -p "$PERF_DATA_DIR"
sudo chmod 755 "$PERF_DATA_DIR"
log_step "7/8" "Creating performance profiling script"
sudo tee "/usr/local/bin/$SCRIPT_NAME" >/dev/null <<'EOF'
#!/bin/bash
# Perf profiling script with flame graph generation
# Usage: ./perf-profile.sh [duration] [process_name_or_pid]
set -euo pipefail
DURATION=${1:-30}
TARGET=${2:-""}
OUTPUT_DIR="/tmp/perf-data/$(date +%Y%m%d_%H%M%S)"
DATA_FILE="$OUTPUT_DIR/perf.data"
FLAMEGRAPH_FILE="$OUTPUT_DIR/flamegraph.svg"
FLAMEGRAPH_PATH="/opt/FlameGraph"
if [ ! -d "$FLAMEGRAPH_PATH" ]; then
echo "Error: FlameGraph toolkit not found at $FLAMEGRAPH_PATH"
exit 1
fi
mkdir -p "$OUTPUT_DIR"
echo "Starting perf recording for $DURATION seconds..."
echo "Output directory: $OUTPUT_DIR"
if [ -n "$TARGET" ]; then
# Profile specific process
if [[ "$TARGET" =~ ^[0-9]+$ ]]; then
# PID provided
perf record -F 997 -g --call-graph=dwarf -p "$TARGET" \
-o "$DATA_FILE" sleep "$DURATION"
else
# Process name provided
PID=$(pgrep -f "$TARGET" | head -1)
if [ -z "$PID" ]; then
echo "Error: Process '$TARGET' not found"
exit 1
fi
perf record -F 997 -g --call-graph=dwarf -p "$PID" \
-o "$DATA_FILE" sleep "$DURATION"
fi
else
# System-wide profiling
sudo perf record -F 997 -g --call-graph=dwarf -a \
-o "$DATA_FILE" sleep "$DURATION"
fi
echo "Generating flame graph..."
perf script -i "$DATA_FILE" | "$FLAMEGRAPH_PATH/stackcollapse-perf.pl" | \
"$FLAMEGRAPH_PATH/flamegraph.pl" > "$FLAMEGRAPH_FILE"
echo "Performance analysis complete!"
echo "Perf data: $DATA_FILE"
echo "Flame graph: $FLAMEGRAPH_FILE"
echo "Report: perf report -i $DATA_FILE"
EOF
sudo chmod 755 "/usr/local/bin/$SCRIPT_NAME"
log_step "8/8" "Configuring system limits"
echo '* soft nofile 65536
* hard nofile 65536
* soft nproc 32768
* hard nproc 32768' | sudo tee /etc/security/limits.d/99-perf.conf >/dev/null
echo -e "\n${GREEN}Installation completed successfully!${NC}"
echo -e "\nUsage examples:"
echo -e " ${YELLOW}$SCRIPT_NAME${NC} # System-wide profiling for 30 seconds"
echo -e " ${YELLOW}$SCRIPT_NAME 60${NC} # System-wide profiling for 60 seconds"
echo -e " ${YELLOW}$SCRIPT_NAME 30 nginx${NC} # Profile nginx process for 30 seconds"
echo -e " ${YELLOW}$SCRIPT_NAME 30 1234${NC} # Profile PID 1234 for 30 seconds"
echo -e "\nManual commands:"
echo -e " ${YELLOW}perf stat -e cycles,instructions,cache-references,cache-misses sleep 3${NC}"
echo -e " ${YELLOW}perf top${NC} # Live performance monitoring"
echo -e "\nFlameGraph tools are available in: ${YELLOW}$INSTALL_DIR${NC}"
echo -e "Add to PATH: ${YELLOW}export PATH=\"$INSTALL_DIR:\$PATH\"${NC}"
log_step "VERIFY" "Testing basic perf functionality"
if command -v perf >/dev/null 2>&1; then
echo -e "${GREEN}✓${NC} perf command available"
if perf stat -e cycles,instructions sleep 1 >/dev/null 2>&1; then
echo -e "${GREEN}✓${NC} perf profiling functional"
else
log_warn "perf profiling may require additional permissions"
fi
else
log_error "perf command not found after installation"
exit 1
fi
if [ -f "$INSTALL_DIR/flamegraph.pl" ]; then
echo -e "${GREEN}✓${NC} FlameGraph toolkit installed"
else
log_error "FlameGraph toolkit installation failed"
exit 1
fi
echo -e "\n${GREEN}All checks passed! Performance monitoring is ready.${NC}"
Review the script before running. Execute with: bash install.sh