Skip to main content

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