2021-10-16 10:00:25 +00:00
|
|
|
use std::fs::File;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::io::{Write, BufReader};
|
|
|
|
use std::ffi::OsStr;
|
2021-10-18 11:45:30 +00:00
|
|
|
use std::os::unix::io::AsRawFd;
|
2021-10-19 08:51:22 +00:00
|
|
|
use std::str::FromStr;
|
2021-10-16 10:00:25 +00:00
|
|
|
|
2021-10-19 08:51:22 +00:00
|
|
|
use anyhow::{bail, format_err, Error};
|
2021-10-16 10:00:25 +00:00
|
|
|
use nix::fcntl::OFlag;
|
|
|
|
use crossbeam_channel::Receiver;
|
|
|
|
|
2021-11-23 16:57:00 +00:00
|
|
|
use proxmox_sys::fs::atomic_open_or_create_file;
|
2021-10-16 10:00:25 +00:00
|
|
|
|
|
|
|
const RRD_JOURNAL_NAME: &str = "rrd.journal";
|
|
|
|
|
|
|
|
use crate::rrd::DST;
|
|
|
|
use crate::cache::CacheConfig;
|
|
|
|
|
|
|
|
// shared state behind RwLock
|
|
|
|
pub struct JournalState {
|
|
|
|
config: Arc<CacheConfig>,
|
|
|
|
journal: File,
|
|
|
|
pub last_journal_flush: f64,
|
|
|
|
pub journal_applied: bool,
|
|
|
|
pub apply_thread_result: Option<Receiver<Result<(), String>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct JournalEntry {
|
|
|
|
pub time: f64,
|
|
|
|
pub value: f64,
|
|
|
|
pub dst: DST,
|
|
|
|
pub rel_path: String,
|
|
|
|
}
|
|
|
|
|
2021-10-19 08:51:22 +00:00
|
|
|
impl FromStr for JournalEntry {
|
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
|
|
|
let line = line.trim();
|
|
|
|
|
|
|
|
let parts: Vec<&str> = line.splitn(4, ':').collect();
|
|
|
|
if parts.len() != 4 {
|
|
|
|
bail!("wrong numper of components");
|
|
|
|
}
|
|
|
|
|
|
|
|
let time: f64 = parts[0].parse()
|
|
|
|
.map_err(|_| format_err!("unable to parse time"))?;
|
|
|
|
let value: f64 = parts[1].parse()
|
|
|
|
.map_err(|_| format_err!("unable to parse value"))?;
|
|
|
|
let dst: u8 = parts[2].parse()
|
|
|
|
.map_err(|_| format_err!("unable to parse data source type"))?;
|
|
|
|
|
|
|
|
let dst = match dst {
|
|
|
|
0 => DST::Gauge,
|
|
|
|
1 => DST::Derive,
|
|
|
|
_ => bail!("got strange value for data source type '{}'", dst),
|
|
|
|
};
|
|
|
|
|
|
|
|
let rel_path = parts[3].to_string();
|
|
|
|
|
|
|
|
Ok(JournalEntry { time, value, dst, rel_path })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-16 10:38:34 +00:00
|
|
|
pub struct JournalFileInfo {
|
|
|
|
pub time: u64,
|
|
|
|
pub name: String,
|
|
|
|
pub path: PathBuf,
|
|
|
|
}
|
|
|
|
|
2021-10-16 10:00:25 +00:00
|
|
|
impl JournalState {
|
|
|
|
|
|
|
|
pub(crate) fn new(config: Arc<CacheConfig>) -> Result<Self, Error> {
|
|
|
|
let journal = JournalState::open_journal_writer(&config)?;
|
|
|
|
Ok(Self {
|
|
|
|
config,
|
|
|
|
journal,
|
|
|
|
last_journal_flush: 0.0,
|
|
|
|
journal_applied: false,
|
|
|
|
apply_thread_result: None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-18 11:45:30 +00:00
|
|
|
pub fn sync_journal(&self) -> Result<(), Error> {
|
|
|
|
nix::unistd::fdatasync(self.journal.as_raw_fd())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-16 10:00:25 +00:00
|
|
|
pub fn append_journal_entry(
|
|
|
|
&mut self,
|
|
|
|
time: f64,
|
|
|
|
value: f64,
|
|
|
|
dst: DST,
|
|
|
|
rel_path: &str,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let journal_entry = format!(
|
|
|
|
"{}:{}:{}:{}\n", time, value, dst as u8, rel_path);
|
|
|
|
self.journal.write_all(journal_entry.as_bytes())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn open_journal_reader(&self) -> Result<BufReader<File>, Error> {
|
|
|
|
|
|
|
|
// fixme : dup self.journal instead??
|
|
|
|
let mut journal_path = self.config.basedir.clone();
|
|
|
|
journal_path.push(RRD_JOURNAL_NAME);
|
|
|
|
|
|
|
|
let flags = OFlag::O_CLOEXEC|OFlag::O_RDONLY;
|
|
|
|
let journal = atomic_open_or_create_file(
|
|
|
|
&journal_path,
|
|
|
|
flags,
|
|
|
|
&[],
|
|
|
|
self.config.file_options.clone(),
|
2021-10-20 12:56:15 +00:00
|
|
|
false,
|
2021-10-16 10:00:25 +00:00
|
|
|
)?;
|
|
|
|
Ok(BufReader::new(journal))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn open_journal_writer(config: &CacheConfig) -> Result<File, Error> {
|
|
|
|
let mut journal_path = config.basedir.clone();
|
|
|
|
journal_path.push(RRD_JOURNAL_NAME);
|
|
|
|
|
|
|
|
let flags = OFlag::O_CLOEXEC|OFlag::O_WRONLY|OFlag::O_APPEND;
|
|
|
|
let journal = atomic_open_or_create_file(
|
|
|
|
&journal_path,
|
|
|
|
flags,
|
|
|
|
&[],
|
|
|
|
config.file_options.clone(),
|
2021-10-20 12:56:15 +00:00
|
|
|
false,
|
2021-10-16 10:00:25 +00:00
|
|
|
)?;
|
|
|
|
Ok(journal)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rotate_journal(&mut self) -> Result<(), Error> {
|
|
|
|
let mut journal_path = self.config.basedir.clone();
|
|
|
|
journal_path.push(RRD_JOURNAL_NAME);
|
|
|
|
|
|
|
|
let mut new_name = journal_path.clone();
|
|
|
|
let now = proxmox_time::epoch_i64();
|
|
|
|
new_name.set_extension(format!("journal-{:08x}", now));
|
2021-10-19 16:33:19 +00:00
|
|
|
std::fs::rename(journal_path, &new_name)?;
|
2021-10-16 10:00:25 +00:00
|
|
|
|
|
|
|
self.journal = Self::open_journal_writer(&self.config)?;
|
2021-10-19 16:33:19 +00:00
|
|
|
|
|
|
|
// make sure the old journal data landed on the disk
|
|
|
|
super::fsync_file_and_parent(&new_name)?;
|
|
|
|
|
2021-10-16 10:00:25 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_old_journals(&self) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let journal_list = self.list_old_journals()?;
|
|
|
|
|
2021-10-16 10:38:34 +00:00
|
|
|
for entry in journal_list {
|
|
|
|
std::fs::remove_file(entry.path)?;
|
2021-10-16 10:00:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-16 10:38:34 +00:00
|
|
|
pub fn list_old_journals(&self) -> Result<Vec<JournalFileInfo>, Error> {
|
2021-10-16 10:00:25 +00:00
|
|
|
let mut list = Vec::new();
|
|
|
|
for entry in std::fs::read_dir(&self.config.basedir)? {
|
|
|
|
let entry = entry?;
|
|
|
|
let path = entry.path();
|
2021-10-18 08:00:16 +00:00
|
|
|
|
|
|
|
if !path.is_file() { continue; }
|
|
|
|
|
|
|
|
match path.file_stem() {
|
|
|
|
None => continue,
|
|
|
|
Some(stem) if stem != OsStr::new("rrd") => continue,
|
|
|
|
Some(_) => (),
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(extension) = path.extension() {
|
|
|
|
if let Some(extension) = extension.to_str() {
|
|
|
|
if let Some(rest) = extension.strip_prefix("journal-") {
|
|
|
|
if let Ok(time) = u64::from_str_radix(rest, 16) {
|
|
|
|
list.push(JournalFileInfo {
|
|
|
|
time,
|
|
|
|
name: format!("rrd.{}", extension),
|
|
|
|
path: path.to_owned(),
|
|
|
|
});
|
2021-10-16 10:00:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-16 10:38:34 +00:00
|
|
|
list.sort_unstable_by_key(|entry| entry.time);
|
2021-10-16 10:00:25 +00:00
|
|
|
Ok(list)
|
|
|
|
}
|
|
|
|
}
|