use libdd_profiling_protobuf::prost_impls;
use std::ops::{Add, Sub};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ValueType<'a> {
pub r#type: &'a str,
pub unit: &'a str,
}
impl<'a> ValueType<'a> {
#[inline(always)]
pub const fn new(r#type: &'a str, unit: &'a str) -> Self {
Self { r#type, unit }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Period<'a> {
pub r#type: ValueType<'a>,
pub value: i64,
}
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct ManagedStringId {
pub value: u32,
}
impl ManagedStringId {
pub const fn empty() -> Self {
Self::new(0)
}
pub const fn new(value: u32) -> Self {
ManagedStringId { value }
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Mapping<'a> {
pub memory_start: u64,
pub memory_limit: u64,
pub file_offset: u64,
pub filename: &'a str,
pub build_id: &'a str,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct StringIdMapping {
pub memory_start: u64,
pub memory_limit: u64,
pub file_offset: u64,
pub filename: ManagedStringId,
pub build_id: ManagedStringId,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Function<'a> {
pub name: &'a str,
pub system_name: &'a str,
pub filename: &'a str,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct StringIdFunction {
pub name: ManagedStringId,
pub system_name: ManagedStringId,
pub filename: ManagedStringId,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Line<'a> {
pub function: Function<'a>,
pub line: i64,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Location<'a> {
pub mapping: Mapping<'a>,
pub function: Function<'a>,
pub address: u64,
pub line: i64,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct StringIdLocation {
pub mapping: StringIdMapping,
pub function: StringIdFunction,
pub address: u64,
pub line: i64,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Label<'a> {
pub key: &'a str,
pub str: &'a str,
pub num: i64,
pub num_unit: &'a str,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct StringIdLabel {
pub key: ManagedStringId,
pub str: ManagedStringId,
pub num: i64,
pub num_unit: ManagedStringId,
}
impl Label<'_> {
pub fn uses_at_most_one_of_str_and_num(&self) -> bool {
self.str.is_empty() || (self.num == 0 && self.num_unit.is_empty())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Sample<'a> {
pub locations: Vec<Location<'a>>,
pub values: &'a [i64],
pub labels: Vec<Label<'a>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StringIdSample<'a> {
pub locations: Vec<StringIdLocation>,
pub values: &'a [i64],
pub labels: Vec<StringIdLabel>,
}
#[derive(Debug)]
#[cfg_attr(test, derive(bolero::generator::TypeGenerator))]
pub enum UpscalingInfo {
Poisson {
sum_value_offset: usize,
count_value_offset: usize,
sampling_distance: u64,
},
PoissonNonSampleTypeCount {
sum_value_offset: usize,
count_value: u64,
sampling_distance: u64,
},
Proportional {
scale: f64,
},
}
impl std::fmt::Display for UpscalingInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UpscalingInfo::Poisson {
sum_value_offset,
count_value_offset,
sampling_distance,
} => write!(
f,
"Poisson = sum_value_offset: {sum_value_offset}, count_value_offset: {count_value_offset}, sampling_distance: {sampling_distance}"
),
UpscalingInfo::PoissonNonSampleTypeCount {
sum_value_offset,
count_value,
sampling_distance,
} => write!(
f,
"Poisson = sum_value_offset: {sum_value_offset}, count_value: {count_value}, sampling_distance: {sampling_distance}",
),
UpscalingInfo::Proportional { scale } => {
write!(f, "Proportional = scale: {scale}")
}
}
}
}
impl UpscalingInfo {
pub fn check_validity(&self, number_of_values: usize) -> anyhow::Result<()> {
match self {
UpscalingInfo::Poisson {
sum_value_offset,
count_value_offset,
sampling_distance,
} => {
anyhow::ensure!(
sum_value_offset < &number_of_values && count_value_offset < &number_of_values,
"sum_value_offset {sum_value_offset} and count_value_offset {count_value_offset} must be strictly less than {number_of_values}"
);
anyhow::ensure!(
sampling_distance != &0,
"sampling_distance {sampling_distance} must be greater than 0"
)
}
UpscalingInfo::PoissonNonSampleTypeCount {
sum_value_offset,
count_value,
sampling_distance,
} => {
anyhow::ensure!(
sum_value_offset < &number_of_values,
"sum_value_offset {sum_value_offset} must be strictly less than {number_of_values}"
);
anyhow::ensure!(
count_value != &0,
"count_value {count_value} must be greater than 0"
);
anyhow::ensure!(
sampling_distance != &0,
"sampling_distance {sampling_distance} must be greater than 0"
)
}
UpscalingInfo::Proportional { scale: _ } => (),
}
anyhow::Ok(())
}
}
pub struct Profile<'a> {
pub duration: Duration,
pub period: Option<(i64, ValueType<'a>)>,
pub sample_types: Vec<ValueType<'a>>,
pub samples: Vec<Sample<'a>>,
pub start_time: SystemTime,
}
fn string_table_fetch(pprof: &prost_impls::Profile, id: i64) -> anyhow::Result<&String> {
pprof
.string_table
.get(id as u64 as usize)
.ok_or_else(|| anyhow::anyhow!("String {id} was not found."))
}
fn mapping_fetch(pprof: &prost_impls::Profile, id: u64) -> anyhow::Result<Mapping<'_>> {
if id == 0 {
return Ok(Mapping::default());
}
match pprof.mappings.iter().find(|item| item.id == id) {
Some(mapping) => Ok(Mapping {
memory_start: mapping.memory_start,
memory_limit: mapping.memory_limit,
file_offset: mapping.file_offset,
filename: string_table_fetch(pprof, mapping.filename)?,
build_id: string_table_fetch(pprof, mapping.build_id)?,
}),
None => anyhow::bail!("Mapping {id} was not found."),
}
}
fn function_fetch(pprof: &prost_impls::Profile, id: u64) -> anyhow::Result<Function<'_>> {
if id == 0 {
return Ok(Function::default());
}
match pprof.functions.iter().find(|item| item.id == id) {
Some(function) => Ok(Function {
name: string_table_fetch(pprof, function.name)?,
system_name: string_table_fetch(pprof, function.system_name)?,
filename: string_table_fetch(pprof, function.filename)?,
}),
None => anyhow::bail!("Function {id} was not found."),
}
}
fn location_fetch(pprof: &prost_impls::Profile, id: u64) -> anyhow::Result<Location<'_>> {
if id == 0 {
return Ok(Location::default());
}
match pprof.locations.iter().find(|item| item.id == id) {
Some(location) => {
anyhow::ensure!(!location.is_folded, "expected Location to not be folded");
anyhow::ensure!(
location.lines.len() == 1,
"expected Location to have exactly 1 Line"
);
let line = unsafe { location.lines.get_unchecked(0) };
let function = function_fetch(pprof, line.function_id)?;
Ok(Location {
mapping: mapping_fetch(pprof, location.mapping_id)?,
function,
address: location.address,
line: line.line,
})
}
None => anyhow::bail!("Location {id} was not found."),
}
}
fn locations_fetch<'a>(
pprof: &'a prost_impls::Profile,
ids: &'a [u64],
) -> anyhow::Result<Vec<Location<'a>>> {
let mut locations = Vec::with_capacity(ids.len());
for id in ids {
let location = location_fetch(pprof, *id)?;
locations.push(location);
}
Ok(locations)
}
impl<'a> TryFrom<&'a prost_impls::Profile> for Profile<'a> {
type Error = anyhow::Error;
fn try_from(pprof: &'a prost_impls::Profile) -> Result<Self, Self::Error> {
assert!(pprof.duration_nanos >= 0);
let duration = Duration::from_nanos(pprof.duration_nanos as u64);
let start_time = if pprof.time_nanos.is_negative() {
UNIX_EPOCH.sub(Duration::from_nanos(pprof.time_nanos.unsigned_abs()))
} else {
UNIX_EPOCH.add(Duration::from_nanos(pprof.time_nanos as u64))
};
let period = match pprof.period_type {
Some(t) => {
let r#type = ValueType::new(
string_table_fetch(pprof, t.r#type)?,
string_table_fetch(pprof, t.unit)?,
);
Some((pprof.period, r#type))
}
None => None,
};
let mut sample_types = Vec::with_capacity(pprof.samples.len());
for t in pprof.sample_types.iter() {
sample_types.push(ValueType::new(
string_table_fetch(pprof, t.r#type)?,
string_table_fetch(pprof, t.unit)?,
));
}
let mut samples = Vec::with_capacity(pprof.samples.len());
for sample in pprof.samples.iter() {
let locations = locations_fetch(pprof, &sample.location_ids)?;
let mut labels = Vec::with_capacity(sample.labels.len());
for label in sample.labels.iter() {
labels.push(Label {
key: string_table_fetch(pprof, label.key)?,
str: string_table_fetch(pprof, label.str)?,
num: label.num,
num_unit: string_table_fetch(pprof, label.num_unit)?,
})
}
let sample = Sample {
locations,
values: &sample.values,
labels,
};
samples.push(sample);
}
Ok(Profile {
duration,
period,
sample_types,
samples,
start_time,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn label_uses_at_most_one_of_str_and_num() {
let label = Label {
key: "name",
str: "levi",
num: 0,
num_unit: "name", };
assert!(!label.uses_at_most_one_of_str_and_num());
let label = Label {
key: "name",
str: "levi",
num: 10, num_unit: "",
};
assert!(!label.uses_at_most_one_of_str_and_num());
let label = Label {
key: "name",
str: "levi",
num: 0,
num_unit: "",
};
assert!(label.uses_at_most_one_of_str_and_num());
let label = Label {
key: "process_id",
str: "",
num: 0,
num_unit: "",
};
assert!(label.uses_at_most_one_of_str_and_num());
let label = Label {
key: "local root span id",
str: "",
num: 10901,
num_unit: "",
};
assert!(label.uses_at_most_one_of_str_and_num());
let label = Label {
key: "duration",
str: "",
num: 12345,
num_unit: "nanoseconds",
};
assert!(label.uses_at_most_one_of_str_and_num());
}
}