relay_profiling/perfetto/
mod.rs1use bytes::Bytes;
2
3use crate::sample::v2;
4use crate::{ProfileError, V2ProfileChunk};
5
6mod convert;
7#[allow(dead_code)]
8mod proto;
9
10#[derive(Debug)]
12pub struct Chunk {
13 inner: v2::ProfileChunk,
14 perfetto: Bytes,
15}
16
17impl Chunk {
18 pub fn parse(sample: &[u8], perfetto: Bytes) -> Result<Self, ProfileError> {
31 let mut inner: v2::ProfileChunk = {
32 let deserializer = &mut serde_json::Deserializer::from_slice(sample);
33 serde_path_to_error::deserialize(deserializer).map_err(ProfileError::InvalidJson)?
34 };
35
36 if inner.profile.is_empty() {
37 let (profile_data, debug_images) = convert::convert(&perfetto)?;
38 inner.profile = profile_data;
39 inner.metadata.debug_meta.images.extend(debug_images);
40 }
41
42 Ok(Self { inner, perfetto })
43 }
44
45 pub fn perfetto(&self) -> &Bytes {
47 &self.perfetto
48 }
49
50 pub fn as_v2(&self) -> &V2ProfileChunk {
52 &self.inner
53 }
54}
55
56impl crate::profile_chunk::ProfileChunk for Chunk {
57 fn platform(&self) -> &str {
58 &self.inner.metadata.platform
59 }
60
61 fn normalize(&mut self) -> Result<(), ProfileError> {
62 self.inner.normalize()
63 }
64}
65
66impl relay_filter::Filterable for Chunk {
67 fn release(&self) -> Option<&str> {
68 self.inner.metadata.release.as_deref()
69 }
70}
71
72impl relay_protocol::Getter for Chunk {
73 fn get_value(&self, path: &str) -> Option<relay_protocol::Val<'_>> {
74 self.inner.get_value(path)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 use crate::debug_image::ImageType;
83 use crate::{ProfileChunk, ProfileType};
84
85 const PERFETTO_ANDROID: Bytes = Bytes::from_static(include_bytes!(
86 "../../tests/fixtures/android/perfetto/android.pftrace"
87 ));
88
89 #[test]
90 fn test_parse_perfetto() {
91 let metadata_json = serde_json::json!({
92 "version": "2",
93 "chunk_id": "0432a0a4c25f4697bf9f0a2fcbe6a814",
94 "profiler_id": "4d229f1d3807421ba62a5f8bc295d836",
95 "platform": "android",
96 "content_type": "perfetto",
97 "client_sdk": {"name": "sentry-android", "version": "1.0"},
98 "debug_meta": {
99 "images": [
100 {
101 "uuid": "32420279-25e2-34e6-8bc7-8a006a8f2425",
102 "type": "proguard",
103 },
104 ],
105 },
106 });
107 let metadata_bytes = serde_json::to_vec(&metadata_json).unwrap();
108
109 let chunk = Chunk::parse(&metadata_bytes, PERFETTO_ANDROID).unwrap();
110
111 assert_eq!(chunk.inner.metadata.platform, "android");
112 assert_eq!(chunk.profile_type(), ProfileType::Ui);
113
114 let images = &chunk.inner.metadata.debug_meta.images;
115 assert_eq!(images[0].image_type, ImageType::Proguard);
116 assert!(
117 images.len() > 1,
118 "expected native images to be appended after the Proguard image"
119 );
120 insta::assert_json_snapshot!(chunk.inner);
121 }
122
123 #[test]
124 fn test_parse_perfetto_invalid_metadata() {
125 let result = Chunk::parse(b"not json", PERFETTO_ANDROID);
126 assert!(result.is_err());
127 }
128
129 #[test]
130 fn test_parse_perfetto_empty_trace() {
131 let metadata_bytes = serde_json::to_vec(&serde_json::json!({
133 "version": "2",
134 "chunk_id": "0432a0a4c25f4697bf9f0a2fcbe6a814",
135 "profiler_id": "4d229f1d3807421ba62a5f8bc295d836",
136 "platform": "android",
137 "content_type": "perfetto",
138 "client_sdk": {"name": "sentry-android", "version": "1.0"},
139 }))
140 .unwrap();
141
142 let _ = Chunk::parse(&metadata_bytes, Bytes::from_static(b"")).unwrap_err();
143 }
144
145 #[test]
146 fn test_parse_perfetto_missing_required_field() {
147 let metadata_bytes = serde_json::to_vec(&serde_json::json!({
149 "version": "2",
150 "profiler_id": "4d229f1d3807421ba62a5f8bc295d836",
151 "platform": "android",
152 "client_sdk": {"name": "sentry-android", "version": "1.0"},
153 }))
154 .unwrap();
155
156 let result = Chunk::parse(&metadata_bytes, PERFETTO_ANDROID);
157 assert!(
158 matches!(result, Err(ProfileError::InvalidJson(_))),
159 "expected InvalidJson, got {result:?}"
160 );
161 }
162}