diff --git a/CMakeLists.txt b/CMakeLists.txt index 58cdbeaa..4198f401 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,17 @@ option(BUILD_FALCO_UNIT_TESTS "Build falco unit tests" OFF) option(USE_ASAN "Build with AddressSanitizer" OFF) option(USE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF) option(UBSAN_HALT_ON_ERROR "Halt on error when building with UBSan" ON) +option(USE_GPERFTOOLS "Build with gperftools CPU profiler support" OFF) +option(USE_FRAME_POINTER "Build with frame pointers for accurate profiling" OFF) + +# Enable frame pointers by default when using gperftools for accurate stack traces +if(USE_GPERFTOOLS AND NOT USE_FRAME_POINTER) + set(USE_FRAME_POINTER + ON + CACHE BOOL "Build with frame pointers for accurate profiling" FORCE + ) + message(STATUS "Enabling USE_FRAME_POINTER since USE_GPERFTOOLS is enabled") +endif() # Mem allocators - linux only for now if(NOT WIN32 @@ -186,6 +197,11 @@ endif() include(zlib) include(valijson) + +# CPU Profiling with gperftools +if(USE_GPERFTOOLS) + include(gperftools) +endif() if(NOT MINIMAL_BUILD) if(NOT WIN32 AND NOT APPLE diff --git a/cmake/modules/CompilerFlags.cmake b/cmake/modules/CompilerFlags.cmake index 97ade139..56388a02 100644 --- a/cmake/modules/CompilerFlags.cmake +++ b/cmake/modules/CompilerFlags.cmake @@ -68,6 +68,10 @@ if(NOT MSVC) endif() endif() + if(USE_FRAME_POINTER) + set(FALCO_SECURITY_FLAGS "${FALCO_SECURITY_FLAGS} -fno-omit-frame-pointer") + endif() + set(CMAKE_COMMON_FLAGS "${FALCO_SECURITY_FLAGS} -Wall -ggdb ${FALCO_EXTRA_FEATURE_FLAGS} ${MINIMAL_BUILD_FLAGS} ${MUSL_FLAGS}" ) diff --git a/cmake/modules/gperftools.cmake b/cmake/modules/gperftools.cmake new file mode 100644 index 00000000..102f78df --- /dev/null +++ b/cmake/modules/gperftools.cmake @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2026 The Falco Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +# gperftools CPU profiler support This module provides: GPERFTOOLS_INCLUDE_DIR (include directory) +# and GPERFTOOLS_PROFILER_LIB (the profiler library path) + +option(USE_BUNDLED_GPERFTOOLS "Build gperftools from source" ${USE_BUNDLED_DEPS}) + +if(GPERFTOOLS_INCLUDE_DIR) + # Already have gperftools configured +elseif(NOT USE_BUNDLED_GPERFTOOLS) + # Use system gperftools + find_path( + GPERFTOOLS_INCLUDE_DIR + NAMES gperftools/profiler.h + PATHS /usr/include /usr/local/include + ) + + find_library( + GPERFTOOLS_PROFILER_LIB + NAMES profiler + PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib/aarch64-linux-gnu + ) + + if(GPERFTOOLS_INCLUDE_DIR AND GPERFTOOLS_PROFILER_LIB) + message( + STATUS + "Found system gperftools: include: ${GPERFTOOLS_INCLUDE_DIR}, lib: ${GPERFTOOLS_PROFILER_LIB}" + ) + else() + message( + FATAL_ERROR + "Couldn't find system gperftools. Install it or use -DUSE_BUNDLED_GPERFTOOLS=ON\n" + " Ubuntu/Debian: sudo apt-get install libgoogle-perftools-dev\n" + " Fedora/RHEL: sudo dnf install gperftools-devel\n" + " macOS: brew install gperftools" + ) + endif() +else() + # Build gperftools from source + set(GPERFTOOLS_SRC "${PROJECT_BINARY_DIR}/gperftools-prefix/src/gperftools") + set(GPERFTOOLS_INCLUDE_DIR "${GPERFTOOLS_SRC}/src") + + if(BUILD_SHARED_LIBS) + set(GPERFTOOLS_LIB_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(GPERFTOOLS_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + + # The library is built in .libs subdirectory + set(GPERFTOOLS_PROFILER_LIB "${GPERFTOOLS_SRC}/.libs/libprofiler${GPERFTOOLS_LIB_SUFFIX}") + + # gperftools version 2.15 (latest stable as of 2024) + set(GPERFTOOLS_VERSION "2.15") + set(GPERFTOOLS_URL + "https://github.com/gperftools/gperftools/releases/download/gperftools-${GPERFTOOLS_VERSION}/gperftools-${GPERFTOOLS_VERSION}.tar.gz" + ) + set(GPERFTOOLS_URL_HASH + "SHA256=c69fef855628c81ef56f12e3c58f2b7ce1f326c0a1fe783e5cae0b88cbbe9a80" + ) + + message(STATUS "Building gperftools ${GPERFTOOLS_VERSION} from source") + + # Configure options for gperftools + set(GPERFTOOLS_CONFIGURE_ARGS --enable-cpu-profiler --disable-heap-profiler + --disable-heap-checker --disable-debugalloc + ) + + # Check if libunwind is available for better stack traces + find_library(LIBUNWIND_LIBRARY NAMES unwind) + if(LIBUNWIND_LIBRARY) + list(APPEND GPERFTOOLS_CONFIGURE_ARGS --enable-libunwind) + message(STATUS "gperftools: libunwind found, enabling for better stack traces") + else() + list(APPEND GPERFTOOLS_CONFIGURE_ARGS --disable-libunwind) + message(STATUS "gperftools: libunwind not found, using frame pointers for stack traces") + endif() + + ExternalProject_Add( + gperftools + PREFIX "${PROJECT_BINARY_DIR}/gperftools-prefix" + URL "${GPERFTOOLS_URL}" + URL_HASH "${GPERFTOOLS_URL_HASH}" + CONFIGURE_COMMAND /configure ${GPERFTOOLS_CONFIGURE_ARGS} + BUILD_COMMAND ${CMD_MAKE} ${PROCESSOUR_COUNT_MAKE_FLAG} + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + UPDATE_COMMAND "" + BUILD_BYPRODUCTS ${GPERFTOOLS_PROFILER_LIB} + ) + + install( + FILES "${GPERFTOOLS_PROFILER_LIB}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${LIBS_PACKAGE_NAME}" + COMPONENT "libs-deps" + OPTIONAL + ) +endif() + +# Create a custom target so we can always depend on 'gperftools' regardless of bundled/system +if(NOT TARGET gperftools) + add_custom_target(gperftools) +endif() + +# Add include directory globally +include_directories(${GPERFTOOLS_INCLUDE_DIR}) + +# Add compile definition so code can detect profiling support +add_compile_definitions(HAS_GPERFTOOLS) + +# Wrap the profiler library with --whole-archive to ensure the profiler's initialization code is +# linked even though we don't call ProfilerStart() directly. This is required for the CPUPROFILE +# environment variable to work. +set(GPERFTOOLS_PROFILER_LIB "-Wl,--whole-archive" "${GPERFTOOLS_PROFILER_LIB}" + "-Wl,--no-whole-archive" +) + +message(STATUS "gperftools CPU profiler enabled") +message(STATUS " Include dir: ${GPERFTOOLS_INCLUDE_DIR}") +message(STATUS " Library: ${GPERFTOOLS_PROFILER_LIB}") diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index 027ccfc6..39e48753 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -72,6 +72,11 @@ if(USE_JEMALLOC OR USE_MIMALLOC) list(APPEND FALCO_LIBRARIES ${MALLOC_LIB}) endif() +if(USE_GPERFTOOLS) + list(APPEND FALCO_DEPENDENCIES gperftools) + list(APPEND FALCO_LIBRARIES "${GPERFTOOLS_PROFILER_LIB}") +endif() + if(NOT WIN32) target_sources(falco_application PRIVATE outputs_program.cpp outputs_syslog.cpp) endif()