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