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
21 changes: 19 additions & 2 deletions ddtrace/internal/datadog/profiling/stack/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,25 @@ if(NOT Threads_FOUND OR NOT CMAKE_USE_PTHREADS_INIT)
endif()

# Specify the target C-extension that we want to build
add_library(${EXTENSION_NAME} SHARED src/echion/danger.cc src/echion/frame.cc src/sampler.cpp src/stack.cpp
src/stack_renderer.cpp src/thread_span_links.cpp)
add_library(
${EXTENSION_NAME} SHARED
src/echion/danger.cc
src/echion/frame.cc
src/echion/greenlets.cc
src/echion/interp.cc
src/echion/long.cc
src/echion/mirrors.cc
src/echion/stack_chunk.cc
src/echion/stacks.cc
src/echion/strings.cc
src/echion/tasks.cc
src/echion/threads.cc
src/echion/timing.cc
src/echion/vm.cc
src/sampler.cpp
src/stack_renderer.cpp
src/stack.cpp
src/thread_span_links.cpp)

# Add common config
add_ddup_config(${EXTENSION_NAME})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
#pragma once

#define PY_SSIZE_T_CLEAN
#define Py_BUILD_CORE
#include <Python.h>

#if PY_VERSION_HEX >= 0x030b0000
#include <cpython/genobject.h>

#define Py_BUILD_CORE
#if PY_VERSION_HEX >= 0x030e0000
#include <cstddef>
#include <internal/pycore_frame.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include <memory>
#include <type_traits>
#include <utility>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#pragma once

#define PY_SSIZE_T_CLEAN
#define Py_BUILD_CORE
#include <Python.h>

#if defined __GNUC__ && defined HAVE_STD_ATOMIC
#undef HAVE_STD_ATOMIC
#endif
Expand All @@ -15,10 +17,8 @@
#endif
#include <frameobject.h>
#if PY_VERSION_HEX >= 0x030e0000
#define Py_BUILD_CORE
#include <internal/pycore_interpframe_structs.h>
#elif PY_VERSION_HEX >= 0x030b0000
#define Py_BUILD_CORE
#include <internal/pycore_frame.h>
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

#pragma once

#include <Python.h>
#define Py_BUILD_CORE
#include <Python.h>

#if PY_VERSION_HEX >= 0x030e0000
#include <internal/pycore_frame.h>
Expand Down Expand Up @@ -39,39 +39,6 @@ class GreenletInfo

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

inline int
GreenletInfo::unwind(PyObject* frame, PyThreadState* tstate, FrameStack& stack)
{
PyObject* frame_addr = NULL;
#if PY_VERSION_HEX >= 0x030d0000
frame_addr = frame == Py_None ? reinterpret_cast<PyObject*>(tstate->current_frame)
: reinterpret_cast<PyObject*>(reinterpret_cast<struct _frame*>(frame)->f_frame);
#elif PY_VERSION_HEX >= 0x030b0000
if (frame == Py_None) {
_PyCFrame cframe;
_PyCFrame* cframe_addr = tstate->cframe;
if (copy_type(cframe_addr, cframe))
// TODO: Invalid frame
return 0;

frame_addr = reinterpret_cast<PyObject*>(cframe.current_frame);
} else {
frame_addr = reinterpret_cast<PyObject*>(reinterpret_cast<struct _frame*>(frame)->f_frame);
}

#else // Python < 3.11
frame_addr = frame == Py_None ? reinterpret_cast<PyObject*>(tstate->frame) : frame;
#endif
auto count = unwind_frame(frame_addr, stack);

stack.push_back(Frame::get(name));

return count + 1; // We add an extra count for the frame with the greenlet
// name.
}

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

// We make this a reference to a heap-allocated object so that we can avoid
// the destruction on exit. We are in charge of cleaning up the object. Note
// that the object will leak, but this is not a problem.
Expand All @@ -91,5 +58,3 @@ inline std::mutex greenlet_info_map_lock;
// ----------------------------------------------------------------------------

inline std::vector<std::unique_ptr<StackInfo>> current_greenlets;

// ----------------------------------------------------------------------------
28 changes: 4 additions & 24 deletions ddtrace/internal/datadog/profiling/stack/echion/echion/interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
#pragma once

#define PY_SSIZE_T_CLEAN

#define Py_BUILD_CORE
#include <Python.h>

