+//! # Round Robin Database file format
+
use std::io::Read;
use std::path::Path;
-use anyhow::Error;
+use anyhow::{bail, Error};
+use bitflags::bitflags;
use proxmox::tools::{fs::replace_file, fs::CreateOptions};
-use crate::{RRDMode, RRDTimeFrameResolution};
+use proxmox_rrd_api_types::{RRDMode, RRDTimeFrameResolution};
/// The number of data entries per RRA
pub const RRD_DATA_ENTRIES: usize = 70;
// openssl::sha::sha256(b"Proxmox Round Robin Database file v1.0")[0..8];
pub const PROXMOX_RRD_MAGIC_1_0: [u8; 8] = [206, 46, 26, 212, 172, 158, 5, 186];
-use bitflags::bitflags;
+use crate::DST;
bitflags!{
- struct RRAFlags: u64 {
+ /// Flags to specify the data soure type and consolidation function
+ pub struct RRAFlags: u64 {
// Data Source Types
const DST_GAUGE = 1;
const DST_DERIVE = 2;
}
}
-/// RRD data source tyoe
-pub enum DST {
- Gauge,
- Derive,
-}
-
+/// Round Robin Archive with [RRD_DATA_ENTRIES] data slots.
+///
+/// This data structure is used inside [RRD] and directly written to the
+/// RRD files.
#[repr(C)]
-struct RRA {
- flags: RRAFlags,
- resolution: u64,
- last_update: f64,
- last_count: u64,
- counter_value: f64, // used for derive/counters
- data: [f64; RRD_DATA_ENTRIES],
+pub struct RRA {
+ /// Defined the data soure type and consolidation function
+ pub flags: RRAFlags,
+ /// Resulution (seconds) from [RRDTimeFrameResolution]
+ pub resolution: u64,
+ /// Last update time (epoch)
+ pub last_update: f64,
+ /// Count values computed inside this update interval
+ pub last_count: u64,
+ /// Stores the last value, used to compute differential value for derive/counters
+ pub counter_value: f64,
+ /// Data slots
+ pub data: [f64; RRD_DATA_ENTRIES],
}
impl RRA {
(last_value*(self.last_count as f64))/(new_count as f64)
+ value/(new_count as f64)
} else {
- eprintln!("rrdb update failed - unknown CF");
+ log::error!("rrdb update failed - unknown CF");
return;
};
self.data[index] = new_value;
self.last_update = time;
}
- fn update(&mut self, time: f64, mut value: f64) {
+ // Note: This may update the state even in case of errors (see counter overflow)
+ fn update(&mut self, time: f64, mut value: f64) -> Result<(), Error> {
if time <= self.last_update {
- eprintln!("rrdb update failed - time in past ({} < {})", time, self.last_update);
+ bail!("time in past ({} < {})", time, self.last_update);
}
if value.is_nan() {
- eprintln!("rrdb update failed - new value is NAN");
- return;
+ bail!("new value is NAN");
}
// derive counter value
let diff = if self.counter_value.is_nan() {
0.0
} else if is_counter && value < 0.0 {
- eprintln!("rrdb update failed - got negative value for counter");
- return;
+ bail!("got negative value for counter");
} else if is_counter && value < self.counter_value {
- // Note: We do not try automatic overflow corrections
+ // Note: We do not try automatic overflow corrections, but
+ // we update counter_value anyways, so that we can compute the diff
+ // next time.
self.counter_value = value;
- eprintln!("rrdb update failed - conter overflow/reset detected");
- return;
+ bail!("conter overflow/reset detected");
} else {
value - self.counter_value
};
self.delete_old(time);
self.compute_new_value(time, value);
+
+ Ok(())
}
}
+/// Round Robin Database file format with fixed number of [RRA]s
#[repr(C)]
// Note: Avoid alignment problems by using 8byte types only
pub struct RRD {
- magic: [u8; 8],
- hour_avg: RRA,
- hour_max: RRA,
- day_avg: RRA,
- day_max: RRA,
- week_avg: RRA,
- week_max: RRA,
- month_avg: RRA,
- month_max: RRA,
- year_avg: RRA,
- year_max: RRA,
+ /// The magic number to identify the file type
+ pub magic: [u8; 8],
+ /// Hourly data (average values)
+ pub hour_avg: RRA,
+ /// Hourly data (maximum values)
+ pub hour_max: RRA,
+ /// Dayly data (average values)
+ pub day_avg: RRA,
+ /// Dayly data (maximum values)
+ pub day_max: RRA,
+ /// Weekly data (average values)
+ pub week_avg: RRA,
+ /// Weekly data (maximum values)
+ pub week_max: RRA,
+ /// Monthly data (average values)
+ pub month_avg: RRA,
+ /// Monthly data (maximum values)
+ pub month_max: RRA,
+ /// Yearly data (average values)
+ pub year_avg: RRA,
+ /// Yearly data (maximum values)
+ pub year_max: RRA,
}
impl RRD {
+ /// Create a new empty instance
pub fn new(dst: DST) -> Self {
let flags = match dst {
DST::Gauge => RRAFlags::DST_GAUGE,
}
}
+ /// Extract data from the archive
pub fn extract_data(
&self,
time: f64,
(start, reso, list)
}
+ /// Create instance from raw data, testing data len and magic number
pub fn from_raw(mut raw: &[u8]) -> Result<Self, std::io::Error> {
let expected_len = std::mem::size_of::<RRD>();
if raw.len() != expected_len {
Ok(rrd)
}
+ /// Load data from a file
pub fn load(path: &Path) -> Result<Self, std::io::Error> {
let raw = std::fs::read(path)?;
Self::from_raw(&raw)
}
+ /// Store data into a file (atomic replace file)
pub fn save(&self, filename: &Path, options: CreateOptions) -> Result<(), Error> {
let rrd_slice = unsafe {
std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of::<RRD>())
replace_file(filename, rrd_slice, options)
}
-
+ /// Update the value (in memory)
+ ///
+ /// Note: This does not call [Self::save].
pub fn update(&mut self, time: f64, value: f64) {
- self.hour_avg.update(time, value);
- self.hour_max.update(time, value);
- self.day_avg.update(time, value);
- self.day_max.update(time, value);
+ let mut log_error = true;
+
+ let mut update_rra = |rra: &mut RRA| {
+ if let Err(err) = rra.update(time, value) {
+ if log_error {
+ log::error!("rrd update failed: {}", err);
+ // we only log the first error, because it is very
+ // likely other calls produce the same error
+ log_error = false;
+ }
+ }
+ };
+
+ update_rra(&mut self.hour_avg);
+ update_rra(&mut self.hour_max);
+
+ update_rra(&mut self.day_avg);
+ update_rra(&mut self.day_max);
- self.week_avg.update(time, value);
- self.week_max.update(time, value);
+ update_rra(&mut self.week_avg);
+ update_rra(&mut self.week_max);
- self.month_avg.update(time, value);
- self.month_max.update(time, value);
+ update_rra(&mut self.month_avg);
+ update_rra(&mut self.month_max);
- self.year_avg.update(time, value);
- self.year_max.update(time, value);
+ update_rra(&mut self.year_avg);
+ update_rra(&mut self.year_max);
}
}