relay_profiling/
profile_chunk.rs1use serde::Deserialize;
2
3use crate::{
4 AndroidProfileChunk, PerfettoProfileChunk, ProfileError, ProfileType, V2ProfileChunk,
5 sample::Version,
6};
7
8pub trait ProfileChunk {
10 fn platform(&self) -> &str;
12
13 fn profile_type(&self) -> ProfileType {
17 ProfileType::from_platform(self.platform())
18 }
19
20 fn normalize(&mut self) -> Result<(), ProfileError>;
22}
23
24#[derive(Debug)]
26pub enum AnyProfileChunk {
27 Android(Box<AndroidProfileChunk>),
28 Perfetto(Box<PerfettoProfileChunk>),
29 V2(Box<V2ProfileChunk>),
30}
31
32impl From<Box<V2ProfileChunk>> for AnyProfileChunk {
33 fn from(chunk: Box<V2ProfileChunk>) -> Self {
34 Self::V2(chunk)
35 }
36}
37
38impl From<Box<AndroidProfileChunk>> for AnyProfileChunk {
39 fn from(chunk: Box<AndroidProfileChunk>) -> Self {
40 Self::Android(chunk)
41 }
42}
43
44impl From<Box<PerfettoProfileChunk>> for AnyProfileChunk {
45 fn from(chunk: Box<PerfettoProfileChunk>) -> Self {
46 Self::Perfetto(chunk)
47 }
48}
49
50impl From<AndroidOrV2ProfileChunk> for AnyProfileChunk {
51 fn from(chunk: AndroidOrV2ProfileChunk) -> Self {
52 match chunk {
53 AndroidOrV2ProfileChunk::Android(c) => Self::Android(c),
54 AndroidOrV2ProfileChunk::V2(c) => Self::V2(c),
55 }
56 }
57}
58
59impl ProfileChunk for AnyProfileChunk {
60 fn platform(&self) -> &str {
61 match self {
62 AnyProfileChunk::Android(chunk) => chunk.platform(),
63 AnyProfileChunk::Perfetto(chunk) => chunk.platform(),
64 AnyProfileChunk::V2(chunk) => chunk.platform(),
65 }
66 }
67
68 fn normalize(&mut self) -> Result<(), ProfileError> {
69 match self {
70 AnyProfileChunk::Android(chunk) => chunk.normalize(),
71 AnyProfileChunk::Perfetto(chunk) => chunk.normalize(),
72 AnyProfileChunk::V2(chunk) => chunk.normalize(),
73 }
74 }
75}
76
77impl relay_protocol::Getter for AnyProfileChunk {
78 fn get_value(&self, path: &str) -> Option<relay_protocol::Val<'_>> {
79 match self {
80 AnyProfileChunk::Android(chunk) => chunk.get_value(path),
81 AnyProfileChunk::Perfetto(chunk) => chunk.get_value(path),
82 AnyProfileChunk::V2(chunk) => chunk.get_value(path),
83 }
84 }
85}
86
87impl relay_filter::Filterable for AnyProfileChunk {
88 fn release(&self) -> Option<&str> {
89 match self {
90 AnyProfileChunk::Android(chunk) => chunk.release(),
91 AnyProfileChunk::Perfetto(chunk) => chunk.release(),
92 AnyProfileChunk::V2(chunk) => chunk.release(),
93 }
94 }
95}
96
97#[derive(Debug)]
99pub enum AndroidOrV2ProfileChunk {
100 Android(Box<AndroidProfileChunk>),
101 V2(Box<V2ProfileChunk>),
102}
103
104impl ProfileChunk for AndroidOrV2ProfileChunk {
105 fn platform(&self) -> &str {
106 match self {
107 AndroidOrV2ProfileChunk::Android(chunk) => chunk.platform(),
108 AndroidOrV2ProfileChunk::V2(chunk) => chunk.platform(),
109 }
110 }
111
112 fn normalize(&mut self) -> Result<(), ProfileError> {
113 match self {
114 AndroidOrV2ProfileChunk::Android(chunk) => chunk.normalize(),
115 AndroidOrV2ProfileChunk::V2(chunk) => chunk.normalize(),
116 }
117 }
118}
119
120impl AndroidOrV2ProfileChunk {
121 pub fn parse(data: &[u8]) -> Result<Self, ProfileError> {
123 #[derive(Debug, Deserialize)]
124 struct MinimalProfile {
125 platform: String,
126 #[serde(default)]
127 version: Version,
128 #[serde(default)]
129 sampled_profile: Option<serde::de::IgnoredAny>,
130 }
131
132 let minimal: MinimalProfile = {
133 let d = &mut serde_json::Deserializer::from_slice(data);
134 serde_path_to_error::deserialize(d)
135 }?;
136
137 let is_android_trace_profile =
166 minimal.platform == "android" && minimal.sampled_profile.is_some();
167
168 match minimal.version {
169 Version::V2AndroidTrace => AndroidProfileChunk::parse(data)
170 .map(Box::new)
171 .map(Self::Android),
172 Version::V2 if is_android_trace_profile => AndroidProfileChunk::parse(data)
174 .map(Box::new)
175 .map(Self::Android),
176 Version::V2 => V2ProfileChunk::parse(data).map(Box::new).map(Self::V2),
177 Version::V1 | Version::Unknown => Err(ProfileError::PlatformNotSupported),
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use std::assert_matches;
185
186 use serde_json::{Value, json};
187
188 use super::*;
189
190 #[test]
191 fn test_parse_correctly_versioned_android_trace_profile_into_android_profile_chunk() {
192 let mut payload: Value =
193 serde_json::from_slice(include_bytes!("../tests/fixtures/android/chunk/valid.json"))
194 .unwrap();
195 payload["version"] = json!("2.android-trace");
196 let data = serde_json::to_vec(&payload).unwrap();
197
198 let sdk_chunk = AndroidOrV2ProfileChunk::parse(&data).unwrap();
200 assert_matches!(sdk_chunk, AndroidOrV2ProfileChunk::Android(_));
201
202 let AndroidOrV2ProfileChunk::Android(android_chunk) = sdk_chunk else {
204 unreachable!()
205 };
206 let reserialized = serde_json::to_vec(&android_chunk).unwrap();
207 let value: Value = serde_json::from_slice(&reserialized).unwrap();
208 assert!(value.get("sampled_profile").is_none());
209 assert!(value.get("profile").is_some());
210
211 let round_tripped = AndroidOrV2ProfileChunk::parse(&reserialized).unwrap();
212 assert_matches!(round_tripped, AndroidOrV2ProfileChunk::Android(_));
213 }
214
215 #[test]
216 fn test_parse_legacy_versioned_android_trace_profile_into_android_profile_chunk() {
217 let mut payload: Value =
218 serde_json::from_slice(include_bytes!("../tests/fixtures/android/chunk/valid.json"))
219 .unwrap();
220 payload["version"] = json!("2");
221 let data = serde_json::to_vec(&payload).unwrap();
222
223 let chunk = AndroidOrV2ProfileChunk::parse(&data).unwrap();
224 assert_matches!(chunk, AndroidOrV2ProfileChunk::Android(_));
225 }
226
227 #[test]
228 fn test_parse_sample_v2_profile_into_v2_profile_chunk() {
229 let base_payload: Value =
230 serde_json::from_slice(include_bytes!("../tests/fixtures/sample/v2/valid.json"))
231 .unwrap();
232
233 for platform in ["android", "cocoa", "javascript", "python"] {
234 let mut payload = base_payload.clone();
235 payload["platform"] = json!(platform);
236 let data = serde_json::to_vec(&payload).unwrap();
237
238 let chunk = AndroidOrV2ProfileChunk::parse(&data).unwrap();
239
240 assert_matches!(chunk, AndroidOrV2ProfileChunk::V2(_));
241 }
242 }
243
244 #[test]
245 fn test_return_error_for_version_1_profile() {
246 for payload in [
247 &include_bytes!("../tests/fixtures/sample/v2/valid.json")[..],
248 &include_bytes!("../tests/fixtures/android/chunk/valid.json")[..],
249 &include_bytes!("../tests/fixtures/android/chunk/valid-rn.json")[..],
250 ] {
251 let mut payload: Value = serde_json::from_slice(payload).unwrap();
252 payload["version"] = json!("1");
253 let data = serde_json::to_vec(&payload).unwrap();
254
255 let err = AndroidOrV2ProfileChunk::parse(&data).unwrap_err();
256 assert_matches!(err, ProfileError::PlatformNotSupported);
257 }
258 }
259
260 #[test]
261 fn test_return_error_for_unknown_version_profile() {
262 for payload in [
263 &include_bytes!("../tests/fixtures/sample/v2/valid.json")[..],
264 &include_bytes!("../tests/fixtures/android/chunk/valid.json")[..],
265 &include_bytes!("../tests/fixtures/android/chunk/valid-rn.json")[..],
266 ] {
267 let mut payload: Value = serde_json::from_slice(payload).unwrap();
268 payload.as_object_mut().unwrap().remove("version");
269 let data = serde_json::to_vec(&payload).unwrap();
270
271 let err = AndroidOrV2ProfileChunk::parse(&data).unwrap_err();
272 assert_matches!(err, ProfileError::PlatformNotSupported);
273 }
274 }
275}