1use crate::Cache;
6use fidl_fuchsia_storage_block as fblock;
7use fuchsia_async as fasync;
8use fuchsia_hash::Hash;
9use futures::future::{BoxFuture, join_all};
10use std::collections::{HashMap, HashSet, hash_map};
11use std::sync::Arc;
12use storage_profile::{BlobMessage, FileMessage, Message};
13
14const PROFILE_BLOCK_SIZE: usize = 4096;
18
19const REPLAY_THREADS: usize = 2;
22
23struct ReplayRequest {
24 vmo: Arc<zx::Vmo>,
25 offset: u64,
26}
27
28struct ReplayHandle {
29 replay_threads: BoxFuture<'static, ()>,
30 _cache_task: fasync::Task<()>,
34}
35
36impl ReplayHandle {
37 fn start(cache: &Arc<Cache>, replay_vmo: zx::Vmo) -> Result<Self, zx::Status> {
38 let messages =
39 BlobMessage::decode_from_vmo(replay_vmo, PROFILE_BLOCK_SIZE).map_err(|error| {
40 log::warn!(error:?; "Failed to decode replay profile VMO");
41 if error == zx::Status::OUT_OF_RANGE {
42 zx::Status::OUT_OF_RANGE
43 } else {
44 zx::Status::IO_DATA_INTEGRITY
45 }
46 })?;
47
48 let (sender, receiver) = async_channel::unbounded::<ReplayRequest>();
49
50 let mut replay_threads = Vec::with_capacity(REPLAY_THREADS);
53 let page_size = zx::system_get_page_size() as u64;
54 for _ in 0..REPLAY_THREADS {
55 let receiver = receiver.clone();
56 replay_threads.push(fasync::unblock(move || {
57 while let Ok(request) = receiver.recv_blocking() {
58 if let Err(error) =
59 request.vmo.op_range(zx::VmoOp::PREFETCH, request.offset, page_size)
60 {
61 log::warn!(
62 error:?,
63 offset = request.offset;
64 "Failed to prefetch blob page during profile replay"
65 );
66 }
67 if receiver.sender_count() == 0 {
72 return;
73 }
74 }
75 }));
76 }
77 let replay_threads = Box::pin(async move {
78 join_all(replay_threads).await;
79 });
80
81 let cache_clone = cache.clone();
82 let cache_task = cache
83 .scope
84 .spawn(async move {
85 let mut local_cache: HashMap<Hash, Option<Arc<zx::Vmo>>> = HashMap::new();
88 for msg in messages {
89 if msg.is_open_marker() {
90 continue;
91 }
92 let vmo = match local_cache.entry(msg.id) {
93 hash_map::Entry::Occupied(entry) => match entry.get() {
94 Some(vmo) => vmo.clone(),
95 None => continue,
97 },
98 hash_map::Entry::Vacant(entry) => {
99 match cache_clone.open_vmo(&msg.id.into()).await {
100 Ok(vmo) => {
101 let vmo = Arc::new(vmo);
102 entry.insert(Some(vmo.clone()));
103 vmo
104 }
105 Err(error) => {
106 log::debug!(
107 error:?,
108 hash:? = msg.id;
109 "Failed to open blob from profile replay"
110 );
111 entry.insert(None);
113 continue;
114 }
115 }
116 }
117 };
118 if let Err(error) = sender.send(ReplayRequest { vmo, offset: msg.offset }).await
119 {
120 log::warn!(error:?; "Failed to send request to replay thread");
121 break;
122 }
123 }
124 drop(cache_clone);
125 sender.close();
126 let () = std::future::pending().await;
128 })
129 .into();
130
131 Ok(Self { replay_threads, _cache_task: cache_task })
132 }
133}
134
135pub struct ProfileRecording {
140 name: String,
141 cache: Arc<Cache>,
142 recording_proxy: Option<fblock::RecordingProxy>,
143 replay_handle: Option<ReplayHandle>,
144}
145
146impl std::fmt::Debug for ProfileRecording {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 f.debug_struct("ProfileRecording")
149 .field("name", &self.name)
150 .field("has_replay", &self.replay_handle.is_some())
151 .finish_non_exhaustive()
152 }
153}
154
155impl ProfileRecording {
156 pub(crate) async fn start(
157 cache: Arc<Cache>,
158 mapper_session: &fblock::MapperSessionProxy,
159 name: String,
160 ) -> Result<Self, zx::Status> {
161 cache.start_recording()?;
162 let mut this = Self { name, cache, recording_proxy: None, replay_handle: None };
165
166 let (recording_proxy, recording_server) =
167 fidl::endpoints::create_proxy::<fblock::RecordingMarker>();
168 match mapper_session.record_profile(recording_server).await {
169 Ok(Ok(())) => {}
170 Ok(Err(raw_status)) => return Err(zx::Status::err_from_raw(raw_status)),
171 Err(error) => {
172 log::warn!(error:?; "FIDL error calling MapperSession.RecordProfile");
173 return Err(zx::Status::INTERNAL);
174 }
175 }
176 this.recording_proxy = Some(recording_proxy);
177
178 let replay_vmo = match this.cache.mapping_session.get_profile(&this.name).await {
179 Ok(Ok(vmo)) => Some(vmo),
180 Ok(Err(raw_status)) => {
181 let status = zx::Status::err_from_raw(raw_status);
182 if status == zx::Status::NOT_FOUND {
183 None
184 } else {
185 log::warn!(
186 status:?,
187 name = this.name.as_str();
188 "MappingSession.GetProfile failed"
189 );
190 return Err(status);
191 }
192 }
193 Err(error) => {
194 log::warn!(
195 error:?,
196 name = this.name.as_str();
197 "FIDL error calling MappingSession.GetProfile"
198 );
199 return Err(zx::Status::INTERNAL);
200 }
201 };
202
203 if let Some(vmo) = replay_vmo {
204 this.replay_handle = Some(ReplayHandle::start(&this.cache, vmo)?);
205 }
206
207 Ok(this)
208 }
209
210 pub async fn stop(mut self) -> Result<(), zx::Status> {
213 if let Some(replay_handle) = self.replay_handle.take() {
214 replay_handle.replay_threads.await;
215 }
216
217 let recording_proxy = self.recording_proxy.take().ok_or(zx::Status::BAD_STATE)?;
218 let stop_res = recording_proxy.stop().await;
219 let active_recording = self.cache.take_recording().ok_or(zx::Status::BAD_STATE)?;
220
221 let driver_vmo = match stop_res {
222 Ok(Ok(vmo)) => vmo,
223 Ok(Err(raw_status)) => return Err(zx::Status::err_from_raw(raw_status)),
224 Err(error) => {
225 log::warn!(error:?; "FIDL error calling Recording.Stop");
226 return Err(zx::Status::INTERNAL);
227 }
228 };
229
230 let file_messages =
231 FileMessage::decode_from_vmo(driver_vmo, PROFILE_BLOCK_SIZE).map_err(|error| {
232 if error == zx::Status::OUT_OF_RANGE {
233 log::warn!("Driver profile VMO exceeds maximum allowed size");
234 zx::Status::OUT_OF_RANGE
235 } else {
236 log::warn!(error:?; "Failed to decode driver profile VMO");
237 zx::Status::IO_DATA_INTEGRITY
238 }
239 })?;
240
241 let mut seen = HashSet::new();
242 let blob_messages = file_messages
243 .filter_map(|msg| {
244 let &hash = active_recording.hash_by_key.get(&msg.id)?;
245 active_recording
246 .opened_hashes
247 .contains(&hash)
248 .then_some(BlobMessage { id: hash, offset: msg.offset })
249 })
250 .filter(|&blob_msg| seen.insert(blob_msg));
251
252 let recorded_vmo = BlobMessage::encode_to_vmo(blob_messages, PROFILE_BLOCK_SIZE)?;
253
254 match self.cache.mapping_session.write_profile(&self.name, recorded_vmo).await {
255 Ok(Ok(())) => Ok(()),
256 Ok(Err(raw_status)) => {
257 let status = zx::Status::err_from_raw(raw_status);
258 log::warn!(
259 status:?,
260 name = self.name.as_str();
261 "MappingSession.WriteProfile failed"
262 );
263 Err(status)
264 }
265 Err(error) => {
266 log::warn!(
267 error:?,
268 name = self.name.as_str();
269 "FIDL error calling MappingSession.WriteProfile"
270 );
271 Err(zx::Status::INTERNAL)
272 }
273 }
274 }
275}
276
277impl Drop for ProfileRecording {
278 fn drop(&mut self) {
279 self.cache.abort_recording();
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::BlobPagerAndVerifier;
287 use fidl_fuchsia_storage_mapping as fmapping;
288 use fuchsia_sync::Mutex;
289 use futures::channel::oneshot;
290 use futures::{StreamExt, TryStreamExt};
291 use mapping::{DeliveryCommand, RawDeliveryCommand};
292 use vmo_fifo::SyncSender;
293
294 #[fuchsia::test]
295 async fn test_profile_recorder_replay_filtering_and_eviction() {
296 let (mapping_proxy, mut mapping_stream) =
297 fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
298 let (mapper_proxy, mut mapper_stream) =
299 fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
300
301 let blob_data = vec![0x42u8; 8192];
302 let (root1, _) = fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
303 let hash1: [u8; 32] = root1.into();
304 let blob_data2 = vec![0x24u8; 8192];
305 let (root2, _) = fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data2);
306 let hash2: [u8; 32] = root2.into();
307 let blob_data3 = vec![0x99u8; 8192];
308 let (root3, _) = fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data3);
309 let hash3: [u8; 32] = root3.into();
310 let missing_hash: [u8; 32] = [0xde; 32];
311
312 let replay_entries = vec![
316 BlobMessage { id: Hash::from(missing_hash), offset: 0 },
317 BlobMessage { id: Hash::from(missing_hash), offset: 4096 },
318 BlobMessage { id: Hash::from(hash1), offset: 0 },
319 BlobMessage { id: Hash::from(hash2), offset: 0 },
320 ];
321 let next_replay_vmo = Arc::new(Mutex::new(Some(
322 BlobMessage::encode_to_vmo(&replay_entries, PROFILE_BLOCK_SIZE)
323 .expect("encode_to_vmo failed"),
324 )));
325 let next_replay_vmo_for_server = next_replay_vmo.clone();
326
327 let (written_tx, written_rx) = oneshot::channel();
328 let (close_tx, mut close_rx) = futures::channel::mpsc::unbounded();
329 let opened_keys = Arc::new(Mutex::new(HashMap::<[u8; 32], Vec<u64>>::new()));
330 let opened_keys_clone = opened_keys.clone();
331
332 let mapping_task = fasync::Task::spawn(async move {
333 let mut written_tx = Some(written_tx);
334 if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
335 mapping_stream.try_next().await.expect("try_next failed")
336 {
337 let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
338 .expect("zx::Vmo::create failed");
339 responder.send(Ok(mapping_vmo)).expect("send failed");
340
341 let mut session_stream = session.into_stream();
342 while let Some(request) = session_stream.try_next().await.expect("try_next failed")
343 {
344 match request {
345 fmapping::MappingSessionRequest::GetProfile { name, responder } => {
346 assert_eq!(name, ".boot");
347 match next_replay_vmo_for_server.lock().take() {
348 Some(vmo) => responder.send(Ok(vmo)).expect("send failed"),
349 None => responder
350 .send(Err(zx::Status::NOT_FOUND.into_raw()))
351 .expect("send failed"),
352 }
353 }
354 fmapping::MappingSessionRequest::WriteProfile {
355 name,
356 profile_vmo,
357 responder,
358 } => {
359 assert_eq!(name, ".boot");
360 if let Some(tx) = written_tx.take() {
361 let _ = tx.send(profile_vmo);
362 }
363 responder.send(Ok(())).expect("send failed");
364 }
365 fmapping::MappingSessionRequest::Open { key, identifier, responder } => {
366 let arr: [u8; 32] =
367 identifier.as_slice().try_into().expect("invalid hash slice");
368 opened_keys_clone.lock().entry(arr).or_default().push(key);
369 if arr == missing_hash {
370 responder
371 .send(Err(zx::Status::NOT_FOUND.into_raw()))
372 .expect("send failed");
373 } else {
374 responder.send(Ok(8192)).expect("send failed");
375 }
376 }
377 fmapping::MappingSessionRequest::Close { key, responder } => {
378 responder.send(Ok(())).expect("send failed");
379 let _ = close_tx.unbounded_send(key);
380 }
381 _ => {}
382 }
383 }
384 }
385 });
386
387 let (tx_delivery, rx_delivery) = oneshot::channel();
388 let (recording_dropped_tx, recording_dropped_rx) = oneshot::channel();
389 let opened_keys_for_mapper = opened_keys.clone();
390 let mapper_task = fasync::Task::spawn(async move {
391 let mut recording_dropped_tx = Some(recording_dropped_tx);
392 if let Some(fblock::MapperRequest::OpenSession {
393 session,
394 port,
395 delivery_queue,
396 responder,
397 ..
398 }) = mapper_stream.try_next().await.expect("try_next failed")
399 {
400 let port = port.expect("expected port");
401 let delivery_vmo = delivery_queue.expect("expected delivery_queue");
402 tx_delivery
403 .send(
404 delivery_vmo
405 .duplicate_handle(zx::Rights::SAME_RIGHTS)
406 .expect("duplicate_handle failed"),
407 )
408 .expect("send failed");
409 responder.send(Ok(())).expect("send failed");
410
411 let opened_keys_for_pager = opened_keys_for_mapper.clone();
414 std::thread::spawn(move || {
415 let mut sender = SyncSender::<RawDeliveryCommand>::new(
416 delivery_vmo,
417 zx::system_get_page_size() as usize,
418 mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
419 )
420 .expect("SyncSender::new failed");
421 while let Ok(packet) = port.wait(zx::MonotonicInstant::INFINITE) {
422 if let zx::PacketContents::Pager(pager_packet) = packet.contents() {
423 if pager_packet.command()
424 == zx::sys::zx_page_request_command_t::ZX_PAGER_VMO_READ
425 {
426 let key = packet.key();
427 let range = pager_packet.range();
428 let reg_payload =
429 sender.reserve_payload(0).expect("reserve_payload failed");
430 let reg_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
431 key,
432 offset: reg_payload.offset(),
433 length: 0,
434 read_alignment: 8192,
435 }
436 .into();
437 reg_payload.commit(reg_cmd).expect("commit failed");
438
439 let data = if opened_keys_for_pager
440 .lock()
441 .get(&hash1)
442 .is_some_and(|ks| ks.contains(&key))
443 {
444 &blob_data
445 } else {
446 &blob_data2
447 };
448 let mut payload = sender
449 .reserve_payload(data.len())
450 .expect("reserve_payload failed");
451 payload.data().copy_from_slice(data);
452 let cmd: RawDeliveryCommand = DeliveryCommand::Data {
453 key,
454 offset: payload.offset(),
455 length: data.len() as u32,
456 target_offset: range.start,
457 }
458 .into();
459 payload.commit(cmd).expect("commit failed");
460 }
461 }
462 }
463 });
464
465 let mut session_stream = session.into_stream();
466 while let Some(request) = session_stream.try_next().await.expect("try_next failed")
467 {
468 if let fblock::MapperSessionRequest::RecordProfile { recording, responder } =
469 request
470 {
471 responder.send(Ok(())).expect("send failed");
472 let mut rec_stream = recording.into_stream();
473 match rec_stream.try_next().await.expect("try_next failed") {
474 Some(fblock::RecordingRequest::Stop { responder }) => {
475 let keys = opened_keys_for_mapper.lock().clone();
476 let k1 = keys.get(&hash1).expect("hash1 keys missing")[0];
477 let k2 = keys.get(&hash2).expect("hash2 keys missing")[0];
478 let k3_first = keys.get(&hash3).expect("hash3 keys missing")[0];
479 let k3_second = keys.get(&hash3).expect("hash3 keys missing")[1];
480 let driver_entries = vec![
481 FileMessage { id: k1, offset: 0 },
482 FileMessage { id: k2, offset: 0 },
483 FileMessage { id: k3_first, offset: 0 },
484 FileMessage { id: k3_second, offset: 0 },
486 FileMessage { id: k3_second, offset: 4096 },
487 ];
488 let vmo =
489 FileMessage::encode_to_vmo(&driver_entries, PROFILE_BLOCK_SIZE)
490 .expect("encode_to_vmo failed");
491 responder.send(Ok(vmo)).expect("send failed");
492 }
493 None => {
494 if let Some(tx) = recording_dropped_tx.take() {
495 let _ = tx.send(());
496 }
497 }
498 _ => {}
499 }
500 }
501 }
502 }
503 });
504
505 let pager_and_verifier = Arc::new(
506 BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
507 .await
508 .expect("BlobPagerAndVerifier::new failed"),
509 );
510 let _delivery_vmo = rx_delivery.await.expect("rx_delivery failed");
511
512 let recording =
513 pager_and_verifier.start_profile(".boot").await.expect("start_profile failed");
514
515 let err = pager_and_verifier
517 .start_profile(".boot")
518 .await
519 .expect_err("Concurrent start_profile succeeded");
520 assert_eq!(err, zx::Status::BAD_STATE);
521
522 let missing_closed_key = close_rx.next().await.expect("expected close for missing_hash");
524 assert_eq!(
525 missing_closed_key,
526 opened_keys.lock().get(&missing_hash).expect("missing_hash key missing")[0]
527 );
528
529 let vmo1 = pager_and_verifier.create_vmo(&hash1).await.expect("create_vmo hash1 failed");
531 let vmo3 = pager_and_verifier.create_vmo(&hash3).await.expect("create_vmo hash3 failed");
532
533 drop(vmo3);
536 let _ = close_rx.next().await.expect("expected close for first vmo3");
537 let vmo3_reopened =
538 pager_and_verifier.create_vmo(&hash3).await.expect("re-open hash3 failed");
539 drop(vmo3_reopened);
540 let _ = close_rx.next().await.expect("expected close for second vmo3");
541
542 recording.stop().await.expect("recording.stop failed");
546 let recorded_vmo = written_rx.await.expect("written_rx failed");
547
548 assert_eq!(
551 opened_keys.lock().get(&missing_hash).expect("missing_hash key missing").len(),
552 1
553 );
554
555 let closed_key = close_rx.next().await.expect("expected close for unpinned replay blob");
557 assert_eq!(closed_key, opened_keys.lock().get(&hash2).expect("hash2 key missing")[0]);
558
559 let decoded: Vec<_> = BlobMessage::decode_from_vmo(recorded_vmo, PROFILE_BLOCK_SIZE)
560 .expect("decode_from_vmo failed")
561 .collect();
562 assert_eq!(
563 decoded,
564 vec![
565 BlobMessage { id: Hash::from(hash1), offset: 0 },
566 BlobMessage { id: Hash::from(hash3), offset: 0 },
567 BlobMessage { id: Hash::from(hash3), offset: 4096 },
568 ]
569 );
570
571 *next_replay_vmo.lock() = Some(
574 BlobMessage::encode_to_vmo(
575 &[BlobMessage { id: Hash::from(hash2), offset: 0 }],
576 PROFILE_BLOCK_SIZE,
577 )
578 .expect("encode_to_vmo failed"),
579 );
580 let recording2 =
581 pager_and_verifier.start_profile(".boot").await.expect("second start_profile failed");
582 drop(recording2);
583 recording_dropped_rx.await.expect("expected driver Recording channel to close on abort");
584
585 drop(vmo1);
586 let _ = close_rx.next().await.expect("expected close for vmo1 or aborted replay");
587 drop(pager_and_verifier);
588 mapping_task.await;
589 mapper_task.await;
590 }
591}