Add a basic reader implementation
This commit is contained in:
parent
4c65056856
commit
95a9006681
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "bare-metrics-reader"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.48"
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
log = "0.4.14"
|
||||||
|
bare-metrics-core = { version = "0.1.0", path = "../bare-metrics-core" }
|
||||||
|
serde_bare = "0.5.0"
|
||||||
|
hdrhistogram = "7.4.0"
|
|
@ -0,0 +1,88 @@
|
||||||
|
use anyhow::bail;
|
||||||
|
use bare_metrics_core::structures::{
|
||||||
|
get_supported_version, Frame, LogHeader, UnixTimestampMilliseconds,
|
||||||
|
};
|
||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
/// Token that is known to be usable for seeking to a frame in a metrics log.
|
||||||
|
/// TODO identify which reader is applicable? Or don't bother?
|
||||||
|
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||||
|
pub struct SeekToken(pub UnixTimestampMilliseconds, pub u64);
|
||||||
|
|
||||||
|
/// A streaming reader for metric logs.
|
||||||
|
/// If the underlying reader supports seeks, it is possible to get SeekTokens which may be used
|
||||||
|
/// to seek to previous frames in the stream.
|
||||||
|
pub struct MetricsLogReader<R: Read> {
|
||||||
|
reader: R,
|
||||||
|
pub header: LogHeader,
|
||||||
|
last_read_ts: UnixTimestampMilliseconds,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> MetricsLogReader<R> {
|
||||||
|
/// Constructs a bare metrics log reader.
|
||||||
|
pub fn new(mut reader: R) -> anyhow::Result<Self> {
|
||||||
|
let header: LogHeader = serde_bare::from_reader(&mut reader)?;
|
||||||
|
if header.bare_metrics_version != get_supported_version() {
|
||||||
|
bail!("Wrong version. Expected {:?} got {:?}. Later versions of Bare Metrics may use a stable format.", get_supported_version(), header.bare_metrics_version);
|
||||||
|
}
|
||||||
|
let last_read_ts = header.start_time;
|
||||||
|
Ok(MetricsLogReader {
|
||||||
|
reader,
|
||||||
|
header,
|
||||||
|
last_read_ts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a frame.
|
||||||
|
/// Returns the start time of the frame and the frame itself.
|
||||||
|
pub fn read_frame(&mut self) -> anyhow::Result<Option<(UnixTimestampMilliseconds, Frame)>> {
|
||||||
|
match serde_bare::from_reader::<_, Frame>(&mut self.reader) {
|
||||||
|
Ok(frame) => Ok(Some((self.last_read_ts, frame))),
|
||||||
|
Err(err) if err.classify().is_eof() => Ok(None),
|
||||||
|
Err(other_err) => {
|
||||||
|
bail!("Failed to read frame: {:?}", other_err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> MetricsLogReader<R> {
|
||||||
|
/// Reads a new frame, and returns a seek token that can be used to rewind such that you can
|
||||||
|
/// 'undo' the read.
|
||||||
|
pub fn read_frame_rewindable(
|
||||||
|
&mut self,
|
||||||
|
) -> anyhow::Result<Option<(SeekToken, UnixTimestampMilliseconds, Frame)>> {
|
||||||
|
let current_pos_in_file = self.reader.stream_position()?;
|
||||||
|
// TODO should we rewind in case of error?
|
||||||
|
if let Some((timestamp, frame)) = self.read_frame()? {
|
||||||
|
Ok(Some((
|
||||||
|
SeekToken(timestamp, current_pos_in_file),
|
||||||
|
timestamp,
|
||||||
|
frame,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
// EOF (no more things to read here).
|
||||||
|
// Rewind to where we were before.
|
||||||
|
self.reader.seek(SeekFrom::Start(current_pos_in_file))?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seeks to a position in the stream.
|
||||||
|
/// The given seek token MUST have come from this instance's `read_from_rewindable` function.
|
||||||
|
/// Otherwise, corrupt frames may be read.
|
||||||
|
/// The old position is returned as a seek token.
|
||||||
|
pub fn seek(
|
||||||
|
&mut self,
|
||||||
|
SeekToken(seek_timestamp, seek_pos): SeekToken,
|
||||||
|
) -> anyhow::Result<SeekToken> {
|
||||||
|
let old_pos_in_file = self.reader.stream_position()?;
|
||||||
|
let old_timestamp = self.last_read_ts;
|
||||||
|
|
||||||
|
// TODO should we rewind in case of error?
|
||||||
|
self.reader.seek(SeekFrom::Start(seek_pos))?;
|
||||||
|
self.last_read_ts = seek_timestamp;
|
||||||
|
|
||||||
|
Ok(SeekToken(old_timestamp, old_pos_in_file))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue