#[allow(dead_code)]
mod common;
use common::*;
use hyperfile::file::hyper::Hyper;
use hyperfile::file::flags::FileFlags;
use hyperfile::file::mode::FileMode;
const BLK: usize = 4096;
async fn seed(client: &aws_sdk_s3::Client, uri: &str, blocks: usize) {
let mut h = Hyper::fs_open_or_create_with_default_opt(
client, uri, FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
let _ = h.fs_write(0, &vec![0x5Au8; blocks * BLK]).await.expect("write");
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_release().await.expect("release");
}
#[tokio::test]
#[ignore]
async fn contiguous_read_costs_one_request() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
const N: usize = 64;
seed(&client, tf.uri(), N).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let mut buf = vec![0u8; N * BLK];
let n = h.fs_read(0, &mut buf).await.expect("read");
assert_eq!(n, N * BLK);
assert!(buf.iter().all(|b| *b == 0x5A));
let t = h.read_timing().snapshot();
assert_eq!(t.data_gets, 1,
"{N} contiguous blocks in one segment should coalesce into 1 request, got {}",
t.data_gets);
assert_eq!(t.data_bytes, (N * BLK) as u64,
"fetched bytes should match the range read");
assert_eq!(t.cache_hits, 0, "nothing should have been cached on a fresh open");
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn byte_read_does_not_populate_the_cache() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
seed(&client, tf.uri(), 8).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(3 * BLK, &mut buf).await.expect("first read");
let first = h.read_timing().snapshot();
assert_eq!(first.data_gets, 1);
assert_eq!(first.cache_hits, 0);
let _ = h.fs_read(3 * BLK, &mut buf).await.expect("second read");
let second = h.read_timing().snapshot();
assert_eq!(second.data_gets, 2,
"a byte read does not cache, so the second read must fetch again");
assert_eq!(second.cache_hits, 0,
"a byte read leaves nothing in the data cache to hit");
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn block_borrow_populates_the_cache() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
seed(&client, tf.uri(), 8).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
{
let blk = h.fs_block(3).await.expect("borrow").expect("mapped");
assert_eq!(blk.as_slice()[0], 0x5A);
}
let first = h.read_timing().snapshot();
assert_eq!(first.data_gets, 1, "a cold borrow fetches once");
assert_eq!(first.cache_hits, 0);
{
let blk = h.fs_block(3).await.expect("borrow again").expect("mapped");
assert_eq!(blk.as_slice()[0], 0x5A);
}
let second = h.read_timing().snapshot();
assert_eq!(second.data_gets, 1, "a second borrow must not fetch");
assert_eq!(second.cache_hits, 1, "a second borrow is a cache hit");
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(3 * BLK, &mut buf).await.expect("read");
let third = h.read_timing().snapshot();
assert_eq!(third.data_gets, 1, "a byte read of a cached block must not fetch");
assert_eq!(third.cache_hits, 2);
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn index_reads_are_counted_separately() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
seed(&client, tf.uri(), 64).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
let after_open = h.read_timing().snapshot();
assert!(after_open.inode_gets >= 1, "opening must read the inode");
h.read_timing_reset();
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(60 * BLK, &mut buf).await.expect("read");
let t = h.read_timing().snapshot();
assert_eq!(t.data_gets, 1, "one block, one data request");
assert!(t.meta_gets >= 1,
"a spilled map must be descended, so at least one index request; got {}",
t.meta_gets);
assert!(t.meta_bytes > 0, "index requests should report bytes");
assert_eq!(t.total_gets(), t.data_gets + t.meta_gets + t.inode_gets);
assert!(t.staging_ns > 0, "time spent awaiting staging should be recorded");
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn index_fetch_counts_every_request_it_makes() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
seed(&client, tf.uri(), 64).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(60 * BLK, &mut buf).await.expect("read");
let t = h.read_timing().snapshot();
assert!(t.meta_gets >= 1, "index requests should be counted, got {}", t.meta_gets);
assert!(t.meta_bytes >= t.meta_gets,
"each counted index request should have contributed bytes: {} requests, {} bytes",
t.meta_gets, t.meta_bytes);
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn holes_and_unflushed_writes_cost_nothing() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_default_opt(
&client, tf.uri(), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
let _ = h.fs_write(0, &vec![0x11u8; BLK]).await.expect("write 0");
let _ = h.fs_write(2 * BLK, &vec![0x22u8; BLK]).await.expect("write 2");
h.read_timing_reset();
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read dirty");
assert!(buf.iter().all(|b| *b == 0x11));
let t = h.read_timing().snapshot();
assert_eq!(t.data_gets, 0, "a dirty block must be served from cache");
assert_eq!(t.cache_hits, 1);
h.read_timing_reset();
let mut buf = vec![0xFFu8; BLK];
let _ = h.fs_read(BLK, &mut buf).await.expect("read hole");
assert!(buf.iter().all(|b| *b == 0), "hole reads as zeros");
let t = h.read_timing().snapshot();
assert_eq!(t.data_gets, 0, "a hole costs no request");
assert_eq!(t.cache_hits, 0, "a hole is not a cache hit either");
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn reset_zeroes_every_counter() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
seed(&client, tf.uri(), 8).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read");
let _ = h.fs_block(1).await.expect("borrow");
let _ = h.fs_block(1).await.expect("borrow again");
let before = h.read_timing().snapshot();
assert!(before.total_gets() > 0 && before.cache_hits > 0,
"the measurement should have recorded something to reset");
h.read_timing_reset();
let after = h.read_timing().snapshot();
assert_eq!(after.total_gets(), 0);
assert_eq!(after.total_bytes(), 0);
assert_eq!(after.cache_hits, 0);
assert_eq!(after.staging_ns, 0);
assert_eq!(after.data_gets, 0);
assert_eq!(after.meta_gets, 0);
assert_eq!(after.inode_gets, 0);
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn read_ahead_warms_the_cache_and_reads_cost_nothing() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
const N: usize = 64;
seed(&client, tf.uri(), N).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let cached = h.fs_read_ahead(0, N * BLK).await.expect("read_ahead");
assert_eq!(cached, N, "every block in the range should have been installed");
let warm = h.read_timing().snapshot();
assert_eq!(warm.data_gets, 1,
"{N} contiguous blocks should warm in 1 request, got {}", warm.data_gets);
let mut buf = vec![0u8; N * BLK];
let n = h.fs_read(0, &mut buf).await.expect("read");
assert_eq!(n, N * BLK);
assert!(buf.iter().all(|b| *b == 0x5A), "warmed data came back wrong");
let after = h.read_timing().snapshot();
assert_eq!(after.data_gets, warm.data_gets,
"a read over a warmed range must not fetch, but data_gets went {} -> {}",
warm.data_gets, after.data_gets);
assert!(after.cache_hits > warm.cache_hits,
"and the read should be counted as cache hits");
let _ = h.fs_release().await;
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn read_ahead_does_not_report_cache_hits() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
const N: usize = 64;
seed(&client, tf.uri(), N).await;
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let _ = h.fs_read_ahead(0, N * BLK).await.expect("warm");
let after_first = h.read_timing().snapshot();
assert_eq!(after_first.cache_hits, 0,
"a read-ahead should report no hits, got {}", after_first.cache_hits);
let again = h.fs_read_ahead(0, N * BLK).await.expect("warm again");
assert_eq!(again, 0, "a warmed range installs nothing the second time");
let after_second = h.read_timing().snapshot();
assert_eq!(after_second.cache_hits, 0,
"warming an already-warm range should still report no hits, got {}",
after_second.cache_hits);
let mut buf = vec![0u8; N * BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read");
let after_read = h.read_timing().snapshot();
assert!(after_read.cache_hits > 0,
"a read served from the cache has to report hits");
assert!(after_read.cache_hits <= N as u64,
"{} blocks read once cannot be more than {} hits, got {}",
N, N, after_read.cache_hits);
let _ = h.fs_release().await;
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn the_plan_agrees_with_a_direct_read() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
const NB: usize = 48;
{
let mut h = Hyper::fs_open_or_create_with_default_opt(
&client, tf.uri(), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
for b in (0..NB).filter(|b| b % 2 == 0) {
let _ = h.fs_write(b * BLK, &vec![(b % 251) as u8; BLK]).await.expect("write");
}
let _ = h.fs_flush().await.expect("flush");
for b in (0..NB).filter(|b| b % 2 == 1) {
let _ = h.fs_write(b * BLK, &vec![(b % 251) as u8; BLK]).await.expect("write");
}
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_release().await.expect("release");
}
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let plan = h.fs_read_plan(0, (NB * BLK) as u64).await.expect("fs_read_plan");
let bytes: u64 = plan.iter().map(|e| e.range().1).sum();
let gets = plan.iter().filter(|e| e.is_get()).count();
assert_eq!(bytes, (NB * BLK) as u64, "entries must account for the whole range");
assert!(gets > 1, "alternating segments cannot merge into one request, got {}", gets);
assert_eq!(h.read_timing().snapshot().data_gets, 0, "asking must issue no data request");
let mut buf = vec![0u8; NB * BLK];
let n = h.fs_read(0, &mut buf).await.expect("read");
assert_eq!(n, NB * BLK);
let t = h.read_timing().snapshot();
assert_eq!(t.data_gets as usize, gets,
"the plan predicted {} requests and the read issued {}", gets, t.data_gets);
let places = h.fs_block_placement(0, NB).await.expect("fs_block_placement");
assert_eq!(places.len(), NB);
let segs: std::collections::BTreeSet<_> = places.iter()
.filter_map(|p| p.map(|(s, _)| s)).collect();
assert_eq!(segs.len(), 2, "two flushes, two segments, got {:?}", segs);
let ranges = [(0u64, (4 * BLK) as u64), ((10 * BLK) as u64, (3 * BLK) as u64)];
let batch = h.fs_read_plan_many(&ranges).await.expect("fs_read_plan_many");
assert_eq!(batch.len(), ranges.len());
for (i, (off, len)) in ranges.iter().copied().enumerate() {
assert_eq!(batch[i], h.fs_read_plan(off, len).await.expect("fs_read_plan"),
"range {} answered differently in a batch", i);
}
let b_batch = h.fs_block_placement_many(&[(0, 4), (10, 3)]).await.expect("fs_block_placement_many");
assert_eq!(b_batch.len(), 2);
assert_eq!(b_batch[0], h.fs_block_placement(0, 4).await.expect("fs_block_placement"));
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn read_ahead_requests_are_attributable() {
let _ = env_logger::try_init();
let client = make_client().await;
let tf = TestFile::new(&client).await;
const NB: usize = 64;
seed(&client, tf.uri(), NB).await;
{
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let mut buf = vec![0u8; NB * BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read");
let t = h.read_timing().snapshot();
assert!(t.data_gets >= 1, "a cold read must fetch");
assert_eq!(t.read_ahead_gets, 0, "no read-ahead was asked for");
assert_eq!(t.read_ahead_bytes, 0);
let _ = h.fs_release().await.expect("release");
}
{
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let _ = h.fs_read_ahead(0, NB * BLK).await.expect("read ahead");
let warm = h.read_timing().snapshot();
assert!(warm.data_gets >= 1, "warming must fetch");
assert_eq!(warm.read_ahead_gets, warm.data_gets,
"every request so far was read-ahead's: {} of {}", warm.read_ahead_gets, warm.data_gets);
assert_eq!(warm.read_ahead_bytes, warm.data_bytes);
let mut buf = vec![0u8; NB * BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read");
let after = h.read_timing().snapshot();
assert_eq!(after.data_gets, warm.data_gets, "the read was served from the cache");
assert_eq!(
after.data_gets - after.read_ahead_gets, 0,
"so the read's own requests subtract to nothing",
);
let _ = h.fs_release().await.expect("release");
}
{
let mut h = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
h.read_timing_reset();
let half = NB / 2 * BLK;
let _ = h.fs_read_ahead(0, half).await.expect("read ahead");
let ra = h.read_timing().snapshot().read_ahead_gets;
assert!(ra >= 1);
let mut buf = vec![0u8; half];
let _ = h.fs_read(half, &mut buf).await.expect("read the cold half");
let t = h.read_timing().snapshot();
assert_eq!(t.read_ahead_gets, ra, "reading must not add to read-ahead's count");
assert!(t.data_gets > ra, "the cold half had to be fetched");
assert_eq!(t.data_gets - t.read_ahead_gets, t.data_gets - ra,
"the read's own requests are what is left over");
assert!(t.read_ahead_bytes < t.data_bytes, "and the same holds for bytes");
assert!(t.read_ahead_gets <= t.data_gets);
assert!(t.read_ahead_bytes <= t.data_bytes);
let _ = h.fs_release().await.expect("release");
}
tf.cleanup(&client).await;
}