diff --git a/cmake/Findlibdatadog.cmake b/cmake/Findlibdatadog.cmake index fe1c207cb..633b35cc8 100644 --- a/cmake/Findlibdatadog.cmake +++ b/cmake/Findlibdatadog.cmake @@ -5,7 +5,7 @@ # libdatadog : common profiler imported libraries # https://github.com/DataDog/libdatadog/releases/tag/v0.8.0 set(TAG_LIBDATADOG - "v0.8.0" + "v2.1.0" CACHE STRING "libdatadog github tag") set(Datadog_ROOT ${VENDOR_PATH}/libdatadog-${TAG_LIBDATADOG}) diff --git a/include/exporter/ddprof_exporter.hpp b/include/exporter/ddprof_exporter.hpp index cc1b59344..cf9bacc39 100644 --- a/include/exporter/ddprof_exporter.hpp +++ b/include/exporter/ddprof_exporter.hpp @@ -12,7 +12,7 @@ #include "string_view.hpp" #include "tags.hpp" -typedef struct ddog_ProfileExporter ddog_ProfileExporter; +typedef struct ddog_prof_Exporter ddog_prof_Exporter; typedef struct ddog_Profile ddog_Profile; typedef struct UserTags UserTags; @@ -22,7 +22,7 @@ typedef struct DDProfExporter { ExporterInput _input; char *_url; // url contains path and port const char *_debug_pprof_prefix; // write pprofs to folder - ddog_ProfileExporter *_exporter; + ddog_prof_Exporter *_exporter; bool _agent; bool _export; // debug mode : should we send profiles ? int32_t _nb_consecutive_errors; @@ -34,7 +34,7 @@ DDRes ddprof_exporter_init(const ExporterInput *exporter_input, DDRes ddprof_exporter_new(const UserTags *user_tags, DDProfExporter *exporter); -DDRes ddprof_exporter_export(const struct ddog_Profile *profile, +DDRes ddprof_exporter_export(const struct ddog_prof_Profile *profile, const ddprof::Tags &additional_tags, uint32_t profile_seq, DDProfExporter *exporter); diff --git a/include/pprof/ddprof_pprof.hpp b/include/pprof/ddprof_pprof.hpp index e902d04a0..226f5e972 100644 --- a/include/pprof/ddprof_pprof.hpp +++ b/include/pprof/ddprof_pprof.hpp @@ -12,13 +12,13 @@ #include "tags.hpp" #include "unwind_output.hpp" -struct ddog_Profile; +struct ddog_prof_Profile; struct SymbolHdr; struct DDProfPProf { DDProfPProf() noexcept {} /* single profile gathering several value types */ - ddog_Profile *_profile = nullptr; + ddog_prof_Profile *_profile = nullptr; unsigned _nb_values = 0; ddprof::Tags _tags; }; diff --git a/src/exporter/ddprof_exporter.cc b/src/exporter/ddprof_exporter.cc index d4500fff8..190426be4 100644 --- a/src/exporter/ddprof_exporter.cc +++ b/src/exporter/ddprof_exporter.cc @@ -62,8 +62,9 @@ static DDRes create_pprof_file(ddog_Timespec start, } /// Write pprof to a valid file descriptor : allows to use pprof tools -static DDRes write_profile(const ddog_EncodedProfile *encoded_profile, int fd) { - const ddog_Vec_u8 *buffer = &encoded_profile->buffer; +static DDRes write_profile(const ddog_prof_EncodedProfile *encoded_profile, + int fd) { + const ddog_Vec_U8 *buffer = &encoded_profile->buffer; if (write(fd, buffer->ptr, buffer->len) == 0) { DDRES_RETURN_ERROR_LOG(DD_WHAT_EXPORTER, "Failed to write byte buffer to stdout! %s\n", @@ -72,7 +73,7 @@ static DDRes write_profile(const ddog_EncodedProfile *encoded_profile, int fd) { return ddres_init(); } -static DDRes write_pprof_file(const ddog_EncodedProfile *encoded_profile, +static DDRes write_pprof_file(const ddog_prof_EncodedProfile *encoded_profile, const char *dbg_pprof_prefix) { int fd = -1; DDRES_CHECK_FWD( @@ -168,14 +169,15 @@ DDRes ddprof_exporter_init(const ExporterInput *exporter_input, return ddres_init(); } -static DDRes add_single_tag(ddog_Vec_tag &tags_exporter, std::string_view key, +static DDRes add_single_tag(ddog_Vec_Tag &tags_exporter, std::string_view key, std::string_view value) { - ddog_PushTagResult push_tag_res = - ddog_Vec_tag_push(&tags_exporter, to_CharSlice(key), to_CharSlice(value)); - defer { ddog_PushTagResult_drop(push_tag_res); }; - if (push_tag_res.tag == DDOG_PUSH_TAG_RESULT_ERR) { - LG_ERR("[EXPORTER] Failure generate tag (%.*s)", (int)push_tag_res.err.len, - push_tag_res.err.ptr); + ddog_Vec_Tag_PushResult push_tag_res = + ddog_Vec_Tag_push(&tags_exporter, to_CharSlice(key), to_CharSlice(value)); + if (push_tag_res.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) { + defer { ddog_Error_drop(&push_tag_res.err); }; + + LG_ERR("[EXPORTER] Failure generate tag (%.*s)", + (int)push_tag_res.err.message.len, push_tag_res.err.message.ptr); DDRES_RETURN_ERROR_LOG(DD_WHAT_EXPORTER, "Failed to generate tags"); } return ddres_init(); @@ -183,7 +185,7 @@ static DDRes add_single_tag(ddog_Vec_tag &tags_exporter, std::string_view key, static DDRes fill_stable_tags(const UserTags *user_tags, const DDProfExporter *exporter, - ddog_Vec_tag &tags_exporter) { + ddog_Vec_Tag &tags_exporter) { // language is guaranteed to be filled DDRES_CHECK_FWD( @@ -216,7 +218,9 @@ static DDRes fill_stable_tags(const UserTags *user_tags, } DDRes ddprof_exporter_new(const UserTags *user_tags, DDProfExporter *exporter) { - ddog_Vec_tag tags_exporter = ddog_Vec_tag_new(); + ddog_Vec_Tag tags_exporter = ddog_Vec_Tag_new(); + defer { ddog_Vec_Tag_drop(tags_exporter); }; + fill_stable_tags(user_tags, exporter, tags_exporter); ddog_CharSlice base_url = to_CharSlice(exporter->_url); @@ -228,17 +232,18 @@ DDRes ddprof_exporter_new(const UserTags *user_tags, DDProfExporter *exporter) { endpoint = ddog_Endpoint_agentless(base_url, api_key); } - ddog_NewProfileExporterResult new_exporter = ddog_ProfileExporter_new( + ddog_prof_Exporter_NewResult res_exporter = ddog_prof_Exporter_new( + to_CharSlice(exporter->_input.user_agent), + to_CharSlice(exporter->_input.profiler_version), to_CharSlice(exporter->_input.family), &tags_exporter, endpoint); - if (new_exporter.tag == DDOG_NEW_PROFILE_EXPORTER_RESULT_OK) { - exporter->_exporter = new_exporter.ok; + if (res_exporter.tag == DDOG_PROF_EXPORTER_NEW_RESULT_OK) { + exporter->_exporter = res_exporter.ok; } else { + defer { ddog_Error_drop(&res_exporter.err); }; DDRES_RETURN_ERROR_LOG(DD_WHAT_EXPORTER, "Failure creating exporter - %s", - new_exporter.err.ptr); - ddog_NewProfileExporterResult_drop(new_exporter); + res_exporter.err.message.ptr); } - ddog_Vec_tag_drop(tags_exporter); return ddres_init(); } @@ -271,7 +276,7 @@ static DDRes check_send_response_code(uint16_t send_response_code) { static DDRes fill_cycle_tags(const ddprof::Tags &additional_tags, uint32_t profile_seq, - ddog_Vec_tag &ffi_additional_tags) { + ddog_Vec_Tag &ffi_additional_tags) { DDRES_CHECK_FWD(add_single_tag(ffi_additional_tags, "profile_seq", std::to_string(profile_seq))); @@ -282,18 +287,20 @@ static DDRes fill_cycle_tags(const ddprof::Tags &additional_tags, return ddres_init(); } -DDRes ddprof_exporter_export(const ddog_Profile *profile, +DDRes ddprof_exporter_export(const ddog_prof_Profile *profile, const ddprof::Tags &additional_tags, uint32_t profile_seq, DDProfExporter *exporter) { DDRes res = ddres_init(); - ddog_SerializeResult serialized_result = - ddog_Profile_serialize(profile, nullptr, nullptr); - defer { ddog_SerializeResult_drop(serialized_result); }; - if (serialized_result.tag != DDOG_SERIALIZE_RESULT_OK) { - DDRES_RETURN_ERROR_LOG(DD_WHAT_EXPORTER, "Failed to serialize"); + ddog_prof_Profile_SerializeResult serialized_result = + ddog_prof_Profile_serialize(profile, nullptr, nullptr); + if (serialized_result.tag != DDOG_PROF_PROFILE_SERIALIZE_RESULT_OK) { + defer { ddog_Error_drop(&serialized_result.err); }; + DDRES_RETURN_ERROR_LOG(DD_WHAT_EXPORTER, "Failed to serialize: %s", + serialized_result.err.message.ptr); } - ddog_EncodedProfile *encoded_profile = &serialized_result.ok; + ddog_prof_EncodedProfile *encoded_profile = &serialized_result.ok; + defer { ddog_prof_EncodedProfile_drop(encoded_profile); }; if (exporter->_debug_pprof_prefix) { write_pprof_file(encoded_profile, exporter->_debug_pprof_prefix); @@ -310,32 +317,42 @@ DDRes ddprof_exporter_export(const ddog_Profile *profile, exporter->_last_pprof_size = profile_data.len; if (exporter->_export) { - ddog_Vec_tag ffi_additional_tags = ddog_Vec_tag_new(); - defer { ddog_Vec_tag_drop(ffi_additional_tags); }; + ddog_Vec_Tag ffi_additional_tags = ddog_Vec_Tag_new(); + defer { ddog_Vec_Tag_drop(ffi_additional_tags); }; DDRES_CHECK_FWD( fill_cycle_tags(additional_tags, profile_seq, ffi_additional_tags);); LG_NTC("[EXPORTER] Export buffer of size %lu", profile_data.len); // Backend has some logic based on the following naming - ddog_File files_[] = {{ + ddog_prof_Exporter_File files_[] = {{ .name = to_CharSlice("auto.pprof"), .file = profile_data, }}; - ddog_Slice_file files = {.ptr = files_, .len = std::size(files_)}; + ddog_prof_Exporter_Slice_File files = {.ptr = files_, + .len = std::size(files_)}; + + ddog_prof_Exporter_Request_BuildResult res_request = + ddog_prof_Exporter_Request_build(exporter->_exporter, start, end, files, + &ffi_additional_tags, nullptr, + k_timeout_ms); + + if (res_request.tag == DDOG_PROF_EXPORTER_REQUEST_BUILD_RESULT_OK) { + ddog_prof_Exporter_Request *request = res_request.ok; + + // dropping the request is not useful if we have a send + // however the send will replace the request by null when it takes + // ownership + defer { ddog_prof_Exporter_Request_drop(&request); }; - ddog_Request *request = - ddog_ProfileExporter_build(exporter->_exporter, start, end, files, - &ffi_additional_tags, k_timeout_ms); - if (request) { - ddog_SendResult result = - ddog_ProfileExporter_send(exporter->_exporter, request, nullptr); - defer { ddog_SendResult_drop(result); }; + ddog_prof_Exporter_SendResult result = + ddog_prof_Exporter_send(exporter->_exporter, &request, nullptr); - if (result.tag == DDOG_SEND_RESULT_ERR) { + if (result.tag == DDOG_PROF_EXPORTER_SEND_RESULT_ERR) { + defer { ddog_Error_drop(&result.err); }; LG_WRN("Failure to establish connection, check url %s", exporter->_url); - LG_WRN("Failure to send profiles (%.*s)", (int)result.err.len, - result.err.ptr); + LG_WRN("Failure to send profiles (%.*s)", (int)result.err.message.len, + result.err.message.ptr); // Free error buffer (prefer this API to the free API) if (exporter->_nb_consecutive_errors++ >= K_NB_CONSECUTIVE_ERRORS_ALLOWED) { @@ -350,7 +367,8 @@ DDRes ddprof_exporter_export(const ddog_Profile *profile, res = check_send_response_code(result.http_response.code); } } else { - LG_ERR("[EXPORTER] Failure to build request"); + LG_ERR("[EXPORTER] Failure to build request: %s", + res_request.err.message.ptr); res = ddres_error(DD_WHAT_EXPORTER); } } @@ -359,7 +377,7 @@ DDRes ddprof_exporter_export(const ddog_Profile *profile, DDRes ddprof_exporter_free(DDProfExporter *exporter) { if (exporter->_exporter) - ddog_ProfileExporter_delete(exporter->_exporter); + ddog_prof_Exporter_drop(exporter->_exporter); exporter->_exporter = nullptr; exporter_input_free(&exporter->_input); free(exporter->_url); diff --git a/src/pprof/ddprof_pprof.cc b/src/pprof/ddprof_pprof.cc index a32962cf7..d2306f3eb 100644 --- a/src/pprof/ddprof_pprof.cc +++ b/src/pprof/ddprof_pprof.cc @@ -8,6 +8,7 @@ #include "ddog_profiling_utils.hpp" #include "ddprof_defs.hpp" #include "ddres.hpp" +#include "defer.hpp" #include "pevent_lib.hpp" #include "span.hpp" #include "string_format.hpp" @@ -23,7 +24,7 @@ DDRes pprof_create_profile(DDProfPProf *pprof, DDProfContext *ctx) { PerfWatcher *watchers = ctx->watchers; size_t num_watchers = ctx->num_watchers; - ddog_ValueType perf_value_type[DDPROF_PWT_LENGTH]; + ddog_prof_ValueType perf_value_type[DDPROF_PWT_LENGTH]; // Figure out which sample_type_ids are used by active watchers // We also record the watcher with the lowest valid sample_type id, since that @@ -90,10 +91,10 @@ DDRes pprof_create_profile(DDProfPProf *pprof, DDProfContext *ctx) { } pprof->_nb_values = num_sample_type_ids; - ddog_Slice_value_type sample_types = {.ptr = perf_value_type, - .len = pprof->_nb_values}; + ddog_prof_Slice_ValueType sample_types = {.ptr = perf_value_type, + .len = pprof->_nb_values}; - ddog_Period period; + ddog_prof_Period period; if (num_sample_type_ids > 0) { // Populate the default. If we have a frequency, assume it is given in // hertz and convert to a period in nanoseconds. This is broken for many @@ -108,7 +109,7 @@ DDRes pprof_create_profile(DDProfPProf *pprof, DDProfContext *ctx) { .value = default_period, }; } - pprof->_profile = ddog_Profile_new( + pprof->_profile = ddog_prof_Profile_new( sample_types, num_sample_type_ids > 0 ? &period : nullptr, nullptr); if (!pprof->_profile) { DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to create profile"); @@ -127,16 +128,14 @@ DDRes pprof_create_profile(DDProfPProf *pprof, DDProfContext *ctx) { } DDRes pprof_free_profile(DDProfPProf *pprof) { - if (pprof->_profile) { - ddog_Profile_free(pprof->_profile); - } + ddog_prof_Profile_drop(pprof->_profile); pprof->_profile = NULL; pprof->_nb_values = 0; return ddres_init(); } static void write_function(const ddprof::Symbol &symbol, - ddog_Function *ffi_func) { + ddog_prof_Function *ffi_func) { ffi_func->name = to_CharSlice(symbol._demangle_name); ffi_func->system_name = to_CharSlice(symbol._symname); ffi_func->filename = to_CharSlice(symbol._srcpath); @@ -145,7 +144,7 @@ static void write_function(const ddprof::Symbol &symbol, } static void write_mapping(const ddprof::MapInfo &mapinfo, - ddog_Mapping *ffi_mapping) { + ddog_prof_Mapping *ffi_mapping) { ffi_mapping->memory_start = mapinfo._low_addr; ffi_mapping->memory_limit = mapinfo._high_addr; ffi_mapping->file_offset = mapinfo._offset; @@ -154,8 +153,8 @@ static void write_mapping(const ddprof::MapInfo &mapinfo, } static void write_location(const FunLoc *loc, const ddprof::MapInfo &mapinfo, - const ddog_Slice_line *lines, - ddog_Location *ffi_location) { + const ddog_prof_Slice_Line *lines, + ddog_prof_Location *ffi_location) { write_mapping(mapinfo, &ffi_location->mapping); ffi_location->address = loc->ip; ffi_location->lines = *lines; @@ -163,7 +162,7 @@ static void write_location(const FunLoc *loc, const ddprof::MapInfo &mapinfo, ffi_location->is_folded = false; } -static void write_line(const ddprof::Symbol &symbol, ddog_Line *ffi_line) { +static void write_line(const ddprof::Symbol &symbol, ddog_prof_Line *ffi_line) { write_function(symbol, &ffi_line->function); ffi_line->line = symbol._lineno; } @@ -176,7 +175,7 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, const ddprof::SymbolTable &symbol_table = symbol_hdr->_symbol_table; const ddprof::MapInfoTable &mapinfo_table = symbol_hdr->_mapinfo_table; - ddog_Profile *profile = pprof->_profile; + ddog_prof_Profile *profile = pprof->_profile; int64_t values[DDPROF_PWT_LENGTH] = {}; values[watcher->pprof_sample_idx] = value * count; @@ -184,9 +183,9 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, values[watcher->pprof_count_sample_idx] = count; } - ddog_Location locations_buff[DD_MAX_STACK_DEPTH]; + ddog_prof_Location locations_buff[DD_MAX_STACK_DEPTH]; // assumption of single line per loc for now - ddog_Line line_buff[DD_MAX_STACK_DEPTH]; + ddog_prof_Line line_buff[DD_MAX_STACK_DEPTH]; ddprof::span locs{uw_output->locs, uw_output->nb_locs}; @@ -204,7 +203,7 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, for (const FunLoc &loc : locs) { // possibly several lines to handle inlined function (not handled for now) write_line(symbol_table[loc._symbol_idx], &line_buff[cur_loc]); - ddog_Slice_line lines = {.ptr = &line_buff[cur_loc], .len = 1}; + ddog_prof_Slice_Line lines = {.ptr = &line_buff[cur_loc], .len = 1}; write_location(&loc, mapinfo_table[loc._map_info_idx], &lines, &locations_buff[cur_loc]); ++cur_loc; @@ -213,7 +212,7 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, // Create the labels for the sample. Two samples are the same only when // their locations _and_ all labels are identical, so we admit a very limited // number of labels at present - ddog_Label labels[PPROF_MAX_LABELS] = {}; + ddog_prof_Label labels[PPROF_MAX_LABELS] = {}; size_t labels_num = 0; char pid_str[sizeof("536870912")] = {}; // reserve space up to 2^29 base-10 char tid_str[sizeof("536870912")] = {}; // reserve space up to 2^29 base-10 @@ -247,22 +246,25 @@ DDRes pprof_aggregate(const UnwindOutput *uw_output, } ++labels_num; } - ddog_Sample sample = { + ddog_prof_Sample sample = { .locations = {.ptr = locations_buff, .len = cur_loc}, .values = {.ptr = values, .len = pprof->_nb_values}, .labels = {.ptr = labels, .len = labels_num}, }; - uint64_t id_sample = ddog_Profile_add(profile, sample); - if (id_sample == 0) { - DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to add profile"); + // uint64_t id_sample = ddog_prof_Profile_add(profile, sample); + 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); } return ddres_init(); } DDRes pprof_reset(DDProfPProf *pprof) { - if (!ddog_Profile_reset(pprof->_profile, nullptr)) { + if (!ddog_prof_Profile_reset(pprof->_profile, nullptr)) { DDRES_RETURN_ERROR_LOG(DD_WHAT_PPROF, "Unable to reset profile"); } return ddres_init(); diff --git a/test/ddprof_pprof-ut.cc b/test/ddprof_pprof-ut.cc index 21d06bf3b..31f16b464 100644 --- a/test/ddprof_pprof-ut.cc +++ b/test/ddprof_pprof-ut.cc @@ -36,12 +36,12 @@ TEST(DDProfPProf, init_profiles) { } void test_pprof(const DDProfPProf *pprofs) { - const ddog_Profile *profile = pprofs->_profile; + const ddog_prof_Profile *profile = pprofs->_profile; - struct ddog_SerializeResult serialized_result = - ddog_Profile_serialize(profile, nullptr, nullptr); + struct ddog_prof_Profile_SerializeResult serialized_result = + ddog_prof_Profile_serialize(profile, nullptr, nullptr); - ASSERT_EQ(serialized_result.tag, DDOG_SERIALIZE_RESULT_OK); + ASSERT_EQ(serialized_result.tag, DDOG_PROF_PROFILE_SERIALIZE_RESULT_OK); ddog_Timespec start = serialized_result.ok.start; @@ -49,7 +49,7 @@ void test_pprof(const DDProfPProf *pprofs) { time_t local_time = time(NULL); EXPECT_TRUE(local_time - start.seconds < 2); - ddog_Vec_u8 profile_vec = serialized_result.ok.buffer; + ddog_Vec_U8 profile_vec = serialized_result.ok.buffer; EXPECT_TRUE(profile_vec.ptr); @@ -57,7 +57,7 @@ void test_pprof(const DDProfPProf *pprofs) { EXPECT_TRUE(profile_vec.len > 500); EXPECT_TRUE(profile_vec.capacity >= profile_vec.len); - ddog_SerializeResult_drop(serialized_result); + ddog_prof_EncodedProfile_drop(&serialized_result.ok); } TEST(DDProfPProf, aggregate) { diff --git a/tools/fetch_libdatadog.sh b/tools/fetch_libdatadog.sh index 4c656fe1c..2e8143b1c 100755 --- a/tools/fetch_libdatadog.sh +++ b/tools/fetch_libdatadog.sh @@ -43,6 +43,12 @@ TAR_LIBDATADOG=libdatadog-${MARCH}-${DISTRIBUTION}.tar.gz GITHUB_URL_LIBDATADOG=https://github.com/DataDog/libdatadog/releases/download/${TAG_LIBDATADOG}/${TAR_LIBDATADOG} SHA256_LIBDATADOG=$(grep "${TAR_LIBDATADOG}" "${CHECKSUM_FILE}") +if echo "${SHA256_LIBDATADOG}" | grep -qE '^[[:xdigit:]]{64}[[:space:]]{2}'; then + echo "Using libdatadog sha256: ${SHA256_LIBDATADOG}" +else + echo "Badly formatted sha256. There should be 2 spaces between sha and file name." + exit 1 +fi mkdir -p "$TARGET_EXTRACT" || true cd "$TARGET_EXTRACT" @@ -56,7 +62,7 @@ else fi echo "Checking libdatadog sha256" -if ! echo "${SHA256_LIBDATADOG}" | sha256sum -c; then +if ! echo "${SHA256_LIBDATADOG}" | sha256sum -c -; then echo "Error validating libdatadog SHA256" echo "Please clear $TARGET_EXTRACT before restarting" exit 1 diff --git a/tools/libdatadog_checksums.txt b/tools/libdatadog_checksums.txt index 538cec404..61225d866 100644 --- a/tools/libdatadog_checksums.txt +++ b/tools/libdatadog_checksums.txt @@ -1,4 +1,4 @@ -68919ddf9bc6491927bf16fb819b18fd052209d77774097b57f7879ebafc9bdf libdatadog-aarch64-alpine-linux-musl.tar.gz -9c6dd7058c7d0c9af8ffe18b4565fcda08462debc81f60ce0eb87aa5f7b74a0b libdatadog-aarch64-unknown-linux-gnu.tar.gz -e410300255d93f016562e7e072dcb09f94d0550ff3e289f97fff4cd155a4d3a4 libdatadog-x86_64-alpine-linux-musl.tar.gz -94f52edaed31f8c2a25cd569b0b065f8bb221120706d57ef2ca592b0512333f2 libdatadog-x86_64-unknown-linux-gnu.tar.gz +7a5f8f37b2925ee3e54cc1da8db1a461a4db082f8fd0492e40d4b33c5e771306 libdatadog-aarch64-alpine-linux-musl.tar.gz +a326e9552e65b945c64e7119c23d670ffdfb99aa96d9d90928a8a2ff6427199d libdatadog-aarch64-unknown-linux-gnu.tar.gz +59f8e014b80b5e44bfcc325d03cdcf7c147987e2a106883d91fe80e1cba79f4b libdatadog-x86_64-alpine-linux-musl.tar.gz +e9ee7172dd7b8f12ff8125e0ee699d01df7698604f64299c4094ae47629ccec1 libdatadog-x86_64-unknown-linux-gnu.tar.gz