Skip to content
Merged
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
3 changes: 3 additions & 0 deletions datadog-profiling-replayer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ fn main() -> anyhow::Result<()> {
let mut outprof =
libdd_profiling::internal::Profile::try_new(&replayer.sample_types, replayer.period)?
.with_start_time(replayer.start_time)?;
for (slot, value_type) in replayer.custom_type_mappings.iter().copied() {
outprof.set_custom_sample_type(slot, value_type)?;
}

// Before benchmarking, let's calculate some statistics.
// No point doing that if there aren't at least 4 samples though.
Expand Down
63 changes: 54 additions & 9 deletions datadog-profiling-replayer/src/replayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type SamplesAndEndpointInfo<'pprof> = (
Vec<(Option<Timestamp>, api::Sample<'pprof>)>,
Vec<(u64, &'pprof str)>,
);
type CustomTypeMappings<'pprof> = Vec<(api::SampleType, api::ValueType<'pprof>)>;

pub struct Replayer<'pprof> {
pub profile_index: ProfileIndex<'pprof>,
Expand All @@ -26,6 +27,7 @@ pub struct Replayer<'pprof> {
pub end_time: SystemTime, // start_time + duration
pub sample_types: Vec<api::SampleType>,
pub period: Option<api::Period>,
pub custom_type_mappings: CustomTypeMappings<'pprof>,
pub endpoints: Vec<(u64, &'pprof str)>,
pub samples: Vec<(Option<Timestamp>, api::Sample<'pprof>)>,
}
Expand Down Expand Up @@ -55,28 +57,69 @@ impl<'pprof> Replayer<'pprof> {
}
}

fn sample_types<'a>(
profile_index: &'a ProfileIndex<'pprof>,
fn custom_slots() -> [api::SampleType; 5] {
[
api::SampleType::Custom1,
api::SampleType::Custom2,
api::SampleType::Custom3,
api::SampleType::Custom4,
api::SampleType::Custom5,
]
}

fn resolve_sample_type(
type_str: &'pprof str,
unit: &'pprof str,
custom_type_mappings: &mut CustomTypeMappings<'pprof>,
) -> anyhow::Result<api::SampleType> {
let borrowed = api::ValueType::new(type_str, unit);
if let Ok(sample_type) = api::SampleType::try_from(borrowed) {
return Ok(sample_type);
}

if let Some((slot, _)) = custom_type_mappings
.iter()
.find(|(_, value_type)| value_type.r#type == type_str && value_type.unit == unit)
{
return Ok(*slot);
}

let slots = Self::custom_slots();
let slot = *slots
.get(custom_type_mappings.len())
.ok_or_else(|| anyhow::anyhow!("pprof uses more than 5 custom sample types"))?;
custom_type_mappings.push((slot, api::ValueType::new(type_str, unit)));
Ok(slot)
}

fn sample_types(
profile_index: &ProfileIndex<'pprof>,
custom_type_mappings: &mut CustomTypeMappings<'pprof>,
) -> anyhow::Result<Vec<api::SampleType>> {
let mut sample_types = Vec::with_capacity(profile_index.pprof.sample_types.len());
for sample_type in profile_index.pprof.sample_types.iter() {
let type_str = profile_index.get_string(sample_type.r#type)?;
let unit = profile_index.get_string(sample_type.unit)?;
let vt = api::ValueType::new(type_str, unit);
sample_types.push(vt.try_into()?);
sample_types.push(Self::resolve_sample_type(
type_str,
unit,
custom_type_mappings,
)?);
}
Ok(sample_types)
}

fn period<'a>(profile_index: &'a ProfileIndex<'pprof>) -> anyhow::Result<Option<api::Period>> {
fn period(
profile_index: &ProfileIndex<'pprof>,
custom_type_mappings: &mut CustomTypeMappings<'pprof>,
) -> anyhow::Result<Option<api::Period>> {
let value = profile_index.pprof.period;

match profile_index.pprof.period_type {
Some(period_type) => {
let type_str = profile_index.get_string(period_type.r#type)?;
let unit = profile_index.get_string(period_type.unit)?;
let vt = api::ValueType::new(type_str, unit);
let sample_type = vt.try_into()?;
let sample_type = Self::resolve_sample_type(type_str, unit, custom_type_mappings)?;
Ok(Some(api::Period { sample_type, value }))
}
None => Ok(None),
Expand Down Expand Up @@ -252,8 +295,9 @@ impl<'pprof> TryFrom<&'pprof prost_impls::Profile> for Replayer<'pprof> {
let start_time = Self::start_time(pprof);
let duration = Self::duration(pprof)?;
let end_time = start_time.add(duration);
let sample_types = Self::sample_types(&profile_index)?;
let period = Self::period(&profile_index)?;
let mut custom_type_mappings = Vec::new();
let sample_types = Self::sample_types(&profile_index, &mut custom_type_mappings)?;
let period = Self::period(&profile_index, &mut custom_type_mappings)?;
let (samples, endpoints) = Self::samples(&profile_index)?;

Ok(Self {
Expand All @@ -263,6 +307,7 @@ impl<'pprof> TryFrom<&'pprof prost_impls::Profile> for Replayer<'pprof> {
end_time,
sample_types,
period,
custom_type_mappings,
endpoints,
samples,
})
Expand Down
6 changes: 3 additions & 3 deletions examples/cxx/profiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ int main() {
.value = 60
};

// Create profile with predefined sample types
// Note: ExperimentalCount, ExperimentalNanoseconds, and ExperimentalBytes
// are available for custom profiling metrics
// Create profile with predefined sample types. For prototyping a type
// not yet in SampleType, use Custom1..Custom5 and configure the slot
// with Profile::set_custom_sample_type before serialization.
auto profile = Profile::create({SampleType::WallTime}, period);
std::cout << "✅ Profile created" << std::endl;

Expand Down
4 changes: 4 additions & 0 deletions examples/ffi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ add_executable(profiles profiles.c)
target_link_libraries(profiles PRIVATE Datadog::Profiling)
set_vcruntime_link_type(profiles ${VCRUNTIME_LINK_TYPE})

add_executable(custom_profile_types custom_profile_types.c)
target_link_libraries(custom_profile_types PRIVATE Datadog::Profiling)
set_vcruntime_link_type(custom_profile_types ${VCRUNTIME_LINK_TYPE})

if(BUILD_SYMBOLIZER)
add_executable(symbolizer symbolizer.cpp)
target_link_libraries(symbolizer PRIVATE Datadog::Profiling)
Expand Down
138 changes: 138 additions & 0 deletions examples/ffi/custom_profile_types.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

// Example: creating a profile with a custom sample type slot.
//
// Use DDOG_PROF_SAMPLE_TYPE_CUSTOM1 through DDOG_PROF_SAMPLE_TYPE_CUSTOM5 when
// the desired sample type is not yet in the stable ddog_prof_SampleType enum,
// then configure the slot with ddog_prof_Profile_set_custom_sample_type.
//
// TODO: Once custom profile types are stable and agreed upon across profiler
// teams, add a dedicated ddog_prof_SampleType variant and switch callers to it.

#include <datadog/profiling.h>
#include <stdio.h>
#include <stdlib.h>

static int check_profile_result(ddog_prof_Profile_Result result, const char *context) {
if (result.tag != DDOG_PROF_PROFILE_RESULT_OK) {
ddog_CharSlice msg = ddog_Error_message(&result.err);
fprintf(stderr, "%s: %.*s\n", context, (int)msg.len, msg.ptr);
ddog_Error_drop(&result.err);
return 0;
}
return 1;
}

int main(void) {
// -------------------------------------------------------------------------
// 1. Select one of the custom sample type slots.
// -------------------------------------------------------------------------
const ddog_prof_SampleType sample_types[] = {
DDOG_PROF_SAMPLE_TYPE_CUSTOM1,
};
const ddog_prof_Slice_SampleType sample_types_slice = {
.ptr = sample_types,
.len = sizeof(sample_types) / sizeof(sample_types[0]),
};

// The profile period is optional and is profile-level sampling metadata, not
// per-sample-type metadata. It describes the sampling distance/cadence; the
// profile duration describes the upload/reporting window.
//
// memory-breakdown is a point-in-time byte measurement in this example, so
// there is no meaningful sampling cadence to report and we pass NULL.

// -------------------------------------------------------------------------
// 2. Create the profile.
// -------------------------------------------------------------------------
ddog_prof_Profile_NewResult new_result =
ddog_prof_Profile_new(sample_types_slice, NULL);
if (new_result.tag != DDOG_PROF_PROFILE_NEW_RESULT_OK) {
ddog_CharSlice msg = ddog_Error_message(&new_result.err);
fprintf(stderr, "ddog_prof_Profile_new failed: %.*s\n", (int)msg.len, msg.ptr);
ddog_Error_drop(&new_result.err);
return EXIT_FAILURE;
}
ddog_prof_Profile profile = new_result.ok;

if (!check_profile_result(
ddog_prof_Profile_set_custom_sample_type(&profile,
DDOG_PROF_SAMPLE_TYPE_CUSTOM1,
DDOG_CHARSLICE_C("memory-breakdown"),
DDOG_CHARSLICE_C("bytes")),
"set_custom_sample_type")) {
ddog_prof_Profile_drop(&profile);
return EXIT_FAILURE;
}

if (!check_profile_result(
ddog_prof_Profile_set_omit_local_root_span_id_when_serializing(&profile, true),
"set_omit_local_root_span_id")) {
ddog_prof_Profile_drop(&profile);
return EXIT_FAILURE;
}

// -------------------------------------------------------------------------
// 3. Add a sample (one value per sample type).
// Separate stacks / labels can distinguish anonymous, file-backed, JIT,
// or other memory categories while sharing the same sample type.
// -------------------------------------------------------------------------
ddog_prof_Location location = {
.mapping = (ddog_prof_Mapping){0},
.function = {.name = DDOG_CHARSLICE_C("my_alloc_function"),
.filename = DDOG_CHARSLICE_C("/src/allocator.c")},
};
int64_t values[] = {4096};
const ddog_prof_Sample sample = {
.locations = {&location, 1},
.values = {values, sizeof(values) / sizeof(values[0])},
.labels = {NULL, 0},
};

if (!check_profile_result(ddog_prof_Profile_add(&profile, sample, 0),
"ddog_prof_Profile_add")) {
ddog_prof_Profile_drop(&profile);
return EXIT_FAILURE;
}

// -------------------------------------------------------------------------
// 4. Serialize and verify we get back a non-empty buffer.
// -------------------------------------------------------------------------
ddog_prof_Profile_SerializeResult ser_result =
ddog_prof_Profile_serialize(&profile, NULL, NULL);
if (ser_result.tag != DDOG_PROF_PROFILE_SERIALIZE_RESULT_OK) {
ddog_CharSlice msg = ddog_Error_message(&ser_result.err);
fprintf(stderr, "serialize failed: %.*s\n", (int)msg.len, msg.ptr);
ddog_Error_drop(&ser_result.err);
ddog_prof_Profile_drop(&profile);
return EXIT_FAILURE;
}
ddog_prof_EncodedProfile encoded = ser_result.ok;

ddog_prof_Result_ByteSlice buf_result = ddog_prof_EncodedProfile_bytes(&encoded);
if (buf_result.tag != DDOG_PROF_RESULT_BYTE_SLICE_OK_BYTE_SLICE) {
ddog_CharSlice msg = ddog_Error_message(&buf_result.err);
fprintf(stderr, "EncodedProfile_bytes failed: %.*s\n", (int)msg.len, msg.ptr);
ddog_Error_drop(&buf_result.err);
ddog_prof_EncodedProfile_drop(&encoded);
ddog_prof_Profile_drop(&profile);
return EXIT_FAILURE;
}

if (buf_result.ok.len == 0) {
fprintf(stderr, "serialize returned an empty buffer\n");
ddog_prof_EncodedProfile_drop(&encoded);
ddog_prof_Profile_drop(&profile);
return EXIT_FAILURE;
}

fprintf(stdout, "custom_profile_types: serialized %zu bytes\n", (size_t)buf_result.ok.len);

// -------------------------------------------------------------------------
// 5. Clean up.
// -------------------------------------------------------------------------
ddog_prof_EncodedProfile_drop(&encoded);
ddog_prof_Profile_drop(&profile);
return EXIT_SUCCESS;
}
Loading
Loading