| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use std::io; |
| use std::sync::atomic::{AtomicU64, Ordering}; |
| |
| pub struct ReadRequest<'a> { |
| pub pos: u64, |
| pub buf: &'a mut [u8], |
| } |
| |
| impl<'a> ReadRequest<'a> { |
| pub fn new(pos: u64, buf: &'a mut [u8]) -> Self { |
| Self { pos, buf } |
| } |
| } |
| |
| /// Positional reader shared by Tantivy file handles. |
| /// |
| /// Implementations must be safe to call concurrently. Each call may contain one |
| /// or more disjoint read requests, and callers may issue multiple `pread` calls |
| /// at the same time through shared references. |
| pub trait SeekRead: Send + Sync { |
| fn pread(&self, ranges: &mut [ReadRequest<'_>]) -> io::Result<()>; |
| } |
| |
| #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] |
| pub struct FullTextReadMetrics { |
| pub pread_calls: u64, |
| pub pread_ranges: u64, |
| pub pread_bytes: u64, |
| pub cache_hits: u64, |
| pub cache_misses: u64, |
| pub cache_evictions: u64, |
| pub cached_blocks: u64, |
| } |
| |
| #[derive(Default)] |
| pub(crate) struct ReadMetrics { |
| pread_calls: AtomicU64, |
| pread_ranges: AtomicU64, |
| pread_bytes: AtomicU64, |
| cache_hits: AtomicU64, |
| cache_misses: AtomicU64, |
| cache_evictions: AtomicU64, |
| cached_blocks: AtomicU64, |
| } |
| |
| impl ReadMetrics { |
| pub(crate) fn record_pread(&self, ranges: &[ReadRequest<'_>]) { |
| self.pread_calls.fetch_add(1, Ordering::Relaxed); |
| self.pread_ranges |
| .fetch_add(ranges.len() as u64, Ordering::Relaxed); |
| self.pread_bytes.fetch_add( |
| ranges.iter().map(|range| range.buf.len() as u64).sum(), |
| Ordering::Relaxed, |
| ); |
| } |
| |
| pub(crate) fn record_cache_hit(&self) { |
| self.cache_hits.fetch_add(1, Ordering::Relaxed); |
| } |
| |
| pub(crate) fn record_cache_miss(&self) { |
| self.cache_misses.fetch_add(1, Ordering::Relaxed); |
| } |
| |
| pub(crate) fn record_cache_insert(&self) { |
| self.cached_blocks.fetch_add(1, Ordering::Relaxed); |
| } |
| |
| pub(crate) fn record_cache_eviction(&self) { |
| self.cache_evictions.fetch_add(1, Ordering::Relaxed); |
| self.cached_blocks.fetch_sub(1, Ordering::Relaxed); |
| } |
| |
| pub(crate) fn snapshot(&self) -> FullTextReadMetrics { |
| FullTextReadMetrics { |
| pread_calls: self.pread_calls.load(Ordering::Relaxed), |
| pread_ranges: self.pread_ranges.load(Ordering::Relaxed), |
| pread_bytes: self.pread_bytes.load(Ordering::Relaxed), |
| cache_hits: self.cache_hits.load(Ordering::Relaxed), |
| cache_misses: self.cache_misses.load(Ordering::Relaxed), |
| cache_evictions: self.cache_evictions.load(Ordering::Relaxed), |
| cached_blocks: self.cached_blocks.load(Ordering::Relaxed), |
| } |
| } |
| } |
| |
| pub trait SeekWrite: Send { |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()>; |
| |
| fn flush(&mut self) -> io::Result<()> { |
| Ok(()) |
| } |
| } |
| |
| pub struct PosWriter<W> { |
| inner: W, |
| } |
| |
| impl<W> PosWriter<W> { |
| pub fn new(inner: W) -> Self { |
| Self { inner } |
| } |
| |
| pub fn into_inner(self) -> W { |
| self.inner |
| } |
| } |
| |
| impl<W: io::Write + Send> SeekWrite for PosWriter<W> { |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| io::Write::write_all(&mut self.inner, buf) |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| io::Write::flush(&mut self.inner) |
| } |
| } |
| |
| pub struct SliceReader { |
| data: Vec<u8>, |
| } |
| |
| impl SliceReader { |
| pub fn new(data: Vec<u8>) -> Self { |
| Self { data } |
| } |
| |
| pub fn len(&self) -> usize { |
| self.data.len() |
| } |
| |
| pub fn is_empty(&self) -> bool { |
| self.data.is_empty() |
| } |
| } |
| |
| impl SeekRead for SliceReader { |
| fn pread(&self, ranges: &mut [ReadRequest<'_>]) -> io::Result<()> { |
| for range in ranges { |
| let start = usize::try_from(range.pos) |
| .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "offset overflow"))?; |
| let end = start |
| .checked_add(range.buf.len()) |
| .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "range overflow"))?; |
| if end > self.data.len() { |
| return Err(io::Error::new( |
| io::ErrorKind::UnexpectedEof, |
| "read past end of slice", |
| )); |
| } |
| range.buf.copy_from_slice(&self.data[start..end]); |
| } |
| Ok(()) |
| } |
| } |