Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ddtrace/internal/datadog/profiling/stack/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,5 @@ endif()
if(BUILD_TESTING)
enable_testing()
add_subdirectory(test)
add_subdirectory(echion/test)
endif()
177 changes: 175 additions & 2 deletions ddtrace/internal/datadog/profiling/stack/echion/echion/stacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <deque>
#include <algorithm>
#include <cassert>
#include <optional>
#include <unordered_set>
#include <vector>

#include <echion/config.h>
#include <echion/frame.h>
Expand All @@ -19,10 +22,180 @@

// ----------------------------------------------------------------------------

class FrameStack : public std::deque<Frame::Ref>
class FrameStack
{
// initial_capacity for the stack, shared between "front" and "back"
static constexpr size_t initial_capacity = 2048;

private:
std::vector<std::optional<Frame::Ref>> buffer_;
size_t start_; // Index of the first element
size_t end_; // Index one past the last element

void grow()
{
size_t current_size = end_ - start_;
size_t new_capacity = std::max(buffer_.capacity() * 2, FrameStack::initial_capacity);

std::vector<std::optional<Frame::Ref>> new_buffer;
new_buffer.reserve(new_capacity);
new_buffer.resize(new_capacity);

// Center elements in the new buffer with equal space at front and back
size_t new_start = (new_capacity - current_size) / 2;
for (size_t i = 0; i < current_size; ++i) {
new_buffer[new_start + i] = std::move(buffer_[start_ + i]);
}

buffer_ = std::move(new_buffer);
start_ = new_start;
end_ = new_start + current_size;
}

// Helper iterator adapter to unwrap optionals
template<typename Iter>
class unwrap_iterator
{
private:
Iter it_;

public:
using iterator_category = typename std::iterator_traits<Iter>::iterator_category;
using value_type = Frame::Ref;
using difference_type = typename std::iterator_traits<Iter>::difference_type;
using pointer = Frame*;
using reference = Frame::Ref;

unwrap_iterator(Iter it)
: it_(it)
{
}

Frame::Ref operator*() const { return **it_; }
Frame* operator->() const { return &((**it_).get()); }

unwrap_iterator& operator++()
{
++it_;
return *this;
}
unwrap_iterator operator++(int)
{
auto tmp = *this;
++(*this);
return tmp;
}
unwrap_iterator& operator--()
{
--it_;
return *this;
}
unwrap_iterator operator--(int)
{
auto tmp = *this;
--(*this);
return tmp;
}

unwrap_iterator operator+(difference_type n) const { return unwrap_iterator(it_ + n); }
unwrap_iterator operator-(difference_type n) const { return unwrap_iterator(it_ - n); }
difference_type operator-(const unwrap_iterator& other) const { return it_ - other.it_; }

bool operator==(const unwrap_iterator& other) const { return it_ == other.it_; }
bool operator!=(const unwrap_iterator& other) const { return it_ != other.it_; }
bool operator<(const unwrap_iterator& other) const { return it_ < other.it_; }
bool operator<=(const unwrap_iterator& other) const { return it_ <= other.it_; }
bool operator>(const unwrap_iterator& other) const { return it_ > other.it_; }
bool operator>=(const unwrap_iterator& other) const { return it_ >= other.it_; }

friend unwrap_iterator operator+(difference_type n, const unwrap_iterator& it) { return it + n; }
};

public:
using Key = Frame::Key;
using value_type = Frame::Ref;
using reference = Frame::Ref;
using const_reference = Frame::Ref;
using iterator = unwrap_iterator<typename std::vector<std::optional<Frame::Ref>>::iterator>;
using const_iterator = unwrap_iterator<typename std::vector<std::optional<Frame::Ref>>::const_iterator>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;

FrameStack()
: start_(initial_capacity / 2)
, end_(initial_capacity / 2)
{
buffer_.reserve(initial_capacity);
buffer_.resize(initial_capacity);
}

size_t size() const { return end_ - start_; }
bool empty() const { return start_ == end_; }

void clear()
{
buffer_.clear();
buffer_.reserve(initial_capacity);
buffer_.resize(initial_capacity);
start_ = initial_capacity / 2;
end_ = initial_capacity / 2;
}

void push_back(const Frame::Ref& value)
{
if (end_ >= buffer_.capacity()) {
grow();
}
if (end_ >= buffer_.size()) {
buffer_.resize(end_ + 1);
}
buffer_[end_++] = value;
}

void push_front(const Frame::Ref& value)
{
if (start_ == 0) {
grow();
}
buffer_[--start_] = value;
}

void pop_back()
{
// UB to pop from empty, like std::deque
assert(!empty() && "pop_back on empty FrameStack");
--end_;
}

void pop_front()
{
// UB to pop from empty, like std::deque
assert(!empty() && "pop_front on empty FrameStack");
++start_;
}

Frame::Ref operator[](size_t index)
{
// UB to access out of bounds, like std::deque
assert(index < size() && "operator[] index out of bounds");
return *buffer_[start_ + index];
}
Frame::Ref operator[](size_t index) const
{
// UB to access out of bounds, like std::deque
assert(index < size() && "operator[] index out of bounds");
return *buffer_[start_ + index];
}

iterator begin() { return iterator(buffer_.begin() + start_); }
iterator end() { return iterator(buffer_.begin() + end_); }
const_iterator begin() const { return const_iterator(buffer_.begin() + start_); }
const_iterator end() const { return const_iterator(buffer_.begin() + end_); }

reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }

// ------------------------------------------------------------------------
void render()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.15.2
TIMEOUT 180
INACTIVITY_TIMEOUT 120)
set(gtest_force_shared_crt
ON
CACHE BOOL "" FORCE)
set(INSTALL_GTEST
OFF
CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
include(GoogleTest)
include(AnalysisFunc)

if(DO_VALGRIND)
find_program(
VALGRIND_EXECUTABLE
NAMES valgrind
PATHS /usr/bin /usr/local/bin)

if(VALGRIND_EXECUTABLE)
set(MEMORYCHECK_COMMAND "${VALGRIND_EXECUTABLE}")
set(MEMORYCHECK_COMMAND_OPTIONS
"--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp"
)
else()
message(FATAL_ERROR "Valgrind not found")
endif()

include(CTest)
endif()

function(echion_add_test name)
add_executable(${name} ${ARGN})

# Echion sources need to be given the current platform
if(APPLE)
target_compile_definitions(${name} PRIVATE PL_DARWIN)
elseif(UNIX)
target_compile_definitions(${name} PRIVATE PL_LINUX)
endif()

target_include_directories(${name} SYSTEM PRIVATE ../)
# this has to refer to the stack extension name to properly link against
target_link_libraries(${name} PRIVATE gmock gtest_main ${EXTENSION_NAME})

if(Python3_LIBRARIES)
target_link_libraries(${name} PRIVATE ${Python3_LIBRARIES})
endif()

if(Python3_INCLUDE_DIRS)
target_include_directories(${name} PRIVATE ${Python3_INCLUDE_DIRS})
endif()

add_ddup_config(${name})

# Test executable needs to find _stack in grandparent directory (build/stack/) Append to INSTALL_RPATH instead of
# replacing to preserve sanitizer library paths set by add_ddup_config
get_target_property(EXISTING_INSTALL_RPATH ${name} INSTALL_RPATH)
if(EXISTING_INSTALL_RPATH)
set_target_properties(${name} PROPERTIES INSTALL_RPATH "${EXISTING_INSTALL_RPATH};$ORIGIN/../.."
BUILD_WITH_INSTALL_RPATH TRUE)
else()
set_target_properties(${name} PROPERTIES INSTALL_RPATH "$ORIGIN/../.." BUILD_WITH_INSTALL_RPATH TRUE)
endif()

gtest_discover_tests(${name} DISCOVERY_MODE PRE_TEST) # Delay test discovery until test execution to avoid running
# sanitizer-built executables during build

# This is supplemental artifact so make sure to install it in the right place
if(INPLACE_LIB_INSTALL_DIR)
set(LIB_INSTALL_DIR "${INPLACE_LIB_INSTALL_DIR}")
endif()

if(LIB_INSTALL_DIR)
install(TARGETS ${name} RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test)
endif()
endfunction()

# Add the tests
echion_add_test(test_stack_frame test_stack_frame.cpp)
Loading
Loading