Skip to main content

fxfs_platform_testing/fuchsia/
profile.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectStore, Timestamp, VOLUME_DATA_KEY_ID,
25    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 vfs::execution_scope::ActiveGuard;
37
38const FILE_OPEN_MARKER: u64 = u64::MAX;
39const REPLAY_THREADS: usize = 2;
40// The number of messages to buffer before sending to record. They are chunked up to reduce the
41// number of allocations in the serving threads.
42const MESSAGE_CHUNK_SIZE: usize = 64;
43const IO_SIZE: usize = 1 << 17; // 128KiB. Needs to be a power of 2 and >= block size.
44
45pub static RECORDED: AtomicU64 = AtomicU64::new(0);
46
47/// A handle for recording a profile to.
48pub trait RecordingHandle: Send + Sync {
49    /// Append data to the handle.
50    fn append<'a>(
51        &'a self,
52        buf: storage_device::buffer::BufferRef<'a>,
53    ) -> futures::future::BoxFuture<'a, Result<u64, Error>>;
54
55    fn allocate_buffer(
56        &self,
57        size: usize,
58    ) -> futures::future::BoxFuture<'_, storage_device::buffer::Buffer<'_>>;
59
60    fn block_size(&self) -> usize;
61
62    /// The recording is finished being appended to the file. Commit it.
63    fn commit(self: Box<Self>) -> futures::future::BoxFuture<'static, Result<(), Error>>;
64
65    /// When the recording fails or is stopped prematurely this will be called to clean up the
66    /// resources, delete the backing data.
67    fn abort_cleanup(self: Box<Self>);
68}
69
70/// For placing the recording in the volume's internal profile directory.
71pub struct FileRecordingHandle {
72    name: String,
73    volume: Arc<FxVolume>,
74    handle: DataObjectHandle<FxVolume>,
75}
76
77impl FileRecordingHandle {
78    pub async fn new(name: &str, volume: Arc<FxVolume>) -> Result<Self, Error> {
79        let store = volume.store();
80        let mut transaction = store.new_transaction(lock_keys![], Options::default()).await?;
81        let handle =
82            ObjectStore::create_object(&volume, &mut transaction, HandleOptions::default(), None)
83                .await?;
84        store.add_to_graveyard(&mut transaction, handle.object_id());
85        transaction.commit().await?;
86
87        Ok(Self { name: name.to_string(), volume, handle })
88    }
89
90    async fn commit_impl(&self) -> Result<(), Error> {
91        let store = self.volume.store();
92        let fs = store.filesystem();
93        let profile_dir = self.volume.get_profile_directory().await?;
94
95        let mut lock_keys =
96            lock_keys![LockKey::object(store.store_object_id(), profile_dir.object_id())];
97        let mut old_id = INVALID_OBJECT_ID;
98        let mut transaction = loop {
99            let transaction = store.new_transaction(lock_keys, Options::default()).await?;
100            if let Some((id, descriptor, _)) = profile_dir.lookup(&self.name).await? {
101                ensure!(matches!(descriptor, ObjectDescriptor::File), FxfsError::Inconsistent);
102                if id == old_id {
103                    break transaction;
104                }
105                lock_keys = lock_keys![
106                    LockKey::object(store.store_object_id(), profile_dir.object_id()),
107                    LockKey::object(store.store_object_id(), id)
108                ];
109                old_id = id;
110            } else {
111                old_id = INVALID_OBJECT_ID;
112                break transaction;
113            }
114        };
115
116        store.remove_from_graveyard(&mut transaction, self.handle.object_id());
117        directory::replace_child_with_object(
118            &mut transaction,
119            Some((self.handle.object_id(), ObjectDescriptor::File)),
120            (&profile_dir, &self.name),
121            0,
122            false,
123            Timestamp::now(),
124        )
125        .await?;
126        transaction.commit().await?;
127
128        if old_id != INVALID_OBJECT_ID {
129            fs.graveyard().queue_tombstone_object(store.store_object_id(), old_id);
130        }
131
132        Ok(())
133    }
134}
135
136impl RecordingHandle for FileRecordingHandle {
137    fn append<'a>(
138        &'a self,
139        buf: storage_device::buffer::BufferRef<'a>,
140    ) -> futures::future::BoxFuture<'a, Result<u64, Error>> {
141        async move { self.handle.write_or_append(None, buf).await.map_err(Into::into) }.boxed()
142    }
143
144    fn allocate_buffer(
145        &self,
146        size: usize,
147    ) -> futures::future::BoxFuture<'_, storage_device::buffer::Buffer<'_>> {
148        self.handle.allocate_buffer(size).boxed()
149    }
150
151    fn block_size(&self) -> usize {
152        self.handle.block_size() as usize
153    }
154
155    fn commit(self: Box<Self>) -> futures::future::BoxFuture<'static, Result<(), Error>> {
156        async move {
157            let store = self.volume.store();
158            self.commit_impl().await.inspect_err(|_| {
159                store
160                    .filesystem()
161                    .graveyard()
162                    .queue_tombstone_object(store.store_object_id(), self.handle.object_id());
163            })
164        }
165        .boxed()
166    }
167
168    fn abort_cleanup(self: Box<Self>) {
169        let this = *self;
170        this.volume
171            .store()
172            .filesystem()
173            .graveyard()
174            .queue_tombstone_object(this.volume.store().store_object_id(), this.handle.object_id());
175    }
176}
177
178trait RecordedVolume: Send + Sync + Sized + Unpin {
179    type IdType: std::fmt::Display + Ord + Send + Sized;
180    type NodeType: PagerBacked;
181    type MessageType: Message<IdType = Self::IdType>;
182
183    fn new(volume: Arc<FxVolume>) -> Self;
184
185    fn open(
186        &self,
187        id: Self::IdType,
188    ) -> impl std::future::Future<Output = Result<OpenedNode<Self::NodeType>, Error>> + Send;
189
190    /// Filters out open markers for files that may not be usable in the profile.
191    fn file_is_replayable(
192        &self,
193        id: &Self::IdType,
194    ) -> impl std::future::Future<Output = bool> + Send;
195
196    fn read_and_queue(
197        &self,
198        handle: Box<dyn ReadObjectHandle>,
199        sender: &async_channel::Sender<Request<Self::NodeType>>,
200        local_cache: &mut BTreeMap<Self::IdType, Option<OpenedNode<Self::NodeType>>>,
201    ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
202        async move {
203            let mut io_buf = handle.allocate_buffer(IO_SIZE).await;
204            let block_size = handle.block_size() as usize;
205            let file_size = handle.get_size() as usize;
206            let mut offset = 0;
207            while offset < file_size {
208                let actual = handle
209                    .read(offset as u64, io_buf.as_mut())
210                    .await
211                    .map_err(|e| e.context(format!("Failed to read at offset: {}", offset)))?;
212                offset += actual;
213                let mut local_offset = 0;
214                let mut next_block = block_size;
215                let mut next_offset = size_of::<Self::MessageType>();
216                while next_offset <= actual {
217                    let msg = Self::MessageType::decode_from(
218                        &io_buf.as_slice()[local_offset..next_offset],
219                    );
220
221                    local_offset = next_offset;
222                    next_offset = local_offset + size_of::<Self::MessageType>();
223                    // Messages don't overlap block boundaries.
224                    if next_offset > next_block {
225                        local_offset = next_block;
226                        next_offset = local_offset + size_of::<Self::MessageType>();
227                        next_block += block_size;
228                    }
229
230                    // Ignore trailing zeroes. This is technically a valid entry but extremely
231                    // unlikely and will only break an optimization.
232                    if msg.is_zeroes() {
233                        break;
234                    }
235
236                    let file = match local_cache.entry(msg.id()) {
237                        Entry::Occupied(entry) => match entry.get() {
238                            Some(opened_file) => (*opened_file).clone(),
239                            // Found a cached error.
240                            None => continue,
241                        },
242                        Entry::Vacant(entry) => match self.open(msg.id()).await {
243                            Err(e) => {
244                                debug!("Failed to open object {} from profile: {:?}", msg.id(), e);
245                                // Cache the error.
246                                entry.insert(None);
247                                continue;
248                            }
249                            Ok(opened_file) => {
250                                let file_clone = opened_file.clone();
251                                entry.insert(Some(opened_file));
252                                file_clone
253                            }
254                        },
255                    };
256
257                    sender.send(Request { file, offset: msg.offset() }).await?;
258                }
259            }
260            Ok(())
261        }
262    }
263
264    fn record(
265        &self,
266        recording_handle: Box<dyn RecordingHandle>,
267        receiver: async_channel::Receiver<Vec<Self::MessageType>>,
268    ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
269        // Ensure that this gets cleaned up if we cancel or fail anywhere.
270        let recording_handle = scopeguard::guard(recording_handle, |recording_handle| {
271            recording_handle.abort_cleanup();
272        });
273
274        async move {
275            let mut recorded_offsets = LinkedHashMap::<Self::MessageType, ()>::new();
276            let mut recorded_opens = BTreeMap::<Self::IdType, bool>::new();
277            while let Ok(buffer) = receiver.recv().await {
278                for message in buffer {
279                    if message.is_open_marker() {
280                        if let Entry::Vacant(entry) = recorded_opens.entry(message.id()) {
281                            let usable = self.file_is_replayable(entry.key()).await;
282                            entry.insert(usable);
283                        }
284                    } else {
285                        recorded_offsets.insert(message, ());
286                    }
287                }
288            }
289
290            let block_size = recording_handle.block_size();
291            let mut offset = 0;
292            let mut io_buf = recording_handle.allocate_buffer(IO_SIZE).await;
293            let mut next_block = block_size;
294            while let Some((message, _)) = recorded_offsets.pop_front() {
295                // If a file opening was never recorded, or it is not usable drop the message.
296                if !recorded_opens.get(&message.id()).copied().unwrap_or(false) {
297                    continue;
298                }
299
300                let mut next_offset = offset + size_of::<Self::MessageType>();
301                if next_offset > next_block {
302                    // Zero the remainder of the block. Stopping on block boundaries allows us to
303                    // resize the I/O without supporting reading/writing half messages to a buffer.
304                    io_buf.as_mut_slice()[offset..next_block].fill(0);
305                    if next_block >= IO_SIZE {
306                        recording_handle
307                            .append(io_buf.as_ref())
308                            .await
309                            .context("Failed to write profile block")?;
310                        offset = 0;
311                        next_offset = size_of::<Self::MessageType>();
312                        next_block = block_size;
313                    } else {
314                        offset = next_block;
315                        next_offset = offset + size_of::<Self::MessageType>();
316                        next_block += block_size;
317                    }
318                }
319                message.encode_to(&mut io_buf.as_mut_slice()[offset..next_offset]);
320                offset = next_offset;
321            }
322            if offset > 0 {
323                io_buf.as_mut_slice()[offset..next_block].fill(0);
324                recording_handle
325                    .append(io_buf.subslice(0..next_block))
326                    .await
327                    .context("Failed to write profile block")?;
328            }
329
330            std::mem::drop(io_buf);
331            // Defuse the cleanup.
332            let recording_handle = ScopeGuard::into_inner(recording_handle);
333            recording_handle.commit().await?;
334
335            Ok(())
336        }
337    }
338}
339
340struct BlobVolume {
341    volume: Arc<FxVolume>,
342    // Cache the open blob directory here. The Mutex is just to make this Send, but it is not
343    // actually used concurrently.
344    root_dir: Mutex<Option<Arc<BlobDirectory>>>,
345}
346
347impl RecordedVolume for BlobVolume {
348    type IdType = Hash;
349    type NodeType = FxBlob;
350    type MessageType = BlobMessage;
351
352    fn new(volume: Arc<FxVolume>) -> Self {
353        Self { volume, root_dir: Mutex::new(None) }
354    }
355
356    async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
357        let mut root_dir = self.root_dir.lock().await;
358        if root_dir.is_none() {
359            *root_dir = Some(
360                self.volume
361                    .get_or_load_node(
362                        self.volume.store().root_directory_object_id(),
363                        ObjectDescriptor::Directory,
364                        None,
365                    )
366                    .await?
367                    .into_any()
368                    .downcast::<BlobDirectory>()
369                    .map_err(|_| FxfsError::Inconsistent)?,
370            );
371        };
372        root_dir
373            .as_ref()
374            .unwrap()
375            .open_blob(&id.into())
376            .await?
377            .ok_or_else(|| FxfsError::NotFound.into())
378    }
379
380    async fn file_is_replayable(&self, _id: &Self::IdType) -> bool {
381        // There is nothing is filter out in blob volumes.
382        true
383    }
384}
385
386struct FileVolume {
387    volume: Arc<FxVolume>,
388}
389
390impl RecordedVolume for FileVolume {
391    type IdType = u64;
392    type NodeType = FxFile;
393    type MessageType = FileMessage;
394
395    fn new(volume: Arc<FxVolume>) -> Self {
396        Self { volume }
397    }
398
399    async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
400        self.volume
401            .get_or_load_node(id, ObjectDescriptor::File, None)
402            .await?
403            .into_any()
404            .downcast::<FxFile>()
405            .map_err(|_| anyhow!("Non-file opened"))?
406            .into_opened_node()
407            .ok_or_else(|| anyhow!("File being purged"))
408    }
409
410    async fn file_is_replayable(&self, id: &Self::IdType) -> bool {
411        match self.volume.store().get_keys(*id).await {
412            // If any keys are not the volume key id, then the file may not be readable later.
413            // If there's more than one, then at least one is not the volume key.
414            Ok(keys)
415                if keys.is_empty()
416                    || (keys.len() == 1 && keys.first().unwrap().0 == VOLUME_DATA_KEY_ID) =>
417            {
418                true
419            }
420            _ => false,
421        }
422    }
423}
424
425trait Message: Eq + PartialEq + Sized + Send + Sync + std::hash::Hash + 'static {
426    type IdType: std::fmt::Display + Ord + Send + Sized;
427
428    fn id(&self) -> Self::IdType;
429    fn offset(&self) -> u64;
430    fn encode_to(&self, dest: &mut [u8]);
431    fn decode_from(src: &[u8]) -> Self;
432    fn is_zeroes(&self) -> bool;
433    fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error>;
434    fn is_open_marker(&self) -> bool;
435}
436
437#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
438struct BlobMessage {
439    id: Hash,
440    // Don't bother with offset+length. The kernel is going split up and align it one way and then
441    // we're going to change it all with read-ahead/read-around.
442    offset: u64,
443}
444
445impl BlobMessage {
446    fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
447        let (first, second) = mut_array_refs![dest, size_of::<Hash>(), size_of::<u64>()];
448        *first = self.id.into();
449        *second = self.offset.to_le_bytes();
450    }
451
452    fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
453        let (first, second) = array_refs!(src, size_of::<Hash>(), size_of::<u64>());
454        Self { id: Hash::from_array(*first), offset: u64::from_le_bytes(*second) }
455    }
456}
457
458impl Message for BlobMessage {
459    type IdType = Hash;
460
461    fn id(&self) -> Self::IdType {
462        self.id
463    }
464
465    fn offset(&self) -> u64 {
466        self.offset
467    }
468
469    fn encode_to(&self, dest: &mut [u8]) {
470        self.encode_to_impl(dest.try_into().unwrap());
471    }
472
473    fn decode_from(src: &[u8]) -> Self {
474        Self::decode_from_impl(src.try_into().unwrap())
475    }
476
477    fn is_zeroes(&self) -> bool {
478        self.id == Hash::from_array([0u8; size_of::<Hash>()]) && self.offset == 0
479    }
480
481    fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
482        match node.into_any().downcast::<FxBlob>() {
483            Ok(blob) => Ok(Self { id: blob.root(), offset }),
484            Err(_) => Err(anyhow!("Cannot record non-blob entry.")),
485        }
486    }
487
488    fn is_open_marker(&self) -> bool {
489        self.offset == FILE_OPEN_MARKER
490    }
491}
492
493#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
494struct FileMessage {
495    id: u64,
496    // Don't bother with offset+length. The kernel is going split up and align it one way and then
497    // we're going to change it all with read-ahead/read-around.
498    offset: u64,
499}
500
501impl FileMessage {
502    fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
503        let (first, second) = mut_array_refs![dest, size_of::<u64>(), size_of::<u64>()];
504        *first = self.id.to_le_bytes();
505        *second = self.offset.to_le_bytes();
506    }
507
508    fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
509        let (first, second) = array_refs!(src, size_of::<u64>(), size_of::<u64>());
510        Self { id: u64::from_le_bytes(*first), offset: u64::from_le_bytes(*second) }
511    }
512}
513
514impl Message for FileMessage {
515    type IdType = u64;
516
517    fn id(&self) -> Self::IdType {
518        self.id
519    }
520
521    fn offset(&self) -> u64 {
522        self.offset
523    }
524
525    fn encode_to(&self, dest: &mut [u8]) {
526        self.encode_to_impl(dest.try_into().unwrap())
527    }
528
529    fn decode_from(src: &[u8]) -> Self {
530        Self::decode_from_impl(src.try_into().unwrap())
531    }
532
533    fn is_zeroes(&self) -> bool {
534        self.id == 0 && self.offset == 0
535    }
536
537    fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
538        match node.into_any().downcast::<FxFile>() {
539            Ok(file) => Ok(Self { id: file.object_id(), offset }),
540            Err(_) => Err(anyhow!("Cannot record non-file entry")),
541        }
542    }
543
544    fn is_open_marker(&self) -> bool {
545        self.offset == FILE_OPEN_MARKER
546    }
547}
548
549/// Takes messages to be written into the current profile. This should be dropped before the
550/// recording is stopped to ensure that all messages have been flushed to the writer thread.
551pub trait Recorder: Send + Sync {
552    /// Record a page in request, for the given identifier and offset.
553    fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error>;
554
555    /// Record file opens to gather what files were actually used during the recording.
556    fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error>;
557}
558
559struct RecorderImpl<T: Message> {
560    sender: async_channel::Sender<Vec<T>>,
561    buffer: Vec<T>,
562}
563
564impl<T: Message> RecorderImpl<T> {
565    fn new(sender: async_channel::Sender<Vec<T>>) -> Self {
566        Self { sender, buffer: Vec::with_capacity(MESSAGE_CHUNK_SIZE) }
567    }
568}
569
570impl<T: Message> Recorder for RecorderImpl<T> {
571    fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error> {
572        self.buffer.push(T::from_node_request(node, offset)?);
573        if self.buffer.len() >= MESSAGE_CHUNK_SIZE {
574            // try_send to avoid async await, we use an unbounded channel anyways so any failure
575            // here should only be if the channel is closed, which is permanent anyways.
576            self.sender.try_send(std::mem::replace(
577                &mut self.buffer,
578                Vec::with_capacity(MESSAGE_CHUNK_SIZE),
579            ))?;
580        }
581        RECORDED.fetch_add(1, Ordering::Relaxed);
582        Ok(())
583    }
584
585    fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error> {
586        self.record(node, FILE_OPEN_MARKER)
587    }
588}
589
590impl<T: Message> Drop for RecorderImpl<T> {
591    fn drop(&mut self) {
592        // Best effort sending what messages have already been queued.
593        if self.buffer.len() > 0 {
594            let buffer = std::mem::take(&mut self.buffer);
595            let _ = self.sender.try_send(buffer);
596        }
597    }
598}
599
600struct Request<P: PagerBacked> {
601    file: Arc<P>,
602    offset: u64,
603}
604
605struct ReplayState<T> {
606    replay_threads: future::Shared<BoxFuture<'static, ()>>,
607    _cache_task: fasync::Task<()>,
608    _phantom: PhantomData<T>,
609}
610
611impl<T: RecordedVolume> ReplayState<T> {
612    fn new(handle: Box<dyn ReadObjectHandle>, volume: Arc<FxVolume>, guard: ActiveGuard) -> Self {
613        let (sender, receiver) = async_channel::unbounded::<Request<T::NodeType>>();
614
615        // Create async_channel. An async thread reads and populates the channel, then N threads
616        // consume it and touch pages.
617        let mut replay_threads = Vec::with_capacity(REPLAY_THREADS);
618        for _ in 0..REPLAY_THREADS {
619            let receiver = receiver.clone();
620            // The replay threads can have references to files so we make sure they have a guard
621            // so that shutdown will wait till they have been joined.
622            let guard = guard.clone();
623            replay_threads.push(fasync::unblock(move || {
624                let _guard = guard;
625                Self::page_in_thread(receiver);
626            }));
627        }
628        let replay_threads = (Box::pin(async {
629            join_all(replay_threads).await;
630        }) as BoxFuture<'static, ()>)
631            .shared();
632
633        let scope = volume.scope().clone();
634        let cache_task = scope
635            .spawn({
636                // The replay threads hold active guards, so we must watch for cancellation.  When
637                // cancelled, we'll drop the sender which will cause the replay threads to drop
638                // their guards, which will allow shutdown to proceed.
639                async move {
640                    let mut task = pin!(
641                        async {
642                            // Hold the items in cache until replay is stopped. Optional as None
643                            // indicates that the file could not be opened, and we want to cache that
644                            // failure.
645                            let mut local_cache: BTreeMap<
646                                T::IdType,
647                                Option<OpenedNode<T::NodeType>>,
648                            > = BTreeMap::new();
649
650                            let volume_id = volume.id();
651
652                            if let Err(error) = T::new(volume)
653                                .read_and_queue(handle, &sender, &mut local_cache)
654                                .await
655                            {
656                                error!(error:?; "Failed to read back profile");
657                            }
658                            sender.close();
659
660                            info!(
661                                "Replay for volume {} opened {} of {} objects.",
662                                volume_id,
663                                local_cache.iter().filter(|(_, e)| e.is_some()).count(),
664                                local_cache.len()
665                            );
666
667                            // Keep the cache alive until dropped.
668                            let () = std::future::pending().await;
669                        }
670                        .fuse()
671                    );
672
673                    select! {
674                        _ = task => {}
675                        _ = guard.on_cancel().fuse() => {}
676                    }
677                }
678            })
679            .into();
680
681        Self { replay_threads, _cache_task: cache_task, _phantom: PhantomData }
682    }
683
684    fn page_in_thread(queue: async_channel::Receiver<Request<T::NodeType>>) {
685        while let Ok(request) = queue.recv_blocking() {
686            let res = request.file.vmo().op_range(
687                zx::VmoOp::PREFETCH,
688                request.offset,
689                zx::system_get_page_size() as u64,
690            );
691            if let Err(e) = res {
692                warn!("Failed to prefetch page: {:?}", e);
693            }
694            // If the volume is shutdown, the sender will be dropped.
695            if queue.sender_count() == 0 {
696                return;
697            }
698        }
699    }
700}
701
702/// Holds the current profile recording and/or replay state, and provides methods for state
703/// transitions.
704#[async_trait]
705pub trait ProfileState: Send + Sync {
706    /// Creates a new recording and returns the `Recorder` object to record to. The recording
707    /// finalizes when the associated `Recorder` is dropped.  Stops any recording currently in
708    /// progress.
709    fn record_new(
710        &mut self,
711        volume: &Arc<FxVolume>,
712        recording_handle: Box<dyn RecordingHandle>,
713    ) -> Box<dyn Recorder>;
714
715    /// Reads given handle to parse a profile and replay it by requesting pages via
716    /// ZX_VMO_OP_PREFETCH in blocking background threads. Stops any replay currently in progress.
717    fn replay_profile(
718        &mut self,
719        handle: Box<dyn ReadObjectHandle>,
720        volume: Arc<FxVolume>,
721        guard: ActiveGuard,
722    );
723
724    /// Waits for replay to finish, but does not drop the cache.  The cache will be dropped when
725    /// the ProfileState impl is dropped.  This is fine to call multiple times.
726    async fn wait_for_replay_to_finish(&mut self);
727
728    /// Waits for the recording to finish.
729    async fn wait_for_recording_to_finish(&mut self);
730}
731
732pub fn new_profile_state(is_blob: bool) -> Box<dyn ProfileState> {
733    if is_blob {
734        Box::new(ProfileStateImpl::<BlobVolume>::new())
735    } else {
736        Box::new(ProfileStateImpl::<FileVolume>::new())
737    }
738}
739
740struct ProfileStateImpl<T> {
741    recording: Option<fasync::CancelableJoinHandle<()>>,
742    replay: Option<ReplayState<T>>,
743}
744
745impl<T> ProfileStateImpl<T> {
746    fn new() -> Self {
747        Self { recording: None, replay: None }
748    }
749}
750
751#[async_trait]
752impl<T: RecordedVolume> ProfileState for ProfileStateImpl<T> {
753    fn record_new(
754        &mut self,
755        volume: &Arc<FxVolume>,
756        recording_handle: Box<dyn RecordingHandle>,
757    ) -> Box<dyn Recorder> {
758        let (sender, receiver) = async_channel::unbounded();
759        let volume = volume.clone();
760        // Cancel the previous recording (if any).
761        self.recording = None;
762        let scope = volume.scope().clone();
763        self.recording = Some(
764            scope
765                .spawn(async move {
766                    let recording = T::new(volume);
767                    if let Err(error) = recording.record(recording_handle, receiver).await {
768                        warn!(error:?; "Profile recording failed");
769                    }
770                })
771                .into(),
772        );
773        Box::new(RecorderImpl::new(sender))
774    }
775
776    fn replay_profile(
777        &mut self,
778        handle: Box<dyn ReadObjectHandle>,
779        volume: Arc<FxVolume>,
780        guard: ActiveGuard,
781    ) {
782        self.replay = Some(ReplayState::new(handle, volume, guard));
783    }
784
785    async fn wait_for_replay_to_finish(&mut self) {
786        if let Some(replay) = &mut self.replay {
787            replay.replay_threads.clone().await;
788        }
789    }
790
791    async fn wait_for_recording_to_finish(&mut self) {
792        if let Some(recording) = self.recording.take() {
793            let _ = recording.await;
794        }
795    }
796}
797
798#[cfg(test)]
799mod tests {
800    use super::{
801        BlobMessage, BlobVolume, FileMessage, FileRecordingHandle, FileVolume, IO_SIZE, Message,
802        RecordedVolume, Request, new_profile_state,
803    };
804    use crate::fuchsia::file::FxFile;
805    use crate::fuchsia::fxblob::blob::FxBlob;
806    use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture, open_blob_fixture};
807    use crate::fuchsia::node::{FxNode, OpenedNode};
808    use crate::fuchsia::pager::PagerBacked;
809    use crate::fuchsia::testing::{TestFixture, TestFixtureOptions, open_file_checked};
810    use crate::fuchsia::volume::FxVolume;
811    use anyhow::Error;
812    use async_trait::async_trait;
813    use delivery_blob::CompressionMode;
814    use event_listener::{Event, EventListener};
815    use fidl_fuchsia_io as fio;
816    use fuchsia_async as fasync;
817    use fuchsia_hash::Hash;
818    use fuchsia_sync::Mutex;
819    use fxfs::object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle};
820    use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
821    use fxfs::object_store::{DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectStore};
822    use std::collections::BTreeMap;
823    use std::mem::size_of;
824    use std::sync::Arc;
825    use std::time::Duration;
826    use storage_device::buffer::{BufferRef, MutableBufferRef};
827    use storage_device::buffer_allocator::{BufferAllocator, BufferFuture, BufferSource};
828
829    struct FakeReaderWriterInner {
830        data: Vec<u8>,
831        delays: Vec<EventListener>,
832    }
833
834    struct FakeReaderWriter {
835        allocator: BufferAllocator,
836        inner: Arc<Mutex<FakeReaderWriterInner>>,
837    }
838
839    const BLOCK_SIZE: usize = 4096;
840
841    impl FakeReaderWriter {
842        fn new() -> Self {
843            Self {
844                allocator: BufferAllocator::new(BLOCK_SIZE, BufferSource::new(IO_SIZE * 2)),
845                inner: Arc::new(Mutex::new(FakeReaderWriterInner {
846                    data: Vec::new(),
847                    delays: Vec::new(),
848                })),
849            }
850        }
851
852        fn push_delay(&self, delay: EventListener) {
853            self.inner.lock().delays.insert(0, delay);
854        }
855    }
856
857    impl ObjectHandle for FakeReaderWriter {
858        fn object_id(&self) -> u64 {
859            0
860        }
861
862        fn block_size(&self) -> u64 {
863            self.allocator.block_size() as u64
864        }
865
866        fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
867            self.allocator.allocate_buffer(size)
868        }
869    }
870
871    impl WriteObjectHandle for FakeReaderWriter {
872        async fn write_or_append(
873            &self,
874            offset: Option<u64>,
875            buf: BufferRef<'_>,
876        ) -> Result<u64, Error> {
877            // We only append for now.
878            assert!(offset.is_none());
879            let delay = self.inner.lock().delays.pop();
880            if let Some(delay) = delay {
881                delay.await;
882            }
883            // This relocking has a TOCTOU flavour, but it shouldn't matter for this application.
884            self.inner.lock().data.extend_from_slice(buf.as_slice());
885            Ok(buf.len() as u64)
886        }
887
888        async fn truncate(&self, _size: u64) -> Result<(), Error> {
889            unreachable!();
890        }
891
892        async fn flush(&self) -> Result<(), Error> {
893            unreachable!();
894        }
895    }
896
897    async fn write_file(fixture: &TestFixture, name: &str, data: &[u8]) -> u64 {
898        let root_dir = fixture.volume().root_dir();
899        let mut transaction = fixture
900            .volume()
901            .volume()
902            .store()
903            .new_transaction(
904                lock_keys![LockKey::object(
905                    fixture.volume().volume().store().store_object_id(),
906                    root_dir.object_id()
907                )],
908                Options::default(),
909            )
910            .await
911            .expect("Creating transaction for new file");
912        let id = root_dir
913            .directory()
914            .create_child_file(&mut transaction, name)
915            .await
916            .expect("Creating new_file")
917            .object_id();
918        transaction.commit().await.unwrap();
919        let file = open_file_checked(
920            fixture.root(),
921            name,
922            fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_FILE,
923            &Default::default(),
924        )
925        .await;
926        file.write(data).await.unwrap().expect("Writing file");
927        id
928    }
929
930    #[async_trait]
931    impl ReadObjectHandle for FakeReaderWriter {
932        async fn read(&self, offset: u64, mut buf: MutableBufferRef<'_>) -> Result<usize, Error> {
933            let delay = self.inner.lock().delays.pop();
934            if let Some(delay) = delay {
935                delay.await;
936            }
937            // This relocking has a TOCTOU flavour, but it shouldn't matter for this application.
938            let inner = self.inner.lock();
939            assert!(offset as usize <= inner.data.len());
940            let offset_end = std::cmp::min(offset as usize + buf.len(), inner.data.len());
941            let size = offset_end - offset as usize;
942            buf.as_mut_slice()[..size].clone_from_slice(&inner.data[offset as usize..offset_end]);
943            Ok(size)
944        }
945
946        fn get_size(&self) -> u64 {
947            self.inner.lock().data.len() as u64
948        }
949    }
950
951    #[fuchsia::test]
952    async fn test_encode_decode_blob() {
953        let mut buf = [0u8; size_of::<BlobMessage>()];
954        let m = BlobMessage { id: [88u8; 32].into(), offset: 77 };
955        m.encode_to(&mut buf.as_mut_slice());
956        let m2 = BlobMessage::decode_from(&buf);
957        assert_eq!(m, m2);
958    }
959
960    #[fuchsia::test]
961    async fn test_encode_decode_file() {
962        let mut buf = [0u8; size_of::<FileMessage>()];
963        let m = FileMessage { id: 88, offset: 77 };
964        m.encode_to(&mut buf.as_mut_slice());
965        let m2 = FileMessage::decode_from(&buf);
966        assert!(!m2.is_zeroes());
967        assert_eq!(m, m2);
968    }
969
970    const TEST_PROFILE_NAME: &str = "test_profile";
971
972    async fn get_test_profile_handle(volume: &Arc<FxVolume>) -> DataObjectHandle<FxVolume> {
973        let profile_dir = volume.get_profile_directory().await.unwrap();
974        ObjectStore::open_object(
975            volume,
976            profile_dir
977                .lookup(TEST_PROFILE_NAME)
978                .await
979                .expect("lookup failed")
980                .expect("not found")
981                .0,
982            HandleOptions::default(),
983            None,
984        )
985        .await
986        .unwrap()
987    }
988
989    async fn get_test_profile_contents(volume: &Arc<FxVolume>) -> Vec<u8> {
990        get_test_profile_handle(volume).await.contents(1024 * 1024).await.unwrap().to_vec()
991    }
992
993    #[fuchsia::test]
994    async fn test_recording_basic_blob() {
995        let fixture = new_blob_fixture().await;
996        {
997            let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
998            let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
999
1000            let mut state = new_profile_state(true);
1001            let volume = fixture.volume().volume();
1002
1003            {
1004                // Drop recorder when finished writing to flush data.
1005                let handle =
1006                    FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1007                let mut recorder = state.record_new(volume, Box::new(handle));
1008                recorder.record(blob.clone(), 0).unwrap();
1009                recorder.record_open(blob).unwrap();
1010            }
1011
1012            state.wait_for_recording_to_finish().await;
1013
1014            assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1015        }
1016        fixture.close().await;
1017    }
1018
1019    #[fuchsia::test]
1020    async fn test_recording_basic_file() {
1021        let fixture = TestFixture::new().await;
1022        {
1023            let id = write_file(&fixture, "foo", &[88u8]).await;
1024            let node = fixture
1025                .volume()
1026                .volume()
1027                .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1028                .await
1029                .unwrap();
1030
1031            let mut state = new_profile_state(false);
1032            let volume = fixture.volume().volume();
1033
1034            {
1035                // Drop recorder when finished writing to flush data.
1036                let handle =
1037                    FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1038                let mut recorder = state.record_new(volume, Box::new(handle));
1039                recorder.record(node.clone(), 0).unwrap();
1040                recorder.record_open(node).unwrap();
1041            }
1042            state.wait_for_recording_to_finish().await;
1043
1044            assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1045        }
1046        fixture.close().await;
1047    }
1048
1049    #[fuchsia::test]
1050    async fn test_recording_filtered_without_open() {
1051        let fixture = new_blob_fixture().await;
1052        {
1053            let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1054            let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1055
1056            let mut state = new_profile_state(true);
1057            let volume = fixture.volume().volume();
1058
1059            {
1060                // Drop recorder when finished writing to flush data.
1061                let handle =
1062                    FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1063                let mut recorder = state.record_new(volume, Box::new(handle));
1064                recorder.record(blob.clone(), 0).unwrap();
1065            }
1066            state.wait_for_recording_to_finish().await;
1067
1068            assert_eq!(get_test_profile_contents(volume).await.len(), 0);
1069        }
1070        fixture.close().await;
1071    }
1072
1073    #[fuchsia::test]
1074    async fn test_recording_blob_more_than_block() {
1075        let mut state = new_profile_state(true);
1076
1077        let fixture = new_blob_fixture().await;
1078        assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1079        let message_count = (fixture.fs().block_size() as usize / size_of::<BlobMessage>()) + 1;
1080        let hash;
1081        let volume = fixture.volume().volume();
1082
1083        {
1084            hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1085            let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1086            // Drop recorder when finished writing to flush data.
1087            let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1088            let mut recorder = state.record_new(volume, Box::new(handle));
1089            recorder.record_open(blob.clone()).unwrap();
1090            for i in 0..message_count {
1091                recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1092            }
1093        }
1094        state.wait_for_recording_to_finish().await;
1095
1096        assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1097
1098        let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1099        let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1100
1101        let volume = fixture.volume().volume().clone();
1102        let task = fasync::Task::spawn(async move {
1103            let handle = Box::new(get_test_profile_handle(&volume).await);
1104            let blob = BlobVolume::new(volume);
1105            blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1106        });
1107
1108        let mut recv_count = 0;
1109        while let Ok(msg) = receiver.recv().await {
1110            assert_eq!(msg.file.root(), hash);
1111            assert_eq!(msg.offset, 4096 * recv_count);
1112            recv_count += 1;
1113        }
1114        task.await;
1115        assert_eq!(recv_count, message_count as u64);
1116
1117        fixture.close().await;
1118    }
1119
1120    #[fuchsia::test]
1121    async fn test_recording_file_more_than_block() {
1122        let mut state = new_profile_state(false);
1123
1124        let fixture = TestFixture::new().await;
1125        assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1126        let message_count = (fixture.fs().block_size() as usize / size_of::<FileMessage>()) + 1;
1127        let id;
1128        let volume = fixture.volume().volume();
1129        {
1130            id = write_file(&fixture, "foo", &[88u8]).await;
1131            let node = volume
1132                .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1133                .await
1134                .unwrap();
1135            // Drop recorder when finished writing to flush data.
1136            let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1137            let mut recorder = state.record_new(volume, Box::new(handle));
1138            recorder.record_open(node.clone()).unwrap();
1139            for i in 0..message_count {
1140                recorder.record(node.clone(), 4096 * i as u64).unwrap();
1141            }
1142        }
1143        state.wait_for_recording_to_finish().await;
1144
1145        assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1146
1147        let mut local_cache: BTreeMap<u64, Option<OpenedNode<FxFile>>> = BTreeMap::new();
1148        let (sender, receiver) = async_channel::unbounded::<Request<FxFile>>();
1149
1150        let volume = fixture.volume().volume().clone();
1151        let task = fasync::Task::spawn(async move {
1152            let handle = Box::new(get_test_profile_handle(&volume).await);
1153            let file = FileVolume::new(volume);
1154            file.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1155        });
1156
1157        let mut recv_count = 0;
1158        while let Ok(msg) = receiver.recv().await {
1159            assert_eq!(msg.file.object_id(), id);
1160            assert_eq!(msg.offset, 4096 * recv_count);
1161            recv_count += 1;
1162        }
1163        task.await;
1164        assert_eq!(recv_count, message_count as u64);
1165
1166        fixture.close().await;
1167    }
1168
1169    #[fuchsia::test]
1170    async fn test_recording_more_than_io_size() {
1171        let fixture = new_blob_fixture().await;
1172
1173        {
1174            let mut state = new_profile_state(true);
1175            let message_count = (IO_SIZE as usize / size_of::<BlobMessage>()) + 1;
1176            let hash;
1177            let volume = fixture.volume().volume();
1178            {
1179                hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1180                let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1181                // Drop recorder when finished writing to flush data.
1182                let handle =
1183                    FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1184                let mut recorder = state.record_new(volume, Box::new(handle));
1185                recorder.record_open(blob.clone()).unwrap();
1186                for i in 0..message_count {
1187                    recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1188                }
1189            }
1190            state.wait_for_recording_to_finish().await;
1191            assert_eq!(get_test_profile_contents(volume).await.len(), IO_SIZE + BLOCK_SIZE);
1192
1193            let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1194            let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1195
1196            let volume = volume.clone();
1197            let task = fasync::Task::spawn(async move {
1198                let handle = Box::new(get_test_profile_handle(&volume).await);
1199                let blob = BlobVolume::new(volume);
1200                blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1201            });
1202
1203            let mut recv_count = 0;
1204            while let Ok(msg) = receiver.recv().await {
1205                assert_eq!(msg.file.root(), hash);
1206                assert_eq!(msg.offset, 4096 * recv_count);
1207                recv_count += 1;
1208            }
1209            task.await;
1210            assert_eq!(recv_count, message_count as u64);
1211        }
1212
1213        fixture.close().await;
1214    }
1215
1216    #[fuchsia::test]
1217    async fn test_replay_profile_blob() {
1218        // Create all the files that we need first, then restart the filesystem to clear cache.
1219        let mut state = new_profile_state(true);
1220
1221        let mut hashes = Vec::new();
1222
1223        let fixture = new_blob_fixture().await;
1224        {
1225            assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1226            let message_count = (fixture.fs().block_size() as usize / size_of::<BlobMessage>()) + 1;
1227
1228            let volume = fixture.volume().volume();
1229            let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1230            let mut recorder = state.record_new(volume, Box::new(handle));
1231            // Page in the zero offsets only to avoid readahead strangeness.
1232            for i in 0..message_count {
1233                let hash =
1234                    fixture.write_blob(i.to_string().as_bytes(), CompressionMode::Never).await;
1235                let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1236                recorder.record_open(blob.clone()).unwrap();
1237                hashes.push(hash);
1238                recorder.record(blob.clone(), 0).unwrap();
1239            }
1240        };
1241        let device = fixture.close().await;
1242        device.ensure_unique();
1243        state.wait_for_recording_to_finish().await;
1244
1245        device.reopen(false);
1246        let fixture = open_blob_fixture(device).await;
1247        {
1248            // Need to get the root vmo to check committed bytes.
1249            // Ensure that nothing is paged in right now.
1250            for hash in &hashes {
1251                let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1252                assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1253            }
1254
1255            let volume = fixture.volume().volume();
1256            state.replay_profile(
1257                Box::new(get_test_profile_handle(volume).await),
1258                volume.clone(),
1259                volume.scope().try_active_guard().unwrap(),
1260            );
1261
1262            // Await all data being played back by checking that things have paged in.
1263            for hash in &hashes {
1264                let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1265                while blob.vmo().info().unwrap().committed_bytes == 0 {
1266                    fasync::Timer::new(Duration::from_millis(25)).await;
1267                }
1268            }
1269        }
1270        fixture.close().await;
1271    }
1272
1273    #[fuchsia::test]
1274    async fn test_replay_profile_file() {
1275        // Create all the files that we need first, then restart the filesystem to clear cache.
1276        let mut state = new_profile_state(false);
1277
1278        let mut ids = Vec::new();
1279
1280        let fixture = TestFixture::new().await;
1281        {
1282            assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1283            let message_count = (fixture.fs().block_size() as usize / size_of::<FileMessage>()) + 1;
1284
1285            let volume = fixture.volume().volume();
1286            let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1287            let mut recorder = state.record_new(volume, Box::new(handle));
1288            // Page in the zero offsets only to avoid readahead strangeness.
1289            for i in 0..message_count {
1290                let id = write_file(&fixture, &i.to_string(), &[88u8]).await;
1291                let node = fixture
1292                    .volume()
1293                    .volume()
1294                    .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1295                    .await
1296                    .unwrap();
1297                recorder.record_open(node.clone()).unwrap();
1298                ids.push(id);
1299                recorder.record(node.clone(), 0).unwrap();
1300            }
1301        };
1302        let device = fixture.close().await;
1303        device.ensure_unique();
1304        state.wait_for_recording_to_finish().await;
1305
1306        device.reopen(false);
1307        let fixture = TestFixture::open(
1308            device,
1309            TestFixtureOptions { encrypted: true, format: false, ..Default::default() },
1310        )
1311        .await;
1312        {
1313            // Ensure that nothing is paged in right now.
1314            for id in &ids {
1315                let file = fixture
1316                    .volume()
1317                    .volume()
1318                    .get_or_load_node(
1319                        *id,
1320                        ObjectDescriptor::File,
1321                        Some(fixture.volume().root_dir()),
1322                    )
1323                    .await
1324                    .unwrap()
1325                    .into_any()
1326                    .downcast::<FxFile>()
1327                    .unwrap();
1328                assert_eq!(file.vmo().info().unwrap().committed_bytes, 0);
1329            }
1330
1331            let volume = fixture.volume().volume();
1332            state.replay_profile(
1333                Box::new(get_test_profile_handle(volume).await),
1334                volume.clone(),
1335                volume.scope().try_active_guard().unwrap(),
1336            );
1337
1338            // Await all data being played back by checking that things have paged in.
1339            for id in &ids {
1340                let file = fixture
1341                    .volume()
1342                    .volume()
1343                    .get_or_load_node(
1344                        *id,
1345                        ObjectDescriptor::File,
1346                        Some(fixture.volume().root_dir()),
1347                    )
1348                    .await
1349                    .unwrap()
1350                    .into_any()
1351                    .downcast::<FxFile>()
1352                    .unwrap();
1353                while file.vmo().info().unwrap().committed_bytes == 0 {
1354                    fasync::Timer::new(Duration::from_millis(25)).await;
1355                }
1356            }
1357            state.wait_for_recording_to_finish().await;
1358        }
1359        fixture.close().await;
1360    }
1361
1362    #[fuchsia::test]
1363    async fn test_recording_during_replay() {
1364        let mut state = new_profile_state(true);
1365
1366        let hash;
1367        let first_recording;
1368        let fixture = new_blob_fixture().await;
1369        let volume = fixture.volume().volume();
1370
1371        // First make a simple recording.
1372        {
1373            let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1374            let mut recorder = state.record_new(volume, Box::new(handle));
1375            hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1376            let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1377            recorder.record_open(blob.clone()).unwrap();
1378            recorder.record(blob.clone(), 0).unwrap();
1379        }
1380
1381        state.wait_for_recording_to_finish().await;
1382        first_recording = get_test_profile_contents(volume).await;
1383        assert_ne!(first_recording.len(), 0);
1384        let device = fixture.close().await;
1385        device.ensure_unique();
1386
1387        device.reopen(false);
1388        let fixture = open_blob_fixture(device).await;
1389
1390        {
1391            // Need to get the root vmo to check committed bytes.
1392            // Ensure that nothing is paged in right now.
1393            let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1394            assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1395
1396            // Start recording
1397            let volume = fixture.volume().volume();
1398            let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1399            let mut recorder = state.record_new(volume, Box::new(handle));
1400            recorder.record(blob.clone(), 4096).unwrap();
1401
1402            // Replay the original recording.
1403            let volume = fixture.volume().volume();
1404            state.replay_profile(
1405                Box::new(get_test_profile_handle(volume).await),
1406                volume.clone(),
1407                volume.scope().try_active_guard().unwrap(),
1408            );
1409
1410            // Await all data being played back by checking that things have paged in.
1411            {
1412                let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1413                while blob.vmo().info().unwrap().committed_bytes == 0 {
1414                    fasync::Timer::new(Duration::from_millis(25)).await;
1415                }
1416            }
1417
1418            // Record the open after the replay. Needs both the before and after action to
1419            // capture anything ensuring that the two procedures overlapped.
1420            recorder.record_open(blob.clone()).unwrap();
1421        }
1422
1423        state.wait_for_recording_to_finish().await;
1424
1425        let volume = fixture.volume().volume();
1426        let second_recording = get_test_profile_contents(volume).await;
1427        assert_ne!(second_recording.len(), 0);
1428        assert_ne!(&second_recording, &first_recording);
1429
1430        fixture.close().await;
1431    }
1432
1433    // Doesn't ensure that anything reads back properly, just that everything shuts down when
1434    // stopped early.
1435    #[fuchsia::test]
1436    async fn test_replay_profile_stop_reading_early() {
1437        let mut state = new_profile_state(true);
1438        let fixture = new_blob_fixture().await;
1439
1440        {
1441            let volume = fixture.volume().volume();
1442
1443            // Create the file that we need first.
1444            let message;
1445            {
1446                let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1447                let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1448                message = BlobMessage { id: blob.root(), offset: 0 };
1449            }
1450            state.wait_for_recording_to_finish().await;
1451
1452            // Make a profile long enough to require 2 reads.
1453            let replay_handle = Box::new(FakeReaderWriter::new());
1454            let mut buff = vec![0u8; IO_SIZE * 2];
1455            message.encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1456            message.encode_to_impl(
1457                (&mut buff[IO_SIZE..IO_SIZE + size_of::<BlobMessage>()]).try_into().unwrap(),
1458            );
1459
1460            replay_handle.inner.lock().data = buff;
1461            let delay1 = Event::new();
1462            replay_handle.push_delay(delay1.listen());
1463            let delay2 = Event::new();
1464            replay_handle.push_delay(delay2.listen());
1465
1466            state.replay_profile(
1467                replay_handle,
1468                volume.clone(),
1469                volume.scope().try_active_guard().unwrap(),
1470            );
1471
1472            // Delay the first read long enough so that the stop can be triggered during it.
1473            fasync::Task::spawn(async move {
1474                // Let the profiler wait on this a little.
1475                fasync::Timer::new(Duration::from_millis(100)).await;
1476                delay1.notify(usize::MAX);
1477            })
1478            .detach();
1479        }
1480
1481        // The reader should block indefinitely (we never notify delay2), but that shouldn't block
1482        // termination.
1483        fixture.close().await;
1484    }
1485
1486    #[fuchsia::test]
1487    async fn test_replay_blob_missing() {
1488        let fixture = new_blob_fixture().await;
1489        // Create the blob that comes after the missing blob. Ensure it still gets
1490        // recorded.
1491        let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1492        let mut buff = vec![0u8; IO_SIZE];
1493        {
1494            // First encode the blob that is missing. Just make it up. This will be skipped during
1495            // replay.
1496            {
1497                let message = BlobMessage { id: [42u8; 32].into(), offset: 0 };
1498                message
1499                    .encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1500            }
1501
1502            // Create the blob that won't be missing and encode that.
1503            {
1504                let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1505                let message = BlobMessage { id: blob.root(), offset: 0 };
1506                message.encode_to_impl(
1507                    (&mut buff[size_of::<BlobMessage>()..(size_of::<BlobMessage>() * 2)])
1508                        .try_into()
1509                        .unwrap(),
1510                );
1511            }
1512        }
1513        let device = fixture.close().await;
1514        device.ensure_unique();
1515
1516        device.reopen(false);
1517        let fixture = open_blob_fixture(device).await;
1518        {
1519            let mut state = new_profile_state(true);
1520            let volume = fixture.volume().volume();
1521
1522            let replay_handle = Box::new(FakeReaderWriter::new());
1523            replay_handle.inner.lock().data = buff;
1524
1525            state.replay_profile(
1526                replay_handle,
1527                volume.clone(),
1528                volume.scope().try_active_guard().unwrap(),
1529            );
1530
1531            // Wait for the replay to populate the page.
1532            let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1533            while blob.vmo().info().unwrap().committed_bytes == 0 {
1534                fasync::Timer::new(Duration::from_millis(25)).await;
1535            }
1536        }
1537        fixture.close().await;
1538    }
1539
1540    #[fuchsia::test]
1541    async fn test_replay_file_missing_or_tombstoned() {
1542        let fixture = TestFixture::new().await;
1543        let mut buff = vec![0u8; IO_SIZE];
1544        // Create the blob that comes after the missing blob. Ensure it still gets
1545        // recorded.
1546        let remaining_file_id;
1547        let tombstoned_file_id;
1548        // First encode the file that is missing.
1549        {
1550            let id = write_file(&fixture, "foo", &[1, 2, 3, 4]).await;
1551            let message = FileMessage { id, offset: 0 };
1552            message.encode_to_impl((&mut buff[0..size_of::<FileMessage>()]).try_into().unwrap());
1553        }
1554        // Remove the file now.
1555        fixture
1556            .root()
1557            .unlink("foo", &fio::UnlinkOptions::default())
1558            .await
1559            .unwrap()
1560            .expect("Unlinking");
1561
1562        // Encode the file that will be tombstoned during replay.
1563        {
1564            tombstoned_file_id = write_file(&fixture, "bar", &[1, 2, 3, 4]).await;
1565            let message = FileMessage { id: tombstoned_file_id, offset: 0 };
1566            message.encode_to_impl(
1567                (&mut buff[size_of::<FileMessage>()..(size_of::<FileMessage>() * 2)])
1568                    .try_into()
1569                    .unwrap(),
1570            );
1571        }
1572
1573        // Encode the file that will remain and be replayed last.
1574        {
1575            remaining_file_id = write_file(&fixture, "baz", &[1, 2, 3, 4]).await;
1576            let message = FileMessage { id: remaining_file_id, offset: 0 };
1577            message.encode_to_impl(
1578                (&mut buff[(size_of::<FileMessage>() * 2)..(size_of::<FileMessage>() * 3)])
1579                    .try_into()
1580                    .unwrap(),
1581            );
1582        }
1583        let device = fixture.close().await;
1584        device.ensure_unique();
1585
1586        device.reopen(false);
1587        let fixture =
1588            TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
1589                .await;
1590        {
1591            // Get a ref to the Arc on the file, then unlink it. Since the open count is zero it
1592            // should get marked for tombstone right away.
1593            let tombstoned_file = fixture
1594                .volume()
1595                .volume()
1596                .get_or_load_node(tombstoned_file_id, ObjectDescriptor::File, None)
1597                .await
1598                .expect("Opening file object")
1599                .into_any()
1600                .downcast::<FxFile>()
1601                .unwrap();
1602            fixture
1603                .root()
1604                .unlink("bar", &fio::UnlinkOptions::default())
1605                .await
1606                .unwrap()
1607                .expect("Unlinking");
1608
1609            let mut state = new_profile_state(false);
1610            let volume = fixture.volume().volume();
1611
1612            let replay_handle = Box::new(FakeReaderWriter::new());
1613            replay_handle.inner.lock().data = buff;
1614
1615            state.replay_profile(
1616                replay_handle,
1617                volume.clone(),
1618                volume.scope().try_active_guard().unwrap(),
1619            );
1620
1621            // Wait for the replay to populate the page.
1622            let remaining_file = fixture
1623                .volume()
1624                .volume()
1625                .get_or_load_node(remaining_file_id, ObjectDescriptor::File, None)
1626                .await
1627                .expect("Opening file object")
1628                .into_any()
1629                .downcast::<FxFile>()
1630                .unwrap();
1631            while remaining_file.vmo().info().unwrap().committed_bytes == 0 {
1632                fasync::Timer::new(Duration::from_millis(25)).await;
1633            }
1634
1635            // The tombstoned file should not have anything committed because it shouldn't be able
1636            // to open.
1637            assert_eq!(tombstoned_file.vmo().info().unwrap().committed_bytes, 0);
1638        }
1639        fixture.close().await;
1640    }
1641
1642    #[fuchsia::test]
1643    async fn test_recording_stopped_by_scope_shutdown() {
1644        let fixture = TestFixture::new().await;
1645        {
1646            let volumes_directory = fixture.volumes_directory();
1647            let mut state = new_profile_state(false);
1648            // The recorder outlives the shutdown here, to show that we stopped the recording due to
1649            // scope shutdown.
1650            let (store_id, _recorder) = {
1651                let volume_and_root = volumes_directory
1652                    .create_and_mount_volume("other_volume", None, false, None)
1653                    .await
1654                    .unwrap();
1655                let volume = volume_and_root.into_volume();
1656
1657                let handle =
1658                    FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1659                (volume.store().store_object_id(), state.record_new(&volume, Box::new(handle)))
1660            };
1661            volumes_directory.lock().await.unmount(store_id).await.expect("Unmounting");
1662            state.wait_for_recording_to_finish().await;
1663
1664            let volume_and_root =
1665                volumes_directory.mount_volume("other_volume", None, false).await.unwrap();
1666            let volume = volume_and_root.into_volume();
1667            let profile_dir = volume.get_profile_directory().await.unwrap();
1668            assert!(profile_dir.lookup(TEST_PROFILE_NAME).await.unwrap().is_none());
1669        }
1670        fixture.close().await;
1671    }
1672}