1use fidl::MonotonicInstant;
6use flex_fuchsia_memory_heapdump_client as fheapdump_client;
7use futures::StreamExt;
8use std::collections::{HashMap, HashSet};
9use std::rc::Rc;
10
11use crate::Error;
12
13#[derive(Debug)]
15pub struct SnapshotWithHeader {
16 pub process_name: String,
17 pub process_koid: u64,
18 pub snapshot: Snapshot,
19}
20
21#[derive(Debug)]
23pub struct Snapshot {
24 pub allocations: Vec<Allocation>,
26
27 pub executable_regions: HashMap<u64, ExecutableRegion>,
29}
30
31#[derive(Debug)]
33pub struct Allocation {
34 pub address: Option<u64>,
35
36 pub count: u64,
38
39 pub size: u64,
41
42 pub thread_info: Option<Rc<ThreadInfo>>,
44
45 pub stack_trace: Rc<StackTrace>,
47
48 pub timestamp: Option<MonotonicInstant>,
50
51 pub contents: Option<Vec<u8>>,
53}
54
55#[derive(Debug)]
57pub struct StackTrace {
58 pub program_addresses: Vec<u64>,
60}
61
62#[derive(Debug)]
64pub struct ExecutableRegion {
65 pub name: String,
67
68 pub size: u64,
70
71 pub file_offset: u64,
73
74 pub vaddr: Option<u64>,
79
80 pub build_id: Vec<u8>,
82}
83
84#[derive(Debug, PartialEq)]
86pub struct ThreadInfo {
87 pub koid: zx_types::zx_koid_t,
89
90 pub name: String,
92}
93
94macro_rules! read_field {
109 ($e:expr => $c:ident, $f:ident) => {
110 $e.$f.ok_or(Error::MissingField {
111 container: std::stringify!($c),
112 field: std::stringify!($f),
113 })
114 };
115}
116
117impl Snapshot {
118 pub async fn receive_single_from(
120 mut stream: fheapdump_client::SnapshotReceiverRequestStream,
121 ) -> Result<Snapshot, Error> {
122 Snapshot::receive_inner(&mut stream).await
123 }
124
125 #[cfg(fuchsia_api_level_at_least = "HEAD")]
128 pub async fn receive_multi_from(
129 mut stream: fheapdump_client::SnapshotReceiverRequestStream,
130 ) -> Result<Vec<SnapshotWithHeader>, Error> {
131 let mut snapshots = vec![];
132 loop {
133 match stream.next().await.transpose()? {
136 Some(fheapdump_client::SnapshotReceiverRequest::Batch { batch, responder }) => {
137 match &batch[..] {
138 [fheapdump_client::SnapshotElement::SnapshotHeader(header)] => {
139 responder.send()?;
140
141 let snapshot = Snapshot::receive_inner(&mut stream).await?;
143
144 let header = header.clone();
145 snapshots.push(SnapshotWithHeader {
146 process_name: read_field!(header => SnapshotHeader, process_name)?,
147 process_koid: read_field!(header => SnapshotHeader, process_koid)?,
148 snapshot,
149 });
150 }
151 [] => {
152 responder.send()?;
153 return Ok(snapshots);
154 }
155 _ => return Err(Error::HeaderExpected),
156 }
157 }
158 Some(fheapdump_client::SnapshotReceiverRequest::ReportError {
159 error,
160 responder,
161 }) => {
162 let _ = responder.send(); return Err(Error::CollectorError(error));
164 }
165 None => return Err(Error::UnexpectedEndOfStream),
166 };
167 }
168 }
169
170 async fn receive_inner(
171 stream: &mut fheapdump_client::SnapshotReceiverRequestStream,
172 ) -> Result<Snapshot, Error> {
173 struct AllocationValue {
174 address: Option<u64>,
175 count: u64,
176 size: u64,
177 thread_info_key: Option<u64>,
178 stack_trace_key: u64,
179 timestamp: Option<MonotonicInstant>,
180 }
181 let mut allocation_addresses: HashSet<u64> = HashSet::new();
182 let mut allocations: Vec<AllocationValue> = vec![];
183 let mut thread_infos: HashMap<u64, Rc<ThreadInfo>> = HashMap::new();
184 let mut stack_traces: HashMap<u64, Vec<u64>> = HashMap::new();
185 let mut executable_regions: HashMap<u64, ExecutableRegion> = HashMap::new();
186 let mut contents: HashMap<u64, Vec<u8>> = HashMap::new();
187
188 loop {
189 let batch = match stream.next().await.transpose()? {
191 Some(fheapdump_client::SnapshotReceiverRequest::Batch { batch, responder }) => {
192 responder.send()?;
194 batch
195 }
196 Some(fheapdump_client::SnapshotReceiverRequest::ReportError {
197 error,
198 responder,
199 }) => {
200 let _ = responder.send(); return Err(Error::CollectorError(error));
202 }
203 None => return Err(Error::UnexpectedEndOfStream),
204 };
205
206 if !batch.is_empty() {
208 for element in batch {
209 match element {
210 fheapdump_client::SnapshotElement::Allocation(allocation) => {
211 if let Some(address) = allocation.address {
212 if !allocation_addresses.insert(address) {
213 return Err(Error::ConflictingElement {
214 element_type: "Allocation",
215 });
216 }
217 }
218
219 #[cfg(not(fuchsia_api_level_at_least = "29"))]
220 let count = 1;
221 #[cfg(fuchsia_api_level_at_least = "29")]
222 let count = allocation.count.unwrap_or(1);
223
224 let size = read_field!(allocation => Allocation, size)?;
225 let stack_trace_key =
226 read_field!(allocation => Allocation, stack_trace_key)?;
227 allocations.push(AllocationValue {
228 address: allocation.address,
229 count,
230 size,
231 thread_info_key: allocation.thread_info_key,
232 stack_trace_key,
233 timestamp: allocation.timestamp,
234 });
235 }
236 fheapdump_client::SnapshotElement::StackTrace(stack_trace) => {
237 let stack_trace_key =
238 read_field!(stack_trace => StackTrace, stack_trace_key)?;
239 let mut program_addresses =
240 read_field!(stack_trace => StackTrace, program_addresses)?;
241 stack_traces
242 .entry(stack_trace_key)
243 .or_default()
244 .append(&mut program_addresses);
245 }
246 fheapdump_client::SnapshotElement::ThreadInfo(thread_info) => {
247 let thread_info_key =
248 read_field!(thread_info => ThreadInfo, thread_info_key)?;
249 let koid = read_field!(thread_info => ThreadInfo, koid)?;
250 let name = read_field!(thread_info => ThreadInfo, name)?;
251 if thread_infos
252 .insert(thread_info_key, Rc::new(ThreadInfo { koid, name }))
253 .is_some()
254 {
255 return Err(Error::ConflictingElement {
256 element_type: "ThreadInfo",
257 });
258 }
259 }
260 fheapdump_client::SnapshotElement::ExecutableRegion(region) => {
261 let address = read_field!(region => ExecutableRegion, address)?;
262 #[cfg(fuchsia_api_level_at_least = "27")]
263 let name = region.name.unwrap_or_else(|| String::new());
264 #[cfg(not(fuchsia_api_level_at_least = "27"))]
265 let name = String::new();
266 let size = read_field!(region => ExecutableRegion, size)?;
267 let file_offset = read_field!(region => ExecutableRegion, file_offset)?;
268 #[cfg(fuchsia_api_level_at_least = "27")]
269 let vaddr = region.vaddr;
270 #[cfg(not(fuchsia_api_level_at_least = "27"))]
271 let vaddr = None;
272 let build_id = read_field!(region => ExecutableRegion, build_id)?.value;
273 let region =
274 ExecutableRegion { name, size, file_offset, vaddr, build_id };
275 if executable_regions.insert(address, region).is_some() {
276 return Err(Error::ConflictingElement {
277 element_type: "ExecutableRegion",
278 });
279 }
280 }
281 fheapdump_client::SnapshotElement::BlockContents(block_contents) => {
282 let address = read_field!(block_contents => BlockContents, address)?;
283 let mut chunk = read_field!(block_contents => BlockContents, contents)?;
284 contents.entry(address).or_default().append(&mut chunk);
285 }
286 _ => return Err(Error::UnexpectedElementType),
287 }
288 }
289 } else {
290 let final_stack_traces: HashMap<u64, Rc<StackTrace>> = stack_traces
293 .into_iter()
294 .map(|(key, program_addresses)| {
295 (key, Rc::new(StackTrace { program_addresses }))
296 })
297 .collect();
298 let mut final_allocations = vec![];
299 for AllocationValue {
300 address,
301 count,
302 size,
303 thread_info_key,
304 stack_trace_key,
305 timestamp,
306 } in allocations
307 {
308 let thread_info = match thread_info_key {
309 Some(key) => Some(
310 thread_infos
311 .get(&key)
312 .ok_or(Error::InvalidCrossReference { element_type: "ThreadInfo" })?
313 .clone(),
314 ),
315 None => None,
316 };
317 let stack_trace = final_stack_traces
318 .get(&stack_trace_key)
319 .ok_or(Error::InvalidCrossReference { element_type: "StackTrace" })?
320 .clone();
321 let contents = address.and_then(|address| contents.remove(&address));
322 if let Some(data) = &contents {
323 if data.len() as u64 != size {
324 return Err(Error::ConflictingElement {
325 element_type: "BlockContents",
326 });
327 }
328 }
329 final_allocations.push(Allocation {
330 address,
331 count,
332 size,
333 thread_info,
334 stack_trace,
335 timestamp,
336 contents,
337 });
338 }
339
340 return Ok(Snapshot { allocations: final_allocations, executable_regions });
341 }
342 }
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use assert_matches::assert_matches;
350 use fuchsia_async as fasync;
351 use test_case::test_case;
352
353 const FAKE_ALLOCATION_1_ADDRESS: u64 = 1234;
355 const FAKE_ALLOCATION_1_SIZE: u64 = 8;
356 const FAKE_ALLOCATION_1_TIMESTAMP: MonotonicInstant = MonotonicInstant::from_nanos(888888888);
357 const FAKE_ALLOCATION_1_CONTENTS: [u8; FAKE_ALLOCATION_1_SIZE as usize] = *b"12345678";
358 const FAKE_ALLOCATION_2_ADDRESS: u64 = 5678;
359 const FAKE_ALLOCATION_2_SIZE: u64 = 4;
360 const FAKE_ALLOCATION_2_TIMESTAMP: MonotonicInstant = MonotonicInstant::from_nanos(-777777777); const FAKE_THREAD_1_KOID: u64 = 1212;
362 const FAKE_THREAD_1_NAME: &str = "fake-thread-1-name";
363 const FAKE_THREAD_1_KEY: u64 = 4567;
364 const FAKE_THREAD_2_KOID: u64 = 1213;
365 const FAKE_THREAD_2_NAME: &str = "fake-thread-2-name";
366 const FAKE_THREAD_2_KEY: u64 = 7654;
367 const FAKE_STACK_TRACE_1_ADDRESSES: [u64; 6] = [11111, 22222, 33333, 22222, 44444, 55555];
368 const FAKE_STACK_TRACE_1_KEY: u64 = 9876;
369 const FAKE_STACK_TRACE_2_ADDRESSES: [u64; 4] = [11111, 22222, 11111, 66666];
370 const FAKE_STACK_TRACE_2_KEY: u64 = 6789;
371 const FAKE_REGION_1_ADDRESS: u64 = 0x10000000;
372 const FAKE_REGION_1_NAME: &str = "region-1";
373 const FAKE_REGION_1_SIZE: u64 = 0x80000;
374 const FAKE_REGION_1_FILE_OFFSET: u64 = 0x1000;
375 const FAKE_REGION_1_VADDR: u64 = 0x3000;
376 const FAKE_REGION_1_BUILD_ID: &[u8] = &[0xaa; 20];
377 const FAKE_REGION_2_ADDRESS: u64 = 0x7654300000;
378 const FAKE_REGION_2_SIZE: u64 = 0x200000;
379 const FAKE_REGION_2_FILE_OFFSET: u64 = 0x2000;
380 const FAKE_REGION_2_BUILD_ID: &[u8] = &[0x55; 32];
381
382 #[fasync::run_singlethreaded(test)]
383 async fn test_empty() {
384 #[cfg(feature = "fdomain")]
385 let client = fdomain_local::local_client_empty();
386 #[cfg(not(feature = "fdomain"))]
387 let client = fidl::endpoints::ZirconClient;
388 let (receiver_proxy, receiver_stream) =
389 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
390 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
391
392 let fut = receiver_proxy.batch(&[]);
394 fut.await.unwrap();
395
396 let received_snapshot = receive_worker.await.unwrap();
398 assert!(received_snapshot.allocations.is_empty());
399 assert!(received_snapshot.executable_regions.is_empty());
400 }
401
402 #[fasync::run_singlethreaded(test)]
403 async fn test_one_batch() {
404 #[cfg(feature = "fdomain")]
405 let client = fdomain_local::local_client_empty();
406 #[cfg(not(feature = "fdomain"))]
407 let client = fidl::endpoints::ZirconClient;
408 let (receiver_proxy, receiver_stream) =
409 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
410 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
411
412 let fut = receiver_proxy.batch(&[
416 fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
417 address: Some(FAKE_ALLOCATION_1_ADDRESS),
418 contents: Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()),
419 ..Default::default()
420 }),
421 fheapdump_client::SnapshotElement::ExecutableRegion(
422 fheapdump_client::ExecutableRegion {
423 address: Some(FAKE_REGION_1_ADDRESS),
424 name: Some(FAKE_REGION_1_NAME.to_string()),
425 size: Some(FAKE_REGION_1_SIZE),
426 file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
427 vaddr: Some(FAKE_REGION_1_VADDR),
428 build_id: Some(fheapdump_client::BuildId {
429 value: FAKE_REGION_1_BUILD_ID.to_vec(),
430 }),
431 ..Default::default()
432 },
433 ),
434 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
435 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
436 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
437 ..Default::default()
438 }),
439 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
440 address: Some(FAKE_ALLOCATION_1_ADDRESS),
441 size: Some(FAKE_ALLOCATION_1_SIZE),
442 thread_info_key: Some(FAKE_THREAD_1_KEY),
443 stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
444 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
445 ..Default::default()
446 }),
447 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
448 thread_info_key: Some(FAKE_THREAD_1_KEY),
449 koid: Some(FAKE_THREAD_1_KOID),
450 name: Some(FAKE_THREAD_1_NAME.to_string()),
451 ..Default::default()
452 }),
453 fheapdump_client::SnapshotElement::ExecutableRegion(
454 fheapdump_client::ExecutableRegion {
455 address: Some(FAKE_REGION_2_ADDRESS),
456 size: Some(FAKE_REGION_2_SIZE),
457 file_offset: Some(FAKE_REGION_2_FILE_OFFSET),
458 build_id: Some(fheapdump_client::BuildId {
459 value: FAKE_REGION_2_BUILD_ID.to_vec(),
460 }),
461 ..Default::default()
462 },
463 ),
464 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
465 thread_info_key: Some(FAKE_THREAD_2_KEY),
466 koid: Some(FAKE_THREAD_2_KOID),
467 name: Some(FAKE_THREAD_2_NAME.to_string()),
468 ..Default::default()
469 }),
470 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
471 address: Some(FAKE_ALLOCATION_2_ADDRESS),
472 size: Some(FAKE_ALLOCATION_2_SIZE),
473 thread_info_key: Some(FAKE_THREAD_2_KEY),
474 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
475 timestamp: Some(FAKE_ALLOCATION_2_TIMESTAMP),
476 ..Default::default()
477 }),
478 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
479 stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
480 program_addresses: Some(FAKE_STACK_TRACE_2_ADDRESSES.to_vec()),
481 ..Default::default()
482 }),
483 ]);
484 fut.await.unwrap();
485
486 let fut = receiver_proxy.batch(&[]);
488 fut.await.unwrap();
489
490 let mut received_snapshot = receive_worker.await.unwrap();
492 let allocation1 = received_snapshot.allocations.swap_remove(
493 received_snapshot
494 .allocations
495 .iter()
496 .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
497 .unwrap(),
498 );
499 assert_eq!(allocation1.size, FAKE_ALLOCATION_1_SIZE);
500 assert_eq!(
501 allocation1.thread_info,
502 Some(Rc::new(ThreadInfo {
503 koid: FAKE_THREAD_1_KOID,
504 name: FAKE_THREAD_1_NAME.to_owned()
505 }))
506 );
507 assert_eq!(allocation1.stack_trace.program_addresses, FAKE_STACK_TRACE_2_ADDRESSES);
508 assert_eq!(allocation1.timestamp, Some(FAKE_ALLOCATION_1_TIMESTAMP));
509 assert_eq!(
510 allocation1.contents.as_ref().expect("contents must be set"),
511 &FAKE_ALLOCATION_1_CONTENTS.to_vec()
512 );
513 let allocation2 = received_snapshot.allocations.swap_remove(
514 received_snapshot
515 .allocations
516 .iter()
517 .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_2_ADDRESS))
518 .unwrap(),
519 );
520 assert_eq!(allocation2.size, FAKE_ALLOCATION_2_SIZE);
521 assert_eq!(
522 allocation2.thread_info,
523 Some(Rc::new(ThreadInfo {
524 koid: FAKE_THREAD_2_KOID,
525 name: FAKE_THREAD_2_NAME.to_owned()
526 }))
527 );
528 assert_eq!(allocation2.stack_trace.program_addresses, FAKE_STACK_TRACE_1_ADDRESSES);
529 assert_eq!(allocation2.timestamp, Some(FAKE_ALLOCATION_2_TIMESTAMP));
530 assert_matches!(allocation2.contents, None, "no contents are sent for this allocation");
531 assert!(received_snapshot.allocations.is_empty(), "all the entries have been removed");
532 let region1 = received_snapshot.executable_regions.remove(&FAKE_REGION_1_ADDRESS).unwrap();
533 assert_eq!(region1.name, FAKE_REGION_1_NAME);
534 assert_eq!(region1.size, FAKE_REGION_1_SIZE);
535 assert_eq!(region1.file_offset, FAKE_REGION_1_FILE_OFFSET);
536 assert_eq!(region1.vaddr.unwrap(), FAKE_REGION_1_VADDR);
537 assert_eq!(region1.build_id, FAKE_REGION_1_BUILD_ID);
538 let region2 = received_snapshot.executable_regions.remove(&FAKE_REGION_2_ADDRESS).unwrap();
539 assert_eq!(region2.size, FAKE_REGION_2_SIZE);
540 assert_eq!(region2.file_offset, FAKE_REGION_2_FILE_OFFSET);
541 assert_eq!(region2.build_id, FAKE_REGION_2_BUILD_ID);
542 assert!(received_snapshot.executable_regions.is_empty(), "all entries have been removed");
543 }
544
545 #[fasync::run_singlethreaded(test)]
546 async fn test_two_batches() {
547 #[cfg(feature = "fdomain")]
548 let client = fdomain_local::local_client_empty();
549 #[cfg(not(feature = "fdomain"))]
550 let client = fidl::endpoints::ZirconClient;
551 let (receiver_proxy, receiver_stream) =
552 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
553 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
554
555 let fut = receiver_proxy.batch(&[
557 fheapdump_client::SnapshotElement::ExecutableRegion(
558 fheapdump_client::ExecutableRegion {
559 address: Some(FAKE_REGION_2_ADDRESS),
560 size: Some(FAKE_REGION_2_SIZE),
561 file_offset: Some(FAKE_REGION_2_FILE_OFFSET),
562 build_id: Some(fheapdump_client::BuildId {
563 value: FAKE_REGION_2_BUILD_ID.to_vec(),
564 }),
565 ..Default::default()
566 },
567 ),
568 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
569 address: Some(FAKE_ALLOCATION_1_ADDRESS),
570 size: Some(FAKE_ALLOCATION_1_SIZE),
571 thread_info_key: Some(FAKE_THREAD_1_KEY),
572 stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
573 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
574 ..Default::default()
575 }),
576 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
577 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
578 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
579 ..Default::default()
580 }),
581 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
582 thread_info_key: Some(FAKE_THREAD_2_KEY),
583 koid: Some(FAKE_THREAD_2_KOID),
584 name: Some(FAKE_THREAD_2_NAME.to_string()),
585 ..Default::default()
586 }),
587 ]);
588 fut.await.unwrap();
589
590 let fut = receiver_proxy.batch(&[
592 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
593 thread_info_key: Some(FAKE_THREAD_1_KEY),
594 koid: Some(FAKE_THREAD_1_KOID),
595 name: Some(FAKE_THREAD_1_NAME.to_string()),
596 ..Default::default()
597 }),
598 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
599 address: Some(FAKE_ALLOCATION_2_ADDRESS),
600 size: Some(FAKE_ALLOCATION_2_SIZE),
601 thread_info_key: Some(FAKE_THREAD_2_KEY),
602 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
603 timestamp: Some(FAKE_ALLOCATION_2_TIMESTAMP),
604 ..Default::default()
605 }),
606 fheapdump_client::SnapshotElement::ExecutableRegion(
607 fheapdump_client::ExecutableRegion {
608 address: Some(FAKE_REGION_1_ADDRESS),
609 name: Some(FAKE_REGION_1_NAME.to_string()),
610 size: Some(FAKE_REGION_1_SIZE),
611 file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
612 vaddr: Some(FAKE_REGION_1_VADDR),
613 build_id: Some(fheapdump_client::BuildId {
614 value: FAKE_REGION_1_BUILD_ID.to_vec(),
615 }),
616 ..Default::default()
617 },
618 ),
619 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
620 stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
621 program_addresses: Some(FAKE_STACK_TRACE_2_ADDRESSES.to_vec()),
622 ..Default::default()
623 }),
624 fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
625 address: Some(FAKE_ALLOCATION_1_ADDRESS),
626 contents: Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()),
627 ..Default::default()
628 }),
629 ]);
630 fut.await.unwrap();
631
632 let fut = receiver_proxy.batch(&[]);
634 fut.await.unwrap();
635
636 let mut received_snapshot = receive_worker.await.unwrap();
638 let allocation1 = received_snapshot.allocations.swap_remove(
639 received_snapshot
640 .allocations
641 .iter()
642 .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
643 .unwrap(),
644 );
645 assert_eq!(allocation1.size, FAKE_ALLOCATION_1_SIZE);
646 assert_eq!(
647 allocation1.thread_info,
648 Some(Rc::new(ThreadInfo {
649 koid: FAKE_THREAD_1_KOID,
650 name: FAKE_THREAD_1_NAME.to_owned()
651 }))
652 );
653 assert_eq!(allocation1.stack_trace.program_addresses, FAKE_STACK_TRACE_2_ADDRESSES);
654 assert_eq!(allocation1.timestamp, Some(FAKE_ALLOCATION_1_TIMESTAMP));
655 assert_eq!(
656 allocation1.contents.as_ref().expect("contents must be set"),
657 &FAKE_ALLOCATION_1_CONTENTS.to_vec()
658 );
659 let allocation2 = received_snapshot.allocations.swap_remove(
660 received_snapshot
661 .allocations
662 .iter()
663 .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_2_ADDRESS))
664 .unwrap(),
665 );
666 assert_eq!(allocation2.size, FAKE_ALLOCATION_2_SIZE);
667 assert_eq!(
668 allocation2.thread_info,
669 Some(Rc::new(ThreadInfo {
670 koid: FAKE_THREAD_2_KOID,
671 name: FAKE_THREAD_2_NAME.to_owned()
672 }))
673 );
674 assert_eq!(allocation2.stack_trace.program_addresses, FAKE_STACK_TRACE_1_ADDRESSES);
675 assert_eq!(allocation2.timestamp, Some(FAKE_ALLOCATION_2_TIMESTAMP));
676 assert_matches!(allocation2.contents, None, "no contents are sent for this allocation");
677 assert!(received_snapshot.allocations.is_empty(), "all the entries have been removed");
678 let region1 = received_snapshot.executable_regions.remove(&FAKE_REGION_1_ADDRESS).unwrap();
679 assert_eq!(region1.name, FAKE_REGION_1_NAME);
680 assert_eq!(region1.size, FAKE_REGION_1_SIZE);
681 assert_eq!(region1.file_offset, FAKE_REGION_1_FILE_OFFSET);
682 assert_eq!(region1.vaddr.unwrap(), FAKE_REGION_1_VADDR);
683 assert_eq!(region1.build_id, FAKE_REGION_1_BUILD_ID);
684 let region2 = received_snapshot.executable_regions.remove(&FAKE_REGION_2_ADDRESS).unwrap();
685 assert_eq!(region2.size, FAKE_REGION_2_SIZE);
686 assert_eq!(region2.file_offset, FAKE_REGION_2_FILE_OFFSET);
687 assert_eq!(region2.build_id, FAKE_REGION_2_BUILD_ID);
688 assert!(received_snapshot.executable_regions.is_empty(), "all entries have been removed");
689 }
690
691 #[test_case(|allocation| allocation.size = None => matches
692 Err(Error::MissingField { container: "Allocation", field: "size" }) ; "size")]
693 #[test_case(|allocation| allocation.stack_trace_key = None => matches
694 Err(Error::MissingField { container: "Allocation", field: "stack_trace_key" }) ; "stack_trace_key")]
695 #[test_case(|allocation| allocation.address = None => matches
696 Ok(_) ; "address_is_optional")]
697 #[test_case(|allocation| allocation.thread_info_key = None => matches
698 Ok(_) ; "thread_info_is_optional")]
699 #[test_case(|allocation| allocation.timestamp = None => matches
700 Ok(_) ; "timestamp_is_optional")]
701 #[test_case(|_| () => matches
702 Ok(_) ; "success")]
703 #[fasync::run_singlethreaded(test)]
704 async fn test_allocation_required_fields(
705 set_one_field_to_none: fn(&mut fheapdump_client::Allocation),
706 ) -> Result<Snapshot, Error> {
707 #[cfg(feature = "fdomain")]
708 let client = fdomain_local::local_client_empty();
709 #[cfg(not(feature = "fdomain"))]
710 let client = fidl::endpoints::ZirconClient;
711 let (receiver_proxy, receiver_stream) =
712 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
713 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
714
715 let mut allocation = fheapdump_client::Allocation {
717 address: Some(FAKE_ALLOCATION_1_ADDRESS),
718 size: Some(FAKE_ALLOCATION_1_SIZE),
719 thread_info_key: Some(FAKE_THREAD_1_KEY),
720 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
721 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
722 ..Default::default()
723 };
724
725 set_one_field_to_none(&mut allocation);
727
728 let fut = receiver_proxy.batch(&[
730 fheapdump_client::SnapshotElement::Allocation(allocation),
731 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
732 thread_info_key: Some(FAKE_THREAD_1_KEY),
733 koid: Some(FAKE_THREAD_1_KOID),
734 name: Some(FAKE_THREAD_1_NAME.to_string()),
735 ..Default::default()
736 }),
737 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
738 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
739 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
740 ..Default::default()
741 }),
742 ]);
743 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
747 let _ = fut.await; receive_worker.await
751 }
752
753 #[test_case(|thread_info| thread_info.thread_info_key = None => matches
754 Err(Error::MissingField { container: "ThreadInfo", field: "thread_info_key" }) ; "thread_info_key")]
755 #[test_case(|thread_info| thread_info.koid = None => matches
756 Err(Error::MissingField { container: "ThreadInfo", field: "koid" }) ; "koid")]
757 #[test_case(|thread_info| thread_info.name = None => matches
758 Err(Error::MissingField { container: "ThreadInfo", field: "name" }) ; "name")]
759 #[test_case(|_| () => matches
760 Ok(_) ; "success")]
761 #[fasync::run_singlethreaded(test)]
762 async fn test_thread_info_required_fields(
763 set_one_field_to_none: fn(&mut fheapdump_client::ThreadInfo),
764 ) -> Result<Snapshot, Error> {
765 #[cfg(feature = "fdomain")]
766 let client = fdomain_local::local_client_empty();
767 #[cfg(not(feature = "fdomain"))]
768 let client = fidl::endpoints::ZirconClient;
769 let (receiver_proxy, receiver_stream) =
770 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
771 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
772
773 let mut thread_info = fheapdump_client::ThreadInfo {
775 thread_info_key: Some(FAKE_THREAD_1_KEY),
776 koid: Some(FAKE_THREAD_1_KOID),
777 name: Some(FAKE_THREAD_1_NAME.to_string()),
778 ..Default::default()
779 };
780
781 set_one_field_to_none(&mut thread_info);
783
784 let fut =
786 receiver_proxy.batch(&[fheapdump_client::SnapshotElement::ThreadInfo(thread_info)]);
787 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
791 let _ = fut.await; receive_worker.await
795 }
796
797 #[test_case(|stack_trace| stack_trace.stack_trace_key = None => matches
798 Err(Error::MissingField { container: "StackTrace", field: "stack_trace_key" }) ; "stack_trace_key")]
799 #[test_case(|stack_trace| stack_trace.program_addresses = None => matches
800 Err(Error::MissingField { container: "StackTrace", field: "program_addresses" }) ; "program_addresses")]
801 #[test_case(|_| () => matches
802 Ok(_) ; "success")]
803 #[fasync::run_singlethreaded(test)]
804 async fn test_stack_trace_required_fields(
805 set_one_field_to_none: fn(&mut fheapdump_client::StackTrace),
806 ) -> Result<Snapshot, Error> {
807 #[cfg(feature = "fdomain")]
808 let client = fdomain_local::local_client_empty();
809 #[cfg(not(feature = "fdomain"))]
810 let client = fidl::endpoints::ZirconClient;
811 let (receiver_proxy, receiver_stream) =
812 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
813 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
814
815 let mut stack_trace = fheapdump_client::StackTrace {
817 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
818 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
819 ..Default::default()
820 };
821
822 set_one_field_to_none(&mut stack_trace);
824
825 let fut =
827 receiver_proxy.batch(&[fheapdump_client::SnapshotElement::StackTrace(stack_trace)]);
828 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
832 let _ = fut.await; receive_worker.await
836 }
837
838 #[test_case(|region| region.address = None => matches
839 Err(Error::MissingField { container: "ExecutableRegion", field: "address" }) ; "address")]
840 #[test_case(|region| region.size = None => matches
841 Err(Error::MissingField { container: "ExecutableRegion", field: "size" }) ; "size")]
842 #[test_case(|region| region.file_offset = None => matches
843 Err(Error::MissingField { container: "ExecutableRegion", field: "file_offset" }) ; "file_offset")]
844 #[test_case(|region| region.build_id = None => matches
845 Err(Error::MissingField { container: "ExecutableRegion", field: "build_id" }) ; "build_id")]
846 #[test_case(|_| () => matches
847 Ok(_) ; "success")]
848 #[fasync::run_singlethreaded(test)]
849 async fn test_executable_region_required_fields(
850 set_one_field_to_none: fn(&mut fheapdump_client::ExecutableRegion),
851 ) -> Result<Snapshot, Error> {
852 #[cfg(feature = "fdomain")]
853 let client = fdomain_local::local_client_empty();
854 #[cfg(not(feature = "fdomain"))]
855 let client = fidl::endpoints::ZirconClient;
856 let (receiver_proxy, receiver_stream) =
857 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
858 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
859
860 let mut region = fheapdump_client::ExecutableRegion {
862 address: Some(FAKE_REGION_1_ADDRESS),
863 size: Some(FAKE_REGION_1_SIZE),
864 file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
865 build_id: Some(fheapdump_client::BuildId { value: FAKE_REGION_1_BUILD_ID.to_vec() }),
866 ..Default::default()
867 };
868
869 set_one_field_to_none(&mut region);
871
872 let fut =
874 receiver_proxy.batch(&[fheapdump_client::SnapshotElement::ExecutableRegion(region)]);
875 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
879 let _ = fut.await; receive_worker.await
883 }
884
885 #[test_case(|block_contents| block_contents.address = None => matches
886 Err(Error::MissingField { container: "BlockContents", field: "address" }) ; "address")]
887 #[test_case(|block_contents| block_contents.contents = None => matches
888 Err(Error::MissingField { container: "BlockContents", field: "contents" }) ; "contents")]
889 #[test_case(|_| () => matches
890 Ok(_) ; "success")]
891 #[fasync::run_singlethreaded(test)]
892 async fn test_block_contents_required_fields(
893 set_one_field_to_none: fn(&mut fheapdump_client::BlockContents),
894 ) -> Result<Snapshot, Error> {
895 #[cfg(feature = "fdomain")]
896 let client = fdomain_local::local_client_empty();
897 #[cfg(not(feature = "fdomain"))]
898 let client = fidl::endpoints::ZirconClient;
899 let (receiver_proxy, receiver_stream) =
900 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
901 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
902
903 let mut block_contents = fheapdump_client::BlockContents {
905 address: Some(FAKE_ALLOCATION_1_ADDRESS),
906 contents: Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()),
907 ..Default::default()
908 };
909
910 set_one_field_to_none(&mut block_contents);
912
913 let fut = receiver_proxy.batch(&[
915 fheapdump_client::SnapshotElement::BlockContents(block_contents),
916 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
917 address: Some(FAKE_ALLOCATION_1_ADDRESS),
918 size: Some(FAKE_ALLOCATION_1_SIZE),
919 thread_info_key: Some(FAKE_THREAD_1_KEY),
920 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
921 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
922 ..Default::default()
923 }),
924 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
925 thread_info_key: Some(FAKE_THREAD_1_KEY),
926 koid: Some(FAKE_THREAD_1_KOID),
927 name: Some(FAKE_THREAD_1_NAME.to_string()),
928 ..Default::default()
929 }),
930 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
931 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
932 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
933 ..Default::default()
934 }),
935 ]);
936 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
940 let _ = fut.await; receive_worker.await
944 }
945
946 #[fasync::run_singlethreaded(test)]
947 async fn test_conflicting_allocations() {
948 #[cfg(feature = "fdomain")]
949 let client = fdomain_local::local_client_empty();
950 #[cfg(not(feature = "fdomain"))]
951 let client = fidl::endpoints::ZirconClient;
952 let (receiver_proxy, receiver_stream) =
953 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
954 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
955
956 let fut = receiver_proxy.batch(&[
958 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
959 address: Some(FAKE_ALLOCATION_1_ADDRESS),
960 size: Some(FAKE_ALLOCATION_1_SIZE),
961 thread_info_key: Some(FAKE_THREAD_1_KEY),
962 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
963 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
964 ..Default::default()
965 }),
966 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
967 address: Some(FAKE_ALLOCATION_1_ADDRESS),
968 size: Some(FAKE_ALLOCATION_1_SIZE),
969 thread_info_key: Some(FAKE_THREAD_1_KEY),
970 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
971 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
972 ..Default::default()
973 }),
974 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
975 thread_info_key: Some(FAKE_THREAD_1_KEY),
976 koid: Some(FAKE_THREAD_1_KOID),
977 name: Some(FAKE_THREAD_1_NAME.to_string()),
978 ..Default::default()
979 }),
980 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
981 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
982 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
983 ..Default::default()
984 }),
985 ]);
986 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
990 let _ = fut.await; assert_matches!(
994 receive_worker.await,
995 Err(Error::ConflictingElement { element_type: "Allocation" })
996 );
997 }
998
999 #[fasync::run_singlethreaded(test)]
1000 async fn test_conflicting_executable_regions() {
1001 #[cfg(feature = "fdomain")]
1002 let client = fdomain_local::local_client_empty();
1003 #[cfg(not(feature = "fdomain"))]
1004 let client = fidl::endpoints::ZirconClient;
1005 let (receiver_proxy, receiver_stream) =
1006 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1007 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1008
1009 let fut = receiver_proxy.batch(&[
1011 fheapdump_client::SnapshotElement::ExecutableRegion(
1012 fheapdump_client::ExecutableRegion {
1013 address: Some(FAKE_REGION_1_ADDRESS),
1014 size: Some(FAKE_REGION_1_SIZE),
1015 file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
1016 build_id: Some(fheapdump_client::BuildId {
1017 value: FAKE_REGION_1_BUILD_ID.to_vec(),
1018 }),
1019 ..Default::default()
1020 },
1021 ),
1022 fheapdump_client::SnapshotElement::ExecutableRegion(
1023 fheapdump_client::ExecutableRegion {
1024 address: Some(FAKE_REGION_1_ADDRESS),
1025 size: Some(FAKE_REGION_1_SIZE),
1026 file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
1027 build_id: Some(fheapdump_client::BuildId {
1028 value: FAKE_REGION_1_BUILD_ID.to_vec(),
1029 }),
1030 ..Default::default()
1031 },
1032 ),
1033 ]);
1034 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
1038 let _ = fut.await; assert_matches!(
1042 receive_worker.await,
1043 Err(Error::ConflictingElement { element_type: "ExecutableRegion" })
1044 );
1045 }
1046
1047 #[fasync::run_singlethreaded(test)]
1048 async fn test_block_contents_wrong_size() {
1049 #[cfg(feature = "fdomain")]
1050 let client = fdomain_local::local_client_empty();
1051 #[cfg(not(feature = "fdomain"))]
1052 let client = fidl::endpoints::ZirconClient;
1053 let (receiver_proxy, receiver_stream) =
1054 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1055 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1056
1057 let contents_with_wrong_size = vec![0; FAKE_ALLOCATION_1_SIZE as usize + 1];
1059 let fut = receiver_proxy.batch(&[
1060 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1061 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1062 size: Some(FAKE_ALLOCATION_1_SIZE),
1063 thread_info_key: Some(FAKE_THREAD_1_KEY),
1064 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1065 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1066 ..Default::default()
1067 }),
1068 fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
1069 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1070 contents: Some(contents_with_wrong_size),
1071 ..Default::default()
1072 }),
1073 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1074 thread_info_key: Some(FAKE_THREAD_1_KEY),
1075 koid: Some(FAKE_THREAD_1_KOID),
1076 name: Some(FAKE_THREAD_1_NAME.to_string()),
1077 ..Default::default()
1078 }),
1079 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1080 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1081 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1082 ..Default::default()
1083 }),
1084 ]);
1085 let _ = fut.await; let fut = receiver_proxy.batch(&[]);
1089 let _ = fut.await; assert_matches!(
1093 receive_worker.await,
1094 Err(Error::ConflictingElement { element_type: "BlockContents" })
1095 );
1096 }
1097
1098 #[fasync::run_singlethreaded(test)]
1099 async fn test_empty_stack_trace() {
1100 #[cfg(feature = "fdomain")]
1101 let client = fdomain_local::local_client_empty();
1102 #[cfg(not(feature = "fdomain"))]
1103 let client = fidl::endpoints::ZirconClient;
1104 let (receiver_proxy, receiver_stream) =
1105 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1106 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1107
1108 let fut = receiver_proxy.batch(&[
1110 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1111 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1112 size: Some(FAKE_ALLOCATION_1_SIZE),
1113 thread_info_key: Some(FAKE_THREAD_1_KEY),
1114 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1115 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1116 ..Default::default()
1117 }),
1118 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1119 thread_info_key: Some(FAKE_THREAD_1_KEY),
1120 koid: Some(FAKE_THREAD_1_KOID),
1121 name: Some(FAKE_THREAD_1_NAME.to_string()),
1122 ..Default::default()
1123 }),
1124 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1125 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1126 program_addresses: Some(vec![]),
1127 ..Default::default()
1128 }),
1129 ]);
1130 fut.await.unwrap();
1131
1132 let fut = receiver_proxy.batch(&[]);
1134 fut.await.unwrap();
1135
1136 let received_snapshot = receive_worker.await.unwrap();
1138 let allocation1 = received_snapshot
1139 .allocations
1140 .iter()
1141 .find(|a| a.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1142 .unwrap();
1143 assert_eq!(allocation1.stack_trace.program_addresses, []);
1144 }
1145
1146 #[fasync::run_singlethreaded(test)]
1147 async fn test_chunked_stack_trace() {
1148 #[cfg(feature = "fdomain")]
1149 let client = fdomain_local::local_client_empty();
1150 #[cfg(not(feature = "fdomain"))]
1151 let client = fidl::endpoints::ZirconClient;
1152 let (receiver_proxy, receiver_stream) =
1153 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1154 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1155
1156 let fut = receiver_proxy.batch(&[
1158 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1159 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1160 size: Some(FAKE_ALLOCATION_1_SIZE),
1161 thread_info_key: Some(FAKE_THREAD_1_KEY),
1162 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1163 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1164 ..Default::default()
1165 }),
1166 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1167 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1168 program_addresses: Some(vec![1111, 2222]),
1169 ..Default::default()
1170 }),
1171 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1172 thread_info_key: Some(FAKE_THREAD_1_KEY),
1173 koid: Some(FAKE_THREAD_1_KOID),
1174 name: Some(FAKE_THREAD_1_NAME.to_string()),
1175 ..Default::default()
1176 }),
1177 ]);
1178 fut.await.unwrap();
1179
1180 let fut = receiver_proxy.batch(&[fheapdump_client::SnapshotElement::StackTrace(
1182 fheapdump_client::StackTrace {
1183 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1184 program_addresses: Some(vec![3333]),
1185 ..Default::default()
1186 },
1187 )]);
1188 fut.await.unwrap();
1189
1190 let fut = receiver_proxy.batch(&[]);
1192 fut.await.unwrap();
1193
1194 let received_snapshot = receive_worker.await.unwrap();
1196 let allocation1 = received_snapshot
1197 .allocations
1198 .iter()
1199 .find(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1200 .unwrap();
1201 assert_eq!(allocation1.stack_trace.program_addresses, [1111, 2222, 3333]);
1202 }
1203
1204 #[fasync::run_singlethreaded(test)]
1205 async fn test_empty_block_contents() {
1206 #[cfg(feature = "fdomain")]
1207 let client = fdomain_local::local_client_empty();
1208 #[cfg(not(feature = "fdomain"))]
1209 let client = fidl::endpoints::ZirconClient;
1210 let (receiver_proxy, receiver_stream) =
1211 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1212 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1213
1214 let fut = receiver_proxy.batch(&[
1216 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1217 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1218 size: Some(0),
1219 thread_info_key: Some(FAKE_THREAD_1_KEY),
1220 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1221 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1222 ..Default::default()
1223 }),
1224 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1225 thread_info_key: Some(FAKE_THREAD_1_KEY),
1226 koid: Some(FAKE_THREAD_1_KOID),
1227 name: Some(FAKE_THREAD_1_NAME.to_string()),
1228 ..Default::default()
1229 }),
1230 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1231 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1232 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1233 ..Default::default()
1234 }),
1235 fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
1236 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1237 contents: Some(vec![]),
1238 ..Default::default()
1239 }),
1240 ]);
1241 fut.await.unwrap();
1242
1243 let fut = receiver_proxy.batch(&[]);
1245 fut.await.unwrap();
1246
1247 let received_snapshot = receive_worker.await.unwrap();
1249 let allocation1 = received_snapshot
1250 .allocations
1251 .iter()
1252 .find(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1253 .unwrap();
1254 assert_eq!(allocation1.contents.as_ref().expect("contents must be set"), &vec![]);
1255 }
1256
1257 #[fasync::run_singlethreaded(test)]
1258 async fn test_chunked_block_contents() {
1259 #[cfg(feature = "fdomain")]
1260 let client = fdomain_local::local_client_empty();
1261 #[cfg(not(feature = "fdomain"))]
1262 let client = fidl::endpoints::ZirconClient;
1263 let (receiver_proxy, receiver_stream) =
1264 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1265 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1266
1267 let (content_first_chunk, contents_second_chunk) =
1269 FAKE_ALLOCATION_1_CONTENTS.split_at(FAKE_ALLOCATION_1_CONTENTS.len() / 2);
1270
1271 let fut = receiver_proxy.batch(&[
1273 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1274 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1275 size: Some(FAKE_ALLOCATION_1_SIZE),
1276 thread_info_key: Some(FAKE_THREAD_1_KEY),
1277 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1278 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1279 ..Default::default()
1280 }),
1281 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1282 thread_info_key: Some(FAKE_THREAD_1_KEY),
1283 koid: Some(FAKE_THREAD_1_KOID),
1284 name: Some(FAKE_THREAD_1_NAME.to_string()),
1285 ..Default::default()
1286 }),
1287 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1288 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1289 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1290 ..Default::default()
1291 }),
1292 fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
1293 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1294 contents: Some(content_first_chunk.to_vec()),
1295 ..Default::default()
1296 }),
1297 ]);
1298 fut.await.unwrap();
1299
1300 let fut = receiver_proxy.batch(&[fheapdump_client::SnapshotElement::BlockContents(
1302 fheapdump_client::BlockContents {
1303 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1304 contents: Some(contents_second_chunk.to_vec()),
1305 ..Default::default()
1306 },
1307 )]);
1308 fut.await.unwrap();
1309
1310 let fut = receiver_proxy.batch(&[]);
1312 fut.await.unwrap();
1313
1314 let received_snapshot = receive_worker.await.unwrap();
1316 let allocation1 = received_snapshot
1317 .allocations
1318 .iter()
1319 .find(|a| a.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1320 .unwrap();
1321 assert_eq!(allocation1.contents, Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()));
1322 }
1323
1324 #[fasync::run_singlethreaded(test)]
1325 async fn test_missing_end_of_stream() {
1326 #[cfg(feature = "fdomain")]
1327 let client = fdomain_local::local_client_empty();
1328 #[cfg(not(feature = "fdomain"))]
1329 let client = fidl::endpoints::ZirconClient;
1330 let (receiver_proxy, receiver_stream) =
1331 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1332 let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1333
1334 let fut = receiver_proxy.batch(&[
1336 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1337 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1338 size: Some(FAKE_ALLOCATION_1_SIZE),
1339 thread_info_key: Some(FAKE_THREAD_1_KEY),
1340 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1341 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1342 ..Default::default()
1343 }),
1344 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1345 thread_info_key: Some(FAKE_THREAD_1_KEY),
1346 koid: Some(FAKE_THREAD_1_KOID),
1347 name: Some(FAKE_THREAD_1_NAME.to_string()),
1348 ..Default::default()
1349 }),
1350 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1351 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1352 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1353 ..Default::default()
1354 }),
1355 ]);
1356 fut.await.unwrap();
1357
1358 std::mem::drop(receiver_proxy);
1360
1361 assert_matches!(receive_worker.await, Err(Error::UnexpectedEndOfStream));
1363 }
1364
1365 #[fasync::run_singlethreaded(test)]
1366 async fn test_multi_contents() {
1367 #[cfg(feature = "fdomain")]
1368 let client = fdomain_local::local_client_empty();
1369 #[cfg(not(feature = "fdomain"))]
1370 let client = fidl::endpoints::ZirconClient;
1371 let (receiver_proxy, receiver_stream) =
1372 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1373 let receive_worker = fasync::Task::local(Snapshot::receive_multi_from(receiver_stream));
1374
1375 for koid in [1111, 2222] {
1377 receiver_proxy
1378 .batch(&[fheapdump_client::SnapshotElement::SnapshotHeader(
1379 fheapdump_client::SnapshotHeader {
1380 process_name: Some(format!("test-process-{koid}")),
1381 process_koid: Some(koid),
1382 ..Default::default()
1383 },
1384 )])
1385 .await
1386 .unwrap();
1387
1388 receiver_proxy
1389 .batch(&[
1390 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1391 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1392 size: Some(FAKE_ALLOCATION_1_SIZE),
1393 thread_info_key: Some(FAKE_THREAD_1_KEY),
1394 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1395 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1396 ..Default::default()
1397 }),
1398 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1399 thread_info_key: Some(FAKE_THREAD_1_KEY),
1400 koid: Some(FAKE_THREAD_1_KOID),
1401 name: Some(FAKE_THREAD_1_NAME.to_string()),
1402 ..Default::default()
1403 }),
1404 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1405 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1406 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1407 ..Default::default()
1408 }),
1409 ])
1410 .await
1411 .unwrap();
1412
1413 receiver_proxy.batch(&[]).await.unwrap(); }
1415
1416 receiver_proxy.batch(&[]).await.unwrap();
1418
1419 let received_snapshots = receive_worker.await.unwrap();
1421 assert_eq!(received_snapshots.len(), 2);
1422 assert_eq!(received_snapshots[0].process_name, "test-process-1111");
1423 assert_eq!(received_snapshots[0].process_koid, 1111);
1424 assert_eq!(received_snapshots[1].process_name, "test-process-2222");
1425 assert_eq!(received_snapshots[1].process_koid, 2222);
1426 }
1427
1428 #[fasync::run_singlethreaded(test)]
1429 async fn test_multi_missing_end_of_stream() {
1430 #[cfg(feature = "fdomain")]
1431 let client = fdomain_local::local_client_empty();
1432 #[cfg(not(feature = "fdomain"))]
1433 let client = fidl::endpoints::ZirconClient;
1434 let (receiver_proxy, receiver_stream) =
1435 client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1436 let receive_worker = fasync::Task::local(Snapshot::receive_multi_from(receiver_stream));
1437
1438 for koid in [1111, 2222] {
1440 receiver_proxy
1441 .batch(&[fheapdump_client::SnapshotElement::SnapshotHeader(
1442 fheapdump_client::SnapshotHeader {
1443 process_name: Some("test-process-name".to_string()),
1444 process_koid: Some(koid),
1445 ..Default::default()
1446 },
1447 )])
1448 .await
1449 .unwrap();
1450
1451 receiver_proxy
1452 .batch(&[
1453 fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1454 address: Some(FAKE_ALLOCATION_1_ADDRESS),
1455 size: Some(FAKE_ALLOCATION_1_SIZE),
1456 thread_info_key: Some(FAKE_THREAD_1_KEY),
1457 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1458 timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1459 ..Default::default()
1460 }),
1461 fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1462 thread_info_key: Some(FAKE_THREAD_1_KEY),
1463 koid: Some(FAKE_THREAD_1_KOID),
1464 name: Some(FAKE_THREAD_1_NAME.to_string()),
1465 ..Default::default()
1466 }),
1467 fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1468 stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1469 program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1470 ..Default::default()
1471 }),
1472 ])
1473 .await
1474 .unwrap();
1475
1476 receiver_proxy.batch(&[]).await.unwrap(); }
1478
1479 std::mem::drop(receiver_proxy);
1481
1482 assert_matches!(receive_worker.await, Err(Error::UnexpectedEndOfStream));
1484 }
1485}