relay_profiling/sample/
v2.rsuse std::collections::{BTreeMap, HashSet};
use serde::{Deserialize, Serialize};
use relay_event_schema::protocol::EventId;
use relay_metrics::FiniteF64;
use crate::error::ProfileError;
use crate::measurements::ChunkMeasurement;
use crate::sample::{DebugMeta, Frame, ThreadMetadata, Version};
use crate::types::ClientSdk;
use crate::utils::default_client_sdk;
#[derive(Debug, Serialize, Deserialize)]
pub struct ProfileMetadata {
pub chunk_id: EventId,
pub profiler_id: EventId,
#[serde(default, skip_serializing_if = "DebugMeta::is_empty")]
pub debug_meta: DebugMeta,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<String>,
pub platform: String,
pub release: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_sdk: Option<ClientSdk>,
pub version: Version,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Sample {
pub timestamp: FiniteF64,
pub stack_id: usize,
pub thread_id: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProfileChunk {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub measurements: BTreeMap<String, ChunkMeasurement>,
#[serde(flatten)]
pub metadata: ProfileMetadata,
pub profile: ProfileData,
}
impl ProfileChunk {
pub fn normalize(&mut self) -> Result<(), ProfileError> {
let platform = self.metadata.platform.as_str();
if self.metadata.client_sdk.is_none() {
self.metadata.client_sdk = default_client_sdk(platform);
}
self.profile.normalize(platform)
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ProfileData {
pub samples: Vec<Sample>,
pub stacks: Vec<Vec<usize>>,
pub frames: Vec<Frame>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub thread_metadata: BTreeMap<String, ThreadMetadata>,
}
impl ProfileData {
pub fn normalize(&mut self, platform: &str) -> Result<(), ProfileError> {
if self.samples.is_empty() {
return Err(ProfileError::NotEnoughSamples);
}
self.samples.sort_by_key(|s| s.timestamp);
if !self.all_stacks_referenced_by_samples_exist() {
return Err(ProfileError::MalformedSamples);
}
if !self.all_frames_referenced_by_stacks_exist() {
return Err(ProfileError::MalformedStacks);
}
self.strip_pointer_authentication_code(platform);
self.remove_unreferenced_threads();
Ok(())
}
fn strip_pointer_authentication_code(&mut self, platform: &str) {
let addr = match platform {
"cocoa" => 0x0000000FFFFFFFFF,
_ => return,
};
for frame in &mut self.frames {
frame.strip_pointer_authentication_code(addr);
}
}
fn all_stacks_referenced_by_samples_exist(&self) -> bool {
self.samples
.iter()
.all(|sample| self.stacks.get(sample.stack_id).is_some())
}
fn all_frames_referenced_by_stacks_exist(&self) -> bool {
self.stacks.iter().all(|stack| {
stack
.iter()
.all(|frame_id| self.frames.get(*frame_id).is_some())
})
}
fn remove_unreferenced_threads(&mut self) {
let thread_ids = self
.samples
.iter()
.map(|sample| sample.thread_id.clone())
.collect::<HashSet<_>>();
self.thread_metadata
.retain(|thread_id, _| thread_ids.contains(thread_id));
}
}
pub fn parse(payload: &[u8]) -> Result<ProfileChunk, ProfileError> {
let d = &mut serde_json::Deserializer::from_slice(payload);
serde_path_to_error::deserialize(d).map_err(ProfileError::InvalidJson)
}
#[cfg(test)]
mod tests {
use relay_metrics::FiniteF64;
use crate::sample::v2::{parse, ProfileData, Sample};
#[test]
fn test_roundtrip() {
let first_payload = include_bytes!("../../tests/fixtures/sample/v2/valid.json");
let first_parse = parse(first_payload);
assert!(first_parse.is_ok(), "{:#?}", first_parse);
let second_payload = serde_json::to_vec(&first_parse.unwrap()).unwrap();
let second_parse = parse(&second_payload[..]);
assert!(second_parse.is_ok(), "{:#?}", second_parse);
}
#[test]
fn test_samples_are_sorted() {
let mut chunk = ProfileData {
samples: vec![
Sample {
stack_id: 0,
thread_id: "1".into(),
timestamp: FiniteF64::new(2000.0).unwrap(),
},
Sample {
stack_id: 0,
thread_id: "1".to_string(),
timestamp: FiniteF64::new(1000.0).unwrap(),
},
],
stacks: vec![vec![0]],
frames: vec![Default::default()],
..Default::default()
};
assert!(chunk.normalize("python").is_ok());
let timestamps: Vec<FiniteF64> = chunk.samples.iter().map(|s| s.timestamp).collect();
assert_eq!(
timestamps,
vec![
FiniteF64::new(1000.0).unwrap(),
FiniteF64::new(2000.0).unwrap(),
]
);
}
}