#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ByteRange {
pub start: u64,
pub end: u64,
}
impl ByteRange {
pub fn new(start: u64, end: u64) -> Self {
debug_assert!(end >= start, "end must be >= start");
Self { start, end }
}
#[must_use]
pub fn len(&self) -> u64 {
self.end.saturating_sub(self.start)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.start >= self.end
}
#[must_use]
pub fn overlaps_or_adjoins(&self, other: &ByteRange) -> bool {
self.start <= other.end && other.start <= self.end
}
#[must_use]
pub fn merge(&self, other: &ByteRange) -> ByteRange {
ByteRange {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
#[must_use]
pub fn gap_to(&self, other: &ByteRange) -> u64 {
other.start.saturating_sub(self.end)
}
}
#[derive(Debug, Clone)]
pub struct CoalescingConfig {
pub max_gap_bytes: u64,
pub max_merged_size: u64,
pub max_parallel_requests: usize,
}
impl Default for CoalescingConfig {
fn default() -> Self {
Self {
max_gap_bytes: 8 * 1024,
max_merged_size: 16 * 1024 * 1024,
max_parallel_requests: 8,
}
}
}
#[derive(Debug, Clone)]
pub struct CoalescedRequest {
pub fetch_range: ByteRange,
pub sub_ranges: Vec<ByteRange>,
}
impl CoalescedRequest {
#[must_use]
pub fn extract<'a>(&self, merged_data: &'a [u8], sub_range: &ByteRange) -> Option<&'a [u8]> {
if sub_range.start < self.fetch_range.start || sub_range.end > self.fetch_range.end {
return None;
}
let offset = (sub_range.start - self.fetch_range.start) as usize;
let len = sub_range.len() as usize;
let end = offset + len;
if end <= merged_data.len() {
Some(&merged_data[offset..end])
} else {
None
}
}
}
pub fn coalesce_ranges(
mut ranges: Vec<ByteRange>,
config: &CoalescingConfig,
) -> Vec<CoalescedRequest> {
if ranges.is_empty() {
return Vec::new();
}
ranges.sort();
ranges.dedup();
let mut result: Vec<CoalescedRequest> = Vec::new();
let mut current = CoalescedRequest {
fetch_range: ranges[0].clone(),
sub_ranges: vec![ranges[0].clone()],
};
for range in ranges.into_iter().skip(1) {
let gap = current.fetch_range.gap_to(&range);
let merged_size = range.end.saturating_sub(current.fetch_range.start);
if gap <= config.max_gap_bytes && merged_size <= config.max_merged_size {
current.fetch_range = current.fetch_range.merge(&range);
current.sub_ranges.push(range);
} else {
result.push(current);
current = CoalescedRequest {
fetch_range: range.clone(),
sub_ranges: vec![range],
};
}
}
result.push(current);
result
}
#[derive(Debug, Clone, Default)]
pub struct CoalescingStats {
pub original_requests: usize,
pub coalesced_requests: usize,
pub bytes_fetched: u64,
pub bytes_needed: u64,
pub overhead_bytes: u64,
}
impl CoalescingStats {
#[must_use]
pub fn overhead_ratio(&self) -> f64 {
if self.bytes_needed == 0 {
0.0
} else {
self.overhead_bytes as f64 / self.bytes_needed as f64
}
}
#[must_use]
pub fn request_reduction(&self) -> f64 {
if self.original_requests == 0 {
0.0
} else {
1.0 - self.coalesced_requests as f64 / self.original_requests as f64
}
}
}
#[must_use]
pub fn compute_stats(original: &[ByteRange], coalesced: &[CoalescedRequest]) -> CoalescingStats {
let bytes_needed: u64 = original.iter().map(ByteRange::len).sum();
let bytes_fetched: u64 = coalesced.iter().map(|c| c.fetch_range.len()).sum();
CoalescingStats {
original_requests: original.len(),
coalesced_requests: coalesced.len(),
bytes_fetched,
bytes_needed,
overhead_bytes: bytes_fetched.saturating_sub(bytes_needed),
}
}