1use async_trait::async_trait;
6use block_matcher::{BlockDeviceMatcher, Guid, create_random_guid, find_block_device};
7use fidl::endpoints::{
8 DiscoverableProtocolMarker as _, Proxy, create_proxy, create_request_stream,
9};
10use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_storage_block::BlockMarker;
13use fidl_fuchsia_storage_partitions as fpartitions;
14use fs_management::Fvm;
15use fs_management::filesystem::{
16 BlockConnector, DirBasedBlockConnector, ServingMultiVolumeFilesystem,
17};
18use fs_management::format::constants::{
19 BENCHMARK_FVM_TYPE_GUID, BENCHMARK_FVM_VOLUME_NAME, PAD_RW_PARTITION_LABEL,
20};
21use fuchsia_async as fasync;
22use fuchsia_component::client::{Service, connect_to_protocol, connect_to_protocol_at_dir_root};
23use std::sync::Arc;
24use storage_benchmarks::block_device::BlockDevice;
25use storage_benchmarks::{BlockDeviceConfig, BlockDeviceFactory};
26
27const BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
28const BENCHMARK_FVM_SLICE_SIZE_BYTES: u64 = 8 * 1024 * 1024;
33
34const BENCHMARK_TYPE_GUID: &Guid = &[
38 0x67, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
39];
40const BENCHMARK_VOLUME_NAME: &str = "benchmark";
41
42pub async fn create_fvm_volume(
45 fvm: &fidl_fuchsia_fs_startup::VolumesProxy,
46 instance_guid: [u8; 16],
47 config: &BlockDeviceConfig,
48) -> (fio::DirectoryProxy, Option<fasync::Task<()>>) {
49 loop {
50 let (crypt, crypt_task) = if config.use_zxcrypt {
51 let (crypt, stream) = create_request_stream::<fidl_fuchsia_fxfs::CryptMarker>();
52 let task = fasync::Task::spawn(async {
53 if let Err(err) =
54 zxcrypt_crypt::run_crypt_service(crypt_policy::Policy::Null, stream).await
55 {
56 log::error!(err:?; "Crypt service failure");
57 }
58 });
59 (Some(crypt), Some(task))
60 } else {
61 (None, None)
62 };
63
64 let (volume_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
65 let res = fvm
66 .create(
67 BENCHMARK_VOLUME_NAME,
68 server_end,
69 CreateOptions {
70 initial_size: config.volume_size,
71 type_guid: Some(BENCHMARK_TYPE_GUID.clone()),
72 guid: Some(instance_guid),
73 ..Default::default()
74 },
75 MountOptions { crypt, ..Default::default() },
76 )
77 .await
78 .expect("FIDL error")
79 .map_err(zx::Status::from_raw);
80
81 if res == Err(zx::Status::ALREADY_EXISTS) {
83 fvm.remove(BENCHMARK_VOLUME_NAME)
84 .await
85 .expect("FIDL error")
86 .map_err(zx::Status::from_raw)
87 .expect("Failed to remove volume");
88 continue;
89 }
90
91 res.expect("Failed to create volume");
92 return (volume_dir, crypt_task);
93 }
94}
95
96pub enum BenchmarkVolumeFactory {
100 SystemFvm(
101 Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
102 Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
103 ),
104 SystemGpt(Arc<fpartitions::PartitionServiceProxy>),
105}
106
107struct RawBlockDeviceInGpt(Arc<fpartitions::PartitionServiceProxy>);
108
109impl BlockDevice for RawBlockDeviceInGpt {
110 fn connector(&self) -> Box<dyn BlockConnector> {
111 Box::new(self.0.clone())
112 }
113}
114
115#[async_trait]
116impl BlockDeviceFactory for BenchmarkVolumeFactory {
117 async fn create_block_device(&self, config: &BlockDeviceConfig) -> Box<dyn BlockDevice> {
118 let instance_guid = create_random_guid();
119 match self {
120 Self::SystemFvm(volumes_connector, _) => {
121 let volumes = volumes_connector();
122 Box::new(Self::create_fvm_volume(volumes, instance_guid, config).await)
123 }
124 Self::SystemGpt(partition_service) => {
125 if config.requires_fvm {
126 Box::new(
127 Self::create_fvm_instance_and_volume(
128 partition_service.clone(),
129 instance_guid,
130 config,
131 )
132 .await,
133 )
134 } else {
135 Box::new(RawBlockDeviceInGpt(partition_service.clone()))
136 }
137 }
138 }
139 }
140}
141
142impl BenchmarkVolumeFactory {
143 pub async fn from_config(fxfs_blob: bool) -> BenchmarkVolumeFactory {
146 if fxfs_blob {
147 let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
148 let manager = connect_to_protocol::<fpartitions::PartitionsManagerMarker>().unwrap();
149 let instance =
150 BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager).await;
151 assert!(
152 instance.is_some(),
153 "Failed to open or create testing FVM in GPT. \
154 Perhaps the system doesn't have a GPT-formatted block device?"
155 );
156 instance.unwrap()
157 } else {
158 let volumes_connector = Box::new(move || {
159 connect_to_protocol::<fidl_fuchsia_fs_startup::VolumesMarker>().unwrap()
160 });
161 let volumes_dir_connector = {
162 Box::new(move || {
163 fuchsia_fs::directory::open_in_namespace("volumes", fio::PERM_READABLE).unwrap()
164 })
165 };
166 BenchmarkVolumeFactory::connect_to_system_fvm(volumes_connector, volumes_dir_connector)
167 .unwrap()
168 }
169 }
170
171 pub fn connect_to_system_fvm(
173 volumes_connector: Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
174 volumes_dir_connector: Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
175 ) -> Option<BenchmarkVolumeFactory> {
176 Some(BenchmarkVolumeFactory::SystemFvm(volumes_connector, volumes_dir_connector))
177 }
178
179 pub async fn connect_to_test_partition(
183 service: Service<fpartitions::PartitionServiceMarker>,
184 manager: fpartitions::PartitionsManagerProxy,
185 ) -> Option<BenchmarkVolumeFactory> {
186 let connector = if let Some(connector) = find_block_device(
187 &[
188 BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
189 BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
190 ],
191 service.clone().enumerate().await.expect("Failed to enumerate partitions").into_iter(),
192 )
193 .await
194 .expect("Error while searching for benchmark-fvm")
195 {
196 connector
198 } else if let Some(connector) = find_block_device(
199 &[BlockDeviceMatcher::Name(PAD_RW_PARTITION_LABEL)],
200 service.clone().enumerate().await.expect("Failed to enumerate partitions").into_iter(),
201 )
202 .await
203 .expect("Error while searching for pad_rw")
204 {
205 connector
207 } else {
208 let info =
210 manager.get_block_info().await.expect("FIDL error").expect("get_block_info failed");
211 let transaction = manager
212 .create_transaction()
213 .await
214 .expect("FIDL error")
215 .map_err(zx::Status::from_raw)
216 .expect("create_transaction failed");
217 let request = fpartitions::PartitionsManagerAddPartitionRequest {
218 transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
219 name: Some(BENCHMARK_FVM_VOLUME_NAME.to_string()),
220 type_guid: Some(fidl_fuchsia_storage_block::Guid {
221 value: BENCHMARK_FVM_TYPE_GUID.clone(),
222 }),
223 num_blocks: Some(BENCHMARK_FVM_SIZE_BYTES / info.1 as u64),
224 ..Default::default()
225 };
226 manager
227 .add_partition(request)
228 .await
229 .expect("FIDL error")
230 .map_err(zx::Status::from_raw)
231 .expect("add_partition failed");
232 manager
233 .commit_transaction(transaction)
234 .await
235 .expect("FIDL error")
236 .map_err(zx::Status::from_raw)
237 .expect("add_partition failed");
238 let service_instances =
239 service.enumerate().await.expect("Failed to enumerate partitions");
240 log::info!("len {}", service_instances.len());
241 find_block_device(
242 &[
243 BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
244 BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
245 ],
246 service_instances.into_iter(),
247 )
248 .await
249 .expect("Failed to find block device")?
250 };
251
252 Some(BenchmarkVolumeFactory::SystemGpt(Arc::new(connector)))
253 }
254
255 #[cfg(test)]
256 pub async fn contains_fvm_volume(&self, name: &str) -> bool {
257 match self {
258 Self::SystemFvm(_, volumes_dir_connector) => {
259 let dir = volumes_dir_connector();
260 fuchsia_fs::directory::dir_contains(&dir, name).await.unwrap()
261 }
262 _ => false,
265 }
266 }
267
268 async fn create_fvm_volume(
269 volumes: fidl_fuchsia_fs_startup::VolumesProxy,
270 instance_guid: [u8; 16],
271 config: &BlockDeviceConfig,
272 ) -> FvmVolume {
273 let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
274 let volumes = volumes.into_client_end().unwrap().into_sync_proxy();
275 FvmVolume {
276 destroy_fn: Some(Box::new(move || {
277 volumes
278 .remove(BENCHMARK_VOLUME_NAME, zx::MonotonicInstant::INFINITE)
279 .unwrap()
280 .map_err(zx::Status::from_raw)
281 })),
282 volume_dir: Some(volume_dir),
283 fvm_instance: None,
284 block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
285 crypt_task,
286 }
287 }
288
289 async fn create_fvm_instance_and_volume(
290 partition: Arc<fpartitions::PartitionServiceProxy>,
291 instance_guid: [u8; 16],
292 config: &BlockDeviceConfig,
293 ) -> FvmVolume {
294 let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
295 Box::new(partition),
296 Box::new(Fvm { slice_size: BENCHMARK_FVM_SLICE_SIZE_BYTES, ..Fvm::default() }),
297 );
298 fs.format().await.expect("Failed to format FVM");
299 let fvm_instance = fs.serve_multi_volume().await.expect("Failed to serve FVM");
300 let volumes = connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
301 fvm_instance.exposed_dir(),
302 )
303 .unwrap();
304
305 let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
306 FvmVolume {
307 destroy_fn: None,
308 volume_dir: Some(volume_dir),
309 fvm_instance: Some(fvm_instance),
310 block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
311 crypt_task,
312 }
313 }
314}
315
316pub struct FvmVolume {
318 destroy_fn: Option<Box<dyn Send + Sync + FnOnce() -> Result<(), zx::Status>>>,
319 fvm_instance: Option<ServingMultiVolumeFilesystem>,
320 volume_dir: Option<fio::DirectoryProxy>,
321 crypt_task: Option<fasync::Task<()>>,
322 block_path: String,
324}
325
326impl BlockDevice for FvmVolume {
327 fn connector(&self) -> Box<dyn BlockConnector> {
328 let volume_dir = fuchsia_fs::directory::clone(self.volume_dir.as_ref().unwrap()).unwrap();
329 Box::new(DirBasedBlockConnector::new(volume_dir, self.block_path.clone()))
330 }
331}
332
333impl Drop for FvmVolume {
334 fn drop(&mut self) {
335 self.volume_dir = None;
336 self.fvm_instance = None;
337 self.crypt_task = None;
338 if let Some(destroy_fn) = self.destroy_fn.take() {
339 destroy_fn().expect("Failed to destroy FVM volume");
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use crate::testing::{RAMDISK_FVM_SLICE_SIZE, RamdiskFactory};
348 use block_client::RemoteBlockClient;
349 use fidl_fuchsia_fs_startup::VolumesMarker;
350 use fs_management::Gpt;
351 use ramdevice_client::{RamdiskClient, RamdiskClientBuilder};
352 use std::sync::Arc;
353 use vmo_backed_block_server::VmoBackedServer;
354
355 const BLOCK_SIZE: u64 = 4 * 1024;
356 const BLOCK_COUNT: u64 = 1024;
357 const GPT_BLOCK_COUNT: u64 = 49152;
360
361 #[fuchsia::test]
362 async fn ramdisk_create_block_device_with_zxcrypt() {
363 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
364 let _ = ramdisk_factory
365 .create_block_device(&BlockDeviceConfig {
366 requires_fvm: true,
367 use_zxcrypt: true,
368 volume_size: None,
369 })
370 .await;
371 }
372
373 #[fuchsia::test]
374 async fn ramdisk_create_block_device_without_zxcrypt() {
375 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
376 let _ = ramdisk_factory
377 .create_block_device(&BlockDeviceConfig {
378 requires_fvm: true,
379 use_zxcrypt: false,
380 volume_size: None,
381 })
382 .await;
383 }
384
385 #[fuchsia::test]
386 async fn ramdisk_create_block_device_without_volume_size() {
387 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
388 let ramdisk = ramdisk_factory
389 .create_block_device(&BlockDeviceConfig {
390 requires_fvm: true,
391 use_zxcrypt: false,
392 volume_size: None,
393 })
394 .await;
395 let volume_info = ramdisk
396 .connector()
397 .connect_block()
398 .unwrap()
399 .into_proxy()
400 .get_volume_info()
401 .await
402 .unwrap();
403 zx::ok(volume_info.0).unwrap();
404 let volume_info = volume_info.2.unwrap();
405 assert_eq!(volume_info.partition_slice_count, 1);
406 }
407
408 #[fuchsia::test]
409 async fn ramdisk_create_block_device_with_volume_size() {
410 let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
411 let ramdisk = ramdisk_factory
412 .create_block_device(&BlockDeviceConfig {
413 requires_fvm: false,
414 use_zxcrypt: false,
415 volume_size: Some(RAMDISK_FVM_SLICE_SIZE as u64 * 3),
416 })
417 .await;
418 let volume_info = ramdisk
419 .connector()
420 .connect_block()
421 .unwrap()
422 .into_proxy()
423 .get_volume_info()
424 .await
425 .unwrap();
426 zx::ok(volume_info.0).unwrap();
427 let volume_info = volume_info.2.unwrap();
428 assert_eq!(volume_info.partition_slice_count, 3);
429 }
430
431 async fn init_gpt(block_size: u32, block_count: u64) -> zx::Vmo {
432 let vmo = zx::Vmo::create(block_size as u64 * block_count).unwrap();
433 let server = VmoBackedServer::from_vmo(
434 block_size,
435 vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap(),
436 )
437 .expect("Failed to create VmoBackedServer");
438 let (client, server_end) =
439 fidl::endpoints::create_proxy::<fidl_fuchsia_storage_block::BlockMarker>();
440
441 let _task =
442 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
443 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
444 gpt::Gpt::format(client.clone(), vec![gpt::PartitionInfo::nil(); 128])
445 .await
446 .expect("format failed");
447 vmo
448 }
449
450 struct FvmTestConfig {
451 fxblob_enabled: bool,
452 }
453
454 struct TestState(
459 #[allow(dead_code)] RamdiskClient,
460 #[allow(dead_code)] ServingMultiVolumeFilesystem,
461 );
462
463 async fn initialize(config: FvmTestConfig) -> (TestState, BenchmarkVolumeFactory) {
464 if config.fxblob_enabled {
465 let vmo = init_gpt(BLOCK_SIZE as u32, GPT_BLOCK_COUNT).await;
467 let ramdisk = RamdiskClientBuilder::new_with_vmo(vmo, Some(BLOCK_SIZE))
468 .build()
469 .await
470 .expect("Failed to create ramdisk");
471
472 let gpt = fs_management::filesystem::Filesystem::from_boxed_config(
473 ramdisk.connector().unwrap(),
474 Box::new(Gpt::dynamic_child()),
475 )
476 .serve_multi_volume()
477 .await
478 .expect("Failed to serve GPT");
479 let partitions =
480 Service::open_from_dir(gpt.exposed_dir(), fpartitions::PartitionServiceMarker)
481 .unwrap();
482 let manager = connect_to_protocol_at_dir_root::<fpartitions::PartitionsManagerMarker>(
483 gpt.exposed_dir(),
484 )
485 .unwrap();
486 let fvm = BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager)
487 .await
488 .expect("Failed to connect to FVM");
489 (TestState(ramdisk, gpt), fvm)
490 } else {
491 let ramdisk = RamdiskClientBuilder::new(BLOCK_SIZE, BLOCK_COUNT)
493 .build()
494 .await
495 .expect("Failed to create ramdisk");
496 let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
497 ramdisk.connector().unwrap(),
498 Box::new(Fvm { slice_size: RAMDISK_FVM_SLICE_SIZE, ..Fvm::dynamic_child() }),
499 );
500 fs.format().await.expect("Failed to format FVM");
501 let fvm_component = match fs.serve_multi_volume().await {
502 Ok(fvm_component) => fvm_component,
503 Err(_) => loop {},
504 };
505 let volumes_connector = {
506 let exposed_dir =
507 fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
508 Box::new(move || {
509 connect_to_protocol_at_dir_root::<VolumesMarker>(&exposed_dir).unwrap()
510 })
511 };
512 let volumes_dir_connector = {
513 let exposed_dir =
514 fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
515 Box::new(move || {
516 fuchsia_fs::directory::open_directory_async(
517 &exposed_dir,
518 "volumes",
519 fio::PERM_READABLE,
520 )
521 .unwrap()
522 })
523 };
524 let fvm = BenchmarkVolumeFactory::connect_to_system_fvm(
525 volumes_connector,
526 volumes_dir_connector,
527 );
528 (TestState(ramdisk, fvm_component), fvm.unwrap())
529 }
530 }
531
532 async fn benchmark_volume_factory_can_find_fvm_instance(config: FvmTestConfig) {
533 let (_state, volume_factory) = initialize(config).await;
534
535 volume_factory
537 .create_block_device(&BlockDeviceConfig {
538 requires_fvm: true,
539 use_zxcrypt: false,
540 volume_size: None,
541 })
542 .await;
543 }
544
545 #[fuchsia::test]
546 async fn benchmark_volume_factory_can_find_fvm_instance_fvm() {
547 benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: false })
548 .await;
549 }
550
551 #[fuchsia::test]
552 async fn benchmark_volume_factory_can_find_fvm_instance_gpt() {
553 benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: true })
554 .await;
555 }
556
557 async fn dropping_an_fvm_volume_removes_the_volume(config: FvmTestConfig) {
558 let (_state, volume_factory) = initialize(config).await;
559 {
560 let _volume = volume_factory
561 .create_block_device(&BlockDeviceConfig {
562 requires_fvm: true,
563 use_zxcrypt: false,
564 volume_size: None,
565 })
566 .await;
567 assert!(volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
568 };
569 assert!(!volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
570 }
571
572 #[fuchsia::test]
573 async fn dropping_an_fvm_volume_removes_the_volume_fvm() {
574 dropping_an_fvm_volume_removes_the_volume(FvmTestConfig { fxblob_enabled: false }).await;
575 }
576
577 async fn benchmark_volume_factory_create_block_device_with_zxcrypt(config: FvmTestConfig) {
578 let (_state, volume_factory) = initialize(config).await;
579 let _ = volume_factory
580 .create_block_device(&BlockDeviceConfig {
581 requires_fvm: true,
582 use_zxcrypt: true,
583 volume_size: None,
584 })
585 .await;
586 }
587
588 #[fuchsia::test]
589 async fn benchmark_volume_factory_create_block_device_with_zxcrypt_fvm() {
590 benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
591 fxblob_enabled: false,
592 })
593 .await;
594 }
595
596 #[fuchsia::test]
597 async fn benchmark_volume_factory_create_block_device_with_zxcrypt_gpt() {
598 benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
599 fxblob_enabled: true,
600 })
601 .await;
602 }
603}