extern crate anyhow;
extern crate flate2;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::time::SystemTime;
use crate::core::types::Header;
use crate::core::types::StackTrace;
use self::flate2::Compression;
use anyhow::{Error, Result};
use thiserror::Error;
mod v0;
mod v1;
mod v2;
pub struct Store {
encoder: flate2::write::GzEncoder<File>,
}
impl Store {
pub fn new(out_path: &Path, sample_rate: u32) -> Result<Store, io::Error> {
let file = File::create(out_path)?;
let mut encoder = flate2::write::GzEncoder::new(file, Compression::default());
encoder.write_all("rbspy02\n".as_bytes())?;
let json = serde_json::to_string(&Header {
sample_rate: Some(sample_rate),
rbspy_version: Some(env!("CARGO_PKG_VERSION").to_string()),
start_time: Some(SystemTime::now()),
})?;
writeln!(&mut encoder, "{}", json)?;
Ok(Store { encoder })
}
pub fn write(&mut self, trace: &StackTrace) -> Result<(), Error> {
let json = serde_json::to_string(trace)?;
writeln!(&mut self.encoder, "{}", json)?;
Ok(())
}
pub fn complete(self) {
drop(self.encoder)
}
}
#[derive(Clone, Debug, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) struct Version(u64);
impl ::std::fmt::Display for Version {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Version {
fn try_from(b: &[u8]) -> Result<Version, StorageError> {
if &b[0..3] == "00\n".as_bytes() {
Ok(Version(0))
} else if &b[0..3] == "01\n".as_bytes() {
Ok(Version(1))
} else if &b[0..3] == "02\n".as_bytes() {
Ok(Version(2))
} else {
Err(StorageError::Invalid)
}
}
}
#[derive(Error, Debug)]
pub(crate) enum StorageError {
#[error("Invalid rbspy file")]
Invalid,
#[error("Cannot handle rbspy format {}", _0)]
UnknownVersion(Version),
#[error("IO error {:?}", _0)]
Io(io::Error),
}
pub(crate) trait Storage: Into<v2::Data> {
fn from_reader<R: Read>(r: R) -> Result<Self>;
fn version() -> Version;
}
fn read_version(r: &mut dyn Read) -> Result<Version, StorageError> {
let mut buf = [0u8; 8];
r.read(&mut buf).map_err(StorageError::Io)?;
match &buf[..5] {
b"rbspy" => Ok(Version::try_from(&buf[5..])?),
_ => Err(StorageError::Invalid),
}
}
pub(crate) fn from_reader<R: Read>(r: R) -> Result<v2::Data, Error> {
let mut reader = flate2::read::GzDecoder::new(r);
let version = read_version(&mut reader)?;
match version {
Version(0) => {
let intermediate = v0::Data::from_reader(reader)?;
Ok(intermediate.into())
}
Version(1) => {
let intermediate = v1::Data::from_reader(reader)?;
Ok(intermediate.into())
}
Version(2) => {
let intermediate = v2::Data::from_reader(reader)?;
Ok(intermediate)
}
v => Err(StorageError::UnknownVersion(v).into()),
}
}