Skip to content

Commit

Permalink
fix(profiling): code provenance using libdatadog exporter [backport 2…
Browse files Browse the repository at this point in the history
….14] (#10856)

Backport 4363516 from #10796 to 2.14.

## Checklist
- [x] PR author has checked that all the criteria below are met
- The PR description includes an overview of the change
- The PR description articulates the motivation for the change
- The change includes tests OR the PR description describes a testing
strategy
- The PR description notes risks associated with the change, if any
- Newly-added code is easy to change
- The change follows the [library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
- The change includes or references documentation updates if necessary
- Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))

## Reviewer Checklist
- [x] Reviewer has checked that all the criteria below are met 
- Title is accurate
- All changes are related to the pull request's stated goal
- Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- Testing strategy adequately addresses listed risks
- Newly-added code is easy to change
- Release note makes sense to a user of the library
- If necessary, author has acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment
- Backport labels are set in a manner that is consistent with the
[release branch maintenance
policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)

Co-authored-by: Taegyun Kim <[email protected]>
  • Loading branch information
github-actions[bot] and taegyunkim authored Oct 2, 2024
1 parent a213e8d commit 73d3d41
Show file tree
Hide file tree
Showing 16 changed files with 569 additions and 53 deletions.
10 changes: 6 additions & 4 deletions ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ add_library(dd_wrapper SHARED
src/crashtracker_interface.cpp
src/receiver_interface.cpp
src/synchronized_sample_pool.cpp
src/code_provenance.cpp
src/code_provenance_interface.cpp
)

# Add common configuration flags
Expand Down Expand Up @@ -96,13 +98,13 @@ set(CRASHTRACKER_EXE_TARGET_NAME ${CRASHTRACKER_EXE_TARGET_NAME} PARENT_SCOPE)
# will handle the extension artifacts themselves, supplementary files like this one will be left uncopied.
# One way around this is to propagate the original source dir of the extension, which can be used to deduce the
# ideal install directory.
if (INPLACE_LIB_INSTALL_DIR)
if(INPLACE_LIB_INSTALL_DIR)
set(LIB_INSTALL_DIR "${INPLACE_LIB_INSTALL_DIR}")
endif()

