1use crate::fuchsia::file::FxFile;
6use crate::fuchsia::fxblob::BlobDirectory;
7use crate::fuchsia::fxblob::blob::FxBlob;
8use crate::fuchsia::node::{FxNode, OpenedNode};
9use crate::fuchsia::pager::PagerBacked;
10use crate::fuchsia::volume::FxVolume;
11use anyhow::{Context as _, Error, anyhow, ensure};
12use arrayref::{array_refs, mut_array_refs};
13use async_trait::async_trait;
14use fuchsia_async as fasync;
15use fuchsia_hash::Hash;
16use futures::future::{self, BoxFuture, join_all};
17use futures::lock::Mutex;
18use futures::{FutureExt, select};
19use fxfs::errors::FxfsError;
20use fxfs::log::*;
21use fxfs::object_handle::{INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle, WriteObjectHandle};
22use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
23use fxfs::object_store::{
24 AttributeId, AttributeKey, DataObjectHandle, HandleOptions, Mutation, ObjectDescriptor,
25 ObjectKey, ObjectStore, ObjectValue, Timestamp, VOLUME_DATA_KEY_ID, directory,
26};
27use linked_hash_map::LinkedHashMap;
28use scopeguard::ScopeGuard;
29use std::cmp::{Eq, PartialEq};
30use std::collections::btree_map::{BTreeMap, Entry};
31use std::marker::PhantomData;
32use std::mem::size_of;
33use std::pin::pin;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicU64, Ordering};
36use storage_device::buffer::{BufferFuture, BufferRef};
37use storage_ptr_slice::{MutPtrByteSlice, PtrByteSlice};
38use storage_units::page_size;
39use vfs::execution_scope::ActiveGuard;
40
41const FILE_OPEN_MARKER: u64 = u64::MAX;
42const REPLAY_THREADS: usize = 2;
43const MESSAGE_CHUNK_SIZE: usize = 64;
46const IO_SIZE: usize = 1 << 17; pub static RECORDED: AtomicU64 = AtomicU64::new(0);
49
50#[async_trait]
52pub trait RecordingHandle: Send + Sync {
53 async fn append<'a>(&'a self, buf: BufferRef<'a>) -> Result<u64, Error>;
55
56 fn allocate_buffer(&self, size: usize) -> BufferFuture<'_>;
57
58 fn block_size(&self) -> usize;
59
60 async fn commit(self: Box<Self>) -> Result<(), Error>;
62
63 fn abort_cleanup(self: Box<Self>);
66
67 fn filter_events(&self) -> bool;
71}
72
73pub struct FileRecordingHandle {
75 name: String,
76 volume: Arc<FxVolume>,
77 handle: DataObjectHandle<FxVolume>,
78}
79
80impl FileRecordingHandle {
81 pub async fn new(name: &str, volume: Arc<FxVolume>) -> Result<Self, Error> {
82 let store = volume.store();
83 let mut transaction = store.new_transaction(lock_keys![], Options::default()).await?;
84 let handle =
85 ObjectStore::create_object(&volume, &mut transaction, HandleOptions::default(), None)
86 .await?;
87 store.add_to_graveyard(&mut transaction, handle.object_id());
88 transaction.commit().await?;
89
90 Ok(Self { name: name.to_string(), volume, handle })
91 }
92
93 async fn commit_impl(&self) -> Result<(), Error> {
94 let store = self.volume.store();
95 let fs = store.filesystem();
96 let profile_dir = self.volume.get_profile_directory().await?;
97
98 let mut lock_keys =
99 lock_keys![LockKey::object(store.store_object_id(), profile_dir.object_id())];
100 let mut old_id = INVALID_OBJECT_ID;
101 let mut transaction = loop {
102 let transaction = store.new_transaction(lock_keys, Options::default()).await?;
103 if let Some((id, descriptor, _)) = profile_dir.lookup(&self.name).await? {
104 ensure!(matches!(descriptor, ObjectDescriptor::File), FxfsError::Inconsistent);
105 if id == old_id {
106 break transaction;
107 }
108 lock_keys = lock_keys![
109 LockKey::object(store.store_object_id(), profile_dir.object_id()),
110 LockKey::object(store.store_object_id(), id)
111 ];
112 old_id = id;
113 } else {
114 old_id = INVALID_OBJECT_ID;
115 break transaction;
116 }
117 };
118
119 store.remove_from_graveyard(&mut transaction, self.handle.object_id());
120 directory::replace_child_with_object(
121 &mut transaction,
122 Some((self.handle.object_id(), ObjectDescriptor::File)),
123 (&profile_dir, &self.name),
124 0,
125 false,
126 Timestamp::now(),
127 )
128 .await?;
129 transaction.commit().await?;
130
131 if old_id != INVALID_OBJECT_ID {
132 fs.graveyard().queue_tombstone_object(store.store_object_id(), old_id);
133 }
134
135 Ok(())
136 }
137}
138
139#[async_trait]
140impl RecordingHandle for FileRecordingHandle {
141 async fn append<'a>(&'a self, buf: BufferRef<'a>) -> Result<u64, Error> {
142 self.handle.write_or_append(None, buf).await.map_err(Into::into)
143 }
144
145 fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
146 self.handle.allocate_buffer(size)
147 }
148
149 fn block_size(&self) -> usize {
150 self.handle.block_size().get() as usize
151 }
152
153 async fn commit(self: Box<Self>) -> Result<(), Error> {
154 let store = self.volume.store();
155 self.commit_impl().await.inspect_err(|_| {
156 store
157 .filesystem()
158 .graveyard()
159 .queue_tombstone_object(store.store_object_id(), self.handle.object_id());
160 })
161 }
162
163 fn abort_cleanup(self: Box<Self>) {
164 self.volume
165 .store()
166 .filesystem()
167 .graveyard()
168 .queue_tombstone_object(self.volume.store().store_object_id(), self.handle.object_id());
169 }
170
171 fn filter_events(&self) -> bool {
172 true
173 }
174}
175
176pub struct AttributeRecordingHandle {
179 volume: Arc<FxVolume>,
180 handle: DataObjectHandle<FxVolume>,
181}
182
183impl AttributeRecordingHandle {
184 pub async fn new(object_id: u64, volume: Arc<FxVolume>) -> Result<Self, Error> {
187 let store = volume.store();
188
189 let mut transaction = store.new_transaction(lock_keys![], Options::default()).await?;
190 transaction.add(
191 store.store_object_id(),
192 Mutation::replace_or_insert_object(
193 ObjectKey::attribute(
194 object_id,
195 AttributeId::PROFILE_RECORDING,
196 AttributeKey::Attribute,
197 ),
198 ObjectValue::attribute(0, false),
199 ),
200 );
201 transaction.add(
202 store.store_object_id(),
203 Mutation::replace_or_insert_object(
204 ObjectKey::graveyard_attribute_entry(
205 store.graveyard_directory_object_id(),
206 object_id,
207 AttributeId::PROFILE_RECORDING,
208 ),
209 ObjectValue::Some,
210 ),
211 );
212 transaction.commit().await?;
213
214 let handle = DataObjectHandle::new(
215 volume.clone(),
216 object_id,
217 false,
218 AttributeId::PROFILE_RECORDING,
219 0,
220 HandleOptions::default(),
221 false,
222 &[],
223 );
224
225 Ok(Self { volume, handle })
226 }
227
228 pub async fn commit_impl(&self) -> Result<(), Error> {
229 let store = self.volume.store();
230 let mut transaction = store.new_transaction(lock_keys![], Options::default()).await?;
231
232 store.remove_attribute_from_graveyard(
233 &mut transaction,
234 self.handle.object_id(),
235 AttributeId::PROFILE_RECORDING,
236 );
237 transaction.commit().await?;
238 Ok(())
239 }
240}
241
242#[async_trait]
243impl RecordingHandle for AttributeRecordingHandle {
244 async fn append<'a>(
245 &'a self,
246 buf: storage_device::buffer::BufferRef<'a>,
247 ) -> Result<u64, Error> {
248 self.handle.write_or_append(None, buf).await.map_err(Into::into)
249 }
250
251 fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
252 self.handle.allocate_buffer(size)
253 }
254
255 fn block_size(&self) -> usize {
256 self.handle.block_size().get() as usize
257 }
258
259 async fn commit(self: Box<Self>) -> Result<(), Error> {
260 self.commit_impl().await.inspect_err(|_| {
261 self.abort_cleanup();
262 })
263 }
264
265 fn abort_cleanup(self: Box<Self>) {
266 self.volume.store().filesystem().graveyard().queue_tombstone_attribute(
267 self.volume.store().store_object_id(),
268 self.handle.object_id(),
269 AttributeId::PROFILE_RECORDING,
270 );
271 }
272
273 fn filter_events(&self) -> bool {
274 false
275 }
276}
277
278trait RecordedVolume: Send + Sync + Sized + Unpin {
279 type IdType: std::fmt::Display + Ord + Send + Sized;
280 type NodeType: PagerBacked;
281 type MessageType: Message<IdType = Self::IdType>;
282
283 fn new(volume: Arc<FxVolume>) -> Self;
284
285 fn open(
286 &self,
287 id: Self::IdType,
288 ) -> impl std::future::Future<Output = Result<OpenedNode<Self::NodeType>, Error>> + Send;
289
290 fn file_is_replayable(
292 &self,
293 id: &Self::IdType,
294 ) -> impl std::future::Future<Output = bool> + Send;
295
296 fn read_and_queue(
297 &self,
298 handle: Box<dyn ReadObjectHandle>,
299 sender: &async_channel::Sender<Request<Self::NodeType>>,
300 local_cache: &mut BTreeMap<Self::IdType, Option<OpenedNode<Self::NodeType>>>,
301 ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
302 async move {
303 let mut io_buf = handle.allocate_buffer(IO_SIZE).await;
304 let block_size = handle.block_size().get() as usize;
305 let file_size = handle.get_size() as usize;
306 let mut offset = 0;
307 while offset < file_size {
308 let actual = handle
309 .read_aligned(offset as u64, io_buf.as_mut())
310 .await
311 .map_err(|e| e.context(format!("Failed to read at offset: {}", offset)))?;
312 offset += actual;
313 let mut local_offset = 0;
314 let mut next_block = block_size;
315 let mut next_offset = size_of::<Self::MessageType>();
316 while next_offset <= actual {
317 let data = io_buf.subslice(local_offset..next_offset);
318 let msg = Self::MessageType::decode_from(data.as_ptr_slice());
319
320 local_offset = next_offset;
321 next_offset = local_offset + size_of::<Self::MessageType>();
322 if next_offset > next_block {
324 local_offset = next_block;
325 next_offset = local_offset + size_of::<Self::MessageType>();
326 next_block += block_size;
327 }
328
329 if msg.is_zeroes() {
332 break;
333 }
334
335 let file = match local_cache.entry(msg.id()) {
336 Entry::Occupied(entry) => match entry.get() {
337 Some(opened_file) => (*opened_file).clone(),
338 None => continue,
340 },
341 Entry::Vacant(entry) => match self.open(msg.id()).await {
342 Err(e) => {
343 debug!("Failed to open object {} from profile: {:?}", msg.id(), e);
344 entry.insert(None);
346 continue;
347 }
348 Ok(opened_file) => {
349 let file_clone = opened_file.clone();
350 entry.insert(Some(opened_file));
351 file_clone
352 }
353 },
354 };
355
356 sender.send(Request { file, offset: msg.offset() }).await?;
357 }
358 }
359 Ok(())
360 }
361 }
362
363 fn record(
364 &self,
365 recording_handle: Box<dyn RecordingHandle>,
366 receiver: async_channel::Receiver<Vec<Self::MessageType>>,
367 ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
368 let recording_handle = scopeguard::guard(recording_handle, |recording_handle| {
370 recording_handle.abort_cleanup();
371 });
372
373 async move {
374 let mut recorded_offsets = LinkedHashMap::<Self::MessageType, ()>::new();
375 let mut recorded_opens = BTreeMap::<Self::IdType, bool>::new();
376 let filter = recording_handle.filter_events();
377 while let Ok(buffer) = receiver.recv().await {
378 for message in buffer {
379 if message.is_open_marker() {
380 if filter {
381 if let Entry::Vacant(entry) = recorded_opens.entry(message.id()) {
382 let usable = self.file_is_replayable(entry.key()).await;
383 entry.insert(usable);
384 }
385 }
386 } else {
387 recorded_offsets.insert(message, ());
388 }
389 }
390 }
391
392 let block_size = recording_handle.block_size();
393 let mut offset = 0;
394 let mut io_buf = recording_handle.allocate_buffer(IO_SIZE).await;
395 let mut next_block = block_size;
396 while let Some((message, _)) = recorded_offsets.pop_front() {
397 if filter && !recorded_opens.get(&message.id()).copied().unwrap_or(false) {
399 continue;
400 }
401
402 let mut next_offset = offset + size_of::<Self::MessageType>();
403 if next_offset > next_block {
404 io_buf.as_mut().subslice_mut(offset..next_block).fill(0);
407 if next_block >= IO_SIZE {
408 recording_handle
409 .append(io_buf.as_ref())
410 .await
411 .context("Failed to write profile block")?;
412 offset = 0;
413 next_offset = size_of::<Self::MessageType>();
414 next_block = block_size;
415 } else {
416 offset = next_block;
417 next_offset = offset + size_of::<Self::MessageType>();
418 next_block += block_size;
419 }
420 }
421 message.encode_to(
422 io_buf.as_mut().subslice_mut(offset..next_offset).as_mut_ptr_slice(),
423 );
424 offset = next_offset;
425 }
426 if offset > 0 {
427 io_buf.as_mut().subslice_mut(offset..next_block).fill(0);
428 recording_handle
429 .append(io_buf.subslice(0..next_block))
430 .await
431 .context("Failed to write profile block")?;
432 }
433
434 std::mem::drop(io_buf);
435 let recording_handle = ScopeGuard::into_inner(recording_handle);
437 recording_handle.commit().await?;
438
439 Ok(())
440 }
441 }
442}
443
444struct BlobVolume {
445 volume: Arc<FxVolume>,
446 root_dir: Mutex<Option<Arc<BlobDirectory>>>,
449}
450
451impl RecordedVolume for BlobVolume {
452 type IdType = Hash;
453 type NodeType = FxBlob;
454 type MessageType = BlobMessage;
455
456 fn new(volume: Arc<FxVolume>) -> Self {
457 Self { volume, root_dir: Mutex::new(None) }
458 }
459
460 async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
461 let mut root_dir = self.root_dir.lock().await;
462 if root_dir.is_none() {
463 *root_dir = Some(
464 self.volume
465 .get_or_load_node(
466 self.volume.store().root_directory_object_id(),
467 ObjectDescriptor::Directory,
468 None,
469 )
470 .await?
471 .into_any()
472 .downcast::<BlobDirectory>()
473 .map_err(|_| FxfsError::Inconsistent)?,
474 );
475 };
476 root_dir
477 .as_ref()
478 .unwrap()
479 .open_blob(&id.into())
480 .await?
481 .ok_or_else(|| FxfsError::NotFound.into())
482 }
483
484 async fn file_is_replayable(&self, _id: &Self::IdType) -> bool {
485 true
487 }
488}
489
490struct FileVolume {
491 volume: Arc<FxVolume>,
492}
493
494impl RecordedVolume for FileVolume {
495 type IdType = u64;
496 type NodeType = FxFile;
497 type MessageType = FileMessage;
498
499 fn new(volume: Arc<FxVolume>) -> Self {
500 Self { volume }
501 }
502
503 async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
504 self.volume
505 .get_or_load_node(id, ObjectDescriptor::File, None)
506 .await?
507 .into_any()
508 .downcast::<FxFile>()
509 .map_err(|_| anyhow!("Non-file opened"))?
510 .into_opened_node()
511 .ok_or_else(|| anyhow!("File being purged"))
512 }
513
514 async fn file_is_replayable(&self, id: &Self::IdType) -> bool {
515 match self.volume.store().get_keys(*id).await {
516 Ok(keys)
519 if keys.is_empty()
520 || (keys.len() == 1 && keys.first().unwrap().0 == VOLUME_DATA_KEY_ID) =>
521 {
522 true
523 }
524 _ => false,
525 }
526 }
527}
528
529trait Message: Eq + PartialEq + Sized + Send + Sync + std::hash::Hash + 'static {
530 type IdType: std::fmt::Display + Ord + Send + Sized;
531
532 fn id(&self) -> Self::IdType;
533 fn offset(&self) -> u64;
534 fn encode_to(&self, dest: MutPtrByteSlice<'_>);
535 fn decode_from(src: PtrByteSlice<'_>) -> Self;
536 fn is_zeroes(&self) -> bool;
537 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error>;
538 fn is_open_marker(&self) -> bool;
539}
540
541#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
542struct BlobMessage {
543 id: Hash,
544 offset: u64,
547}
548
549impl BlobMessage {
550 fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
551 let (first, second) = mut_array_refs![dest, size_of::<Hash>(), size_of::<u64>()];
552 *first = self.id.into();
553 *second = self.offset.to_le_bytes();
554 }
555
556 fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
557 let (first, second) = array_refs!(src, size_of::<Hash>(), size_of::<u64>());
558 Self { id: Hash::from_array(*first), offset: u64::from_le_bytes(*second) }
559 }
560}
561
562impl Message for BlobMessage {
563 type IdType = Hash;
564
565 fn id(&self) -> Self::IdType {
566 self.id
567 }
568
569 fn offset(&self) -> u64 {
570 self.offset
571 }
572
573 fn encode_to(&self, mut dest: MutPtrByteSlice<'_>) {
574 let mut buf = [0u8; size_of::<Self>()];
575 self.encode_to_impl(&mut buf);
576 dest.copy_from_slice(&buf);
577 }
578
579 fn decode_from(src: PtrByteSlice<'_>) -> Self {
580 let mut buf = [0u8; size_of::<Self>()];
581 src.copy_to_slice(&mut buf);
582 Self::decode_from_impl(&buf)
583 }
584
585 fn is_zeroes(&self) -> bool {
586 self.id == Hash::from_array([0u8; size_of::<Hash>()]) && self.offset == 0
587 }
588
589 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
590 match node.into_any().downcast::<FxBlob>() {
591 Ok(blob) => Ok(Self { id: blob.root(), offset }),
592 Err(_) => Err(anyhow!("Cannot record non-blob entry.")),
593 }
594 }
595
596 fn is_open_marker(&self) -> bool {
597 self.offset == FILE_OPEN_MARKER
598 }
599}
600
601#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
602struct FileMessage {
603 id: u64,
604 offset: u64,
607}
608
609impl FileMessage {
610 fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
611 let (first, second) = mut_array_refs![dest, size_of::<u64>(), size_of::<u64>()];
612 *first = self.id.to_le_bytes();
613 *second = self.offset.to_le_bytes();
614 }
615
616 fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
617 let (first, second) = array_refs!(src, size_of::<u64>(), size_of::<u64>());
618 Self { id: u64::from_le_bytes(*first), offset: u64::from_le_bytes(*second) }
619 }
620}
621
622impl Message for FileMessage {
623 type IdType = u64;
624
625 fn id(&self) -> Self::IdType {
626 self.id
627 }
628
629 fn offset(&self) -> u64 {
630 self.offset
631 }
632
633 fn encode_to(&self, mut dest: MutPtrByteSlice<'_>) {
634 let mut buf = [0u8; size_of::<Self>()];
635 self.encode_to_impl(&mut buf);
636 dest.copy_from_slice(&buf);
637 }
638
639 fn decode_from(src: PtrByteSlice<'_>) -> Self {
640 let mut buf = [0u8; size_of::<Self>()];
641 src.copy_to_slice(&mut buf);
642 Self::decode_from_impl(&buf)
643 }
644
645 fn is_zeroes(&self) -> bool {
646 self.id == 0 && self.offset == 0
647 }
648
649 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
650 match node.into_any().downcast::<FxFile>() {
651 Ok(file) => Ok(Self { id: file.object_id(), offset }),
652 Err(_) => Err(anyhow!("Cannot record non-file entry")),
653 }
654 }
655
656 fn is_open_marker(&self) -> bool {
657 self.offset == FILE_OPEN_MARKER
658 }
659}
660
661pub trait Recorder: Send + Sync {
664 fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error>;
666
667 fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error>;
669}
670
671struct RecorderImpl<T: Message> {
672 sender: async_channel::Sender<Vec<T>>,
673 buffer: Vec<T>,
674}
675
676impl<T: Message> RecorderImpl<T> {
677 fn new(sender: async_channel::Sender<Vec<T>>) -> Self {
678 Self { sender, buffer: Vec::with_capacity(MESSAGE_CHUNK_SIZE) }
679 }
680}
681
682impl<T: Message> Recorder for RecorderImpl<T> {
683 fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error> {
684 self.buffer.push(T::from_node_request(node, offset)?);
685 if self.buffer.len() >= MESSAGE_CHUNK_SIZE {
686 self.sender.try_send(std::mem::replace(
689 &mut self.buffer,
690 Vec::with_capacity(MESSAGE_CHUNK_SIZE),
691 ))?;
692 }
693 RECORDED.fetch_add(1, Ordering::Relaxed);
694 Ok(())
695 }
696
697 fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error> {
698 self.record(node, FILE_OPEN_MARKER)
699 }
700}
701
702impl<T: Message> Drop for RecorderImpl<T> {
703 fn drop(&mut self) {
704 if self.buffer.len() > 0 {
706 let buffer = std::mem::take(&mut self.buffer);
707 let _ = self.sender.try_send(buffer);
708 }
709 }
710}
711
712struct Request<P: PagerBacked> {
713 file: Arc<P>,
714 offset: u64,
715}
716
717struct ReplayState<T> {
718 replay_threads: future::Shared<BoxFuture<'static, ()>>,
719 _cache_task: fasync::Task<()>,
720 _phantom: PhantomData<T>,
721}
722
723impl<T: RecordedVolume> ReplayState<T> {
724 fn new(handle: Box<dyn ReadObjectHandle>, volume: Arc<FxVolume>, guard: ActiveGuard) -> Self {
725 let (sender, receiver) = async_channel::unbounded::<Request<T::NodeType>>();
726
727 let mut replay_threads = Vec::with_capacity(REPLAY_THREADS);
730 for _ in 0..REPLAY_THREADS {
731 let receiver = receiver.clone();
732 let guard = guard.clone();
735 replay_threads.push(fasync::unblock(move || {
736 let _guard = guard;
737 Self::page_in_thread(receiver);
738 }));
739 }
740 let replay_threads = (Box::pin(async {
741 join_all(replay_threads).await;
742 }) as BoxFuture<'static, ()>)
743 .shared();
744
745 let scope = volume.scope().clone();
746 let cache_task = scope
747 .spawn({
748 async move {
752 let mut task = pin!(
753 async {
754 let mut local_cache: BTreeMap<
758 T::IdType,
759 Option<OpenedNode<T::NodeType>>,
760 > = BTreeMap::new();
761
762 let volume_id = volume.id();
763
764 if let Err(error) = T::new(volume)
765 .read_and_queue(handle, &sender, &mut local_cache)
766 .await
767 {
768 error!(error:?; "Failed to read back profile");
769 }
770 sender.close();
771
772 info!(
773 "Replay for volume {} opened {} of {} objects.",
774 volume_id,
775 local_cache.iter().filter(|(_, e)| e.is_some()).count(),
776 local_cache.len()
777 );
778
779 let () = std::future::pending().await;
781 }
782 .fuse()
783 );
784
785 select! {
786 _ = task => {}
787 _ = guard.on_cancel().fuse() => {}
788 }
789 }
790 })
791 .into();
792
793 Self { replay_threads, _cache_task: cache_task, _phantom: PhantomData }
794 }
795
796 fn page_in_thread(queue: async_channel::Receiver<Request<T::NodeType>>) {
797 let page_size = page_size().get();
798 while let Ok(request) = queue.recv_blocking() {
799 let res = request.file.vmo().op_range(zx::VmoOp::PREFETCH, request.offset, page_size);
800 if let Err(e) = res {
801 warn!("Failed to prefetch page: {:?}", e);
802 }
803 if queue.sender_count() == 0 {
805 return;
806 }
807 }
808 }
809}
810
811#[async_trait]
814pub trait ProfileState: Send + Sync {
815 fn record_new(
819 &mut self,
820 volume: &Arc<FxVolume>,
821 recording_handle: Box<dyn RecordingHandle>,
822 ) -> Box<dyn Recorder>;
823
824 fn replay_profile(
827 &mut self,
828 handle: Box<dyn ReadObjectHandle>,
829 volume: Arc<FxVolume>,
830 guard: ActiveGuard,
831 );
832
833 async fn wait_for_replay_to_finish(&mut self);
836
837 async fn wait_for_recording_to_finish(&mut self);
839}
840
841pub fn new_profile_state(is_blob: bool) -> Box<dyn ProfileState> {
842 if is_blob {
843 Box::new(ProfileStateImpl::<BlobVolume>::new())
844 } else {
845 Box::new(ProfileStateImpl::<FileVolume>::new())
846 }
847}
848
849struct ProfileStateImpl<T> {
850 recording: Option<fasync::CancelableJoinHandle<()>>,
851 replay: Option<ReplayState<T>>,
852}
853
854impl<T> ProfileStateImpl<T> {
855 fn new() -> Self {
856 Self { recording: None, replay: None }
857 }
858}
859
860#[async_trait]
861impl<T: RecordedVolume> ProfileState for ProfileStateImpl<T> {
862 fn record_new(
863 &mut self,
864 volume: &Arc<FxVolume>,
865 recording_handle: Box<dyn RecordingHandle>,
866 ) -> Box<dyn Recorder> {
867 let (sender, receiver) = async_channel::unbounded();
868 let volume = volume.clone();
869 self.recording = None;
871 let scope = volume.scope().clone();
872 self.recording = Some(
873 scope
874 .spawn(async move {
875 let recording = T::new(volume);
876 if let Err(error) = recording.record(recording_handle, receiver).await {
877 warn!(error:?; "Profile recording failed");
878 }
879 })
880 .into(),
881 );
882 Box::new(RecorderImpl::new(sender))
883 }
884
885 fn replay_profile(
886 &mut self,
887 handle: Box<dyn ReadObjectHandle>,
888 volume: Arc<FxVolume>,
889 guard: ActiveGuard,
890 ) {
891 self.replay = Some(ReplayState::new(handle, volume, guard));
892 }
893
894 async fn wait_for_replay_to_finish(&mut self) {
895 if let Some(replay) = &mut self.replay {
896 replay.replay_threads.clone().await;
897 }
898 }
899
900 async fn wait_for_recording_to_finish(&mut self) {
901 if let Some(recording) = self.recording.take() {
902 let _ = recording.await;
903 }
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use super::{
910 AttributeRecordingHandle, BlobMessage, BlobVolume, FileMessage, FileRecordingHandle,
911 FileVolume, IO_SIZE, Message, MutPtrByteSlice, PtrByteSlice, RecordedVolume, Request,
912 new_profile_state,
913 };
914 use crate::fuchsia::file::FxFile;
915 use crate::fuchsia::fxblob::blob::FxBlob;
916 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture, open_blob_fixture};
917 use crate::fuchsia::node::{FxNode, OpenedNode};
918 use crate::fuchsia::pager::PagerBacked;
919 use crate::fuchsia::testing::{TestFixture, TestFixtureOptions, open_file_checked};
920 use crate::fuchsia::volume::FxVolume;
921 use anyhow::{Error, ensure};
922 use async_trait::async_trait;
923 use delivery_blob::CompressionMode;
924 use event_listener::{Event, EventListener};
925 use fidl_fuchsia_fs_startup::CreateOptions;
926 use fidl_fuchsia_io as fio;
927 use fuchsia_async as fasync;
928 use fuchsia_hash::Hash;
929 use fuchsia_sync::Mutex;
930 use fxfs::errors::FxfsError;
931 use fxfs::object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle};
932 use fxfs::object_store::object_record::ObjectItem;
933 use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
934 use fxfs::object_store::{
935 AttributeId, AttributeKey, DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectKey,
936 ObjectStore, ObjectValue,
937 };
938 use std::collections::BTreeMap;
939 use std::mem::size_of;
940 use std::sync::Arc;
941 use std::time::Duration;
942 use storage_device::buffer::{BufferRef, MutableBufferRef};
943 use storage_device::buffer_allocator::{BufferAllocator, BufferFuture, BufferSource};
944 use storage_units::BlockSize;
945
946 struct FakeReaderWriterInner {
947 data: Vec<u8>,
948 delays: Vec<EventListener>,
949 }
950
951 struct FakeReaderWriter {
952 allocator: BufferAllocator,
953 inner: Arc<Mutex<FakeReaderWriterInner>>,
954 }
955
956 const BLOCK_SIZE: usize = 4096;
957
958 impl FakeReaderWriter {
959 fn new() -> Self {
960 Self {
961 allocator: BufferAllocator::new(BLOCK_SIZE, BufferSource::new(IO_SIZE * 2)),
962 inner: Arc::new(Mutex::new(FakeReaderWriterInner {
963 data: Vec::new(),
964 delays: Vec::new(),
965 })),
966 }
967 }
968
969 fn push_delay(&self, delay: EventListener) {
970 self.inner.lock().delays.insert(0, delay);
971 }
972 }
973
974 impl ObjectHandle for FakeReaderWriter {
975 fn object_id(&self) -> u64 {
976 0
977 }
978
979 fn block_size(&self) -> BlockSize {
980 BlockSize::new(self.allocator.block_size() as u32).unwrap()
981 }
982
983 fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
984 self.allocator.allocate_buffer(size)
985 }
986 }
987
988 impl WriteObjectHandle for FakeReaderWriter {
989 async fn write_or_append(
990 &self,
991 offset: Option<u64>,
992 buf: BufferRef<'_>,
993 ) -> Result<u64, Error> {
994 assert!(offset.is_none());
996 let delay = self.inner.lock().delays.pop();
997 if let Some(delay) = delay {
998 delay.await;
999 }
1000 buf.append_to(&mut self.inner.lock().data);
1002 Ok(buf.len() as u64)
1003 }
1004
1005 async fn truncate(&self, _size: u64) -> Result<(), Error> {
1006 unreachable!();
1007 }
1008
1009 async fn flush(&self) -> Result<(), Error> {
1010 unreachable!();
1011 }
1012 }
1013
1014 async fn write_file(fixture: &TestFixture, name: &str, data: &[u8]) -> u64 {
1015 let root_dir = fixture.volume().root_dir();
1016 let mut transaction = fixture
1017 .volume()
1018 .volume()
1019 .store()
1020 .new_transaction(
1021 lock_keys![LockKey::object(
1022 fixture.volume().volume().store().store_object_id(),
1023 root_dir.object_id()
1024 )],
1025 Options::default(),
1026 )
1027 .await
1028 .expect("Creating transaction for new file");
1029 let id = root_dir
1030 .directory()
1031 .create_child_file(&mut transaction, name)
1032 .await
1033 .expect("Creating new_file")
1034 .object_id();
1035 transaction.commit().await.unwrap();
1036 let file = open_file_checked(
1037 fixture.root(),
1038 name,
1039 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_FILE,
1040 &Default::default(),
1041 )
1042 .await;
1043 file.write(data).await.unwrap().expect("Writing file");
1044 id
1045 }
1046
1047 #[async_trait]
1048 impl ReadObjectHandle for FakeReaderWriter {
1049 async fn read_aligned(
1050 &self,
1051 offset: u64,
1052 buf: MutableBufferRef<'_>,
1053 ) -> Result<usize, Error> {
1054 let block_size = self.block_size();
1055 ensure!(block_size.is_aligned(offset), FxfsError::InvalidArgs);
1056 ensure!(block_size.is_aligned(buf.len() as u64), FxfsError::InvalidArgs);
1057 let delay = self.inner.lock().delays.pop();
1058 if let Some(delay) = delay {
1059 delay.await;
1060 }
1061 let inner = self.inner.lock();
1063 assert!(offset as usize <= inner.data.len());
1064 let offset_end = std::cmp::min(offset as usize + buf.len(), inner.data.len());
1065 let size = offset_end - offset as usize;
1066 buf.subslice_mut(..size).copy_from_slice(&inner.data[offset as usize..offset_end]);
1067 Ok(size)
1068 }
1069
1070 fn get_size(&self) -> u64 {
1071 self.inner.lock().data.len() as u64
1072 }
1073 }
1074
1075 #[fuchsia::test]
1076 async fn test_encode_decode_blob() {
1077 let mut buf = [0u8; size_of::<BlobMessage>()];
1078 let m = BlobMessage { id: [88u8; 32].into(), offset: 77 };
1079 m.encode_to(MutPtrByteSlice::from(&mut buf[..]));
1080 let m2 = BlobMessage::decode_from(PtrByteSlice::from(&buf[..]));
1081 assert_eq!(m, m2);
1082 }
1083
1084 #[fuchsia::test]
1085 async fn test_encode_decode_file() {
1086 let mut buf = [0u8; size_of::<FileMessage>()];
1087 let m = FileMessage { id: 88, offset: 77 };
1088 m.encode_to(MutPtrByteSlice::from(&mut buf[..]));
1089 let m2 = FileMessage::decode_from(PtrByteSlice::from(&buf[..]));
1090 assert!(!m2.is_zeroes());
1091 assert_eq!(m, m2);
1092 }
1093
1094 const TEST_PROFILE_NAME: &str = "test_profile";
1095
1096 async fn get_test_profile_handle(volume: &Arc<FxVolume>) -> DataObjectHandle<FxVolume> {
1097 let profile_dir = volume.get_profile_directory().await.unwrap();
1098 ObjectStore::open_object(
1099 volume,
1100 profile_dir
1101 .lookup(TEST_PROFILE_NAME)
1102 .await
1103 .expect("lookup failed")
1104 .expect("not found")
1105 .0,
1106 HandleOptions::default(),
1107 None,
1108 )
1109 .await
1110 .unwrap()
1111 }
1112
1113 async fn get_test_profile_contents(volume: &Arc<FxVolume>) -> Vec<u8> {
1114 get_test_profile_handle(volume).await.contents(1024 * 1024).await.unwrap().to_vec()
1115 }
1116
1117 #[fuchsia::test]
1118 async fn test_recording_basic_blob() {
1119 let fixture = new_blob_fixture().await;
1120 {
1121 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1122 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1123
1124 let mut state = new_profile_state(true);
1125 let volume = fixture.volume().volume();
1126
1127 {
1128 let handle =
1130 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1131 let mut recorder = state.record_new(volume, Box::new(handle));
1132 recorder.record(blob.clone(), 0).unwrap();
1133 recorder.record_open(blob).unwrap();
1134 }
1135
1136 state.wait_for_recording_to_finish().await;
1137
1138 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1139 }
1140 fixture.close().await;
1141 }
1142
1143 #[fuchsia::test]
1144 async fn test_recording_basic_file() {
1145 let fixture = TestFixture::new().await;
1146 {
1147 let id = write_file(&fixture, "foo", &[88u8]).await;
1148 let node = fixture
1149 .volume()
1150 .volume()
1151 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1152 .await
1153 .unwrap();
1154
1155 let mut state = new_profile_state(false);
1156 let volume = fixture.volume().volume();
1157
1158 {
1159 let handle =
1161 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1162 let mut recorder = state.record_new(volume, Box::new(handle));
1163 recorder.record(node.clone(), 0).unwrap();
1164 recorder.record_open(node).unwrap();
1165 }
1166 state.wait_for_recording_to_finish().await;
1167
1168 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1169 }
1170 fixture.close().await;
1171 }
1172
1173 #[fuchsia::test]
1174 async fn test_recording_filtered_without_open() {
1175 let fixture = new_blob_fixture().await;
1176 {
1177 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1178 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1179
1180 let mut state = new_profile_state(true);
1181 let volume = fixture.volume().volume();
1182
1183 {
1184 let handle =
1186 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1187 let mut recorder = state.record_new(volume, Box::new(handle));
1188 recorder.record(blob.clone(), 0).unwrap();
1189 }
1190 state.wait_for_recording_to_finish().await;
1191
1192 assert_eq!(get_test_profile_contents(volume).await.len(), 0);
1193 }
1194 fixture.close().await;
1195 }
1196
1197 #[fuchsia::test]
1198 async fn test_associated_recording_not_filtered_without_open() {
1199 let fixture = new_blob_fixture().await;
1200 {
1201 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1202 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1203
1204 let mut state = new_profile_state(true);
1205 let volume = fixture.volume().volume();
1206
1207 {
1208 let handle =
1210 AttributeRecordingHandle::new(blob.object_id(), volume.clone()).await.unwrap();
1211 let mut recorder = state.record_new(volume, Box::new(handle));
1212 recorder.record(blob.clone(), 0).unwrap();
1213 }
1214 state.wait_for_recording_to_finish().await;
1215
1216 let key = ObjectKey::attribute(
1219 blob.object_id(),
1220 AttributeId::PROFILE_RECORDING,
1221 AttributeKey::Attribute,
1222 );
1223 let store = volume.store();
1224 let ObjectItem { value, .. } =
1225 store.tree().find(&key).await.unwrap().expect("profile attribute not found");
1226 let size = match value {
1227 ObjectValue::Attribute { size, .. } => size,
1228 _ => panic!("Expected Attribute value"),
1229 };
1230
1231 assert_eq!(size, BLOCK_SIZE as u64);
1232 }
1233 fixture.close().await;
1234 }
1235
1236 #[fuchsia::test]
1237 async fn test_recording_blob_more_than_block() {
1238 let mut state = new_profile_state(true);
1239
1240 let fixture = new_blob_fixture().await;
1241 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1242 let message_count =
1243 (fixture.fs().block_size().get() as usize / size_of::<BlobMessage>()) + 1;
1244 let hash;
1245 let volume = fixture.volume().volume();
1246
1247 {
1248 hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1249 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1250 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1252 let mut recorder = state.record_new(volume, Box::new(handle));
1253 recorder.record_open(blob.clone()).unwrap();
1254 for i in 0..message_count {
1255 recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1256 }
1257 }
1258 state.wait_for_recording_to_finish().await;
1259
1260 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1261
1262 let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1263 let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1264
1265 let volume = fixture.volume().volume().clone();
1266 let task = fasync::Task::spawn(async move {
1267 let handle = Box::new(get_test_profile_handle(&volume).await);
1268 let blob = BlobVolume::new(volume);
1269 blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1270 });
1271
1272 let mut recv_count = 0;
1273 while let Ok(msg) = receiver.recv().await {
1274 assert_eq!(msg.file.root(), hash);
1275 assert_eq!(msg.offset, 4096 * recv_count);
1276 recv_count += 1;
1277 }
1278 task.await;
1279 assert_eq!(recv_count, message_count as u64);
1280
1281 fixture.close().await;
1282 }
1283
1284 #[fuchsia::test]
1285 async fn test_recording_file_more_than_block() {
1286 let mut state = new_profile_state(false);
1287
1288 let fixture = TestFixture::new().await;
1289 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1290 let message_count =
1291 (fixture.fs().block_size().get() as usize / size_of::<FileMessage>()) + 1;
1292 let id;
1293 let volume = fixture.volume().volume();
1294 {
1295 id = write_file(&fixture, "foo", &[88u8]).await;
1296 let node = volume
1297 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1298 .await
1299 .unwrap();
1300 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1302 let mut recorder = state.record_new(volume, Box::new(handle));
1303 recorder.record_open(node.clone()).unwrap();
1304 for i in 0..message_count {
1305 recorder.record(node.clone(), 4096 * i as u64).unwrap();
1306 }
1307 }
1308 state.wait_for_recording_to_finish().await;
1309
1310 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1311
1312 let mut local_cache: BTreeMap<u64, Option<OpenedNode<FxFile>>> = BTreeMap::new();
1313 let (sender, receiver) = async_channel::unbounded::<Request<FxFile>>();
1314
1315 let volume = fixture.volume().volume().clone();
1316 let task = fasync::Task::spawn(async move {
1317 let handle = Box::new(get_test_profile_handle(&volume).await);
1318 let file = FileVolume::new(volume);
1319 file.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1320 });
1321
1322 let mut recv_count = 0;
1323 while let Ok(msg) = receiver.recv().await {
1324 assert_eq!(msg.file.object_id(), id);
1325 assert_eq!(msg.offset, 4096 * recv_count);
1326 recv_count += 1;
1327 }
1328 task.await;
1329 assert_eq!(recv_count, message_count as u64);
1330
1331 fixture.close().await;
1332 }
1333
1334 #[fuchsia::test]
1335 async fn test_recording_more_than_io_size() {
1336 let fixture = new_blob_fixture().await;
1337
1338 {
1339 let mut state = new_profile_state(true);
1340 let message_count = (IO_SIZE as usize / size_of::<BlobMessage>()) + 1;
1341 let hash;
1342 let volume = fixture.volume().volume();
1343 {
1344 hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1345 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1346 let handle =
1348 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1349 let mut recorder = state.record_new(volume, Box::new(handle));
1350 recorder.record_open(blob.clone()).unwrap();
1351 for i in 0..message_count {
1352 recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1353 }
1354 }
1355 state.wait_for_recording_to_finish().await;
1356 assert_eq!(get_test_profile_contents(volume).await.len(), IO_SIZE + BLOCK_SIZE);
1357
1358 let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1359 let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1360
1361 let volume = volume.clone();
1362 let task = fasync::Task::spawn(async move {
1363 let handle = Box::new(get_test_profile_handle(&volume).await);
1364 let blob = BlobVolume::new(volume);
1365 blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1366 });
1367
1368 let mut recv_count = 0;
1369 while let Ok(msg) = receiver.recv().await {
1370 assert_eq!(msg.file.root(), hash);
1371 assert_eq!(msg.offset, 4096 * recv_count);
1372 recv_count += 1;
1373 }
1374 task.await;
1375 assert_eq!(recv_count, message_count as u64);
1376 }
1377
1378 fixture.close().await;
1379 }
1380
1381 #[fuchsia::test]
1382 async fn test_replay_profile_blob() {
1383 let mut state = new_profile_state(true);
1385
1386 let mut hashes = Vec::new();
1387
1388 let fixture = new_blob_fixture().await;
1389 {
1390 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1391 let message_count =
1392 (fixture.fs().block_size().get() as usize / size_of::<BlobMessage>()) + 1;
1393
1394 let volume = fixture.volume().volume();
1395 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1396 let mut recorder = state.record_new(volume, Box::new(handle));
1397 for i in 0..message_count {
1399 let hash =
1400 fixture.write_blob(i.to_string().as_bytes(), CompressionMode::Never).await;
1401 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1402 recorder.record_open(blob.clone()).unwrap();
1403 hashes.push(hash);
1404 recorder.record(blob.clone(), 0).unwrap();
1405 }
1406 };
1407 let device = fixture.close().await;
1408 device.ensure_unique();
1409 state.wait_for_recording_to_finish().await;
1410
1411 device.reopen(false);
1412 let fixture = open_blob_fixture(device).await;
1413 {
1414 for hash in &hashes {
1417 let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1418 assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1419 }
1420
1421 let volume = fixture.volume().volume();
1422 state.replay_profile(
1423 Box::new(get_test_profile_handle(volume).await),
1424 volume.clone(),
1425 volume.scope().try_active_guard().unwrap(),
1426 );
1427
1428 for hash in &hashes {
1430 let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1431 while blob.vmo().info().unwrap().committed_bytes == 0 {
1432 fasync::Timer::new(Duration::from_millis(25)).await;
1433 }
1434 }
1435 }
1436 fixture.close().await;
1437 }
1438
1439 #[fuchsia::test]
1440 async fn test_replay_profile_file() {
1441 let mut state = new_profile_state(false);
1443
1444 let mut ids = Vec::new();
1445
1446 let fixture = TestFixture::new().await;
1447 {
1448 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1449 let message_count =
1450 (fixture.fs().block_size().get() as usize / size_of::<FileMessage>()) + 1;
1451
1452 let volume = fixture.volume().volume();
1453 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1454 let mut recorder = state.record_new(volume, Box::new(handle));
1455 for i in 0..message_count {
1457 let id = write_file(&fixture, &i.to_string(), &[88u8]).await;
1458 let node = fixture
1459 .volume()
1460 .volume()
1461 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1462 .await
1463 .unwrap();
1464 recorder.record_open(node.clone()).unwrap();
1465 ids.push(id);
1466 recorder.record(node.clone(), 0).unwrap();
1467 }
1468 };
1469 let device = fixture.close().await;
1470 device.ensure_unique();
1471 state.wait_for_recording_to_finish().await;
1472
1473 device.reopen(false);
1474 let fixture = TestFixture::open(
1475 device,
1476 TestFixtureOptions { encrypted: true, format: false, ..Default::default() },
1477 )
1478 .await;
1479 {
1480 for id in &ids {
1482 let file = fixture
1483 .volume()
1484 .volume()
1485 .get_or_load_node(
1486 *id,
1487 ObjectDescriptor::File,
1488 Some(fixture.volume().root_dir()),
1489 )
1490 .await
1491 .unwrap()
1492 .into_any()
1493 .downcast::<FxFile>()
1494 .unwrap();
1495 assert_eq!(file.vmo().info().unwrap().committed_bytes, 0);
1496 }
1497
1498 let volume = fixture.volume().volume();
1499 state.replay_profile(
1500 Box::new(get_test_profile_handle(volume).await),
1501 volume.clone(),
1502 volume.scope().try_active_guard().unwrap(),
1503 );
1504
1505 for id in &ids {
1507 let file = fixture
1508 .volume()
1509 .volume()
1510 .get_or_load_node(
1511 *id,
1512 ObjectDescriptor::File,
1513 Some(fixture.volume().root_dir()),
1514 )
1515 .await
1516 .unwrap()
1517 .into_any()
1518 .downcast::<FxFile>()
1519 .unwrap();
1520 while file.vmo().info().unwrap().committed_bytes == 0 {
1521 fasync::Timer::new(Duration::from_millis(25)).await;
1522 }
1523 }
1524 state.wait_for_recording_to_finish().await;
1525 }
1526 fixture.close().await;
1527 }
1528
1529 #[fuchsia::test]
1530 async fn test_recording_during_replay() {
1531 let mut state = new_profile_state(true);
1532
1533 let hash;
1534 let first_recording;
1535 let fixture = new_blob_fixture().await;
1536 let volume = fixture.volume().volume();
1537
1538 {
1540 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1541 let mut recorder = state.record_new(volume, Box::new(handle));
1542 hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1543 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1544 recorder.record_open(blob.clone()).unwrap();
1545 recorder.record(blob.clone(), 0).unwrap();
1546 }
1547
1548 state.wait_for_recording_to_finish().await;
1549 first_recording = get_test_profile_contents(volume).await;
1550 assert_ne!(first_recording.len(), 0);
1551 let device = fixture.close().await;
1552 device.ensure_unique();
1553
1554 device.reopen(false);
1555 let fixture = open_blob_fixture(device).await;
1556
1557 {
1558 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1561 assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1562
1563 let volume = fixture.volume().volume();
1565 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1566 let mut recorder = state.record_new(volume, Box::new(handle));
1567 recorder.record(blob.clone(), 4096).unwrap();
1568
1569 let volume = fixture.volume().volume();
1571 state.replay_profile(
1572 Box::new(get_test_profile_handle(volume).await),
1573 volume.clone(),
1574 volume.scope().try_active_guard().unwrap(),
1575 );
1576
1577 {
1579 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1580 while blob.vmo().info().unwrap().committed_bytes == 0 {
1581 fasync::Timer::new(Duration::from_millis(25)).await;
1582 }
1583 }
1584
1585 recorder.record_open(blob.clone()).unwrap();
1588 }
1589
1590 state.wait_for_recording_to_finish().await;
1591
1592 let volume = fixture.volume().volume();
1593 let second_recording = get_test_profile_contents(volume).await;
1594 assert_ne!(second_recording.len(), 0);
1595 assert_ne!(&second_recording, &first_recording);
1596
1597 fixture.close().await;
1598 }
1599
1600 #[fuchsia::test]
1603 async fn test_replay_profile_stop_reading_early() {
1604 let mut state = new_profile_state(true);
1605 let fixture = new_blob_fixture().await;
1606
1607 {
1608 let volume = fixture.volume().volume();
1609
1610 let message;
1612 {
1613 let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1614 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1615 message = BlobMessage { id: blob.root(), offset: 0 };
1616 }
1617 state.wait_for_recording_to_finish().await;
1618
1619 let replay_handle = Box::new(FakeReaderWriter::new());
1621 let mut buff = vec![0u8; IO_SIZE * 2];
1622 message.encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1623 message.encode_to_impl(
1624 (&mut buff[IO_SIZE..IO_SIZE + size_of::<BlobMessage>()]).try_into().unwrap(),
1625 );
1626
1627 replay_handle.inner.lock().data = buff;
1628 let delay1 = Event::new();
1629 replay_handle.push_delay(delay1.listen());
1630 let delay2 = Event::new();
1631 replay_handle.push_delay(delay2.listen());
1632
1633 state.replay_profile(
1634 replay_handle,
1635 volume.clone(),
1636 volume.scope().try_active_guard().unwrap(),
1637 );
1638
1639 fasync::Task::spawn(async move {
1641 fasync::Timer::new(Duration::from_millis(100)).await;
1643 delay1.notify(usize::MAX);
1644 })
1645 .detach();
1646 }
1647
1648 fixture.close().await;
1651 }
1652
1653 #[fuchsia::test]
1654 async fn test_replay_blob_missing() {
1655 let fixture = new_blob_fixture().await;
1656 let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1659 let mut buff = vec![0u8; IO_SIZE];
1660 {
1661 {
1664 let message = BlobMessage { id: [42u8; 32].into(), offset: 0 };
1665 message
1666 .encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1667 }
1668
1669 {
1671 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1672 let message = BlobMessage { id: blob.root(), offset: 0 };
1673 message.encode_to_impl(
1674 (&mut buff[size_of::<BlobMessage>()..(size_of::<BlobMessage>() * 2)])
1675 .try_into()
1676 .unwrap(),
1677 );
1678 }
1679 }
1680 let device = fixture.close().await;
1681 device.ensure_unique();
1682
1683 device.reopen(false);
1684 let fixture = open_blob_fixture(device).await;
1685 {
1686 let mut state = new_profile_state(true);
1687 let volume = fixture.volume().volume();
1688
1689 let replay_handle = Box::new(FakeReaderWriter::new());
1690 replay_handle.inner.lock().data = buff;
1691
1692 state.replay_profile(
1693 replay_handle,
1694 volume.clone(),
1695 volume.scope().try_active_guard().unwrap(),
1696 );
1697
1698 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1700 while blob.vmo().info().unwrap().committed_bytes == 0 {
1701 fasync::Timer::new(Duration::from_millis(25)).await;
1702 }
1703 }
1704 fixture.close().await;
1705 }
1706
1707 #[fuchsia::test]
1708 async fn test_replay_file_missing_or_tombstoned() {
1709 let fixture = TestFixture::new().await;
1710 let mut buff = vec![0u8; IO_SIZE];
1711 let remaining_file_id;
1714 let tombstoned_file_id;
1715 {
1717 let id = write_file(&fixture, "foo", &[1, 2, 3, 4]).await;
1718 let message = FileMessage { id, offset: 0 };
1719 message.encode_to_impl((&mut buff[0..size_of::<FileMessage>()]).try_into().unwrap());
1720 }
1721 fixture
1723 .root()
1724 .unlink("foo", &fio::UnlinkOptions::default())
1725 .await
1726 .unwrap()
1727 .expect("Unlinking");
1728
1729 {
1731 tombstoned_file_id = write_file(&fixture, "bar", &[1, 2, 3, 4]).await;
1732 let message = FileMessage { id: tombstoned_file_id, offset: 0 };
1733 message.encode_to_impl(
1734 (&mut buff[size_of::<FileMessage>()..(size_of::<FileMessage>() * 2)])
1735 .try_into()
1736 .unwrap(),
1737 );
1738 }
1739
1740 {
1742 remaining_file_id = write_file(&fixture, "baz", &[1, 2, 3, 4]).await;
1743 let message = FileMessage { id: remaining_file_id, offset: 0 };
1744 message.encode_to_impl(
1745 (&mut buff[(size_of::<FileMessage>() * 2)..(size_of::<FileMessage>() * 3)])
1746 .try_into()
1747 .unwrap(),
1748 );
1749 }
1750 let device = fixture.close().await;
1751 device.ensure_unique();
1752
1753 device.reopen(false);
1754 let fixture =
1755 TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
1756 .await;
1757 {
1758 let tombstoned_file = fixture
1761 .volume()
1762 .volume()
1763 .get_or_load_node(tombstoned_file_id, ObjectDescriptor::File, None)
1764 .await
1765 .expect("Opening file object")
1766 .into_any()
1767 .downcast::<FxFile>()
1768 .unwrap();
1769 fixture
1770 .root()
1771 .unlink("bar", &fio::UnlinkOptions::default())
1772 .await
1773 .unwrap()
1774 .expect("Unlinking");
1775
1776 let mut state = new_profile_state(false);
1777 let volume = fixture.volume().volume();
1778
1779 let replay_handle = Box::new(FakeReaderWriter::new());
1780 replay_handle.inner.lock().data = buff;
1781
1782 state.replay_profile(
1783 replay_handle,
1784 volume.clone(),
1785 volume.scope().try_active_guard().unwrap(),
1786 );
1787
1788 let remaining_file = fixture
1790 .volume()
1791 .volume()
1792 .get_or_load_node(remaining_file_id, ObjectDescriptor::File, None)
1793 .await
1794 .expect("Opening file object")
1795 .into_any()
1796 .downcast::<FxFile>()
1797 .unwrap();
1798 while remaining_file.vmo().info().unwrap().committed_bytes == 0 {
1799 fasync::Timer::new(Duration::from_millis(25)).await;
1800 }
1801
1802 assert_eq!(tombstoned_file.vmo().info().unwrap().committed_bytes, 0);
1805 }
1806 fixture.close().await;
1807 }
1808
1809 #[fuchsia::test]
1810 async fn test_recording_stopped_by_scope_shutdown() {
1811 let fixture = TestFixture::new().await;
1812 {
1813 let volumes_directory = fixture.volumes_directory();
1814 let mut state = new_profile_state(false);
1815 let (store_id, _recorder) = {
1818 let volume_and_root = volumes_directory
1819 .create_and_mount_volume("other_volume", None, false, CreateOptions::default())
1820 .await
1821 .unwrap();
1822 let volume = volume_and_root.into_volume();
1823
1824 let handle =
1825 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1826 (volume.store().store_object_id(), state.record_new(&volume, Box::new(handle)))
1827 };
1828 volumes_directory.lock().await.unmount(store_id).await.expect("Unmounting");
1829 state.wait_for_recording_to_finish().await;
1830
1831 let volume_and_root =
1832 volumes_directory.mount_volume("other_volume", None, false).await.unwrap();
1833 let volume = volume_and_root.into_volume();
1834 let profile_dir = volume.get_profile_directory().await.unwrap();
1835 assert!(profile_dir.lookup(TEST_PROFILE_NAME).await.unwrap().is_none());
1836 }
1837 fixture.close().await;
1838 }
1839}