#if PY_VERSION_HEX >= 0x03090000
#define Py_BUILD_CORE
#if defined __GNUC__ && defined HAVE_STD_ATOMIC
#undef HAVE_STD_ATOMIC
#endif
Expand All @@ -28,26 +29,5 @@ class InterpreterInfo
void* next = NULL;
};

static void
for_each_interp(std::function<void(InterpreterInfo& interp)> callback)
{
InterpreterInfo interpreter_info = { 0 };

for (char* interp_addr = reinterpret_cast<char*>(runtime->interpreters.head); interp_addr != NULL;
interp_addr = reinterpret_cast<char*>(interpreter_info.next)) {
if (copy_type(interp_addr + offsetof(PyInterpreterState, id), interpreter_info.id))
continue;

#if PY_VERSION_HEX >= 0x030b0000
if (copy_type(interp_addr + offsetof(PyInterpreterState, threads.head), interpreter_info.tstate_head))
#else
if (copy_type(interp_addr + offsetof(PyInterpreterState, tstate_head), interpreter_info.tstate_head))
#endif
continue;

if (copy_type(interp_addr + offsetof(PyInterpreterState, next), interpreter_info.next))
continue;

callback(interpreter_info);
};
}
void
for_each_interp(std::function<void(InterpreterInfo& interp)> callback);
49 changes: 9 additions & 40 deletions ddtrace/internal/datadog/profiling/stack/echion/echion/long.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
// Copyright (c) 2023 Gabriele N. Tornetta <[email protected]>.
#pragma once

#define Py_BUILD_CORE
#include <Python.h>

#if defined __GNUC__ && defined HAVE_STD_ATOMIC
#undef HAVE_STD_ATOMIC
#endif

#if PY_VERSION_HEX >= 0x030c0000
#define Py_BUILD_CORE
#include <internal/pycore_long.h>

// Note: Even if use the right PYLONG_BITS_IN_DIGIT that is specified in the
// Python we use to build echion, it can be different from the Python that is
// used to run the program.
Expand All @@ -24,46 +30,9 @@ typedef unsigned short digit;

constexpr Py_ssize_t MAX_DIGITS = 128;

// ----------------------------------------------------------------------------
#if PY_VERSION_HEX >= 0x030c0000
[[nodiscard]] static Result<long long>
pylong_to_llong(PyObject* long_addr)
{
// Only used to extract a task-id on Python 3.12, omits overflow checks
PyLongObject long_obj;
long long ret = 0;

if (copy_type(long_addr, long_obj))
return ErrorKind::PyLongError;

if (!PyLong_CheckExact(&long_obj))
return ErrorKind::PyLongError;

if (_PyLong_IsCompact(&long_obj)) {
ret = static_cast<long long>(_PyLong_CompactValue(&long_obj));
} else {
// If we're here, then we need to iterate over the digits
// We might overflow, but we don't care for now
int sign = _PyLong_NonCompactSign(&long_obj);
Py_ssize_t i = _PyLong_DigitCount(&long_obj);

if (i > MAX_DIGITS) {
return ErrorKind::PyLongError;
}

// Copy over the digits as ob_digit is allocated dynamically with
// PyObject_Malloc.
digit digits[MAX_DIGITS];
if (copy_generic(long_obj.long_value.ob_digit, digits, i * sizeof(digit))) {
return ErrorKind::PyLongError;
}
while (--i >= 0) {
ret <<= PyLong_SHIFT;
ret |= digits[i];
}
ret *= sign;
}
[[nodiscard]] Result<long long>
pylong_to_llong(PyObject* long_addr);

return ret;
}
#endif
112 changes: 8 additions & 104 deletions ddtrace/internal/datadog/profiling/stack/echion/echion/mirrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@

#pragma once

#include "echion/errors.h"
#include <memory>
#include <unordered_set>

#define PY_SSIZE_T_CLEAN
#define Py_BUILD_CORE
#include <Python.h>
#include <dictobject.h>
#include <setobject.h>

#include <memory>
#include <echion/errors.h>
#include <echion/vm.h>

#if PY_VERSION_HEX >= 0x030b0000
#define Py_BUILD_CORE
#if defined __GNUC__ && defined HAVE_STD_ATOMIC
#undef HAVE_STD_ATOMIC
#endif
Expand Down Expand Up @@ -51,10 +54,6 @@ typedef struct _dictkeysobject
typedef PyObject* PyDictValues;
#endif

