diff --git a/profiling/src/api.rs b/profiling/src/api.rs index 477fd75ec..98a926b7c 100644 --- a/profiling/src/api.rs +++ b/profiling/src/api.rs @@ -128,6 +128,7 @@ pub struct Sample<'a> { } #[derive(Debug)] +#[cfg_attr(test, derive(bolero_generator::TypeGenerator))] pub enum UpscalingInfo { Poisson { // sum_value_offset and count_value_offset are offsets in the profile values type array diff --git a/profiling/src/internal/endpoint_stats.rs b/profiling/src/internal/endpoint_stats.rs index 60420e26d..9277aec68 100644 --- a/profiling/src/internal/endpoint_stats.rs +++ b/profiling/src/internal/endpoint_stats.rs @@ -19,7 +19,8 @@ impl From> for ProfiledEndpointsStats { impl ProfiledEndpointsStats { pub fn add_endpoint_count(&mut self, endpoint_name: String, value: i64) { - *self.count.entry(endpoint_name).or_insert(0) += value; + let entry = self.count.entry(endpoint_name).or_insert(0); + *entry = entry.saturating_add(value); } pub fn is_empty(&self) -> bool { diff --git a/profiling/src/internal/mod.rs b/profiling/src/internal/mod.rs index 72f11a9c1..063fd7006 100644 --- a/profiling/src/internal/mod.rs +++ b/profiling/src/internal/mod.rs @@ -23,7 +23,6 @@ pub use label::*; pub use location::*; pub use mapping::*; pub use observation::*; -pub use owned_types::*; pub use profile::*; pub use sample::*; pub use stack_trace::*; diff --git a/profiling/src/internal/owned_types.rs b/profiling/src/internal/owned_types.rs deleted file mode 100644 index 4df1058ca..000000000 --- a/profiling/src/internal/owned_types.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ -// SPDX-License-Identifier: Apache-2.0 - -use crate::api::{Period, ValueType}; - -#[derive(Clone)] -pub struct OwnedValueType { - pub typ: Box, - pub unit: Box, -} - -impl<'a> From<&'a ValueType<'a>> for OwnedValueType { - #[inline] - fn from(value_type: &'a ValueType<'a>) -> Self { - Self { - typ: String::from(value_type.r#type).into(), - unit: String::from(value_type.unit).into(), - } - } -} - -#[derive(Clone)] -pub struct OwnedPeriod { - pub typ: OwnedValueType, - pub value: i64, -} - -impl<'a> From<&'a Period<'a>> for OwnedPeriod { - #[inline] - fn from(period: &'a Period<'a>) -> Self { - Self { - typ: OwnedValueType::from(&period.r#type), - value: period.value, - } - } -} diff --git a/profiling/src/internal/owned_types/mod.rs b/profiling/src/internal/owned_types/mod.rs new file mode 100644 index 000000000..e667ee299 --- /dev/null +++ b/profiling/src/internal/owned_types/mod.rs @@ -0,0 +1,44 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::api; + +#[cfg_attr(test, derive(bolero_generator::TypeGenerator))] +#[derive(Clone, Debug)] +pub struct ValueType { + pub typ: Box, + pub unit: Box, +} + +impl<'a> From<&'a api::ValueType<'a>> for ValueType { + #[inline] + fn from(value_type: &'a api::ValueType<'a>) -> Self { + Self { + typ: Box::from(value_type.r#type), + unit: Box::from(value_type.unit), + } + } +} + +impl<'a> From<&'a ValueType> for api::ValueType<'a> { + fn from(value: &'a ValueType) -> Self { + Self::new(&value.typ, &value.unit) + } +} + +#[cfg_attr(test, derive(bolero_generator::TypeGenerator))] +#[derive(Clone, Debug)] +pub struct Period { + pub typ: ValueType, + pub value: i64, +} + +impl<'a> From<&'a api::Period<'a>> for Period { + #[inline] + fn from(period: &'a api::Period<'a>) -> Self { + Self { + typ: ValueType::from(&period.r#type), + value: period.value, + } + } +} diff --git a/profiling/src/internal/profile/fuzz_tests.rs b/profiling/src/internal/profile/fuzz_tests.rs new file mode 100644 index 000000000..de9b8e7b2 --- /dev/null +++ b/profiling/src/internal/profile/fuzz_tests.rs @@ -0,0 +1,649 @@ +// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use super::*; +use bolero::TypeGenerator; +use std::collections::HashSet; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, TypeGenerator)] +pub struct Function { + /// Name of the function, in human-readable form if available. + pub name: Box, + + /// Name of the function, as identified by the system. + /// For instance, it can be a C++ mangled name. + pub system_name: Box, + + /// Source file containing the function. + pub filename: Box, + + /// Line number in source file. + pub start_line: i64, +} + +impl Function { + pub fn new(name: Box, system_name: Box, filename: Box, start_line: i64) -> Self { + Self { + name, + system_name, + filename, + start_line, + } + } +} + +impl<'a> From<&'a Function> for api::Function<'a> { + fn from(value: &'a Function) -> Self { + Self { + name: &value.name, + system_name: &value.system_name, + filename: &value.filename, + start_line: value.start_line, + } + } +} + +#[derive(Clone, Debug, Default, Ord, PartialOrd, TypeGenerator)] +pub struct Label { + pub key: Box, + + /// At most one of the following must be present + pub str: Option>, + pub num: i64, + + /// Should only be present when num is present. + /// Specifies the units of num. + /// Use arbitrary string (for example, "requests") as a custom count unit. + /// If no unit is specified, consumer may apply heuristic to deduce the unit. + /// Consumers may also interpret units like "bytes" and "kilobytes" as memory + /// units and units like "seconds" and "nanoseconds" as time units, + /// and apply appropriate unit conversions to these. + pub num_unit: Option>, +} + +impl<'a> From<&'a Label> for api::Label<'a> { + fn from(value: &'a Label) -> Self { + Self { + key: &value.key, + str: value.str.as_deref(), + num: value.num, + num_unit: value.num_unit.as_deref(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, TypeGenerator)] +pub struct Line { + /// The corresponding profile.Function for this line. + pub function: Function, + + /// Line number in source code. + pub line: i64, +} + +impl<'a> From<&'a Line> for api::Line<'a> { + fn from(value: &'a Line) -> Self { + Self { + function: (&value.function).into(), + line: value.line, + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, TypeGenerator)] +pub struct Location { + pub mapping: Mapping, + pub function: Function, + + /// The instruction address for this location, if available. It + /// should be within [Mapping.memory_start...Mapping.memory_limit] + /// for the corresponding mapping. A non-leaf address may be in the + /// middle of a call instruction. It is up to display tools to find + /// the beginning of the instruction if necessary. + pub address: u64, + pub line: i64, +} + +impl Location { + pub fn new(mapping: Mapping, function: Function, address: u64, line: i64) -> Self { + Self { + mapping, + function, + address, + line, + } + } +} + +impl<'a> From<&'a Location> for api::Location<'a> { + fn from(value: &'a Location) -> Self { + Self { + mapping: (&value.mapping).into(), + function: (&value.function).into(), + address: value.address, + line: value.line, + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, TypeGenerator)] +pub struct Mapping { + /// Address at which the binary (or DLL) is loaded into memory. + pub memory_start: u64, + + /// The limit of the address range occupied by this mapping. + pub memory_limit: u64, + + /// Offset in the binary that corresponds to the first mapped address. + pub file_offset: u64, + + /// The object this entry is loaded from. This can be a filename on + /// disk for the main binary and shared libraries, or virtual + /// abstractions like "[vdso]". + pub filename: Box, + + /// A string that uniquely identifies a particular program version + /// with high probability. E.g., for binaries generated by GNU tools, + /// it could be the contents of the .note.gnu.build-id field. + pub build_id: Box, +} + +impl Mapping { + pub fn new( + memory_start: u64, + memory_limit: u64, + file_offset: u64, + filename: Box, + build_id: Box, + ) -> Self { + Self { + memory_start, + memory_limit, + file_offset, + filename, + build_id, + } + } +} + +impl<'a> From<&'a Mapping> for api::Mapping<'a> { + fn from(value: &'a Mapping) -> Self { + Self { + memory_start: value.memory_start, + memory_limit: value.memory_limit, + file_offset: value.file_offset, + filename: &value.filename, + build_id: &value.build_id, + } + } +} + +#[derive(Clone, Debug, TypeGenerator)] +pub struct Sample { + /// The leaf is at locations[0]. + pub locations: Vec, + + /// The type and unit of each value is defined by the corresponding + /// entry in Profile.sample_type. All samples must have the same + /// number of values, the same as the length of Profile.sample_type. + /// When aggregating multiple samples into a single sample, the + /// result has a list of values that is the element-wise sum of the + /// lists of the originals. + pub values: Vec, + + /// label includes additional context for this sample. It can include + /// things like a thread id, allocation size, etc + pub labels: Vec