1use crate::Error;
6use flex_fuchsia_memory_stacktrack_client as fstacktrack_client;
7use futures::stream::StreamExt;
8
9#[derive(Debug, Default)]
11pub struct Snapshot {
12 pub page_size: u64,
14
15 pub stack_traces: Vec<StackTrace>,
17
18 pub executable_regions: Vec<ExecutableRegion>,
20}
21
22#[derive(Debug)]
24pub struct ExecutableRegion {
25 pub name: String,
27
28 pub address: u64,
30
31 pub size: u64,
33
34 pub vaddr: u64,
36
37 pub build_id: Vec<u8>,
39}
40
41#[derive(Debug)]
43pub struct StackTrace {
44 pub thread_koid: u64,
46
47 pub frames: Vec<CallFrame>,
49}
50
51#[derive(Debug, Clone)]
53pub struct CallFrame {
54 pub program_address: u64,
56
57 pub frame_pointer: u64,
59}
60
61macro_rules! read_field {
76 ($e:expr => $c:ident, $f:ident) => {
77 $e.$f.ok_or(Error::MissingField {
78 container: std::stringify!($c),
79 field: std::stringify!($f),
80 })
81 };
82}
83
84impl Snapshot {
85 pub async fn receive_from(
87 mut stream: fstacktrack_client::SnapshotReceiverRequestStream,
88 ) -> Result<Snapshot, Error> {
89 let mut page_size = None;
90 let mut stack_traces = Vec::new();
91 let mut executable_regions = Vec::new();
92
93 loop {
94 let batch = match stream.next().await.transpose()? {
96 Some(fstacktrack_client::SnapshotReceiverRequest::Batch { batch, responder }) => {
97 responder.send()?;
100 batch
101 }
102 Some(fstacktrack_client::SnapshotReceiverRequest::ReportError {
103 error,
104 responder,
105 }) => {
106 let _ = responder.send(); return Err(Error::CollectorError(error));
108 }
109 None => return Err(Error::UnexpectedEndOfStream),
110 };
111
112 if batch.is_empty() {
113 let page_size = page_size.ok_or(Error::PageSizeMissing)?;
114 return Ok(Snapshot { page_size, stack_traces, executable_regions });
115 }
116
117 for element in batch {
118 match element {
119 fstacktrack_client::SnapshotElement::PageSize(size) => {
120 page_size = Some(size);
121 }
122 fstacktrack_client::SnapshotElement::StackTrace(trace) => {
123 let thread_koid = read_field!(trace => StackTrace, thread_koid)?;
124 let frames = read_field!(trace => StackTrace, frames)?
125 .into_iter()
126 .map(|f| CallFrame {
127 program_address: f.program_address,
128 frame_pointer: f.frame_pointer,
129 })
130 .collect();
131
132 stack_traces.push(StackTrace { thread_koid, frames });
133 }
134 fstacktrack_client::SnapshotElement::ExecutableRegion(region) => {
135 let address = read_field!(region => ExecutableRegion, address)?;
136 let size = read_field!(region => ExecutableRegion, size)?;
137 let name = region.name.unwrap_or_default();
138 let vaddr = read_field!(region => ExecutableRegion, vaddr)?;
139 let build_id = read_field!(region => ExecutableRegion, build_id)?.value;
140
141 executable_regions.push(ExecutableRegion {
142 name,
143 address,
144 size,
145 vaddr,
146 build_id,
147 });
148 }
149 _ => return Err(Error::UnexpectedElementType),
150 }
151 }
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::test_helpers::create_client;
160 use fuchsia_async as fasync;
161 use test_case::test_case;
162
163 #[fasync::run_singlethreaded(test)]
164 async fn test_receive_snapshot() {
165 let client = create_client();
166 let (receiver_proxy, receiver_stream) =
167 client.create_proxy_and_stream::<fstacktrack_client::SnapshotReceiverMarker>();
168
169 let receive_task = fasync::Task::local(Snapshot::receive_from(receiver_stream));
170
171 let elements = vec![
172 fstacktrack_client::SnapshotElement::PageSize(4096),
173 fstacktrack_client::SnapshotElement::ExecutableRegion(
174 fstacktrack_client::ExecutableRegion {
175 address: Some(0x10000),
176 size: Some(0x2000),
177 name: Some("test".to_string()),
178 vaddr: Some(0x5000),
179 build_id: Some(fstacktrack_client::BuildId { value: vec![0xAA, 0xBB] }),
180 ..Default::default()
181 },
182 ),
183 fstacktrack_client::SnapshotElement::StackTrace(fstacktrack_client::StackTrace {
184 thread_koid: Some(8888),
185 frames: Some(vec![fstacktrack_client::CallFrame {
186 program_address: 0x1234,
187 frame_pointer: 0x5678,
188 }]),
189 ..Default::default()
190 }),
191 ];
192
193 receiver_proxy.batch(&elements).await.expect("failed to send batch");
194 receiver_proxy.batch(&[]).await.expect("failed to send end marker");
195
196 let snapshot = receive_task.await.expect("failed to receive snapshot");
197
198 assert_eq!(snapshot.page_size, 4096);
199 assert_eq!(snapshot.executable_regions.len(), 1);
200 assert_eq!(snapshot.stack_traces.len(), 1);
201
202 let region = &snapshot.executable_regions[0];
203 assert_eq!(region.address, 0x10000);
204 assert_eq!(region.size, 0x2000);
205 assert_eq!(region.name, "test");
206 assert_eq!(region.vaddr, 0x5000);
207 assert_eq!(region.build_id, vec![0xAA, 0xBB]);
208
209 let trace = &snapshot.stack_traces[0];
210 assert_eq!(trace.thread_koid, 8888);
211 assert_eq!(trace.frames.len(), 1);
212 assert_eq!(trace.frames[0].program_address, 0x1234);
213 assert_eq!(trace.frames[0].frame_pointer, 0x5678);
214 }
215
216 #[test_case(|trace| trace.thread_koid = None => matches
217 Err(Error::MissingField { container: "StackTrace", field: "thread_koid" }) ; "thread_koid")]
218 #[test_case(|trace| trace.frames = None => matches
219 Err(Error::MissingField { container: "StackTrace", field: "frames" }) ; "frames")]
220 #[test_case(|_| () => matches
221 Ok(_) ; "success")]
222 #[fasync::run_singlethreaded(test)]
223 async fn test_stack_trace_required_fields(
224 set_one_field_to_none: fn(&mut fstacktrack_client::StackTrace),
225 ) -> Result<Snapshot, Error> {
226 let client = create_client();
227 let (receiver_proxy, receiver_stream) =
228 client.create_proxy_and_stream::<fstacktrack_client::SnapshotReceiverMarker>();
229 let receive_worker = fasync::Task::local(Snapshot::receive_from(receiver_stream));
230
231 let mut stack_trace = fstacktrack_client::StackTrace {
232 thread_koid: Some(123),
233 frames: Some(vec![fstacktrack_client::CallFrame {
234 program_address: 0x100,
235 frame_pointer: 0x200,
236 }]),
237 ..Default::default()
238 };
239 set_one_field_to_none(&mut stack_trace);
240
241 let _ = receiver_proxy
243 .batch(&[
244 fstacktrack_client::SnapshotElement::PageSize(4096),
245 fstacktrack_client::SnapshotElement::StackTrace(stack_trace),
246 ])
247 .await;
248 let _ = receiver_proxy.batch(&[]).await;
249
250 receive_worker.await
251 }
252
253 #[test_case(|region| region.address = None => matches
254 Err(Error::MissingField { container: "ExecutableRegion", field: "address" }) ; "address")]
255 #[test_case(|region| region.size = None => matches
256 Err(Error::MissingField { container: "ExecutableRegion", field: "size" }) ; "size")]
257 #[test_case(|region| region.vaddr = None => matches
258 Err(Error::MissingField { container: "ExecutableRegion", field: "vaddr" }) ; "vaddr")]
259 #[test_case(|region| region.build_id = None => matches
260 Err(Error::MissingField { container: "ExecutableRegion", field: "build_id" }) ; "build_id")]
261 #[test_case(|_| () => matches
262 Ok(_) ; "success")]
263 #[fasync::run_singlethreaded(test)]
264 async fn test_executable_region_required_fields(
265 set_one_field_to_none: fn(&mut fstacktrack_client::ExecutableRegion),
266 ) -> Result<Snapshot, Error> {
267 let client = create_client();
268 let (receiver_proxy, receiver_stream) =
269 client.create_proxy_and_stream::<fstacktrack_client::SnapshotReceiverMarker>();
270 let receive_worker = fasync::Task::local(Snapshot::receive_from(receiver_stream));
271
272 let mut region = fstacktrack_client::ExecutableRegion {
273 address: Some(0x10000),
274 size: Some(0x2000),
275 name: Some("test".to_string()),
276 vaddr: Some(0x5000),
277 build_id: Some(fstacktrack_client::BuildId { value: vec![0xAA, 0xBB] }),
278 ..Default::default()
279 };
280 set_one_field_to_none(&mut region);
281
282 let _ = receiver_proxy
284 .batch(&[
285 fstacktrack_client::SnapshotElement::PageSize(4096),
286 fstacktrack_client::SnapshotElement::ExecutableRegion(region),
287 ])
288 .await;
289 let _ = receiver_proxy.batch(&[]).await;
290
291 receive_worker.await
292 }
293}