#include <unordered_set>

#include <echion/vm.h>

constexpr ssize_t MAX_MIRROR_SIZE = 1 << 20; // 1 MiB

class MirrorObject
Expand All @@ -72,7 +71,7 @@ class MirrorObject
class MirrorDict : public MirrorObject
{
public:
[[nodiscard]] static inline Result<MirrorDict> create(PyObject* dict_addr);
[[nodiscard]] static Result<MirrorDict> create(PyObject* dict_addr);

[[nodiscard]] PyObject* get_item(PyObject* key) { return PyDict_GetItem(reinterpret_cast<PyObject*>(&dict), key); }

Expand All @@ -85,63 +84,11 @@ class MirrorDict : public MirrorObject
PyDictObject dict;
};

[[nodiscard]] inline Result<MirrorDict>
MirrorDict::create(PyObject* dict_addr)
{
PyDictObject dict;

if (copy_type(dict_addr, dict)) {
return ErrorKind::MirrorError;
}

PyDictKeysObject keys;
if (copy_type(dict.ma_keys, keys)) {
return ErrorKind::MirrorError;
}

// Compute the full dictionary data size
#if PY_VERSION_HEX >= 0x030b0000
size_t entry_size = keys.dk_kind == DICT_KEYS_UNICODE ? sizeof(PyDictUnicodeEntry) : sizeof(PyDictKeyEntry);
size_t keys_size = sizeof(PyDictKeysObject) + (1 << keys.dk_log2_index_bytes) + (keys.dk_nentries * entry_size);
#else
size_t entry_size = sizeof(PyDictKeyEntry);
size_t keys_size = sizeof(PyDictKeysObject) + (keys.dk_size * sizeof(Py_ssize_t)) + (keys.dk_nentries * entry_size);
#endif
size_t values_size = dict.ma_values != NULL ? keys.dk_nentries * sizeof(PyObject*) : 0;

// Allocate the buffer
ssize_t data_size = keys_size + (keys.dk_nentries * entry_size) + values_size;
if (data_size < 0 || data_size > MAX_MIRROR_SIZE) {
return ErrorKind::MirrorError;
}

auto data = std::make_unique<char[]>(data_size);

// Copy the key data and update the pointer
if (copy_generic(dict.ma_keys, data.get(), keys_size)) {
return ErrorKind::MirrorError;
}

dict.ma_keys = reinterpret_cast<PyDictKeysObject*>(data.get());

if (dict.ma_values != NULL) {
// Copy the value data and update the pointer
char* values_addr = data.get() + keys_size;
if (copy_generic(dict.ma_values, keys_size, values_size)) {
return ErrorKind::MirrorError;
}

dict.ma_values = reinterpret_cast<PyDictValues*>(values_addr);
}

return MirrorDict(dict, std::move(data));
}

// ----------------------------------------------------------------------------
class MirrorSet : public MirrorObject
{
public:
[[nodiscard]] inline static Result<MirrorSet> create(PyObject*);
[[nodiscard]] static Result<MirrorSet> create(PyObject*);
[[nodiscard]] Result<std::unordered_set<PyObject*>> as_unordered_set();

private:
Expand All @@ -155,46 +102,3 @@ class MirrorSet : public MirrorObject
size_t size;
PySetObject set;
};

[[nodiscard]] inline Result<MirrorSet>
MirrorSet::create(PyObject* set_addr)
{
PySetObject set;

if (copy_type(set_addr, set)) {
return ErrorKind::MirrorError;
}

auto size = set.mask + 1;
ssize_t table_size = size * sizeof(setentry);
if (table_size < 0 || table_size > MAX_MIRROR_SIZE) {
return ErrorKind::MirrorError;
}

auto data = std::make_unique<char[]>(table_size);
if (copy_generic(set.table, data.get(), table_size)) {
return ErrorKind::MirrorError;
}

set.table = reinterpret_cast<setentry*>(data.get());

return MirrorSet(size, set, std::move(data));
}

[[nodiscard]] inline Result<std::unordered_set<PyObject*>>
MirrorSet::as_unordered_set()
{
if (data == nullptr) {
return ErrorKind::MirrorError;
}

std::unordered_set<PyObject*> uset;

for (size_t i = 0; i < size; i++) {
auto entry = set.table[i];
if (entry.key != NULL)
uset.insert(entry.key);
}

return uset;
}
Loading
Loading