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