use std::hash::Hasher;
use std::io::Write;
use anyhow::anyhow;
use fdlimit::raise_fd_limit;
use super::*;
use crate::testing::{test_tempdir, Hash};
use crate::Handle;
#[derive(Clone, Copy)]
pub struct TorrentStorageOpts {
pub piece_size: usize,
pub chunk_size: usize,
pub num_pieces: usize,
pub static_tempdir_name: &'static str,
pub disable_hole_punching: bool,
pub rename_values: bool,
pub num_threads: Option<usize>,
}
pub const BENCHMARK_OPTS: TorrentStorageOpts = TorrentStorageOpts {
piece_size: 2 << 20,
static_tempdir_name: "benchmark_torrent_storage_default",
num_pieces: 8,
chunk_size: 1 << 14,
disable_hole_punching: false,
rename_values: true,
num_threads: None,
};
impl TorrentStorageOpts {
pub fn build(self) -> Result<TorrentStorageInner> {
let opts = self;
let _ = env_logger::builder()
.is_test(true)
.format_timestamp_micros()
.try_init();
let _ = raise_fd_limit();
let _tempdir = test_tempdir(opts.static_tempdir_name)?;
let new_handle = || -> anyhow::Result<Handle> {
let mut handle = Handle::new(_tempdir.path.clone())?;
handle.set_instance_limits(handle::Limits {
disable_hole_punching: opts.disable_hole_punching,
max_value_length_sum: Some(opts.piece_size as u64 * opts.num_pieces as u64 / 2),
})?;
Ok(handle)
};
let handle = new_handle()?;
let thread_pool = if let Some(num_threads) = opts.num_threads {
Some(
rayon::ThreadPoolBuilder::new()
.num_threads(num_threads)
.build()?,
)
} else {
None
};
Ok(TorrentStorageInner {
opts,
handle,
thread_pool,
_tempdir,
})
}
}
pub struct TorrentStorageInner {
opts: TorrentStorageOpts,
handle: Handle,
_tempdir: TestTempDir,
thread_pool: Option<rayon::ThreadPool>,
}
impl TorrentStorageInner {
pub fn run(&self) -> Result<()> {
torrent_storage_inner_run(self)
}
}
fn torrent_storage_inner_run(inner: &TorrentStorageInner) -> anyhow::Result<()> {
let TorrentStorageInner {
opts,
handle,
thread_pool,
..
} = inner;
let TorrentStorageOpts {
piece_size,
chunk_size,
num_pieces,
..
} = opts;
let num_pieces = *num_pieces;
let piece_size = *piece_size;
let chunk_size = *chunk_size;
for _piece_index in 0..num_pieces {
let byte = rand::thread_rng().gen_range(1..u8::MAX);
let mut piece_data = io::repeat(byte).take(piece_size as u64);
let piece_data_hash = {
let mut hash = Hash::default();
assert_eq!(
io::copy(&mut piece_data, &mut HashWriter(&mut hash))?,
piece_size as u64,
);
hash.finish()
};
let chunk_offset_iter = (0..piece_size).step_by(chunk_size);
let unverified_key = |offset| format!("unverified/{piece_data_hash:x}/{offset}");
let write_unverified_chunk = |handle: &Handle, offset| -> anyhow::Result<()> {
let key = unverified_key(offset);
let (written, _) = handle
.single_write_from(key.into_bytes(), io::repeat(byte).take(chunk_size as u64))?;
assert_eq!(written, chunk_size as u64);
Ok(())
};
if let Some(thread_pool) = thread_pool {
thread_pool.scope(|scope| {
for offset in chunk_offset_iter.clone() {
let handle = &handle;
scope.spawn(move |_scope| write_unverified_chunk(handle, offset).unwrap());
}
anyhow::Ok(())
})?;
} else {
for offset in chunk_offset_iter.clone() {
write_unverified_chunk(handle, offset)?;
}
}
debug!("starting piece");
let mut reader = handle.read()?;
let values = chunk_offset_iter
.clone()
.map(|offset| {
anyhow::Ok((
offset,
reader
.add(unverified_key(offset).as_ref())?
.ok_or(anyhow!("missing value"))?,
))
})
.collect::<anyhow::Result<Vec<_>, _>>()?;
let snapshot = reader.begin()?;
let mut stored_hash = Hash::default();
let mut writer = handle.new_writer()?;
let make_verified_key =
|offset| format!("verified/{piece_data_hash:016x}/{offset}").into_bytes();
if opts.rename_values {
for (offset, value) in values {
snapshot.value(value.clone()).view(|bytes| {
stored_hash.write(bytes);
compare_reads(bytes, io::repeat(byte).take(chunk_size as u64)).unwrap();
writer.rename_value(value, make_verified_key(offset))
})?;
}
assert_eq!(stored_hash.finish(), piece_data_hash);
writer.commit()?;
let mut reader = handle.read()?;
let mut verified_hash = Hash::default();
let mut values = vec![];
for offset in chunk_offset_iter.clone() {
values.push(reader.add(&make_verified_key(offset))?.unwrap());
}
let snapshot = reader.begin()?;
for value in values {
snapshot.value(value).view(|bytes| {
assert_eq!(bytes.len(), chunk_size);
verified_hash.write(bytes)
})?;
}
assert_eq!(verified_hash.finish(), piece_data_hash);
} else {
let mut completed = writer.new_value().begin()?;
for (_offset, value) in values {
snapshot.value(value).view(|bytes| {
stored_hash.write(bytes);
completed.write_all(bytes)
})??;
}
assert_eq!(stored_hash.finish(), piece_data_hash);
let completed_key = format!("completed/{:016x}", piece_data_hash).into_bytes();
writer.stage_write(completed_key.clone(), completed)?;
writer.commit()?;
}
}
Ok(())
}