# If LIB_INSTALL_DIR is set, install the library.
# Install one directory up--ddup, crashtracker, and stackv2 are set to the same relative level.
if (LIB_INSTALL_DIR)
if(LIB_INSTALL_DIR)
install(TARGETS dd_wrapper
LIBRARY DESTINATION ${LIB_INSTALL_DIR}/..
ARCHIVE DESTINATION ${LIB_INSTALL_DIR}/..
Expand All @@ -114,7 +116,7 @@ endif()
add_cppcheck_target(dd_wrapper
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include
${Datadog_INCLUDE_DIRS}
${Datadog_INCLUDE_DIRS}
SRC ${CMAKE_CURRENT_SOURCE_DIR}/src
)

Expand All @@ -123,7 +125,7 @@ add_infer_target(dd_wrapper)
add_clangtidy_target(dd_wrapper)

# Add the tests
if (BUILD_TESTING)
if(BUILD_TESTING)
enable_testing()
add_subdirectory(test)
endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#pragma once

#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

namespace Datadog {

struct Package
{
std::string name;
std::string version;
};

static constexpr const char* STDLIB = "stdlib";

class CodeProvenance
{
public:
// Public static method to access the CodeProvenance instance
static CodeProvenance& get_instance()
{
static CodeProvenance instance;
return instance;
}

// Delete copy constructor and assignment operator to prevent copies
CodeProvenance(CodeProvenance const&) = delete;
CodeProvenance& operator=(CodeProvenance const&) = delete;

static void postfork_child();

void set_enabled(bool enable);
bool is_enabled();
void set_runtime_version(std::string_view runtime_version);
void set_stdlib_path(std::string_view stdlib_path);
void add_packages(std::unordered_map<std::string_view, std::string_view> packages);
void add_filename(std::string_view filename);
std::optional<std::string> try_serialize_to_json_str();

private:
// Mutex to protect the state
std::mutex mtx;
// Whether this is enabled, set only when DD_PROFILING_ENABLE_CODE_PROVENANCE is set
std::atomic_bool enabled{ false };
std::string runtime_version;
std::string stdlib_path;
// Mapping from package name to Package object
std::unordered_map<std::string_view, std::unique_ptr<Package>> packages;
// Mapping from Package object to list of filenames that are associated with the package
std::unordered_map<const Package*, std::set<std::string>> packages_to_files;

// Private Constructor/Destructor to prevent instantiation/deletion from outside
CodeProvenance() = default;
~CodeProvenance() = default;

void reset();
std::string_view get_package_name(std::string_view filename);
const Package* add_new_package(std::string_view package_name, std::string_view version);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include <string_view>
#include <unordered_map>

#ifdef __cplusplus
extern "C"
{
#endif

void code_provenance_enable(bool enable);
void code_provenance_set_runtime_version(std::string_view runtime_version);
void code_provenance_set_stdlib_path(std::string_view stdlib_path);
void code_provenance_add_packages(std::unordered_map<std::string_view, std::string_view> packages);

#ifdef __cplusplus
} // extern "C"
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ to_slice(std::string_view str)
return { .ptr = str.data(), .len = str.size() };
}

inline ddog_ByteSlice
to_byte_slice(std::string_view str)
{
return { .ptr = reinterpret_cast<const uint8_t*>(str.data()), .len = str.size() };
}

inline std::string
err_to_msg(const ddog_Error* err, std::string_view msg)
{
Expand Down
177 changes: 177 additions & 0 deletions ddtrace/internal/datadog/profiling/dd_wrapper/src/code_provenance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#include "code_provenance.hpp"

#include <memory>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

namespace Datadog {

void
Datadog::CodeProvenance::postfork_child()
{
get_instance().mtx.~mutex(); // Destroy the mutex
new (&get_instance().mtx) std::mutex(); // Recreate the mutex
get_instance().reset(); // Reset the state
}

void
Datadog::CodeProvenance::set_enabled(bool enable)
{
this->enabled.store(enable);
}

bool
Datadog::CodeProvenance::is_enabled()
{
return enabled.load();
}

void
Datadog::CodeProvenance::set_runtime_version(std::string_view _runtime_version)
{
std::lock_guard<std::mutex> lock(mtx);
this->runtime_version = _runtime_version;
}

void
Datadog::CodeProvenance::set_stdlib_path(std::string_view _stdlib_path)
{
std::lock_guard<std::mutex> lock(mtx);
this->stdlib_path = _stdlib_path;
}

void
CodeProvenance::add_packages(std::unordered_map<std::string_view, std::string_view> distributions)
{

if (!is_enabled()) {
return;
}

std::lock_guard<std::mutex> lock(mtx);

for (const auto& [package_name, version] : distributions) {
auto it = packages.find(package_name);
if (it == packages.end()) {
add_new_package(package_name, version);
}
}
}

void
CodeProvenance::add_filename(std::string_view filename)
{
if (!is_enabled()) {
return;
}

std::string_view package_name = get_package_name(filename);
if (package_name.empty() or package_name == STDLIB) {
return;
}

std::lock_guard<std::mutex> lock(mtx);

auto it = packages.find(package_name);
if (it == packages.end()) {
return;
}

const Package* package = it->second.get();
if (package) {
if (packages_to_files.find(package) == packages_to_files.end()) {
packages_to_files[package] = std::set<std::string>();
}
packages_to_files[package].insert(std::string(filename));
}
}

std::optional<std::string>
CodeProvenance::try_serialize_to_json_str()
{
if (!is_enabled()) {
return std::nullopt;
}

std::lock_guard<std::mutex> lock(mtx);

std::ostringstream out;
// DEV: Simple JSON serialization, maybe consider using a JSON library.
out << "{\"v1\":["; // Start of the JSON array
for (const auto& [package, paths] : packages_to_files) {
out << "{"; // Start of the JSON object
out << "\"name\": \"" << package->name << "\",";
out << "\"kind\": \"library\",";
out << "\"version\": \"" << package->version << "\",";
out << "\"paths\":["; // Start of paths array
for (auto it = paths.begin(); it != paths.end(); ++it) {
out << "\"" << *it << "\"";
if (std::next(it) != paths.end()) {
out << ",";
}
}
out << "]"; // End of paths array
out << "},"; // End of the JSON object
}
// Add python runtime information
out << "{"; // Start of stdlib JSON object
out << "\"name\": \"stdlib\",";
out << "\"kind\": \"standard library\",";
out << "\"version\": \"" << runtime_version << "\",";
out << "\"paths\":[";
out << "\"" << stdlib_path << "\"";
out << "]";
out << "}"; // End of stdlib JSON object
out << "]}"; // End of the JSON array

// Clear the state
packages_to_files.clear();
return out.str();
}

void
CodeProvenance::reset()
{
std::lock_guard<std::mutex> lock(mtx);
packages_to_files.clear();
}

std::string_view
CodeProvenance::get_package_name(std::string_view filename)
{
// std::regex is slow, so we use a simple string search
static const std::string site_packages = "site-packages/";

size_t start = filename.find(site_packages);
if (start == std::string::npos) {
return std::string_view(STDLIB);
}

start += site_packages.length();
size_t end = filename.find('/', start);
if (end == std::string::npos) {
return {};
}

return filename.substr(start, end - start);
}

const Package*
CodeProvenance::add_new_package(std::string_view package_name, std::string_view version)
{
std::unique_ptr<Package> package = std::make_unique<Package>();
package->name = package_name;
package->version = version;

const Package* ret_val = package.get();
packages[std::string_view(package->name)] = std::move(package);

return ret_val;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "code_provenance_interface.hpp"

#include "code_provenance.hpp"

#include <string_view>
#include <unordered_map>

void
code_provenance_enable(bool enable)
{
Datadog::CodeProvenance::get_instance().set_enabled(enable);
}

void
code_provenance_set_runtime_version(std::string_view runtime_version)
{
Datadog::CodeProvenance::get_instance().set_runtime_version(runtime_version);
}

void
code_provenance_set_stdlib_path(std::string_view stdlib_path)
{
Datadog::CodeProvenance::get_instance().set_stdlib_path(stdlib_path);
}

void
code_provenance_add_packages(std::unordered_map<std::string_view, std::string_view> distributions)
{
Datadog::CodeProvenance::get_instance().add_packages(distributions);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "ddup_interface.hpp"

#include "code_provenance.hpp"
#include "libdatadog_helpers.hpp"
#include "profile.hpp"
#include "sample.hpp"
Expand All @@ -21,6 +23,7 @@ ddup_postfork_child()
{
Datadog::Uploader::postfork_child();
Datadog::SampleManager::postfork_child();
Datadog::CodeProvenance::postfork_child();
}

void
Expand Down
4 changes: 4 additions & 0 deletions ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "sample.hpp"

#include "code_provenance.hpp"

#include <algorithm>
#include <chrono>
#include <thread>
Expand Down Expand Up @@ -29,6 +31,8 @@ Datadog::Sample::push_frame_impl(std::string_view name, std::string_view filenam
name = profile_state.insert_or_get(name);
filename = profile_state.insert_or_get(filename);

CodeProvenance::get_instance().add_filename(filename);

const ddog_prof_Location loc = {
.mapping = null_mapping, // No support for mappings in Python
.function = {
Expand Down
Loading

0 comments on commit 73d3d41

Please sign in to comment.