In my previous blog, I demonstrated how to compile and install the Linux kernel 6.16.1 from upstream sources on Ubuntu 22.04 LTS. At this point, we have a working kernel named 6.16.1-Demo running on our system.
If you already had perf
installed for a different kernel and attempted to use it with this new kernel, you may have encountered the following error:
WARNING: perf not found for kernel 6.16.1
You may need to install the following packages for this specific kernel:
linux-tools-6.16.1-Demo
linux-cloud-tools-6.16.1-Demo
You may also want to install one of the following packages to keep up to date:
linux-tools-Demo
linux-cloud-tools-Demo
This happens because perf
is tightly coupled to the kernel it runs against. Each kernel version requires its own matching build of perf
. For custom kernels, the best approach is to compile perf
directly from the kernel’s source tree.
We also added the extra version string Demo
to emphasize that this is a custom build. At the time of writing, the latest kernel available in Ubuntu 22.04 LTS repositories is 6.8.0, so relying on Ubuntu packages for kernel 6.16.1 is not possible.
In this tutorial, I’ll show you how to build perf
from source for your custom kernel.
Step 1. Obtain the Kernel Source Code
Since we are working with a custom kernel, you’ll need access to its source code from the developer. The method will vary depending on where your kernel came from.
Step 2. Install Required Dependencies
Attempting to compile perf
without dependencies will quickly result in errors. On my system, the first error was:
Makefile.config:204: *** Error: pkg-config needed by libtraceevent is missing on this system, please install it. Stop.
This can be fixed with:
sudo apt update && sudo apt install pkg-config
But don’t celebrate yet—you’ll likely hit another error:
Makefile.config:910: *** ERROR: No python interpreter needed for jevents generation. Install python or build with NO_JEVENTS=1.. Stop.
On Ubuntu 22.04 LTS, resolve this by installing:
sudo apt install python-dev-is-python3
And, of course, another dependency issue may appear:
Makefile.config:1178: *** ERROR: libtraceevent is missing. Please install libtraceevent-dev/libtraceevent-devel and/or set LIBTRACEEVENT_DIR or build with NO_LIBTRACEEVENT=1. Stop.
To resolve this error:
sudo apt install libtraceevent-dev
At this point, you should have the minimal dependencies needed to build a functional perf
.
Step 3. Compile perf
(Basic Build)
The perf
utility lives inside the kernel source tree under tools/perf
. It includes its own Makefile. Run:
cd linux-6.16.1/tools/perf
make
Note: You don’t need to specify -j
; the Makefile automatically detects and uses the maximum number of available threads:
BUILD: Doing 'make -j40' parallel build
This will produce a working perf
binary, but with limited functionality. During the build, you’ll see which features were enabled or disabled. For example:
Auto-detecting system features:
... libdw: [ OFF ]
... glibc: [ on ]
... libelf: [ on ]
... libnuma: [ OFF ]
... numa_num_possible_cpus: [ OFF ]
... libperl: [ OFF ]
... libpython: [ on ]
... libcrypto: [ on ]
... libcapstone: [ OFF ]
... llvm-perf: [ OFF ]
... zlib: [ on ]
... lzma: [ OFF ]
... get_cpuid: [ on ]
... bpf: [ on ]
... libaio: [ on ]
... libzstd: [ OFF ]
You can inspect the build options afterward with:
./perf version --build-option
Sample output:
perf version 6.16.1-Demo
aio: [ on ] # HAVE_AIO_SUPPORT
bpf: [ on ] # HAVE_LIBBPF_SUPPORT
bpf_skeletons: [ OFF ] # HAVE_BPF_SKEL
debuginfod: [ OFF ] # HAVE_DEBUGINFOD_SUPPORT
dwarf: [ OFF ] # HAVE_LIBDW_SUPPORT
dwarf_getlocations: [ OFF ] # HAVE_LIBDW_SUPPORT
dwarf-unwind: [ OFF ] # HAVE_DWARF_UNWIND_SUPPORT
auxtrace: [ on ] # HAVE_AUXTRACE_SUPPORT
libbfd: [ OFF ] # HAVE_LIBBFD_SUPPORT ( tip: Deprecated, license incompatibility, use BUILD_NONDISTRO=1 and install binutils-dev[el] )
libcapstone: [ OFF ] # HAVE_LIBCAPSTONE_SUPPORT
libcrypto: [ on ] # HAVE_LIBCRYPTO_SUPPORT
libdw-dwarf-unwind: [ OFF ] # HAVE_LIBDW_SUPPORT
libelf: [ on ] # HAVE_LIBELF_SUPPORT
libnuma: [ OFF ] # HAVE_LIBNUMA_SUPPORT
libopencsd: [ OFF ] # HAVE_CSTRACE_SUPPORT
libperl: [ OFF ] # HAVE_LIBPERL_SUPPORT
libpfm4: [ OFF ] # HAVE_LIBPFM
libpython: [ on ] # HAVE_LIBPYTHON_SUPPORT
libslang: [ OFF ] # HAVE_SLANG_SUPPORT
libtraceevent: [ on ] # HAVE_LIBTRACEEVENT
libunwind: [ OFF ] # HAVE_LIBUNWIND_SUPPORT ( tip: Deprecated, use LIBUNWIND=1 and install libunwind-dev[el] to build with it )
lzma: [ OFF ] # HAVE_LZMA_SUPPORT
numa_num_possible_cpus: [ OFF ] # HAVE_LIBNUMA_SUPPORT
zlib: [ on ] # HAVE_ZLIB_SUPPORT
zstd: [ OFF ] # HAVE_ZSTD_SUPPORT
The way to enable these functionalities is also provided during build:
Makefile.config:468: No libdw.h found or old libdw.h found or elfutils is older than 0.157, disables dwarf support. Please install new elfutils-devel/libdw-dev
Makefile.config:564: No elfutils/debuginfod.h found, no debuginfo server support, please install libdebuginfod-dev/elfutils-debuginfod-client-devel or equivalent
Makefile.config:606: No sys/sdt.h found, no SDT events are defined, please install systemtap-sdt-devel or systemtap-sdt-dev
Makefile.config:694: Warning: Disabled BPF skeletons as clang (clang) is missing
Makefile.config:726: Disabling post unwind, no support found.
Makefile.config:789: slang not found, disables TUI support. Please install slang-devel, libslang-dev or libslang2-dev
Makefile.config:836: Missing perl devel files. Disabling perl scripting support, please install perl-ExtUtils-Embed/libperl-dev
Makefile.config:968: No libllvm 13+ found, slower source file resolution, please install llvm-devel/llvm-dev
Makefile.config:1001: No liblzma found, disables xz kernel module decompression, please install xz-devel/liblzma-dev
Makefile.config:1014: No libzstd found, disables trace compression, please install libzstd-dev[el] and/or set LIBZSTD_DIR
Makefile.config:1027: No numa.h found, disables 'perf bench numa mem' benchmark, please install numactl-devel/libnuma-devel/libnuma-dev
Makefile.config:1086: No libbabeltrace found, disables 'perf data' CTF format support, please install libbabeltrace-dev[el]/libbabeltrace-ctf-dev
Makefile.config:1098: No libcapstone found, disables disasm engine support for 'perf script', please install libcapstone-dev/capstone-devel
Makefile.config:1129: No alternatives command found, you need to set JDIR= to point to the root of your Java directory
Makefile.config:1160: libpfm4 not found, disables libpfm4 support. Please install libpfm4-dev
Makefile.config:1192: libtracefs is missing. Please install libtracefs-dev/libtracefs-devel
Note: Package whose name ends with -dev is for Debian-based distros, while -devel is for Red Hat-based distros. Choose the right package name based on your distro.
In the end, if you want to install documentations, you will need to install packages, asciidoc
and xmlto
.
Step 4. Install Additional Libraries (Full Build)
To build a fully functional perf
, you’ll need extra development libraries:
sudo apt update && sudo apt install libdw-dev libdebuginfod-dev systemtap-sdt-dev libslang2-dev libperl-dev llvm-dev liblzma-dev libzstd-dev libnuma-dev libbabeltrace-ctf-dev libcapstone-dev libpfm4-dev libtracefs-dev asciidoc xmlto
This also installs the tools needed to generate man pages.
Step 5. Rebuild perf
(Full Features)
Clean up the previous build and recompile:
make clean
make
Use ./perf version --build-option
to check the supported features again. Sample output:
perf version 6.16.1-Demo
aio: [ on ] # HAVE_AIO_SUPPORT
bpf: [ on ] # HAVE_LIBBPF_SUPPORT
bpf_skeletons: [ OFF ] # HAVE_BPF_SKEL
debuginfod: [ on ] # HAVE_DEBUGINFOD_SUPPORT
dwarf: [ on ] # HAVE_LIBDW_SUPPORT
dwarf_getlocations: [ on ] # HAVE_LIBDW_SUPPORT
dwarf-unwind: [ on ] # HAVE_DWARF_UNWIND_SUPPORT
auxtrace: [ on ] # HAVE_AUXTRACE_SUPPORT
libbfd: [ OFF ] # HAVE_LIBBFD_SUPPORT ( tip: Deprecated, license incompatibility, use BUILD_NONDISTRO=1 and install binutils-dev[el] )
libcapstone: [ on ] # HAVE_LIBCAPSTONE_SUPPORT
libcrypto: [ on ] # HAVE_LIBCRYPTO_SUPPORT
libdw-dwarf-unwind: [ on ] # HAVE_LIBDW_SUPPORT
libelf: [ on ] # HAVE_LIBELF_SUPPORT
libnuma: [ on ] # HAVE_LIBNUMA_SUPPORT
libopencsd: [ OFF ] # HAVE_CSTRACE_SUPPORT
libperl: [ on ] # HAVE_LIBPERL_SUPPORT
libpfm4: [ on ] # HAVE_LIBPFM
libpython: [ on ] # HAVE_LIBPYTHON_SUPPORT
libslang: [ on ] # HAVE_SLANG_SUPPORT
libtraceevent: [ on ] # HAVE_LIBTRACEEVENT
libunwind: [ OFF ] # HAVE_LIBUNWIND_SUPPORT ( tip: Deprecated, use LIBUNWIND=1 and install libunwind-dev[el] to build with it )
lzma: [ on ] # HAVE_LZMA_SUPPORT
numa_num_possible_cpus: [ on ] # HAVE_LIBNUMA_SUPPORT
zlib: [ on ] # HAVE_ZLIB_SUPPORT
zstd: [ on ] # HAVE_ZSTD_SUPPORT
Now most features should be enabled, including support for libdw
, libnuma
, libzstd
, and others. This build is generally sufficient for production use.
Step 6. Install perf into the System PATH
For everyday use, it’s best to install the compiled perf
binary (and its man pages) into the system PATH, so you don’t need to invoke it with a relative or absolute path.
But here’s the catch: you may already have perf
installed system-wide for another kernel. Ubuntu’s packaging system solves this by allowing multiple versions of perf
to coexist. Let’s dig into how this works.
6.1 Inspecting the Wrapper Script
Let’s verify:
which perf
Sample output:
/usr/bin/perf
Looks like a binary file, right? Don't be fooled:
file $(which perf)
Sample output:
/usr/bin/perf: Bourne-Again shell script, ASCII text executable
So, /usr/bin/perf
isn’t a binary—it’s a wrapper script. Let’s take a peek:
#!/bin/bash
full_version=`uname -r`
# First check for a fully qualified version.
this="/usr/lib/linux-tools/$full_version/`basename $0`"
if [ -f "$this" ]; then
exec "$this" "$@"
fi
[...]
This script dynamically selects the correct perf
binary based on your current kernel version (uname -r
). That’s how Ubuntu achieves version-specific polymorphism.
6.2 How the Binaries Are Stored
Let’s see what binary it would have chosen for kernel 5.15.0-151-generic
:
file /usr/lib/linux-tools/5.15.0-151-generic/perf
Sample output:
/usr/lib/linux-tools/5.15.0-151-generic/perf: symbolic link to ../../linux-tools-5.15.0-151/perf
Follow the symlink:
file /usr/lib/linux-tools-5.15.0-151/perf
Sample output:
/usr/lib/linux-tools-5.15.0-151/perf: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=<redacted>, for GNU/Linux 3.2.0, with debug_info, not stripped
So here’s the structure:
/usr/lib/linux-tools-<kernel release without flavor>/perf
→ actual binary/usr/bin/perf
→ wrapper script/usr/lib/linux-tools/$(uname -r)/perf
→ symlink to actual binary
6.3 Package Ownership
Who installs these files? Let’s check:
dpkg -L linux-tools-common
Sample output:
/.
/usr
/usr/bin
/usr/bin/acpidbg
/usr/bin/cpupower
/usr/bin/perf
/usr/bin/turbostat
/usr/bin/usbip
/usr/bin/usbipd
/usr/bin/x86_energy_perf_policy
/usr/sbin
/usr/sbin/bpftool
/usr/share
/usr/share/bash-completion
/usr/share/bash-completion/completions
/usr/share/bash-completion/completions/bpftool
/usr/share/doc
/usr/share/doc/linux-tools-common
/usr/share/doc/linux-tools-common/changelog.Debian.gz
/usr/share/doc/linux-tools-common/copyright
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/cpupower-frequency-info.1.gz
/usr/share/man/man1/cpupower-frequency-set.1.gz
/usr/share/man/man1/cpupower-idle-info.1.gz
/usr/share/man/man1/cpupower-idle-set.1.gz
/usr/share/man/man1/cpupower-info.1.gz
/usr/share/man/man1/cpupower-monitor.1.gz
/usr/share/man/man1/cpupower-set.1.gz
/usr/share/man/man1/cpupower.1.gz
/usr/share/man/man1/perf-annotate.1.gz
/usr/share/man/man1/perf-archive.1.gz
[...]
Among other things, it installs:
/usr/bin/perf
(wrapper script)- Documentation and man pages
dpkg -L linux-tools-5.15.0-151-generic
Sample output:
/.
/usr
/usr/lib
/usr/lib/linux-tools
/usr/lib/linux-tools/5.15.0-151-generic
/usr/share
/usr/share/doc
/usr/share/doc/linux-tools-5.15.0-151-generic
/usr/share/doc/linux-tools-5.15.0-151-generic/copyright
/usr/lib/linux-tools/5.15.0-151-generic/acpidbg
/usr/lib/linux-tools/5.15.0-151-generic/bpftool
/usr/lib/linux-tools/5.15.0-151-generic/cpupower
/usr/lib/linux-tools/5.15.0-151-generic/libperf-jvmti.so
/usr/lib/linux-tools/5.15.0-151-generic/perf
/usr/lib/linux-tools/5.15.0-151-generic/turbostat
/usr/lib/linux-tools/5.15.0-151-generic/usbip
/usr/lib/linux-tools/5.15.0-151-generic/usbipd
/usr/lib/linux-tools/5.15.0-151-generic/x86_energy_perf_policy
/usr/share/doc/linux-tools-5.15.0-151-generic/changelog.Debian.gz
It provides:
/usr/lib/linux-tools/5.15.0-151-generic/perf
(symlink)
dpkg -L linux-tools-5.15.0-151 # note: this is the dependency of the above
Sample output:
/.
/usr
/usr/lib
/usr/lib/linux-tools-5.15.0-151
/usr/lib/linux-tools-5.15.0-151/acpidbg
/usr/lib/linux-tools-5.15.0-151/bpftool
/usr/lib/linux-tools-5.15.0-151/cpupower
/usr/lib/linux-tools-5.15.0-151/libperf-jvmti.so
/usr/lib/linux-tools-5.15.0-151/perf
/usr/lib/linux-tools-5.15.0-151/turbostat
/usr/lib/linux-tools-5.15.0-151/usbip
/usr/lib/linux-tools-5.15.0-151/usbipd
/usr/lib/linux-tools-5.15.0-151/x86_energy_perf_policy
/usr/share
/usr/share/doc
/usr/share/doc/linux-tools-5.15.0-151
/usr/share/doc/linux-tools-5.15.0-151/changelog.Debian.gz
/usr/share/doc/linux-tools-5.15.0-151/copyright
This package contains:
/usr/lib/linux-tools-5.15.0-151/perf
(actual binary)
So the division of responsibility is:
linux-tools-common
→ wrapper script and docslinux-tools-<kernel>-generic
→ symlinks for a specific kernel flavorlinux-tools-<kernel>
→ actual binaries
6.4 Installing Your Custom perf
To integrate your custom perf
into this system, mimic the package structure.
# install binaries and auxilary files to /usr/lib/linux-tools-$(uname -r)
PREFIX="/usr/lib/linux-tools"
sudo make prefix="$PREFIX"-"$(uname -r)" install install-doc
# add symlinks to /usr/lib/linux-tools/$(uname -r)
sudo mkdir -p "$PREFIX"/"$(uname -r)"
sudo ln -sr "$PREFIX"-"$(uname -r)"/bin/perf "$PREFIX"/"$(uname -r)"/perf
Now, when you run perf
, the wrapper script automatically redirects to the correct binary for your current kernel, just like Ubuntu’s own packaging.
6.5 Reading the perf Man Pages
When installing perf
manually into /usr/lib/linux-tools-$(uname -r)
, the man pages won’t automatically be picked up by the system’s default man
path. To read them, you can explicitly point man
to the custom location. For example, for our 6.16.1-Demo kernel:
man --manpath=/usr/lib/linux-tools-6.16.1-Demo/share/man perf
Note that the order of arguments is important: --manpath
must come before the page name (perf
). Otherwise, man
will ignore the custom path and fallback to the system default.
Conclusion
By compiling and installing perf
from the kernel source, you ensure full compatibility with your custom kernel—even when the version isn’t available in Ubuntu’s repositories. With the proper dependency setup, you can build a fully featured perf
, integrate it cleanly into Ubuntu’s version-aware tool system, and even access its dedicated man pages. This approach gives you a reliable, maintainable workflow for performance profiling across custom kernels.
Comments NOTHING