diff --git a/.gitignore b/.gitignore index a4efc556f..096c0f94d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ vendor* .vs .vscode docker-sync.yml +/third_party/libbpf +/third_party/bpftool #==============================================================================# # Directories to ignore only in root directory diff --git a/CMakeLists.txt b/CMakeLists.txt index 887a78d51..30fedc5b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ add_link_options("LINKER:--build-id=sha1") # Define the include path of cmake scripts list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/util") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/third_party") include(ExtendBuildTypes) @@ -86,16 +87,49 @@ find_package(absl REQUIRED) # libdatadog_profiling include(Findlibdatadog) +# ---- Static analysis ---- +include(ClangTidy) +include(Format) + +# Third parties +add_subdirectory(third_party) + +# todo remove hardcode +set(BPFOBJECT_CLANG_EXE "clang-17") + +# Path to the bpftool executable +set(BPFOBJECT_BPFTOOL_EXE ${CMAKE_CURRENT_BINARY_DIR}/third_party/bpftool/bootstrap/bpftool) + +set(LIBBPF_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/third_party/libbpf) +set(LIBBPF_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/third_party/libbpf/libbpf.a) + +# Path where vmlinux.h will be generated +set(BPFOBJECT_VMLINUX_DIR ${CMAKE_CURRENT_BINARY_DIR}/third_party/vmlinux/${ARCH}) +set(BPFOBJECT_VMLINUX_H ${BPFOBJECT_VMLINUX_DIR}/vmlinux.h) + +# Ensure the target directory exists +file(MAKE_DIRECTORY ${BPFOBJECT_VMLINUX_DIR}) + +# Custom command to generate vmlinux.h +add_custom_command( + OUTPUT ${BPFOBJECT_VMLINUX_H} + COMMAND ${BPFOBJECT_BPFTOOL_EXE} btf dump file /sys/kernel/btf/vmlinux format c > + ${BPFOBJECT_VMLINUX_H} + DEPENDS ${BPFOBJECT_BPFTOOL_EXE} ${BPFOBJECT_VMLINUX_DIR} + COMMENT "Generating vmlinux.h") +add_custom_target(generate_vmlinux_h ALL DEPENDS ${BPFOBJECT_VMLINUX_H} bpftool-build libbpf-build) + +find_package(BpfObject REQUIRED) + +# bpf +add_subdirectory(src/bpf) + # Event Parser add_subdirectory(src/event_parser) # elfutils include(Findelfutils) -# ---- Static analysis ---- -include(ClangTidy) -include(Format) - # Generated code needs to be available for cppcheck to work include(CppcheckConfig) add_dependencies(cppcheck DDProf::Parser) @@ -112,9 +146,6 @@ endif() # Install lib cap to retrieve capabilities include(Findlibcap) -# Third parties -add_subdirectory(third_party) - # ---- Benchmarks ---- option(BUILD_BENCHMARKS "Enable benchmarks" OFF) if(${BUILD_BENCHMARKS}) @@ -124,7 +155,7 @@ endif() # ---- Declaration of DDProf ---- # Compile time definitions string(TOLOWER ${CMAKE_PROJECT_NAME} CMAKE_PROJECT_NAME_LC) -list(APPEND DDPROF_DEFINITION_LIST "MYNAME=\"${CMAKE_PROJECT_NAME_LC}\"") +list(APPEND DDPROF_DEFINITION_LIST "MYNAME=\"${CMAKE_PROJECT_NAME_LC}\"" "EBPF_UNWINDING") if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") list(APPEND DDPROF_DEFINITION_LIST "DDPROF_OPTIM=1") @@ -136,7 +167,7 @@ include(Version) string(APPEND CMAKE_C_FLAGS " ${FRAME_PTR_FLAG}") string(APPEND CMAKE_CXX_FLAGS " ${FRAME_PTR_FLAG}") -list(APPEND DDPROF_INCLUDE_LIST ${CMAKE_SOURCE_DIR}/include) +list(APPEND DDPROF_INCLUDE_LIST ${CMAKE_SOURCE_DIR}/include ${BPFOBJECT_VMLINUX_DIR}) # Find the source files aux_source_directory(src COMMON_SRC) @@ -243,6 +274,7 @@ target_include_directories(dd_profiling-embedded PUBLIC ${CMAKE_SOURCE_DIR}/incl ${CMAKE_SOURCE_DIR}/include) set_target_properties(dd_profiling-embedded PROPERTIES PUBLIC_HEADER "${CMAKE_SOURCE_DIR}/include/lib/dd_profiling.h") +# TODO: does EBPF make sense in embedded mode ? # Link libstdc++/libgcc statically and export only profiler API target_static_libcxx(dd_profiling-embedded) @@ -302,8 +334,10 @@ add_exe( ddprof ${DDPROF_GLOBAL_SRC} LIBRARIES ${DDPROF_LIBRARY_LIST} DEFINITIONS ${DDPROF_DEFINITION_LIST}) +add_dependencies(ddprof sample_processor_skel_build) target_link_libraries(ddprof PRIVATE libddprofiling_embedded_object CLI11 absl::base - absl::str_format) + absl::str_format sample_processor_skel) + if(USE_LOADER) target_compile_definitions(ddprof PRIVATE "DDPROF_USE_LOADER") target_link_libraries(ddprof PRIVATE libdd_loader_object) diff --git a/cmake/FindBpfObject.cmake b/cmake/FindBpfObject.cmake new file mode 100644 index 000000000..d589a03f6 --- /dev/null +++ b/cmake/FindBpfObject.cmake @@ -0,0 +1,204 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#[=======================================================================[.rst: +FindBpfObject +-------- + +Find BpfObject + +This module finds if all the dependencies for eBPF Compile-Once-Run-Everywhere +programs are available and where all the components are located. + +The caller may set the following variables to disable automatic +search/processing for the associated component: + + ``BPFOBJECT_BPFTOOL_EXE`` + Path to ``bpftool`` binary + + ``BPFOBJECT_CLANG_EXE`` + Path to ``clang`` binary + + ``LIBBPF_INCLUDE_DIRS`` + Path to ``libbpf`` development headers + + ``LIBBPF_LIBRARIES`` + Path to `libbpf` library + + ``BPFOBJECT_VMLINUX_H`` + Path to ``vmlinux.h`` generated by ``bpftool``. If unset, this module will + attempt to automatically generate a copy. + +This module sets the following result variables: + +:: + + BpfObject_FOUND = TRUE if all components are found + + +This module also provides the ``bpf_object()`` macro. This macro generates a +cmake interface library for the BPF object's generated skeleton as well +as the associated dependencies. + +.. code-block:: cmake + + bpf_object( ) + +Given an abstract ```` for a BPF object and the associated ```` +file, generates an interface library target, ``_skel``, that may be +linked against by other cmake targets. + +Example Usage: + +:: + + find_package(BpfObject REQUIRED) + bpf_object(myobject myobject.bpf.c) + add_executable(myapp myapp.c) + target_link_libraries(myapp myobject_skel) + +#]=======================================================================] + +if(NOT BPFOBJECT_BPFTOOL_EXE) + find_program( + BPFOBJECT_BPFTOOL_EXE + HINTS ${CMAKE_BINARY_DIR}/third_party/bpftool/bootstrap + NAMES bpftool + DOC "Path to bpftool executable") +endif() + +if(NOT BPFOBJECT_CLANG_EXE) + find_program( + BPFOBJECT_CLANG_EXE + NAMES clang + DOC "Path to clang executable") + + execute_process( + COMMAND ${BPFOBJECT_CLANG_EXE} --version + OUTPUT_VARIABLE CLANG_version_output + ERROR_VARIABLE CLANG_version_error + RESULT_VARIABLE CLANG_version_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Check that clang is new enough + if(${CLANG_version_result} EQUAL 0) + if("${CLANG_version_output}" MATCHES "clang version ([^\n]+)\n") + # Transform X.Y.Z into X;Y;Z which can then be interpreted as a list + set(CLANG_VERSION "${CMAKE_MATCH_1}") + string(REPLACE "." ";" CLANG_VERSION_LIST ${CLANG_VERSION}) + list(GET CLANG_VERSION_LIST 0 CLANG_VERSION_MAJOR) + + # Anything older than clang 10 doesn't really work + string(COMPARE LESS ${CLANG_VERSION_MAJOR} 10 CLANG_VERSION_MAJOR_LT10) + if(${CLANG_VERSION_MAJOR_LT10}) + message(FATAL_ERROR "clang ${CLANG_VERSION} is too old for BPF CO-RE") + endif() + + message(STATUS "Found clang version: ${CLANG_VERSION}") + else() + message(FATAL_ERROR "Failed to parse clang version string: ${CLANG_version_output}") + endif() + else() + message( + FATAL_ERROR + "Command \"${BPFOBJECT_CLANG_EXE} --version\" failed with output:\n${CLANG_version_error}") + endif() +endif() + +if(NOT LIBBPF_INCLUDE_DIRS OR NOT LIBBPF_LIBRARIES) + find_package(LibBpf) +endif() + +if(BPFOBJECT_VMLINUX_H) + get_filename_component(GENERATED_VMLINUX_DIR ${BPFOBJECT_VMLINUX_H} DIRECTORY) +elseif(BPFOBJECT_BPFTOOL_EXE) + # Generate vmlinux.h + set(GENERATED_VMLINUX_DIR ${CMAKE_CURRENT_BINARY_DIR}) + set(BPFOBJECT_VMLINUX_H ${GENERATED_VMLINUX_DIR}/vmlinux.h) + execute_process( + COMMAND ${BPFOBJECT_BPFTOOL_EXE} btf dump file /sys/kernel/btf/vmlinux format c + OUTPUT_FILE ${BPFOBJECT_VMLINUX_H} + ERROR_VARIABLE VMLINUX_error + RESULT_VARIABLE VMLINUX_result) + if(${VMLINUX_result} EQUAL 0) + set(VMLINUX ${BPFOBJECT_VMLINUX_H}) + else() + message(FATAL_ERROR "Failed to dump vmlinux.h from BTF: ${VMLINUX_error}") + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + BpfObject REQUIRED_VARS BPFOBJECT_BPFTOOL_EXE BPFOBJECT_CLANG_EXE LIBBPF_INCLUDE_DIRS + LIBBPF_LIBRARIES GENERATED_VMLINUX_DIR) + +# Get clang bpf system includes +execute_process( + COMMAND + bash -c + "${BPFOBJECT_CLANG_EXE} -v -E - < /dev/null 2>&1 | + sed -n '/<...> search starts here:/,/End of search list./{ s| \\(/.*\\)|-idirafter \\1|p }'" + OUTPUT_VARIABLE CLANG_SYSTEM_INCLUDES_output + ERROR_VARIABLE CLANG_SYSTEM_INCLUDES_error + RESULT_VARIABLE CLANG_SYSTEM_INCLUDES_result + OUTPUT_STRIP_TRAILING_WHITESPACE) +if(${CLANG_SYSTEM_INCLUDES_result} EQUAL 0) + separate_arguments(CLANG_SYSTEM_INCLUDES UNIX_COMMAND ${CLANG_SYSTEM_INCLUDES_output}) + message(STATUS "BPF system include flags: ${CLANG_SYSTEM_INCLUDES}") +else() + message(FATAL_ERROR "Failed to determine BPF system includes: ${CLANG_SYSTEM_INCLUDES_error}") +endif() + +# Get target arch +execute_process( + COMMAND uname -m + COMMAND sed -e "s/x86_64/x86/" -e "s/aarch64/arm64/" -e "s/ppc64le/powerpc/" -e "s/mips.*/mips/" + -e "s/riscv64/riscv/" + OUTPUT_VARIABLE ARCH_output + ERROR_VARIABLE ARCH_error + RESULT_VARIABLE ARCH_result + OUTPUT_STRIP_TRAILING_WHITESPACE) +if(${ARCH_result} EQUAL 0) + set(ARCH ${ARCH_output}) + message(STATUS "BPF target arch: ${ARCH}") +else() + message(FATAL_ERROR "Failed to determine target architecture: ${ARCH_error}") +endif() + +# Public macro +macro(bpf_object name input) + set(BPF_C_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${input}) + set(BPF_O_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.bpf.o) + set(BPF_SKEL_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.skel.h) + set(OUTPUT_TARGET ${name}_skel) + + message(STATUS "Start of bpf object" ${BPF_C_FILE} - clang = ${BPFOBJECT_CLANG_EXE}) + + # Build BPF object file + add_custom_command( + OUTPUT ${BPF_O_FILE} + COMMAND + ${BPFOBJECT_CLANG_EXE} -g -O2 -target bpf -D__TARGET_ARCH_${ARCH} ${CLANG_SYSTEM_INCLUDES} + -I${GENERATED_VMLINUX_DIR} -I${CMAKE_SOURCE_DIR}/include -isystem ${LIBBPF_INCLUDE_DIRS} -c + ${BPF_C_FILE} -o ${BPF_O_FILE} + COMMAND_EXPAND_LISTS VERBATIM + DEPENDS ${BPF_C_FILE} + COMMENT "[clang] Building BPF object: ${name}") + + message(STATUS "Output = ${BPF_O_FILE} target = ${OUTPUT_TARGET}") + + # Build BPF skeleton header + add_custom_command( + OUTPUT ${BPF_SKEL_FILE} + COMMAND bash -c "${BPFOBJECT_BPFTOOL_EXE} gen skeleton ${BPF_O_FILE} > ${BPF_SKEL_FILE}" + VERBATIM + DEPENDS ${BPF_O_FILE} + COMMENT "[skel] Building BPF skeleton: ${name}") + + add_custom_target(${OUTPUT_TARGET}_build ALL DEPENDS ${BPF_O_FILE} ${BPF_SKEL_FILE}) + + add_library(${OUTPUT_TARGET} INTERFACE) + target_sources(${OUTPUT_TARGET} INTERFACE ${BPF_SKEL_FILE}) + target_include_directories(${OUTPUT_TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) + target_include_directories(${OUTPUT_TARGET} SYSTEM INTERFACE ${LIBBPF_INCLUDE_DIRS}) + target_link_libraries(${OUTPUT_TARGET} INTERFACE ${LIBBPF_LIBRARIES} -lelf -lz) +endmacro() diff --git a/cmake/FindLibBpf.cmake b/cmake/FindLibBpf.cmake new file mode 100644 index 000000000..814b229b2 --- /dev/null +++ b/cmake/FindLibBpf.cmake @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +find_path( + LIBBPF_INCLUDE_DIRS + NAMES bpf/bpf.h bpf/btf.h bpf/libbpf.h + PATHS /usr/include /usr/local/include /opt/local/include /sw/include ENV CPATH) + +find_library( + LIBBPF_LIBRARIES + NAMES bpf + PATHS /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ENV + LIBRARY_PATH + ENV + LD_LIBRARY_PATH) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(LibBpf "Please install the libbpf development package" + LIBBPF_LIBRARIES LIBBPF_INCLUDE_DIRS) + +mark_as_advanced(LIBBPF_INCLUDE_DIRS LIBBPF_LIBRARIES) diff --git a/docs/eBPF_unwinding.md b/docs/eBPF_unwinding.md new file mode 100644 index 000000000..5f8f8418b --- /dev/null +++ b/docs/eBPF_unwinding.md @@ -0,0 +1,26 @@ +# eBPF unwinding + +## Motivation + +The native profiler was initially designed on top of perf event open. +The capture of every sample causes 32kB of memory to be copied at every sample. + +What would it mean to have an eBPF unwinding instead ? + +## Build + +Thie build does not work well +Manual target generations are still necessary +```bash +make libbpf-build +make bpftool-build +make generate_vmlinux_h +``` + +## Current state + +The proposal is to attach a BPF sample processor + +## Acknowledgements + +This mostly reuses the great tools provided by libbpf. diff --git a/include/base_frame_symbol_lookup.hpp b/include/base_frame_symbol_lookup.hpp index d0221f2cf..72c5161f7 100644 --- a/include/base_frame_symbol_lookup.hpp +++ b/include/base_frame_symbol_lookup.hpp @@ -16,6 +16,8 @@ class BaseFrameSymbolLookup { DsoSymbolLookup &dso_symbol_lookup, DsoHdr &dso_hdr); + SymbolIdx_t get_or_insert_comm(const char*comm, SymbolTable &symbol_table); + // Erase symbol lookup for this pid (warning symbols still exist) void erase(pid_t pid) { _bin_map.erase(pid); @@ -36,6 +38,8 @@ class BaseFrameSymbolLookup { // holds generic symbol for this pid and a number of lookups to keep track of // failures looking for a given binary std::unordered_map _pid_map; + + std::unordered_map _comm_map; }; } // namespace ddprof diff --git a/include/bpf/sample_processor.h b/include/bpf/sample_processor.h new file mode 100644 index 000000000..92d6b59dd --- /dev/null +++ b/include/bpf/sample_processor.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2022 Meta Platforms, Inc. */ +#pragma once + +#ifndef TASK_COMM_LEN +# define TASK_COMM_LEN 16 +#endif + +#ifndef MAX_STACK_DEPTH +# define MAX_STACK_DEPTH 128 +#endif + +typedef __u64 stack_trace_t[MAX_STACK_DEPTH]; + +struct stacktrace_event { + __u32 pid; + __u32 cpu_id; + char comm[TASK_COMM_LEN]; + __s32 kstack_sz; + __s32 ustack_sz; + stack_trace_t kstack; + stack_trace_t ustack; +}; diff --git a/include/ddprof_worker.hpp b/include/ddprof_worker.hpp index 4c38250ee..018db89af 100644 --- a/include/ddprof_worker.hpp +++ b/include/ddprof_worker.hpp @@ -26,6 +26,8 @@ DDRes ddprof_worker_cycle(DDProfContext &ctx, DDRes ddprof_worker_process_event(const perf_event_header *hdr, int watcher_pos, DDProfContext &ctx); +DDRes ddprof_worker_process_bpf_events(DDProfContext &ctx); + // Only init unwinding elements DDRes worker_library_init(DDProfContext &ctx, PersistentWorkerState *persistent_worker_state); diff --git a/include/ddprof_worker_context.hpp b/include/ddprof_worker_context.hpp index 495a8eaae..b6cefee98 100644 --- a/include/ddprof_worker_context.hpp +++ b/include/ddprof_worker_context.hpp @@ -8,10 +8,55 @@ #include "live_allocation.hpp" #include "pevent.hpp" #include "proc_status.hpp" +#include "bpf/sample_processor.h" #include #include +#include +#include + +template +class SimpleRingBuffer { +public: + SimpleRingBuffer() : head(0), tail(0) {} + + bool try_push(const T& value) { + size_t current_head = head.load(std::memory_order_relaxed); + size_t next_head = nextIndex(current_head); + if (next_head == tail.load(std::memory_order_acquire)) { + return false; // Buffer is full + } + buffer[current_head] = value; + head.store(next_head, std::memory_order_release); + return true; + } + + bool pop(T& value) { + size_t current_tail = tail.load(std::memory_order_relaxed); + if (current_tail == head.load(std::memory_order_acquire)) { + return false; // Buffer is empty + } + value = buffer[current_tail]; + tail.store(nextIndex(current_tail), std::memory_order_release); + return true; + } + + bool empty() const { + return head.load(std::memory_order_acquire) == tail.load(std::memory_order_relaxed); + } + +private: + size_t nextIndex(size_t index) const { + return (index + 1) % Size; + } + + std::array buffer; + std::atomic head; + std::atomic tail; +}; + + namespace ddprof { struct DDProfExporter; @@ -21,6 +66,11 @@ struct StackHandler; struct UnwindState; struct UserTags; +struct BPFEvents{ + std::atomic keep_running{true}; + SimpleRingBuffer _events; +}; + // Mutable states within a worker struct DDProfWorkerContext { // Persistent reference to the state shared accross workers @@ -32,6 +82,9 @@ struct DDProfWorkerContext { volatile bool exp_error{false}; pthread_t exp_tid{0}; UnwindState *us{}; + + BPFEvents _bpf_events; + UserTags *user_tags{}; ProcStatus proc_status{}; std::chrono::steady_clock::time_point diff --git a/include/perf.hpp b/include/perf.hpp index fca1cf928..65ecae1f6 100644 --- a/include/perf.hpp +++ b/include/perf.hpp @@ -25,7 +25,7 @@ inline constexpr int k_default_buffer_size_shift{6}; inline constexpr int k_mpsc_buffer_size_shift{8}; // sample frequency check -inline constexpr std::chrono::milliseconds k_sample_default_wakeup{100}; +inline constexpr std::chrono::milliseconds k_sample_default_wakeup{10}; struct read_format { uint64_t value; // The value of the event @@ -147,6 +147,14 @@ long get_page_size(); size_t get_mask_from_size(size_t size); const char *perf_type_str(int type_id); +std::vector bpf_config_from_watcher(const PerfWatcher *watcher, + bool extras, + PerfClockSource perf_clock_source); + +perf_event_attr perf_bpf_config(const PerfWatcher *watcher, + bool extras, + PerfClockSource perf_clock_source); + std::vector all_perf_configs_from_watcher(const PerfWatcher *watcher, bool extras, PerfClockSource perf_clock_source); diff --git a/include/pevent.hpp b/include/pevent.hpp index 6cfbeb143..8847af659 100644 --- a/include/pevent.hpp +++ b/include/pevent.hpp @@ -10,6 +10,10 @@ #include +// fwd declaration of bpf typ +struct sample_processor_bpf; +struct bpf_link; +struct ring_buffer; // ebpf ring buffer namespace ddprof { // Takes into account number of watchers * number of CPUs @@ -19,6 +23,7 @@ struct PEvent { int watcher_pos; // Index to the watcher (containing perf event config) int fd; // Underlying perf event FD for perf_events, otherwise an eventfd that // signals data is available in ring buffer + // or usual stack sample ring buffer int mapfd; // FD for ring buffer, same as `fd` for perf events int attr_idx; // matching perf_event_attr size_t ring_buffer_size; // size of the ring buffer @@ -28,8 +33,20 @@ struct PEvent { RingBuffer rb; // metadata and buffers for processing perf ringbuffer }; +struct BPFPevent { + int watcher_pos; // Index to the watcher (containing perf event config) + int fd; // Underlying perf event FD for perf_events, otherwise an eventfd that + // signals data is available in ring buffer + // bpf link (includes its own ring buffers) + bpf_link *link; +}; + struct PEventHdr { PEvent pes[k_max_nb_perf_event_open]; + std::vector bpf_pes; + // loaded sample processor program + sample_processor_bpf *sample_processor; + ring_buffer *bpf_ring_buf; // Attributes of successful perf event opens size_t size; size_t max_size; diff --git a/include/pevent_lib.hpp b/include/pevent_lib.hpp index b0e334ac3..ac0cbfa7e 100644 --- a/include/pevent_lib.hpp +++ b/include/pevent_lib.hpp @@ -18,6 +18,11 @@ void pevent_init(PEventHdr *pevent_hdr); DDRes pevent_open(DDProfContext *ctx, pid_t pid, int num_cpu, PEventHdr *pevent_hdr); +DDRes pevent_open_bpf(DDProfContext &ctx, pid_t pid, int num_cpu, + PEventHdr *pevent_hdr); + +DDRes pevent_link_bpf(PEventHdr *pevent_hdr); + /// Setup mmap buffers according to content of peventhdr DDRes pevent_mmap(PEventHdr *pevent_hdr, bool use_override); diff --git a/include/unwind_dwfl.hpp b/include/unwind_dwfl.hpp index 7264157db..6c7b8f8ae 100644 --- a/include/unwind_dwfl.hpp +++ b/include/unwind_dwfl.hpp @@ -5,7 +5,27 @@ #pragma once +typedef signed char __s8; +typedef unsigned char __u8; +typedef short int __s16; +typedef short unsigned int __u16; +typedef int __s32; +typedef unsigned int __u32; +typedef long long int __s64; +typedef long long unsigned int __u64; +typedef __s8 s8; +typedef __u8 u8; +typedef __s16 s16; +typedef __u16 u16; +typedef __s32 s32; +typedef __u32 u32; + +typedef __s64 s64; + +typedef __u64 u64; + #include "ddres_def.hpp" +#include "bpf/sample_processor.h" namespace ddprof { @@ -15,4 +35,6 @@ DDRes unwind_init_dwfl(UnwindState *us); DDRes unwind_dwfl(UnwindState *us); +DDRes unwind_symbolize_only(UnwindState *us, stacktrace_event &event); + } // namespace ddprof diff --git a/include/unwind_helpers.hpp b/include/unwind_helpers.hpp index 7788dd57c..165964403 100644 --- a/include/unwind_helpers.hpp +++ b/include/unwind_helpers.hpp @@ -20,6 +20,8 @@ bool is_max_stack_depth_reached(const UnwindState &us); DDRes add_frame(SymbolIdx_t symbol_idx, MapInfoIdx_t map_idx, ElfAddress_t pc, UnwindState *us); +void add_virtual_comm_frame(UnwindState *us, const char *comm); + void add_common_frame(UnwindState *us, SymbolErrors lookup_case); void add_dso_frame(UnwindState *us, const Dso &dso, diff --git a/src/base_frame_symbol_lookup.cc b/src/base_frame_symbol_lookup.cc index 030fc2b71..ced694f75 100644 --- a/src/base_frame_symbol_lookup.cc +++ b/src/base_frame_symbol_lookup.cc @@ -74,4 +74,21 @@ BaseFrameSymbolLookup::get_or_insert(pid_t pid, SymbolTable &symbol_table, return symbol_idx; } +SymbolIdx_t BaseFrameSymbolLookup::get_or_insert_comm(const char*comm, SymbolTable &symbol_table){ + // todo: make hashable + std::string comm_string = comm; + auto const it_comm = _comm_map.find(comm_string); + int symbol_idx = -1; + if (it_comm != _comm_map.end()) { + symbol_idx = it_comm->second; + } else { + symbol_idx = symbol_table.size(); + // Symbol(std::string symname, std::string demangle_name, uint32_t lineno, + // std::string srcpath) + symbol_table.push_back(Symbol{{}, comm, 0, {}}); + _comm_map[comm_string] = symbol_idx; + } + return symbol_idx; +} + } // namespace ddprof diff --git a/src/bpf/CMakeLists.txt b/src/bpf/CMakeLists.txt new file mode 100644 index 000000000..f0ccd351b --- /dev/null +++ b/src/bpf/CMakeLists.txt @@ -0,0 +1,5 @@ +# Defines the skeleton +bpf_object(sample_processor sample_processor.bpf.c) +add_dependencies(sample_processor_skel libbpf-build bpftool-build generate_vmlinux_h) +message(STATUS "bpf object status =" ${BpfObject_FOUND}) +target_include_directories(sample_processor_skel INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/bpf/sample_processor.bpf.c b/src/bpf/sample_processor.bpf.c new file mode 100644 index 000000000..1c2ee8ab7 --- /dev/null +++ b/src/bpf/sample_processor.bpf.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// inspired from the examples given within libbpf repository + +#include "vmlinux.h" +#include +#include +#include + +#include "bpf/sample_processor.h" + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} events SEC(".maps"); + +SEC("perf_event") +int process_sample(struct bpf_perf_event_data *ctx) { + int pid = bpf_get_current_pid_tgid() >> 32; + int cpu_id = bpf_get_smp_processor_id(); + struct stacktrace_event *event; + event = bpf_ringbuf_reserve(&events, sizeof(*event), 0); + if (!event) + return 1; + event->pid = pid; + event->cpu_id = cpu_id; + if (bpf_get_current_comm(event->comm, sizeof(event->comm))) + event->comm[0] = 0; + + // todo: how good is this versus storing stack ids ? + event->kstack_sz = + bpf_get_stack(ctx, event->kstack, sizeof(event->kstack), 0); + event->ustack_sz = bpf_get_stack(ctx, event->ustack, sizeof(event->ustack), + BPF_F_USER_STACK); + bpf_ringbuf_submit(event, 0); + return 0; +} diff --git a/src/bpf_helper.cc b/src/bpf_helper.cc new file mode 100644 index 000000000..e69de29bb diff --git a/src/ddprof_worker.cc b/src/ddprof_worker.cc index 82994f7a3..e555601c8 100644 --- a/src/ddprof_worker.cc +++ b/src/ddprof_worker.cc @@ -22,6 +22,7 @@ #include "unwind.hpp" #include "unwind_helpers.hpp" #include "unwind_state.hpp" +#include "unwind_dwfl.hpp" #include #include @@ -786,4 +787,37 @@ DDRes ddprof_worker_process_event(const perf_event_header *hdr, int watcher_pos, CatchExcept2DDRes(); return {}; } + +DDRes ddprof_worker_process_bpf_events(DDProfContext &ctx) { + + // todo think about watcher pos + int watcher_pos = 0; + PerfWatcher *watcher = &ctx.watchers[watcher_pos]; + + stacktrace_event event; + UnwindState *us = ctx.worker_ctx.us; + while (ctx.worker_ctx._bpf_events._events.pop(event)) { + // + DDRES_CHECK_FWD(unwind_symbolize_only(ctx.worker_ctx.us, event)); + uint64_t const sample_val = static_cast(watcher->sample_period); + // in lib mode we don't aggregate (protect to avoid link failures) + int const i_export = ctx.worker_ctx.i_current_pprof; + DDProfPProf *pprof = ctx.worker_ctx.pprof[i_export]; + // We want to emit 0 for the time unless timeline is specified, and if + // it is, we also want to adjust the source to be in the system_time + // frame + uint64_t timestamp = 0; + const DDProfValuePack pack{static_cast(sample_val), 1, timestamp}; + DDRES_CHECK_FWD(pprof_aggregate(&ctx.worker_ctx.us->output, + ctx.worker_ctx.us->symbol_hdr, pack, + watcher, kSumPos, pprof)); + if (ctx.params.show_samples) { + ddprof_print_sample(us->output, us->symbol_hdr, sample_val, kSumPos, + *watcher); + } + ddprof_stats_add(STATS_SAMPLE_COUNT, 1, nullptr); + } + return {}; +} + } // namespace ddprof diff --git a/src/perf.cc b/src/perf.cc index f99f4f18e..fef394f18 100644 --- a/src/perf.cc +++ b/src/perf.cc @@ -176,6 +176,30 @@ int perfdisown(void *region, size_t size) { : -1; } +perf_event_attr perf_bpf_config(const PerfWatcher *watcher, + bool extras, + PerfClockSource perf_clock_source) { + // todo review this + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = watcher->type; + attr.size = sizeof(attr); + attr.config = watcher->config; + attr.sample_period = watcher->sample_period; // Equivalently, freq + attr.freq = watcher->options.is_freq; + set_perf_clock_source(attr, perf_clock_source); + +// hardcoded config for tests +// memset(&attr, 0, sizeof(attr)); +// attr.type = PERF_TYPE_HARDWARE; +// attr.size = sizeof(attr); +// attr.config = PERF_COUNT_HW_CPU_CYCLES; +// attr.sample_freq = 10; +// attr.freq = 1; + + return attr; +} + // return attr sorted by priority std::vector all_perf_configs_from_watcher(const PerfWatcher *watcher, bool extras, diff --git a/src/perf_mainloop.cc b/src/perf_mainloop.cc index 952f55775..7af1cdfbc 100644 --- a/src/perf_mainloop.cc +++ b/src/perf_mainloop.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -152,13 +153,14 @@ ReplyMessage create_reply_message(const DDProfContext &ctx) { void pollfd_setup(const PEventHdr *pevent_hdr, struct pollfd *pfd, int *pfd_len) { - *pfd_len = pevent_hdr->size; + *pfd_len = 0; const PEvent *pes = pevent_hdr->pes; // Setup poll() to watch perf_event file descriptors - for (int i = 0; i < *pfd_len; ++i) { + for (int i = 0; i < pevent_hdr->size; ++i) { // NOTE: if fd is negative, it will be ignored - pfd[i].fd = pes[i].fd; - pfd[i].events = POLLIN; + pfd[*pfd_len].fd = pes[i].fd; + pfd[*pfd_len].events = POLLIN; + ++(*pfd_len); } } @@ -240,6 +242,15 @@ worker_process_ring_buffers(PEvent *pes, int pe_len, DDProfContext &ctx, return {}; } +void bpf_event_polling(DDProfContext& ctx) { + if (ctx.worker_ctx.pevent_hdr.bpf_ring_buf) { + LG_DBG("Entering BPF loop"); + while (ctx.worker_ctx._bpf_events.keep_running && + ring_buffer__poll(ctx.worker_ctx.pevent_hdr.bpf_ring_buf, 100) >= 0) { + } + } +} + DDRes worker_loop(DDProfContext &ctx, const WorkerAttr *attr, PersistentWorkerState *persistent_worker_state) { @@ -267,6 +278,9 @@ DDRes worker_loop(DDProfContext &ctx, const WorkerAttr *attr, int const ring_buffer_start_idx = 0; bool stop = false; + // bpf loop + std::jthread bpf_thread(bpf_event_polling, std::ref(ctx)); + // Worker poll loop while (!stop) { // Convenience structs @@ -305,12 +319,21 @@ DDRes worker_loop(DDProfContext &ctx, const WorkerAttr *attr, &ring_buffer_start_idx)); DDRES_CHECK_FWD(ddprof_worker_maybe_export(ctx, now)); + // symbolize bpf events + int cpt = 0; + if (!ctx.worker_ctx._bpf_events._events.empty() && ++cpt < 1000) { + DDRES_CHECK_FWD(ddprof_worker_process_bpf_events(ctx)); + } + if (ctx.worker_ctx.persistent_worker_state->restart_worker) { // return directly no need to do a final export return {}; } } + ctx.worker_ctx._bpf_events.keep_running = false; + bpf_thread.join(); + // export current samples before exiting DDRES_CHECK_FWD(ddprof_worker_cycle(ctx, {}, true)); return {}; diff --git a/src/perf_watcher.cc b/src/perf_watcher.cc index d2eb6e82c..5469d4c4c 100644 --- a/src/perf_watcher.cc +++ b/src/perf_watcher.cc @@ -247,7 +247,7 @@ std::string_view watcher_help_text() { "Please note that this documentation is currently under construction. We recommend the use of presets.\n" "Not all options may be fully supported within the Datadog UI at present, and the described grammar is subject to change.\n" "Exercise caution and double-check your configurations before implementation.\n"; - // clang-format on +// clang-format on return help_text; } diff --git a/src/pevent_lib.cc b/src/pevent_lib.cc index 976d4ceed..3aae7f032 100644 --- a/src/pevent_lib.cc +++ b/src/pevent_lib.cc @@ -24,6 +24,43 @@ #include #include +//#define EBPF_UNWINDING +#ifdef EBPF_UNWINDING +extern "C" { +# include "bpf/sample_processor.h" +# include "sample_processor.skel.h" +# include +# include +} + +// todo move me in relevant place +/* Receive events from the ring buffer. */ +static int event_handler(void *_ctx, void *data, size_t size) { + stacktrace_event *event = reinterpret_cast(data); +#ifdef DEBUG + fprintf(stderr, "Event[%d] -- COMM:%s, CPU=%d\n", + event->pid, + event->comm, + event->cpu_id); +#endif + if (event->kstack_sz <= 0 && event->ustack_sz <= 0) { + fprintf(stderr, "error in bpf handler %d \n", __LINE__); + return 1; + } + + if (_ctx) { + ddprof::BPFEvents *bpf_events = reinterpret_cast(_ctx); + + if (bpf_events->_events.try_push(*event)) { +#ifdef DEBUG + fprintf(stderr, "Success pushing bpf event \n"); +#endif + } + } + return 0; +} +#endif + namespace ddprof { namespace { @@ -39,6 +76,14 @@ DDRes pevent_create(PEventHdr *pevent_hdr, int watcher_idx, return {}; } +void pevent_bpf_create(PEventHdr *pevent_hdr, + int watcher_pos, + int fd){ + pevent_hdr->bpf_pes.push_back({.watcher_pos = watcher_pos, + .fd = fd, .link = nullptr}); + return; +} + void display_system_config() { int val; DDRes const res = sys_perf_event_paranoid(val); @@ -164,6 +209,39 @@ int pevent_compute_min_mmap_order(int min_buffer_size_order, return ret_order; } +DDRes pevent_open_bpf(DDProfContext &ctx, pid_t pid, int num_cpu, + PEventHdr *pevent_hdr) { + assert(pevent_hdr->size == 0); // check for previous init + for (unsigned watcher_idx = 0; watcher_idx < ctx.watchers.size(); + ++watcher_idx) { + PerfWatcher *watcher = &ctx.watchers[watcher_idx]; + if (watcher->type >= kDDPROF_TYPE_CUSTOM + || watcher->type == PERF_COUNT_SW_DUMMY) { + // dummy or allocation events should not be managed with bpf + continue; + } + perf_event_attr attr = perf_bpf_config(watcher, false, + ctx.perf_clock_source); + // used the fixed attr for the others + for (int cpu_idx = 0; cpu_idx < num_cpu; ++cpu_idx) { + int const fd = perf_event_open(&attr, pid, cpu_idx, -1, PERF_FLAG_FD_CLOEXEC); + LG_DBG("Create BPF perf event with fd = %d", fd); + if (fd == -1) { + DDRES_RETURN_ERROR_LOG(DD_WHAT_PERFOPEN, + "Error (BPF flow) calling perf_event_open on watcher %u.%d (%s)", + watcher_idx, + cpu_idx, + strerror(errno)); + } + pevent_bpf_create(pevent_hdr, watcher_idx, fd); + } + // Copy the successful config + pevent_hdr->attrs[pevent_hdr->nb_attrs] = attr; + ++pevent_hdr->nb_attrs; + } + return {}; +} + DDRes pevent_open(DDProfContext &ctx, pid_t pid, int num_cpu, PEventHdr *pevent_hdr) { assert(pevent_hdr->size == 0); // check for previous init @@ -208,6 +286,27 @@ DDRes pevent_mmap_event(PEvent *event) { return {}; } + +DDRes pevent_link_bpf(PEventHdr *pevent_hdr) { +#ifdef EBPF_UNWINDING + // mmaps using ebpf + assert(pevent_hdr->sample_processor); + auto *skel = pevent_hdr->sample_processor; + auto defer_munmap = make_defer([&] { pevent_munmap(pevent_hdr); }); + + for (size_t k = 0; k < pevent_hdr->bpf_pes.size(); ++k) { + pevent_hdr->bpf_pes[k].link = + bpf_program__attach_perf_event(skel->progs.process_sample, pevent_hdr->bpf_pes[k].fd); + if (!pevent_hdr->bpf_pes[k].link) { + DDRES_RETURN_ERROR_LOG(DD_WHAT_PERFOPEN, + "Unable to link bpf program (%lu)", k); + } + } + defer_munmap.release(); +#endif + return {}; +} + DDRes pevent_mmap(PEventHdr *pevent_hdr, bool use_override) { // Switch user if needed (when root switch to nobody user) // Pinned memory is accounted by the kernel by (real) uid across containers @@ -238,14 +337,63 @@ DDRes pevent_mmap(PEventHdr *pevent_hdr, bool use_override) { return {}; } +#ifdef EBPF_UNWINDING +static DDRes pevent_load_bpf(DDProfContext &ctx, + PEventHdr *pevent_hdr) { + // we could chose to have separate maps ? + sample_processor_bpf *skel = sample_processor_bpf__open_and_load(); + if (skel) { + LG_DBG("Using sample_processor_bpf to capture events"); + pevent_hdr->sample_processor = skel; + pevent_hdr->bpf_ring_buf = ring_buffer__new(bpf_map__fd(skel->maps.events), + event_handler, + reinterpret_cast(&ctx.worker_ctx._bpf_events), + NULL); + if (!pevent_hdr->bpf_ring_buf) { + DDRES_RETURN_ERROR_LOG(DD_WHAT_PERFRB, + "error when allocating ring buffer"); + + } + return {}; + } else { + LG_DBG("Not able to load sample_processor_bpf"); + } + // No error here + DDRES_RETURN_ERROR_LOG(DD_WHAT_PERFRB, + "error when loading bpf program"); +} +#endif + DDRes pevent_setup(DDProfContext &ctx, pid_t pid, int num_cpu, PEventHdr *pevent_hdr) { - DDRES_CHECK_FWD(pevent_open(ctx, pid, num_cpu, pevent_hdr)); - if (!IsDDResOK(pevent_mmap(pevent_hdr, true))) { - LG_NTC("Retrying attachment without user override"); +#ifdef EBPF_UNWINDING + // attempt to load bpf program + if(IsDDResOK(pevent_load_bpf(ctx, pevent_hdr))) { + LG_DBG("Success loading BPF program"); + DDRES_CHECK_FWD(pevent_open_bpf(ctx, pid, num_cpu, pevent_hdr)); + // bpf links first to know which we should mmap + DDRES_CHECK_FWD(pevent_link_bpf(pevent_hdr)); + // slightly hacky addition of dummy + // todo fix bug with allocation profiler (reorder watchers earlier) + const PerfWatcher *dummy_watcher = ewatcher_from_str("sDUM"); + ctx.watchers.push_back(*dummy_watcher); + DDRES_CHECK_FWD(pevent_open_all_cpus( + &(ctx.watchers.back()), + static_cast(ctx.watchers.size() - 1), + pid, num_cpu, + ctx.perf_clock_source, pevent_hdr)); + // mmap only the relevant configs DDRES_CHECK_FWD(pevent_mmap(pevent_hdr, false)); + } else +#endif + { + // non ebpf flow + DDRES_CHECK_FWD(pevent_open(ctx, pid, num_cpu, pevent_hdr)); + if (!IsDDResOK(pevent_mmap(pevent_hdr, true))) { + LG_NTC("Retrying attachment without user override"); + DDRES_CHECK_FWD(pevent_mmap(pevent_hdr, false)); + } } - return {}; } @@ -287,6 +435,15 @@ DDRes pevent_munmap(PEventHdr *pevent_hdr) { } } +#ifdef EBPF_UNWINDING + for (size_t k = 0; k < pevent_hdr->bpf_pes.size(); ++k) { + if (pevent_hdr->bpf_pes[k].link) { + bpf_link__destroy(pevent_hdr->bpf_pes[k].link); + // TODO: check if this effectively closes the fd ? + pes[k].fd = -1; + } + } +#endif return res; } @@ -341,6 +498,14 @@ DDRes pevent_cleanup(PEventHdr *pevent_hdr) { if (DDRes const ret_tmp = pevent_close(pevent_hdr); !IsDDResOK((ret_tmp))) { ret = ret_tmp; } +#ifdef EBPF_UNWINDING + if (pevent_hdr->sample_processor) { + sample_processor_bpf__destroy(pevent_hdr->sample_processor); + } + if (pevent_hdr->bpf_ring_buf) { + ring_buffer__free(pevent_hdr->bpf_ring_buf); + } +#endif return ret; } -} // namespace ddprof +} diff --git a/src/unwind_dwfl.cc b/src/unwind_dwfl.cc index c533400ab..a0df3651f 100644 --- a/src/unwind_dwfl.cc +++ b/src/unwind_dwfl.cc @@ -324,4 +324,84 @@ DDRes unwind_dwfl(UnwindState *us) { return res; } +std::tuple findAndPopulateDso(DsoHdr &dsoHdr, DsoHdr::PidMapping &pid_mapping, Dwarf_Addr pc, UnwindState *us) { + bool retry; + DsoHdr::DsoFindRes find_res; + DDProfMod *ddprof_mod = nullptr; + + do { + retry = false; + find_res = dsoHdr.dso_find_or_backpopulate(pid_mapping, us->pid, pc); + if (!find_res.second) { + return {false, nullptr, nullptr, k_file_info_error}; // DSO not found + } + + const Dso &dso = find_res.first->second; + FileInfoId_t file_info_id = us->dso_hdr.get_or_insert_file_info(dso); + if (file_info_id <= k_file_info_error) { + return {false, nullptr, nullptr, k_file_info_error}; // Unable to access file + } + + const FileInfoValue &file_info_value = us->dso_hdr.get_file_info_value(file_info_id); + ddprof_mod = us->_dwfl_wrapper->unsafe_get(file_info_id); + if (!ddprof_mod) { + auto dsoRange = DsoHdr::get_elf_range(pid_mapping._map, find_res.first); + auto res = us->_dwfl_wrapper->register_mod(pc, dsoRange, file_info_value, &ddprof_mod); + + if (!IsDDResOK(res)) { + int nb_elts_added = 0; + if (res._what == DD_WHAT_AMBIGUOUS_LOAD_SEGMENT && + dsoHdr.pid_backpopulate(us->pid, nb_elts_added) && + nb_elts_added > 0) { + retry = true; + file_info_value.reset_errored(); + } else { + return {false, nullptr, nullptr, k_file_info_error}; // Unwinding error + } + } + } + } while (retry); + + return {true, ddprof_mod, &find_res.first->second, k_file_info_error}; // Successfully found DSO and module +} + +DDRes unwind_symbolize_only(UnwindState *us, stacktrace_event &event) { + // todo some kind of init + us->output.clear(); + us->pid = event.pid; + DDRES_CHECK_FWD(unwind_init_dwfl(us)); + DsoHdr &dso_hdr = us->dso_hdr; + DsoHdr::PidMapping &pid_mapping = dso_hdr.get_pid_mapping(us->pid); + for (int i = 0; i < event.ustack_sz; ++i) { + ProcessAddress_t pc = event.ustack[i]; + if (!pc) { + break; + } + auto [found, ddprof_mod, dso, file_info_id] = findAndPopulateDso(dso_hdr, pid_mapping, pc, us); + if (!found) { + // fp leads to bad uw + break; + } + std::string_view jitdump_path = {}; + if (has_runtime_symbols(dso->_type)) { + if (pid_mapping._jitdump_addr) { + DsoHdr::DsoFindRes const find_mapping = DsoHdr::dso_find_closest( + pid_mapping._map, pid_mapping._jitdump_addr); + if (find_mapping.second) { // jitdump exists + jitdump_path = find_mapping.first->second._filename; + } + } + return add_runtime_symbol_frame(us, *dso, pc, jitdump_path); + } + + if (IsDDResNotOK(add_dwfl_frame(us, *dso, pc, *ddprof_mod, file_info_id))) { + return ddres_warn(DD_WHAT_UW_ERROR); + } + } + + add_virtual_comm_frame(us, + event.comm); + return {}; +} + } // namespace ddprof diff --git a/src/unwind_helpers.cc b/src/unwind_helpers.cc index b475ec3ec..ddf9a5503 100644 --- a/src/unwind_helpers.cc +++ b/src/unwind_helpers.cc @@ -74,6 +74,12 @@ void add_virtual_base_frame(UnwindState *us) { us->symbol_hdr._dso_symbol_lookup, us->dso_hdr)); } +void add_virtual_comm_frame(UnwindState *us, const char *comm) { + add_frame_without_mapping( + us, + us->symbol_hdr._base_frame_symbol_lookup.get_or_insert_comm(comm,us->symbol_hdr._symbol_table)); +} + // read a word from the given stack bool memory_read(ProcessAddress_t addr, ElfWord_t *result, int regno, void *arg) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d3ee6c5c9..97a406b0b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -151,6 +151,7 @@ add_unit_test(perf_ringbuffer-ut ../src/perf.cc ../src/perf_watcher.cc ../src/pe add_unit_test( pevent-ut ../src/pevent_lib.cc + ../src/ebpf_sample_handler.c ../src/user_override.cc ../src/perf.cc ../src/perf_watcher.cc @@ -158,7 +159,8 @@ add_unit_test( ../src/ringbuffer_utils.cc ../src/sys_utils.cc pevent-ut.cc - DEFINITIONS MYNAME="pevent-ut") + LIBRARIES sample_processor_skel + DEFINITIONS MYNAME="pevent-ut" EBPF_UNWINDING) add_unit_test( ddprof_pprof-ut ddprof_pprof-ut.cc ../src/ddprof_cmdline_watcher.cc ../src/pprof/ddprof_pprof.cc @@ -179,9 +181,7 @@ add_unit_test( ../src/perf.cc ../src/perf_clock.cc ../src/perf_ringbuffer.cc - ../src/pevent_lib.cc ../src/procutils.cc - ../src/ringbuffer_utils.cc ../src/signal_helper.cc ../src/sys_utils.cc ../src/user_override.cc @@ -256,6 +256,7 @@ add_unit_test( set(ALLOCATION_TRACKER_UT_SRCS allocation_tracker-ut.cc + ../src/ebpf_sample_handler.c ../src/lib/allocation_tracker.cc ../src/lib/elfutils.cc ../src/lib/symbol_overrides.cc @@ -299,7 +300,9 @@ set(ALLOCATION_TRACKER_UT_SRCS ../src/unwind.cc ../src/unwind_dwfl.cc ../src/unwind_helpers.cc - ../src/unwind_metrics.cc) + ../src/unwind_metrics.cc + LIBRARIES + sample_processor_skel) add_unit_test( allocation_tracker-ut ${ALLOCATION_TRACKER_UT_SRCS} @@ -324,7 +327,8 @@ add_unit_test( ../src/pevent_lib.cc ../src/ringbuffer_utils.cc ../src/sys_utils.cc - ../src/user_override.cc) + ../src/user_override.cc + LIBRARIES sample_processor_skel) add_unit_test(timer-ut timer-ut.cc ../src/tsc_clock.cc ../src/perf.cc) @@ -392,6 +396,7 @@ add_benchmark( ../src/lib/address_bitset.cc ../src/lib/allocation_tracker.cc ../src/pevent_lib.cc + ../src/ebpf_sample_handler.c ../src/perf.cc ../src/perf_ringbuffer.cc ../src/perf_watcher.cc @@ -404,7 +409,7 @@ add_benchmark( ../src/tsc_clock.cc ../src/user_override.cc ../src/sys_utils.cc - LIBRARIES ${ELFUTILS_LIBRARIES} llvm-demangle + LIBRARIES ${ELFUTILS_LIBRARIES} llvm-demangle sample_processor_skel DEFINITIONS ${DDPROF_DEFINITION_LIST} KMAX_TRACKED_ALLOCATIONS=16384) if(NOT CMAKE_BUILD_TYPE STREQUAL "SanitizedDebug") diff --git a/test/pevent-ut.cc b/test/pevent-ut.cc index 1d37760cf..d544efa2c 100644 --- a/test/pevent-ut.cc +++ b/test/pevent-ut.cc @@ -16,7 +16,9 @@ namespace ddprof { void mock_ddprof_context(DDProfContext *ctx) { + ctx->watchers.push_back(*ewatcher_from_str("sCPU")); + } TEST(PeventTest, setup_cleanup) { diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index bafec2173..3912007b3 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,2 +1,34 @@ add_subdirectory(CLI) add_subdirectory(llvm) + +# bpf +include(ExternalProject) +# Create a libbpf-build target +ExternalProject_Add(libbpf + PREFIX libbpf + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src + CONFIGURE_COMMAND "" + BUILD_COMMAND make + BUILD_STATIC_ONLY=1 + OBJDIR=${CMAKE_CURRENT_BINARY_DIR}/libbpf/libbpf + DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/libbpf + INCLUDEDIR= + LIBDIR= + UAPIDIR= + install install_uapi_headers + BUILD_IN_SOURCE TRUE + INSTALL_COMMAND "" + STEP_TARGETS build + ) + +# Create a bpftool-build target +ExternalProject_Add(bpftool + PREFIX bpftool + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bpftool/src + CONFIGURE_COMMAND "" + BUILD_COMMAND make bootstrap + OUTPUT=${CMAKE_CURRENT_BINARY_DIR}/bpftool/ + BUILD_IN_SOURCE TRUE + INSTALL_COMMAND "" + STEP_TARGETS build + ) diff --git a/tools/get_ebpf_dependencies.sh b/tools/get_ebpf_dependencies.sh new file mode 100644 index 000000000..8dc8934e1 --- /dev/null +++ b/tools/get_ebpf_dependencies.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#todo plug this somewhere in the build +git clone https://github.com/libbpf/libbpf.git +git clone https://github.com/libbpf/bpftool.git +cd bpftool && git submodule update --init --recursive