diff --git a/CMakeLists.txt b/CMakeLists.txt index 3df5c1179..ffeb95c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,6 +264,14 @@ target_link_libraries( ) target_link_libraries(dd_profiling-embedded PUBLIC dl pthread rt) +# add libaustin +find_library(austin_location NAMES libaustin.a) +message(STATUS "libaustin location at:" ${austin_location}) +add_library(austin STATIC IMPORTED) +set_target_properties( + austin PROPERTIES IMPORTED_LOCATION ${austin_location} + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/include/libaustin") + set(LIBDD_PROFILING_EMBEDDED_OBJECT "${CMAKE_BINARY_DIR}/libdd_profiling-embedded.o") add_custom_command( OUTPUT ${LIBDD_PROFILING_EMBEDDED_OBJECT} @@ -283,6 +291,10 @@ add_exe( LIBRARIES ${DDPROF_LIBRARY_LIST} DEFINITIONS ${DDPROF_DEFINITION_LIST}) target_link_libraries(ddprof PRIVATE libddprofiling_embedded_object) + +# link libaustin to ddprof +target_link_libraries(ddprof PRIVATE austin) + if(USE_LOADER) target_compile_definitions(ddprof PRIVATE "DDPROF_USE_LOADER") target_link_libraries(ddprof PRIVATE libdd_loader_object) diff --git a/app/base-env/Dockerfile b/app/base-env/Dockerfile index bd7ea1979..d51490f20 100644 --- a/app/base-env/Dockerfile +++ b/app/base-env/Dockerfile @@ -177,6 +177,22 @@ RUN VERSION="3.21.0" \ # Install cmake_format RUN pip3 install cmake_format +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libtool \ + libiberty-dev + + +# possibly useful command +# CFLAGS="-g -O0 -DTRACE" ./configure +# Valid commit : git checkout a7a292b3f5a1058051b017c0d339a678efdec704 +RUN git clone --branch libaustin https://github.com/P403n1x87/austin.git && \ + cd austin && \ + autoreconf --install && \ + CFLAGS="-O2" ./configure && \ + make && \ + make install + # A specific user is required to get access to perf event ressources. # This enables unit testing using perf-event ressources RUN useradd -ms /bin/bash ddbuild diff --git a/app/python-tests/Dockerfile b/app/python-tests/Dockerfile new file mode 100644 index 000000000..01b555885 --- /dev/null +++ b/app/python-tests/Dockerfile @@ -0,0 +1,2 @@ +FROM python:slim + diff --git a/cmake/Findelfutils.cmake b/cmake/Findelfutils.cmake index e9d4ac48f..d99a91261 100644 --- a/cmake/Findelfutils.cmake +++ b/cmake/Findelfutils.cmake @@ -28,6 +28,9 @@ set(LIBELF_PATH ${ELFUTILS_PATH}/lib/libelf.a) set(ELFUTILS_INCLUDE_DIRS ${ELFUTILS_PATH}/include) +set(PRIVATE_EBL_DIRS ${ELFUTILS_PATH}/src/libebl) +set(LIBDW_INCLUDE_DIRS ${ELFUTILS_PATH}/include/elfutils) + if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") # Variable can contain several args as it is quoted @@ -59,5 +62,8 @@ set_target_properties( INTERFACE_INCLUDE_DIRECTORIES ${ELFUTILS_INCLUDE_DIRS} INTERFACE_LINK_LIBRARIES "${LIBLZMA_LIBRARIES};${ZLIB_LIBRARIES}") +add_library(private_ebl INTERFACE) +target_include_directories(private_ebl INTERFACE ${PRIVATE_EBL_DIRS} ${LIBDW_INCLUDE_DIRS}) + # Elf libraries -set(ELFUTILS_LIBRARIES dw elf) +set(ELFUTILS_LIBRARIES dw elf private_ebl) diff --git a/include/austin_symbol_lookup.hpp b/include/austin_symbol_lookup.hpp new file mode 100644 index 000000000..a9b39f184 --- /dev/null +++ b/include/austin_symbol_lookup.hpp @@ -0,0 +1,31 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. This product includes software +// developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present +// Datadog, Inc. + +#pragma once + +#include "symbol_table.hpp" + +#include "libaustin.h" + +#include + +namespace ddprof { + +class AustinSymbolLookup { +public: + SymbolIdx_t get_or_insert(austin_frame_t *frame, SymbolTable &symbol_table); + void clear() { + for (auto el : _austin_symbols) { + _free_list.push_back(el); + } + _austin_symbols.clear(); + } + +private: + std::vector _austin_symbols; + std::vector _free_list; +}; + +} // namespace ddprof diff --git a/include/ddprof_process.hpp b/include/ddprof_process.hpp index 191124bd9..455b88eea 100644 --- a/include/ddprof_process.hpp +++ b/include/ddprof_process.hpp @@ -13,13 +13,16 @@ #include #include #include +#include + +typedef void *austin_handle_t; namespace ddprof { class Process { public: explicit Process(pid_t pid) : _pid(pid), _cgroup_ns(kCGroupNsNull) {} - + ~Process(); using CGroupId_t = uint64_t; static constexpr CGroupId_t kCGroupNsNull = std::numeric_limits::max(); @@ -33,8 +36,12 @@ class Process { // lazy read of container id const ContainerId &get_container_id(std::string_view path_to_proc = ""); + austin_handle_t get_austin_handle(pid_t tid); + uint64_t _sample_counter = {}; + std::vector _python_register_indices; + private: std::string format_cgroup_file(pid_t pid, std::string_view path_to_proc); @@ -44,6 +51,8 @@ class Process { pid_t _pid; CGroupId_t _cgroup_ns; ContainerId _container_id; + + austin_handle_t _austin_handle = nullptr; }; class ProcessHdr { @@ -53,6 +62,8 @@ class ProcessHdr { const ContainerId &get_container_id(pid_t pid, bool force = false); void clear(pid_t pid) { _process_map.erase(pid); } + Process &get_process(pid_t pid); + private: constexpr static auto k_nb_samples_container_id_lookup = 100; using ProcessMap = std::unordered_map; diff --git a/include/libaustin/libaustin.h b/include/libaustin/libaustin.h new file mode 100644 index 000000000..4a1ba1cb9 --- /dev/null +++ b/include/libaustin/libaustin.h @@ -0,0 +1,60 @@ +// This file is part of "austin" which is released under GPL. +// +// See file LICENCE or go to http://www.gnu.org/licenses/ for full license +// details. +// +// Austin is a Python frame stack sampler for CPython. +// +// Copyright (c) 2018 Gabriele N. Tornetta . +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef LIBAUSTIN_H +#define LIBAUSTIN_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void (*austin_callback_t)(pid_t, pid_t); +typedef void *austin_handle_t; +typedef struct { + uintptr_t key; // private + char *filename; + char *scope; + unsigned int line; +} austin_frame_t; + +extern int austin_up(); + +extern void austin_down(); + +extern austin_handle_t austin_attach(pid_t); + +extern void austin_detach(austin_handle_t); + +extern int austin_sample(austin_handle_t, austin_callback_t); + +extern int austin_sample_thread(austin_handle_t, pid_t); + +extern austin_frame_t *austin_pop_frame(); + +austin_frame_t *austin_read_frame(austin_handle_t, void *); + +#ifdef __cplusplus +} +#endif +#endif // LIBAUSTIN_H diff --git a/include/symbol.hpp b/include/symbol.hpp index 0e54e7a3c..113a2df95 100644 --- a/include/symbol.hpp +++ b/include/symbol.hpp @@ -21,7 +21,7 @@ class Symbol { Symbol(std::string symname, std::string demangle_name, uint32_t lineno, std::string srcpath) : _symname(std::move(symname)), _demangle_name(std::move(demangle_name)), - _lineno(lineno), _srcpath(std::move(srcpath)) {} + _lineno(lineno), _srcpath(std::move(srcpath)), _is_python_frame(false) {} // OUTPUT OF ADDRINFO std::string _symname; @@ -32,5 +32,7 @@ class Symbol { // OUTPUT OF LINE INFO uint32_t _lineno; std::string _srcpath; + + bool _is_python_frame; }; } // namespace ddprof diff --git a/include/symbol_hdr.hpp b/include/symbol_hdr.hpp index 40456fa46..0f20b00f4 100644 --- a/include/symbol_hdr.hpp +++ b/include/symbol_hdr.hpp @@ -5,6 +5,7 @@ #pragma once +#include "austin_symbol_lookup.hpp" #include "base_frame_symbol_lookup.hpp" #include "common_mapinfo_lookup.hpp" #include "common_symbol_lookup.hpp" @@ -42,6 +43,7 @@ struct SymbolHdr { ddprof::DsoSymbolLookup _dso_symbol_lookup; ddprof::DwflSymbolLookup _dwfl_symbol_lookup; ddprof::RuntimeSymbolLookup _runtime_symbol_lookup; + ddprof::AustinSymbolLookup _austin_symbol_lookup; // Symbol table (contains the references to strings) ddprof::SymbolTable _symbol_table; diff --git a/include/unwind.hpp b/include/unwind.hpp index 552aaf709..a88281ce2 100644 --- a/include/unwind.hpp +++ b/include/unwind.hpp @@ -16,7 +16,7 @@ void unwind_init(void); // Fill sample info to prepare for unwinding void unwind_init_sample(UnwindState *us, uint64_t *sample_regs, - pid_t sample_pid, uint64_t sample_size_stack, + pid_t sample_pid, pid_t sample_tid, uint64_t sample_size_stack, char *sample_data_stack); // Main unwind API diff --git a/include/unwind_state.hpp b/include/unwind_state.hpp index 6e56aab9a..219d56cba 100644 --- a/include/unwind_state.hpp +++ b/include/unwind_state.hpp @@ -16,6 +16,8 @@ #include "symbol_hdr.hpp" #include "unwind_output.hpp" +#include "libaustin.h" + #include typedef struct Dwfl Dwfl; @@ -52,6 +54,7 @@ struct UnwindState { ddprof::ProcessHdr process_hdr; pid_t pid; + pid_t tid; char *stack; size_t stack_sz; diff --git a/lib_debug/libaustin.a b/lib_debug/libaustin.a new file mode 100644 index 000000000..82438047d Binary files /dev/null and b/lib_debug/libaustin.a differ diff --git a/src/austin_symbol_lookup.cc b/src/austin_symbol_lookup.cc new file mode 100644 index 000000000..9f7df3f04 --- /dev/null +++ b/src/austin_symbol_lookup.cc @@ -0,0 +1,36 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. This product includes software +// developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present +// Datadog, Inc. + +#include +#include +#include +#include +#include + +#include "austin_symbol_lookup.hpp" + +namespace ddprof { + +SymbolIdx_t AustinSymbolLookup::get_or_insert(austin_frame_t *frame, + SymbolTable &symbol_table) { + SymbolIdx_t idx = -1; + // reuse elements to avoid growing the table + if (!_free_list.empty()) { + auto el = _free_list.back(); + _free_list.pop_back(); + _austin_symbols.push_back(el); + idx = el; + } + if (idx == -1) { + idx = symbol_table.size(); + symbol_table.push_back(Symbol()); + } + std::string symname = std::string(frame->scope); + symbol_table[idx] = + Symbol(symname, symname, frame->line, std::string(frame->filename)); + return idx; +} + +} // namespace ddprof diff --git a/src/ddprof_process.cc b/src/ddprof_process.cc index 0a9d01c39..411508a9b 100644 --- a/src/ddprof_process.cc +++ b/src/ddprof_process.cc @@ -8,6 +8,7 @@ #include "ddres.hpp" #include "string_format.hpp" +#include "libaustin.h" #include // for std::from_chars #include #include @@ -20,6 +21,21 @@ std::string Process::format_cgroup_file(pid_t pid, return string_format("%s/proc/%d/cgroup", path_to_proc.data(), pid); } +Process::~Process() { + if (_austin_handle) { + austin_detach(_austin_handle); + } +} + +austin_handle_t Process::get_austin_handle(pid_t tid) { + if (!_austin_handle) { + LG_NTC("Attaching to PID %d/%d", _pid, tid); + _austin_handle = austin_attach(_pid); + LG_NTC("Result of attach %s", _austin_handle?"Success":"Failure"); + } + return _austin_handle; +} + const ContainerId &Process::get_container_id(std::string_view path_to_proc) { if (!_container_id) { extract_container_id(format_cgroup_file(_pid, path_to_proc), _container_id); @@ -74,6 +90,22 @@ DDRes Process::read_cgroup_ns(pid_t pid, std::string_view path_to_proc, return {}; } +Process &ProcessHdr::get_process(pid_t pid) { + auto it = _process_map.find(pid); + if (it == _process_map.end()) { + // new process, parse cgroup + auto pair = _process_map.try_emplace(pid, pid); + if (pair.second) { + it = pair.first; + } else { + // todo: probably not an exception + LG_WRN("[ProcessHdr] Unable to insert process element"); + throw std::runtime_error("Unable to insert process element"); + } + } + return it->second; +} + const ContainerId &ProcessHdr::get_container_id(pid_t pid, bool force) { // lookup cgroup static const ContainerId unknown_container_id = diff --git a/src/ddprof_worker.cc b/src/ddprof_worker.cc index da10aca4f..cfb4e76e6 100644 --- a/src/ddprof_worker.cc +++ b/src/ddprof_worker.cc @@ -234,7 +234,7 @@ static DDRes ddprof_unwind_sample(DDProfContext &ctx, perf_event_sample *sample, ddprof_stats_add(STATS_UNWIND_AVG_STACK_SIZE, sample->size_stack, nullptr); // copy the sample context into the unwind structure - unwind_init_sample(us, sample->regs, sample->pid, sample->size_stack, + unwind_init_sample(us, sample->regs, sample->pid, sample->tid, sample->size_stack, sample->data_stack); // If a sample has a PID, it has a TID. Include it for downstream labels diff --git a/src/dwfl_symbol_lookup.cc b/src/dwfl_symbol_lookup.cc index 11c39a1c6..271dcf6ef 100644 --- a/src/dwfl_symbol_lookup.cc +++ b/src/dwfl_symbol_lookup.cc @@ -134,6 +134,12 @@ SymbolIdx_t DwflSymbolLookup::insert(const DDProfMod &ddprof_mod, table.push_back(std::move(symbol)); Symbol &sym_ref = table.back(); + // slightly hacky way to detect python frame we should replace + if (sym_ref._symname.find("PyEval_EvalFrameDefault") != std::string::npos || + sym_ref._symname.find("PyEval_EvalFrameEx") != std::string::npos) { + // flag this as something we can replace + sym_ref._is_python_frame = true; + } if (sym_ref._srcpath.empty()) { // override with info from dso (this slightly mixes mappings and sources) // But it helps a lot at Datadog (as mappings are ignored for now in UI) diff --git a/src/pprof/ddprof_pprof.cc b/src/pprof/ddprof_pprof.cc index 796d48aec..60d28ff73 100644 --- a/src/pprof/ddprof_pprof.cc +++ b/src/pprof/ddprof_pprof.cc @@ -268,8 +268,9 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, ddog_prof_Profile_AddResult add_res = ddog_prof_Profile_add(profile, sample); if (add_res.tag == DDOG_PROF_PROFILE_ADD_RESULT_ERR) { defer { ddog_Error_drop(&add_res.err); }; - DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to add profile: %s", - add_res.err.message.ptr); + LG_NTC("Unable to add profile - continue execution."); +// DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to add profile: %s", +// add_res.err.message.ptr); } return ddres_init(); } diff --git a/src/unwind.cc b/src/unwind.cc index 299cd9208..8bcca9143 100644 --- a/src/unwind.cc +++ b/src/unwind.cc @@ -17,6 +17,8 @@ #include "unwind_metrics.hpp" #include "unwind_state.hpp" +#include "libaustin.h" + #include #include #include @@ -31,6 +33,7 @@ static void find_dso_add_error_frame(UnwindState *us) { us->dso_hdr.dso_find_closest(us->pid, us->current_ip); add_error_frame(find_res.second ? &(find_res.first->second) : nullptr, us, us->current_ip); + austin_up(); // Idempotent } static void add_container_id(UnwindState *us) { @@ -41,13 +44,14 @@ static void add_container_id(UnwindState *us) { } void unwind_init_sample(UnwindState *us, uint64_t *sample_regs, - pid_t sample_pid, uint64_t sample_size_stack, + pid_t sample_pid, pid_t sample_tid, uint64_t sample_size_stack, char *sample_data_stack) { us->output.clear(); memcpy(&us->initial_regs.regs[0], sample_regs, K_NB_REGS_UNWIND * sizeof(uint64_t)); us->current_ip = us->initial_regs.regs[REGNAME(PC)]; us->pid = sample_pid; + us->tid = sample_tid; us->stack_sz = sample_size_stack; us->stack = sample_data_stack; } diff --git a/src/unwind_dwfl.cc b/src/unwind_dwfl.cc index 31acc5274..52fe382ef 100644 --- a/src/unwind_dwfl.cc +++ b/src/unwind_dwfl.cc @@ -5,16 +5,98 @@ #include "unwind_dwfl.hpp" +#include "austin_symbol_lookup.hpp" #include "ddprof_stats.hpp" #include "ddres.hpp" #include "dwfl_internals.hpp" #include "dwfl_thread_callbacks.hpp" + #include "logger.hpp" #include "runtime_symbol_lookup.hpp" #include "symbol_hdr.hpp" #include "unwind_helpers.hpp" #include "unwind_state.hpp" +#include "libaustin.h" +extern "C" { +#include "libebl.h" +} + +// clang-format off + +/// Hacky way of accessing registers +// --> imported definition from the libdwflP.h + +struct Dwfl_Process +{ + struct Dwfl *dwfl; + pid_t pid; + const Dwfl_Thread_Callbacks *callbacks; + void *callbacks_arg; + struct ebl *ebl; + bool ebl_close:1; +}; + +/* See its typedef in libdwfl.h. */ + +struct Dwfl_Thread +{ + Dwfl_Process *process; + pid_t tid; + /* Bottom (innermost) frame while we're initializing, NULL afterwards. */ + Dwfl_Frame *unwound; + void *callbacks_arg; +}; + + +struct Dwfl_Frame +{ + Dwfl_Thread *thread; + /* Previous (outer) frame. */ + Dwfl_Frame *unwound; + bool signal_frame : 1; + bool initial_frame : 1; + enum + { + /* This structure is still being initialized or there was an error + initializing it. */ + DWFL_FRAME_STATE_ERROR, + /* PC field is valid. */ + DWFL_FRAME_STATE_PC_SET, + /* PC field is undefined, this means the next (inner) frame was the + outermost frame. */ + DWFL_FRAME_STATE_PC_UNDEFINED + } pc_state; + /* Either initialized from appropriate REGS element or on some archs + initialized separately as the return address has no DWARF register. */ + Dwarf_Addr pc; + /* (1 << X) bitmask where 0 <= X < ebl_frame_nregs. */ + uint64_t regs_set[3]; + /* REGS array size is ebl_frame_nregs. + REGS_SET tells which of the REGS are valid. */ + Dwarf_Addr regs[]; +}; + +/* Fetch value from Dwfl_Frame->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */ +static bool +__libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val) +{ + Ebl *ebl = state->thread->process->ebl; + if (! ebl_dwarf_to_regno (ebl, ®no)) + return false; + if (regno >= ebl_frame_nregs (ebl)) + return false; + if ((state->regs_set[regno / sizeof (*state->regs_set) / 8] + & ((uint64_t) 1U << (regno % (sizeof (*state->regs_set) * 8)))) == 0) + return false; + if (val) + *val = state->regs[regno]; + return true; +} + +// clang-format on + int frame_cb(Dwfl_Frame *, void *); namespace ddprof { @@ -22,6 +104,9 @@ namespace ddprof { DDRes unwind_init_dwfl(UnwindState *us) { // Create or get the dwfl object associated to cache us->_dwfl_wrapper = &(us->dwfl_hdr.get_or_insert(us->pid)); + // clear at every iteration + us->symbol_hdr._austin_symbol_lookup.clear(); + if (!us->_dwfl_wrapper->_attached) { // we need to add at least one module to figure out the architecture (to // create the unwinding backend) @@ -91,15 +176,27 @@ static void trace_unwinding_end(UnwindState *us) { } } +void print_all_registers(Dwfl_Frame *dwfl_frame) { + for (int i = 0; i != PERF_REGS_COUNT; ++i) { + uint64_t val; + if (__libdwfl_frame_reg_get(dwfl_frame, i, &val)) { + LG_DBG("Register %i = %lx", i, val); + } + } +} + static DDRes add_dwfl_frame(UnwindState *us, const Dso &dso, ElfAddress_t pc, const DDProfMod &ddprof_mod, - FileInfoId_t file_info_id); + FileInfoId_t file_info_id, Dwfl_Frame *dwfl_frame); // check for runtime symbols provided in /tmp files static DDRes add_runtime_symbol_frame(UnwindState *us, const Dso &dso, ElfAddress_t pc, std::string_view jitdump_path); +static DDRes add_python_frame(UnwindState *us, SymbolIdx_t symbol_idx, + ElfAddress_t pc, Dwfl_Frame *dwfl_frame); + // returns an OK status if we should continue unwinding static DDRes add_symbol(Dwfl_Frame *dwfl_frame, UnwindState *us) { if (is_max_stack_depth_reached(*us)) { @@ -184,7 +281,8 @@ static DDRes add_symbol(Dwfl_Frame *dwfl_frame, UnwindState *us) { us->current_ip = pc; // Now we register - if (IsDDResNotOK(add_dwfl_frame(us, dso, pc, *ddprof_mod, file_info_id))) { + if (IsDDResNotOK( + add_dwfl_frame(us, dso, pc, *ddprof_mod, file_info_id, dwfl_frame))) { return ddres_warn(DD_WHAT_UW_ERROR); } return ddres_init(); @@ -258,7 +356,7 @@ DDRes unwind_dwfl(UnwindState *us) { static DDRes add_dwfl_frame(UnwindState *us, const Dso &dso, ElfAddress_t pc, const DDProfMod &ddprof_mod, - FileInfoId_t file_info_id) { + FileInfoId_t file_info_id, Dwfl_Frame *dwfl_frame) { SymbolHdr &unwind_symbol_hdr = us->symbol_hdr; @@ -266,6 +364,11 @@ static DDRes add_dwfl_frame(UnwindState *us, const Dso &dso, ElfAddress_t pc, SymbolIdx_t symbol_idx = unwind_symbol_hdr._dwfl_symbol_lookup.get_or_insert( ddprof_mod, unwind_symbol_hdr._symbol_table, unwind_symbol_hdr._dso_symbol_lookup, file_info_id, pc, dso); + + if (IsDDResOK(add_python_frame(us, symbol_idx, pc, dwfl_frame))) { + return ddres_init(); + } + MapInfoIdx_t map_idx = us->symbol_hdr._mapinfo_lookup.get_or_insert( us->pid, us->symbol_hdr._mapinfo_table, dso, ddprof_mod._build_id); return add_frame(symbol_idx, map_idx, pc, us); @@ -298,4 +401,62 @@ static DDRes add_runtime_symbol_frame(UnwindState *us, const Dso &dso, return add_frame(symbol_idx, map_idx, pc, us); } +// check for Python frame evaluation symbols +static DDRes add_python_frame(UnwindState *us, SymbolIdx_t symbol_idx, + ElfAddress_t pc, Dwfl_Frame *dwfl_frame) { + SymbolHdr &unwind_symbol_hdr = us->symbol_hdr; + SymbolTable &symbol_table = unwind_symbol_hdr._symbol_table; + + if (symbol_table.at(symbol_idx)._is_python_frame) { + // only attempt to attach on python frames + Process &p = us->process_hdr.get_process(us->pid); + austin_handle_t handle = p.get_austin_handle(us->tid); + if (!handle) { + return ddres_error(1); + } + AustinSymbolLookup &austin_symbol_lookup = + unwind_symbol_hdr._austin_symbol_lookup; + // The register we are interested in is RSI, but it doesn't seem to be + // available. So we loop over the available registers and stop if we find + // a register value that resolves correctly to a Python frame. + std::vector ®_indices = p._python_register_indices; + SymbolIdx_t python_sym_idx = -1; + static unsigned python_lookup_cpt = 0; + uint64_t val = 0; + + // Hacky way of caching the regs that are actually useful + // They are stable + if (reg_indices.empty() || !(++python_lookup_cpt < 10) || + !(python_lookup_cpt % 100)) { + for (int i = 0; i < PERF_REGS_COUNT; i++) { + if (__libdwfl_frame_reg_get(dwfl_frame, i, &val) && val) { + austin_frame_t *frame = austin_read_frame(handle, (void *)val); + if (frame) { + reg_indices.push_back(i); + python_sym_idx = + austin_symbol_lookup.get_or_insert(frame, symbol_table); + break; + } + } + } + } else { + for (int i : reg_indices) { + if (__libdwfl_frame_reg_get(dwfl_frame, i, &val) && val) { + austin_frame_t *frame = austin_read_frame(handle, (void *)val); + if (frame) { + python_sym_idx = + austin_symbol_lookup.get_or_insert(frame, symbol_table); + break; + } + } + } + } + if (python_sym_idx != -1) { + return add_frame(python_sym_idx, -1, pc, us); + } + } + + return ddres_error(1); +} + } // namespace ddprof diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa0445b57..43fc9d057 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,7 +45,7 @@ function(add_unit_test name) # # Create exe with sources. Always add logger and error management in the unit tests add_exe(${name} ../src/ddres_list.cc ../src/logger.cc ${MY_UNPARSED_ARGUMENTS}) - target_link_libraries(${name} PRIVATE gtest Threads::Threads gmock_main gmock) + target_link_libraries(${name} PRIVATE gtest Threads::Threads gmock_main gmock austin) target_include_directories(${name} PRIVATE ${DDPROF_INCLUDE_LIST} ${GTEST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include/lib) @@ -240,6 +240,7 @@ add_unit_test( ../src/mapinfo_lookup.cc ../src/procutils.cc ../src/runtime_symbol_lookup.cc + ../src/austin_symbol_lookup.cc ../src/symbol_map.cc ../src/signal_helper.cc ../src/statsd.cc @@ -248,7 +249,7 @@ add_unit_test( ../src/unwind_helpers.cc ../src/unwind_metrics.cc ../src/user_override.cc - LIBRARIES ${ELFUTILS_LIBRARIES} llvm-demangle + LIBRARIES ${ELFUTILS_LIBRARIES} llvm-demangle austin DEFINITIONS MYNAME="savecontext-ut") add_unit_test( @@ -285,6 +286,7 @@ add_unit_test( ../src/mapinfo_lookup.cc ../src/procutils.cc ../src/runtime_symbol_lookup.cc + ../src/austin_symbol_lookup.cc ../src/symbol_map.cc ../src/signal_helper.cc ../src/statsd.cc diff --git a/test/allocation_tracker-ut.cc b/test/allocation_tracker-ut.cc index 6bca2f83e..94a5bd556 100644 --- a/test/allocation_tracker-ut.cc +++ b/test/allocation_tracker-ut.cc @@ -75,7 +75,7 @@ TEST(allocation_tracker, start_stop) { ASSERT_EQ(sample->addr, 0xdeadbeef); UnwindState state; - ddprof::unwind_init_sample(&state, sample->regs, sample->pid, + ddprof::unwind_init_sample(&state, sample->regs, sample->pid, sample->tid, sample->size_stack, sample->data_stack); ddprof::unwindstate__unwind(&state); diff --git a/test/savecontext-ut.cc b/test/savecontext-ut.cc index 354586b19..be3c77705 100644 --- a/test/savecontext-ut.cc +++ b/test/savecontext-ut.cc @@ -29,7 +29,7 @@ void funcB() { uint64_t regs[K_NB_REGS_UNWIND]; size_t stack_size = save_context(retrieve_stack_bounds(), regs, stack); - ddprof::unwind_init_sample(&state, regs, getpid(), stack_size, + ddprof::unwind_init_sample(&state, regs, getpid(), 0, stack_size, reinterpret_cast(stack)); ddprof::unwindstate__unwind(&state); @@ -98,7 +98,7 @@ TEST(getcontext, unwind_from_sighandler) { t.join(); UnwindState state; - ddprof::unwind_init_sample(&state, regs, getpid(), stack_size, + ddprof::unwind_init_sample(&state, regs, getpid(), 0, stack_size, reinterpret_cast(stack)); ddprof::unwindstate__unwind(&state); diff --git a/tools/launch_local_build.sh b/tools/launch_local_build.sh index 8e7a02546..f5a9f2b4a 100755 --- a/tools/launch_local_build.sh +++ b/tools/launch_local_build.sh @@ -126,7 +126,7 @@ if [ $PERFORM_CLEAN -eq 1 ]; then echo "Clean image : ${DOCKER_NAME}" # if docker image does not exist, we should not fail docker image rm "${DOCKER_NAME}" || true - CACHE_OPTION="--no-cache" + #CACHE_OPTION="--no-cache" fi # Check if base image exists