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> {
27 let mut inner: v2::ProfileChunk = {
28 let deserializer = &mut serde_json::Deserializer::from_slice(sample);
29 serde_path_to_error::deserialize(deserializer).map_err(ProfileError::InvalidJson)?
30 };
31
32 if inner.profile.is_empty() {
33 let (profile_data, debug_images) = convert::convert(&perfetto)?;
34 inner.profile = profile_data;
35 inner.metadata.debug_meta.images = debug_images;
36 }
37
38 Ok(Self { inner, perfetto })
39 }
40
41 pub fn perfetto(&self) -> &Bytes {
43 &self.perfetto
44 }
45
46 pub fn as_v2(&self) -> &V2ProfileChunk {
48 &self.inner
49 }
50}
51
52impl crate::profile_chunk::ProfileChunk for Chunk {
53 fn platform(&self) -> &str {
54 &self.inner.metadata.platform
55 }
56
57 fn normalize(&mut self) -> Result<(), ProfileError> {
58 self.inner.normalize()
59 }
60}
61
62impl relay_filter::Filterable for Chunk {
63 fn release(&self) -> Option<&str> {
64 self.inner.metadata.release.as_deref()
65 }
66}
67
68impl relay_protocol::Getter for Chunk {
69 fn get_value(&self, path: &str) -> Option<relay_protocol::Val<'_>> {
70 self.inner.get_value(path)
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 use crate::{ProfileChunk, ProfileType};
79
80 const PERFETTO_ANDROID: Bytes = Bytes::from_static(include_bytes!(
81 "../../tests/fixtures/android/perfetto/android.pftrace"
82 ));
83
84 #[test]
85 fn test_parse_perfetto() {
86 let metadata_json = serde_json::json!({
87 "version": "2",
88 "chunk_id": "0432a0a4c25f4697bf9f0a2fcbe6a814",
89 "profiler_id": "4d229f1d3807421ba62a5f8bc295d836",
90 "platform": "android",
91 "content_type": "perfetto",
92 "client_sdk": {"name": "sentry-android", "version": "1.0"},
93 });
94 let metadata_bytes = serde_json::to_vec(&metadata_json).unwrap();
95
96 let chunk = Chunk::parse(&metadata_bytes, PERFETTO_ANDROID).unwrap();
97
98 assert_eq!(chunk.inner.metadata.platform, "android");
99 assert_eq!(chunk.profile_type(), ProfileType::Ui);
100
101 insta::assert_json_snapshot!(chunk.inner);
102 }
103
104 #[test]
105 fn test_parse_perfetto_invalid_metadata() {
106 let result = Chunk::parse(b"not json", PERFETTO_ANDROID);
107 assert!(result.is_err());
108 }
109
110 #[test]
111 fn test_parse_perfetto_empty_trace() {
112 let metadata_bytes = serde_json::to_vec(&serde_json::json!({
114 "version": "2",
115 "chunk_id": "0432a0a4c25f4697bf9f0a2fcbe6a814",
116 "profiler_id": "4d229f1d3807421ba62a5f8bc295d836",
117 "platform": "android",
118 "content_type": "perfetto",
119 "client_sdk": {"name": "sentry-android", "version": "1.0"},
120 }))
121 .unwrap();
122
123 let _ = Chunk::parse(&metadata_bytes, Bytes::from_static(b"")).unwrap_err();
124 }
125
126 #[test]
127 fn test_parse_perfetto_missing_required_field() {
128 let metadata_bytes = serde_json::to_vec(&serde_json::json!({
130 "version": "2",
131 "profiler_id": "4d229f1d3807421ba62a5f8bc295d836",
132 "platform": "android",
133 "client_sdk": {"name": "sentry-android", "version": "1.0"},
134 }))
135 .unwrap();
136
137 let result = Chunk::parse(&metadata_bytes, PERFETTO_ANDROID);
138 assert!(
139 matches!(result, Err(ProfileError::InvalidJson(_))),
140 "expected InvalidJson, got {result:?}"
141 );
142 }
143}