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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class Sample;
extern "C"
{
#endif

using string_id_t = std::add_pointer<void>::type;
using function_id_t = std::add_pointer<void>::type;

string_id_t ddup_intern_string(std::string_view s);
function_id_t ddup_intern_function(string_id_t name, string_id_t filename);

void ddup_config_env(std::string_view dd_env);
void ddup_config_service(std::string_view service);
void ddup_config_version(std::string_view version);
Expand All @@ -32,6 +39,7 @@ extern "C"

bool ddup_is_initialized();
void ddup_start();
void ddup_cleanup();
void ddup_set_runtime_id(std::string_view runtime_id);
void ddup_profile_set_endpoints(std::unordered_map<int64_t, std::string_view> span_ids_to_endpoints);
void ddup_profile_add_endpoint_counts(std::unordered_map<std::string_view, int64_t> trace_endpoints_to_counts);
Expand Down Expand Up @@ -66,6 +74,7 @@ extern "C"
std::string_view _filename,
uint64_t address,
int64_t line);
void ddup_push_frame_id(Datadog::Sample* sample, function_id_t function_id, uint64_t address, int64_t line);
void ddup_push_absolute_ns(Datadog::Sample* sample, int64_t timestamp_ns);
void ddup_push_monotonic_ns(Datadog::Sample* sample, int64_t monotonic_ns);
void ddup_flush_sample(Datadog::Sample* sample);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <iostream>
#include <string>
#include <string_view>
#include <variant>

extern "C"
{
Expand All @@ -13,6 +12,9 @@ extern "C"

namespace Datadog {

ddog_prof_StringId2
intern_string(std::string_view s);

// There's currently no need to offer custom tags, so there's no interface for
// it. Instead, tags are keyed and populated based on this table, then
// referenced in `add_tag()`.
Expand Down Expand Up @@ -139,6 +141,92 @@ add_tag(ddog_Vec_Tag& tags, const ExportTagKey key, std::string_view val, std::s
return add_tag(tags, key_sv, val, errmsg);
}

namespace internal {

ddog_prof_ProfilesDictionaryHandle
get_profiles_dictionary();

// Decreases the refcount on the Profiles Dictionary handle.
// This should be called before the process exits.
// Note that this may not free it immediately if something else
// (e.g. the Profile object) still holds a reference to it.
void
release_profiles_dictionary();

// Reset cached interned strings (required after fork)
void
reset_interned_strings();

// Reset tag and label key caches (required after fork)
void
reset_key_caches();

// Fork-safe cached interning for tag and label keys
// Must come after enum definitions to know the sizes

// Caches for interned tag and label keys
// Use ddog_prof_StringId2 directly to avoid circular dependency with sample.hpp
inline std::array<ddog_prof_StringId2, static_cast<size_t>(ExportTagKey::Length_)>&
get_tag_key_cache()
{
static std::array<ddog_prof_StringId2, static_cast<size_t>(ExportTagKey::Length_)> cache{};
return cache;
}

inline std::array<ddog_prof_StringId2, static_cast<size_t>(ExportLabelKey::Length_)>&
get_label_key_cache()
{
static std::array<ddog_prof_StringId2, static_cast<size_t>(ExportLabelKey::Length_)> cache{};
return cache;
}

inline void
reset_key_caches()
{
auto& tag_cache = get_tag_key_cache();
auto& label_cache = get_label_key_cache();
tag_cache.fill(nullptr);
label_cache.fill(nullptr);
}

inline ddog_prof_StringId2
to_interned_string(ExportTagKey key)
{
auto& cache = internal::get_tag_key_cache();
const auto idx = static_cast<size_t>(key);

if (idx >= cache.size()) {
return nullptr;
}

// Check cache first
if (cache[idx] == nullptr) {
cache[idx] = intern_string(to_string(key));
}

return cache[idx];
}

inline ddog_prof_StringId2
to_interned_string(ExportLabelKey key)
{
auto& cache = internal::get_label_key_cache();
const auto idx = static_cast<size_t>(key);

if (idx >= cache.size()) {
return nullptr;
}

// Check cache first
if (cache[idx] == nullptr) {
cache[idx] = intern_string(to_string(key));
}

return cache[idx];
}

} // namespace internal

// Keep macros from propagating
#undef X_STR
#undef X_ENUM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Profile
// State management
void one_time_init(SampleType type, unsigned int _max_nframes);
bool reset_profile();
void cleanup();
void postfork_child();

// Getters
Expand All @@ -60,6 +61,6 @@ class Profile
const ValueIndex& val();

// collect
bool collect(const ddog_prof_Sample& sample, int64_t endtime_ns);
bool collect(const ddog_prof_Sample2& sample, int64_t endtime_ns);
};
} // namespace Datadog
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ struct StringArena

} // namespace internal

using string_id = ddog_prof_StringId2;
using function_id = ddog_prof_FunctionId2;

string_id
intern_string(std::string_view s);
function_id
intern_function(string_id name, string_id filename);

class SampleManager; // friend

class Sample
Expand All @@ -70,12 +78,12 @@ class Sample
static inline bool timeline_enabled = false;

// Keeps temporary buffer of frames in the stack
std::vector<ddog_prof_Location> locations;
std::vector<ddog_prof_Location2> locations;
size_t dropped_frames = 0;
uint64_t samples = 0;

// Storage for labels
std::vector<ddog_prof_Label> labels{};
std::vector<ddog_prof_Label2> labels{};

// Storage for values
std::vector<int64_t> values = {};
Expand All @@ -91,6 +99,7 @@ class Sample
bool push_label(ExportLabelKey key, std::string_view val);
bool push_label(ExportLabelKey key, int64_t val);
void push_frame_impl(std::string_view name, std::string_view filename, uint64_t address, int64_t line);
void push_frame_impl(function_id function_id, uint64_t address, int64_t line);
void clear_buffers();

// Add values
Expand Down Expand Up @@ -131,12 +140,19 @@ class Sample
int64_t line // for ddog_prof_Location
);

// Assumes frames are pushed in leaf-order
void push_frame(function_id function_id, // for ddog_prof_Location
uint64_t address, // for ddog_prof_Location
int64_t line // for ddog_prof_Location
);

// Flushes the current buffer, clearing it
bool flush_sample(bool reverse_locations = false);

static ddog_prof_Profile& profile_borrow();
static void profile_release();
static void postfork_child();
static void cleanup();
Sample(SampleType _type_mask, unsigned int _max_nframes);

// friend class SampleManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SampleManager

// Handles state management after forks
static void postfork_child();
static void cleanup();

// Initialization
static void init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,49 @@
#include "uploader_builder.hpp"

#include <cstdlib>
#include <datadog/profiling.h>
#include <iostream>
#include <thread>
#include <unistd.h>
#include <unordered_map>

namespace {

// The Profiles Dictionary used for interning strings and functions
ddog_prof_ProfilesDictionaryHandle dict_handle = nullptr;

// Initializes the Profiles Dictionary
bool
init_profiles_dictionary()
{
auto result = ddog_prof_ProfilesDictionary_new(&dict_handle);
if (result.flags) {
std::cerr << "could not initialise profiles dictionary: " << result.err << std::endl;
return false;
}

return true;
}

} // namespace

namespace Datadog::internal {

ddog_prof_ProfilesDictionaryHandle
get_profiles_dictionary()
{
return dict_handle;
}

void
release_profiles_dictionary()
{
ddog_prof_ProfilesDictionary_drop(&dict_handle);
dict_handle = nullptr;
}

} // namespace Datadog::internal

// State
bool is_ddup_initialized = false; // NOLINT (cppcoreguidelines-avoid-non-const-global-variables)
std::once_flag ddup_init_flag; // NOLINT (cppcoreguidelines-avoid-non-const-global-variables)
Expand All @@ -22,16 +61,43 @@ std::once_flag ddup_init_flag; // NOLINT (cppcoreguidelines-avoid-non-const-g
void
ddup_postfork_child()
{
// Free our copy of the Profiles Dictionary. Its String IDs refer
// to memory that doesn't exist in the child process.
Datadog::internal::release_profiles_dictionary();

// Re-initialize the Profiles Dictionary in the child process
if (!init_profiles_dictionary()) {
std::cerr << "failed to initialise profiles dictionary in child process" << std::endl;
}

// Reset cached interned strings that refer to keys in the old Profiles Dictionary
Datadog::internal::reset_interned_strings();

Datadog::Uploader::postfork_child();
Datadog::SampleManager::postfork_child();
}

void
ddup_postfork_parent()
{
// The parent keeps its Profiles Dictionary - no need to drop and recreate.
// The Profile is already associated with this dictionary, and all
// interned strings remain valid.

Datadog::Uploader::postfork_parent();
}

// Cleanup function to free the Profiles Dictionary on exit
void
ddup_cleanup()
{
// (Eventually) order to Profile to be cleared, decreasing the refcount on the Profiles Dictionary
Datadog::SampleManager::cleanup();

// Decrease the refcount on the Profiles Dictionary
Datadog::internal::release_profiles_dictionary();
}

// Since we don't control the internal state of libdatadog's exporter and we want to prevent state-tearing
// after a `fork()`, we just outright prevent uploads from happening during a `fork()` operation. Since a
// new upload may be scheduled _just_ as `fork()` is entered, we also need to prevent new uploads from happening.
Expand Down Expand Up @@ -143,13 +209,23 @@ void
ddup_start() // cppcheck-suppress unusedFunction
{
std::call_once(ddup_init_flag, []() {
// Initialize the profiles dictionary at process start
// This is a prerequisite for Datadog::SampleManager::init()
// which sets the Profiles Dictionary on the Profile object.
if (!init_profiles_dictionary()) {
return false;
}

// Perform any one-time startup operations
Datadog::SampleManager::init();

// install the ddup_fork_handler for pthread_atfork
// Right now, only do things in the child _after_ fork
pthread_atfork(ddup_prefork, ddup_postfork_parent, ddup_postfork_child);

// Register cleanup function to free resources on exit
std::atexit(ddup_cleanup);

// Set the global initialization flag
is_ddup_initialized = true;
return true;
Expand Down Expand Up @@ -292,6 +368,28 @@ ddup_push_frame(Datadog::Sample* sample, // cppcheck-suppress unusedFunction
sample->push_frame(_name, _filename, address, line);
}

void
ddup_push_frame_id(Datadog::Sample* sample, // cppcheck-suppress unusedFunction
function_id_t function_id,
uint64_t address,
int64_t line)
{

sample->push_frame(static_cast<Datadog::function_id>(function_id), address, line);
}

string_id_t
ddup_intern_string(std::string_view s) // cppcheck-suppress unusedFunction
{
return Datadog::intern_string(s);
}

function_id_t
ddup_intern_function(string_id_t name, string_id_t filename) // cppcheck-suppress unusedFunction
{
return Datadog::intern_function(static_cast<Datadog::string_id>(name), static_cast<Datadog::string_id>(filename));
}

void
ddup_push_absolute_ns(Datadog::Sample* sample, int64_t timestamp_ns) // cppcheck-suppress unusedFunction
{
Expand Down
Loading