use std::{
collections::HashMap,
fs::File,
sync::{Arc, Mutex, RwLock},
time::Instant,
};
use super::{Error, Result};
pub trait ByteSource: Send + Sync {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>;
fn size(&self) -> Result<u64>;
fn capabilities(&self) -> ByteSourceCapabilities {
ByteSourceCapabilities::default()
}
fn telemetry_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let mut total_read = 0usize;
while total_read < buf.len() {
let chunk_offset = offset
.checked_add(total_read as u64)
.ok_or_else(|| Error::invalid_range("data source offset overflow"))?;
let read = self.read_at(chunk_offset, &mut buf[total_read..])?;
if read == 0 {
return Err(Error::UnexpectedEof {
offset,
expected: buf.len(),
actual: total_read,
});
}
total_read += read;
}
Ok(())
}
fn read_bytes_at(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; len];
self.read_exact_at(offset, &mut buf)?;
Ok(buf)
}
fn read_all(&self) -> Result<Vec<u8>> {
let size = usize::try_from(self.size()?)
.map_err(|_| Error::invalid_range("data source is too large to read into memory"))?;
let mut buf = vec![0u8; size];
let mut offset = 0usize;
while offset < size {
let read = self.read_at(offset as u64, &mut buf[offset..])?;
if read == 0 {
break;
}
offset += read;
}
buf.truncate(offset);
Ok(buf)
}
}
pub struct BytesDataSource {
bytes: Arc<[u8]>,
}
impl BytesDataSource {
pub fn new(bytes: impl Into<Arc<[u8]>>) -> Self {
Self {
bytes: bytes.into(),
}
}
}
impl ByteSource for BytesDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
let Ok(offset) = usize::try_from(offset) else {
return Ok(0);
};
if offset >= self.bytes.len() || buf.is_empty() {
return Ok(0);
}
let available = (self.bytes.len() - offset).min(buf.len());
buf[..available].copy_from_slice(&self.bytes[offset..offset + available]);
Ok(available)
}
fn size(&self) -> Result<u64> {
Ok(self.bytes.len() as u64)
}
fn capabilities(&self) -> ByteSourceCapabilities {
ByteSourceCapabilities::concurrent(ByteSourceSeekCost::Cheap)
}
fn telemetry_name(&self) -> &'static str {
"core.bytes_data_source"
}
}
pub struct FileDataSource {
file: File,
size: u64,
}
impl FileDataSource {
pub fn open(path: impl AsRef<std::path::Path>) -> Result<Self> {
let file = File::open(path)?;
let size = file.metadata()?.len();
Ok(Self { file, size })
}
}
impl ByteSource for FileDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
read_file_at(&self.file, offset, buf)
}
fn size(&self) -> Result<u64> {
Ok(self.size)
}
fn capabilities(&self) -> ByteSourceCapabilities {
ByteSourceCapabilities::concurrent(ByteSourceSeekCost::Cheap)
}
fn telemetry_name(&self) -> &'static str {
"core.file_data_source"
}
}
#[cfg(unix)]
fn read_file_at(file: &File, offset: u64, buf: &mut [u8]) -> Result<usize> {
use std::os::unix::fs::FileExt as _;
Ok(file.read_at(buf, offset)?)
}
#[cfg(windows)]
fn read_file_at(file: &File, offset: u64, buf: &mut [u8]) -> Result<usize> {
use std::os::windows::fs::FileExt as _;
Ok(file.seek_read(buf, offset)?)
}
#[cfg(not(any(unix, windows)))]
fn read_file_at(file: &File, offset: u64, buf: &mut [u8]) -> Result<usize> {
use std::io::{Read, Seek, SeekFrom};
let mut clone = file.try_clone()?;
clone.seek(SeekFrom::Start(offset))?;
Ok(clone.read(buf)?)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ByteSourceReadConcurrency {
Unknown,
Serialized,
Concurrent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ByteSourceSeekCost {
Unknown,
Cheap,
Expensive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ByteSourceCapabilities {
pub read_concurrency: ByteSourceReadConcurrency,
pub seek_cost: ByteSourceSeekCost,
pub preferred_chunk_size: Option<usize>,
}
impl ByteSourceCapabilities {
pub const fn new(
read_concurrency: ByteSourceReadConcurrency, seek_cost: ByteSourceSeekCost,
) -> Self {
Self {
read_concurrency,
seek_cost,
preferred_chunk_size: None,
}
}
pub const fn serialized(seek_cost: ByteSourceSeekCost) -> Self {
Self::new(ByteSourceReadConcurrency::Serialized, seek_cost)
}
pub const fn concurrent(seek_cost: ByteSourceSeekCost) -> Self {
Self::new(ByteSourceReadConcurrency::Concurrent, seek_cost)
}
pub fn with_preferred_chunk_size(mut self, preferred_chunk_size: usize) -> Self {
self.preferred_chunk_size = Some(preferred_chunk_size);
self
}
}
impl Default for ByteSourceCapabilities {
fn default() -> Self {
Self::new(
ByteSourceReadConcurrency::Unknown,
ByteSourceSeekCost::Unknown,
)
}
}
#[derive(Debug, Clone, Default)]
pub struct ByteSourceReadStats {
inner: Arc<Mutex<DataSourceReadStatsState>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ByteSourceReadStatsSnapshot {
pub read_count: u64,
pub read_bytes: u64,
pub average_read_size: u64,
pub request_offset_distance_bytes: u64,
pub average_offset_distance_bytes: u64,
pub max_read_size: usize,
pub max_offset_distance_bytes: u64,
pub total_read_micros: u128,
pub average_read_micros: u128,
}
#[derive(Debug, Default)]
struct DataSourceReadStatsState {
read_count: u64,
read_bytes: u64,
request_offset_distance_bytes: u64,
max_read_size: usize,
max_offset_distance_bytes: u64,
total_read_micros: u128,
last_offset: Option<u64>,
last_len: usize,
}
impl ByteSourceReadStats {
fn record_read(&self, offset: u64, len: usize, started_at: Instant) {
let mut state = self
.inner
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
state.read_count = state.read_count.saturating_add(1);
state.read_bytes = state.read_bytes.saturating_add(len as u64);
state.max_read_size = state.max_read_size.max(len);
state.total_read_micros = state
.total_read_micros
.saturating_add(started_at.elapsed().as_micros());
if let Some(last_offset) = state.last_offset {
let last_end = last_offset.saturating_add(state.last_len as u64);
let distance = offset.abs_diff(last_end);
state.request_offset_distance_bytes =
state.request_offset_distance_bytes.saturating_add(distance);
state.max_offset_distance_bytes = state.max_offset_distance_bytes.max(distance);
}
state.last_offset = Some(offset);
state.last_len = len;
}
pub fn snapshot(&self) -> ByteSourceReadStatsSnapshot {
let state = self
.inner
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let average_read_size = state.read_bytes.checked_div(state.read_count).unwrap_or(0);
let average_offset_distance_bytes = if state.read_count <= 1 {
0
} else {
state.request_offset_distance_bytes / (state.read_count - 1)
};
let average_read_micros = if state.read_count == 0 {
0
} else {
state.total_read_micros / u128::from(state.read_count)
};
ByteSourceReadStatsSnapshot {
read_count: state.read_count,
read_bytes: state.read_bytes,
average_read_size,
request_offset_distance_bytes: state.request_offset_distance_bytes,
average_offset_distance_bytes,
max_read_size: state.max_read_size,
max_offset_distance_bytes: state.max_offset_distance_bytes,
total_read_micros: state.total_read_micros,
average_read_micros,
}
}
}
pub struct ObservedDataSource {
inner: Arc<dyn ByteSource>,
stats: ByteSourceReadStats,
}
impl ObservedDataSource {
pub fn new(inner: Arc<dyn ByteSource>) -> Self {
Self {
inner,
stats: ByteSourceReadStats::default(),
}
}
pub fn stats(&self) -> ByteSourceReadStats {
self.stats.clone()
}
}
impl ByteSource for ObservedDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
let started_at = Instant::now();
let read = self.inner.read_at(offset, buf)?;
self.stats.record_read(offset, read, started_at);
Ok(read)
}
fn size(&self) -> Result<u64> {
self.inner.size()
}
fn capabilities(&self) -> ByteSourceCapabilities {
self.inner.capabilities()
}
fn telemetry_name(&self) -> &'static str {
self.inner.telemetry_name()
}
}
pub struct SharedDataSource {
inner: Arc<dyn ByteSource>,
}
impl SharedDataSource {
pub fn new(inner: Arc<dyn ByteSource>) -> Self {
Self { inner }
}
}
impl ByteSource for SharedDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
self.inner.read_at(offset, buf)
}
fn size(&self) -> Result<u64> {
self.inner.size()
}
fn capabilities(&self) -> ByteSourceCapabilities {
self.inner.capabilities()
}
fn telemetry_name(&self) -> &'static str {
self.inner.telemetry_name()
}
}
pub struct SliceDataSource {
inner: Arc<dyn ByteSource>,
base_offset: u64,
size: u64,
}
impl SliceDataSource {
pub fn new(inner: Arc<dyn ByteSource>, base_offset: u64, size: u64) -> Self {
Self {
inner,
base_offset,
size,
}
}
}
impl ByteSource for SliceDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
if offset >= self.size || buf.is_empty() {
return Ok(0);
}
let available = usize::try_from(self.size - offset)
.unwrap_or(usize::MAX)
.min(buf.len());
let absolute_offset = self
.base_offset
.checked_add(offset)
.ok_or_else(|| Error::invalid_range("slice data source offset overflow"))?;
self.inner.read_at(absolute_offset, &mut buf[..available])
}
fn size(&self) -> Result<u64> {
Ok(self.size)
}
fn capabilities(&self) -> ByteSourceCapabilities {
self.inner.capabilities()
}
fn telemetry_name(&self) -> &'static str {
self.inner.telemetry_name()
}
}
const PROBE_CACHE_WINDOW_SIZE: usize = 4096;
const PROBE_CACHE_LIMIT: u64 = 64 * 1024;
pub struct ProbeCachedDataSource<'a> {
inner: &'a dyn ByteSource,
windows: RwLock<HashMap<u64, Arc<[u8]>>>,
}
impl<'a> ProbeCachedDataSource<'a> {
pub fn new(inner: &'a dyn ByteSource) -> Self {
Self {
inner,
windows: RwLock::new(HashMap::new()),
}
}
fn cacheable(offset: u64, len: usize) -> bool {
if len == 0 {
return false;
}
let Some(end) = offset.checked_add(len as u64) else {
return false;
};
end <= PROBE_CACHE_LIMIT
}
fn read_window(&self, window_offset: u64) -> Result<Arc<[u8]>> {
if let Some(window) = self
.windows
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.get(&window_offset)
.cloned()
{
return Ok(window);
}
let mut data = vec![0u8; PROBE_CACHE_WINDOW_SIZE];
let read = self.inner.read_at(window_offset, &mut data)?;
data.truncate(read);
let window: Arc<[u8]> = data.into();
let mut cache = self
.windows
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let entry = cache.entry(window_offset).or_insert_with(|| window.clone());
Ok(entry.clone())
}
}
impl ByteSource for ProbeCachedDataSource<'_> {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
if !Self::cacheable(offset, buf.len()) {
return self.inner.read_at(offset, buf);
}
let mut written = 0usize;
while written < buf.len() {
let absolute = offset
.checked_add(written as u64)
.ok_or_else(|| Error::invalid_range("probe cache offset overflow"))?;
let window_offset =
(absolute / PROBE_CACHE_WINDOW_SIZE as u64) * PROBE_CACHE_WINDOW_SIZE as u64;
let window = self.read_window(window_offset)?;
let window_inner = (absolute - window_offset) as usize;
if window_inner >= window.len() {
break;
}
let available = (window.len() - window_inner).min(buf.len() - written);
buf[written..written + available]
.copy_from_slice(&window[window_inner..window_inner + available]);
written += available;
if window.len() < PROBE_CACHE_WINDOW_SIZE {
break;
}
}
Ok(written)
}
fn size(&self) -> Result<u64> {
self.inner.size()
}
fn capabilities(&self) -> ByteSourceCapabilities {
self.inner.capabilities()
}
fn telemetry_name(&self) -> &'static str {
self.inner.telemetry_name()
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use super::*;
struct MemDataSource {
data: Vec<u8>,
}
impl ByteSource for MemDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
let offset = offset as usize;
if offset >= self.data.len() {
return Ok(0);
}
let available = &self.data[offset..];
let read = buf.len().min(available.len());
buf[..read].copy_from_slice(&available[..read]);
Ok(read)
}
fn size(&self) -> Result<u64> {
Ok(self.data.len() as u64)
}
fn capabilities(&self) -> ByteSourceCapabilities {
ByteSourceCapabilities::concurrent(ByteSourceSeekCost::Cheap).with_preferred_chunk_size(4096)
}
}
#[test]
fn read_all_materializes_the_source() {
let source = MemDataSource {
data: b"read-all".to_vec(),
};
assert_eq!(source.read_all().unwrap(), b"read-all");
}
#[test]
fn bytes_data_source_reads_shared_memory() {
let source = BytesDataSource::new(Arc::<[u8]>::from(&b"shared-bytes"[..]));
let mut buf = [0u8; 6];
let read = source.read_at(7, &mut buf).unwrap();
assert_eq!(read, 5);
assert_eq!(&buf[..read], b"bytes");
}
#[test]
fn observed_data_source_tracks_requested_read_patterns() {
let source: Arc<dyn ByteSource> = Arc::new(MemDataSource {
data: b"abcdefghijklmnopqrstuvwxyz".to_vec(),
});
let observed = ObservedDataSource::new(source);
let stats = observed.stats();
let mut first = [0u8; 4];
let mut second = [0u8; 2];
let mut third = [0u8; 3];
observed.read_at(0, &mut first).unwrap();
observed.read_at(4, &mut second).unwrap();
observed.read_at(10, &mut third).unwrap();
let snapshot = stats.snapshot();
assert_eq!(snapshot.read_count, 3);
assert_eq!(snapshot.read_bytes, 9);
assert_eq!(snapshot.average_read_size, 3);
assert_eq!(snapshot.request_offset_distance_bytes, 4);
assert_eq!(snapshot.average_offset_distance_bytes, 2);
assert_eq!(snapshot.max_read_size, 4);
assert_eq!(snapshot.max_offset_distance_bytes, 4);
}
#[test]
fn observed_data_source_forwards_capabilities() {
let source: Arc<dyn ByteSource> = Arc::new(MemDataSource {
data: b"capabilities".to_vec(),
});
let observed = ObservedDataSource::new(source);
assert_eq!(
observed.capabilities(),
ByteSourceCapabilities::concurrent(ByteSourceSeekCost::Cheap).with_preferred_chunk_size(4096)
);
}
#[test]
fn shared_data_source_forwards_reads() {
let source: Arc<dyn ByteSource> = Arc::new(MemDataSource {
data: b"shared".to_vec(),
});
let shared = SharedDataSource::new(source);
let mut buf = [0u8; 3];
let read = shared.read_at(1, &mut buf).unwrap();
assert_eq!(read, 3);
assert_eq!(&buf, b"har");
}
#[test]
fn slice_data_source_reads_from_the_requested_window() {
let source: Arc<dyn ByteSource> = Arc::new(MemDataSource {
data: b"abcdefghijklmnopqrstuvwxyz".to_vec(),
});
let slice = SliceDataSource::new(source, 5, 7);
let mut buf = [0u8; 8];
let read = slice.read_at(0, &mut buf).unwrap();
assert_eq!(read, 7);
assert_eq!(&buf[..read], b"fghijkl");
let read = slice.read_at(4, &mut buf).unwrap();
assert_eq!(read, 3);
assert_eq!(&buf[..read], b"jkl");
}
#[test]
fn slice_data_source_forwards_capabilities() {
let source: Arc<dyn ByteSource> = Arc::new(MemDataSource {
data: b"capabilities".to_vec(),
});
let slice = SliceDataSource::new(source, 2, 5);
assert_eq!(
slice.capabilities(),
ByteSourceCapabilities::concurrent(ByteSourceSeekCost::Cheap).with_preferred_chunk_size(4096)
);
}
#[test]
fn probe_cached_data_source_reuses_small_probe_windows() {
struct CountingDataSource {
data: Vec<u8>,
reads: Arc<AtomicUsize>,
}
impl ByteSource for CountingDataSource {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
self.reads.fetch_add(1, Ordering::Relaxed);
let offset = offset as usize;
if offset >= self.data.len() {
return Ok(0);
}
let read = buf.len().min(self.data.len() - offset);
buf[..read].copy_from_slice(&self.data[offset..offset + read]);
Ok(read)
}
fn size(&self) -> Result<u64> {
Ok(self.data.len() as u64)
}
}
let reads = Arc::new(AtomicUsize::new(0));
let source = CountingDataSource {
data: (0..128u8).collect(),
reads: reads.clone(),
};
let cached = ProbeCachedDataSource::new(&source);
let mut first = [0u8; 16];
let mut second = [0u8; 8];
cached.read_at(0, &mut first).unwrap();
cached.read_at(4, &mut second).unwrap();
assert_eq!(reads.load(Ordering::Relaxed), 1);
assert_eq!(&first[..4], &[0, 1, 2, 3]);
assert_eq!(&second[..4], &[4, 5, 6, 7]);
}
}