proxmox-backup/proxmox-rrd/src/rrd.rs

403 lines
13 KiB
Rust
Raw Normal View History

2021-10-06 10:19:54 +00:00
//! # Round Robin Database file format
2020-05-23 07:29:33 +00:00
use std::io::Read;
use std::path::Path;
2021-10-07 06:01:12 +00:00
use anyhow::{bail, Error};
2021-10-06 10:19:54 +00:00
use bitflags::bitflags;
2020-05-23 07:29:33 +00:00
2021-10-06 05:06:17 +00:00
use proxmox::tools::{fs::replace_file, fs::CreateOptions};
2020-05-23 07:29:33 +00:00
use proxmox_rrd_api_types::{RRDMode, RRDTimeFrameResolution};
2021-10-06 05:06:17 +00:00
/// The number of data entries per RRA
pub const RRD_DATA_ENTRIES: usize = 70;
2020-05-23 07:29:33 +00:00
2021-10-06 05:06:17 +00:00
/// Proxmox RRD file magic number
// 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];
2021-10-06 10:19:54 +00:00
use crate::DST;
2020-05-24 14:51:28 +00:00
bitflags!{
2021-10-06 10:19:54 +00:00
/// Flags to specify the data soure type and consolidation function
pub struct RRAFlags: u64 {
2020-05-24 14:51:28 +00:00
// Data Source Types
const DST_GAUGE = 1;
const DST_DERIVE = 2;
2020-05-25 06:14:30 +00:00
const DST_COUNTER = 4;
2020-05-24 14:51:28 +00:00
const DST_MASK = 255; // first 8 bits
// Consolidation Functions
const CF_AVERAGE = 1 << 8;
const CF_MAX = 2 << 8;
const CF_MASK = 255 << 8;
}
}
2021-10-06 10:19:54 +00:00
/// Round Robin Archive with [RRD_DATA_ENTRIES] data slots.
///
/// This data structure is used inside [RRD] and directly written to the
/// RRD files.
2020-05-23 07:29:33 +00:00
#[repr(C)]
2021-10-06 10:19:54 +00:00
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],
}
2020-05-24 14:51:28 +00:00
impl RRA {
fn new(flags: RRAFlags, resolution: u64) -> Self {
Self {
flags, resolution,
last_update: 0.0,
2020-05-24 14:51:28 +00:00
last_count: 0,
2020-05-24 15:03:02 +00:00
counter_value: f64::NAN,
2020-05-24 14:51:28 +00:00
data: [f64::NAN; RRD_DATA_ENTRIES],
}
}
fn delete_old(&mut self, time: f64) {
let epoch = time as u64;
let last_update = self.last_update as u64;
2020-05-24 14:51:28 +00:00
let reso = self.resolution;
2020-05-24 14:51:28 +00:00
let min_time = epoch - (RRD_DATA_ENTRIES as u64)*reso;
let min_time = (min_time/reso + 1)*reso;
let mut t = last_update.saturating_sub((RRD_DATA_ENTRIES as u64)*reso);
2020-05-24 14:51:28 +00:00
let mut index = ((t/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
for _ in 0..RRD_DATA_ENTRIES {
t += reso; index = (index + 1) % RRD_DATA_ENTRIES;
if t < min_time {
self.data[index] = f64::NAN;
} else {
break;
}
}
}
2020-05-25 06:14:30 +00:00
fn compute_new_value(&mut self, time: f64, value: f64) {
let epoch = time as u64;
let last_update = self.last_update as u64;
2020-05-24 14:51:28 +00:00
let reso = self.resolution;
2020-05-24 14:51:28 +00:00
let index = ((epoch/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
let last_index = ((last_update/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
2020-05-24 14:51:28 +00:00
if (epoch - (last_update as u64)) > reso || index != last_index {
2020-05-24 14:51:28 +00:00
self.last_count = 0;
}
let last_value = self.data[index];
if last_value.is_nan() {
self.last_count = 0;
}
2020-05-24 15:03:02 +00:00
let new_count = if self.last_count < u64::MAX {
self.last_count + 1
} else {
u64::MAX // should never happen
};
2020-05-24 14:51:28 +00:00
if self.last_count == 0 {
self.data[index] = value;
self.last_count = 1;
} else {
let new_value = if self.flags.contains(RRAFlags::CF_MAX) {
if last_value > value { last_value } else { value }
} else if self.flags.contains(RRAFlags::CF_AVERAGE) {
(last_value*(self.last_count as f64))/(new_count as f64)
+ value/(new_count as f64)
} else {
log::error!("rrdb update failed - unknown CF");
2020-05-24 14:51:28 +00:00
return;
};
self.data[index] = new_value;
self.last_count = new_count;
}
self.last_update = time;
2020-05-24 14:51:28 +00:00
}
2021-10-07 06:01:12 +00:00
// 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> {
2020-05-25 06:14:30 +00:00
if time <= self.last_update {
2021-10-07 06:01:12 +00:00
bail!("time in past ({} < {})", time, self.last_update);
2020-05-24 14:51:28 +00:00
}
2020-05-25 06:14:30 +00:00
2020-05-24 14:51:28 +00:00
if value.is_nan() {
2021-10-07 06:01:12 +00:00
bail!("new value is NAN");
2020-05-24 14:51:28 +00:00
}
2020-05-25 06:14:30 +00:00
// derive counter value
if self.flags.intersects(RRAFlags::DST_DERIVE | RRAFlags::DST_COUNTER) {
let time_diff = time - self.last_update;
let is_counter = self.flags.contains(RRAFlags::DST_COUNTER);
2020-05-25 06:14:30 +00:00
let diff = if self.counter_value.is_nan() {
0.0
} else if is_counter && value < 0.0 {
2021-10-07 06:01:12 +00:00
bail!("got negative value for counter");
} else if is_counter && value < self.counter_value {
2021-10-07 06:01:12 +00:00
// 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;
2021-10-07 06:01:12 +00:00
bail!("conter overflow/reset detected");
2020-05-25 06:14:30 +00:00
} else {
value - self.counter_value
2020-05-25 06:14:30 +00:00
};
self.counter_value = value;
value = diff/time_diff;
}
self.delete_old(time);
self.compute_new_value(time, value);
2021-10-07 06:01:12 +00:00
Ok(())
}
2020-05-23 07:29:33 +00:00
}
2021-10-06 10:19:54 +00:00
/// Round Robin Database file format with fixed number of [RRA]s
2020-05-23 07:29:33 +00:00
#[repr(C)]
// Note: Avoid alignment problems by using 8byte types only
pub struct RRD {
2021-10-06 10:19:54 +00:00
/// 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,
2020-05-23 07:29:33 +00:00
}
impl RRD {
2021-10-06 10:19:54 +00:00
/// Create a new empty instance
2020-05-24 14:51:28 +00:00
pub fn new(dst: DST) -> Self {
let flags = match dst {
DST::Gauge => RRAFlags::DST_GAUGE,
DST::Derive => RRAFlags::DST_DERIVE,
};
2020-05-23 07:29:33 +00:00
Self {
magic: PROXMOX_RRD_MAGIC_1_0,
2020-05-24 14:51:28 +00:00
hour_avg: RRA::new(
flags | RRAFlags::CF_AVERAGE,
RRDTimeFrameResolution::Hour as u64,
),
hour_max: RRA::new(
flags | RRAFlags::CF_MAX,
RRDTimeFrameResolution::Hour as u64,
),
day_avg: RRA::new(
flags | RRAFlags::CF_AVERAGE,
RRDTimeFrameResolution::Day as u64,
),
day_max: RRA::new(
flags | RRAFlags::CF_MAX,
RRDTimeFrameResolution::Day as u64,
),
week_avg: RRA::new(
flags | RRAFlags::CF_AVERAGE,
RRDTimeFrameResolution::Week as u64,
),
week_max: RRA::new(
flags | RRAFlags::CF_MAX,
RRDTimeFrameResolution::Week as u64,
),
month_avg: RRA::new(
flags | RRAFlags::CF_AVERAGE,
RRDTimeFrameResolution::Month as u64,
),
month_max: RRA::new(
flags | RRAFlags::CF_MAX,
RRDTimeFrameResolution::Month as u64,
),
year_avg: RRA::new(
flags | RRAFlags::CF_AVERAGE,
RRDTimeFrameResolution::Year as u64,
),
year_max: RRA::new(
flags | RRAFlags::CF_MAX,
RRDTimeFrameResolution::Year as u64,
),
2020-05-23 07:29:33 +00:00
}
}
2021-10-06 10:19:54 +00:00
/// Extract data from the archive
2020-05-23 07:29:33 +00:00
pub fn extract_data(
&self,
time: f64,
2020-05-23 07:29:33 +00:00
timeframe: RRDTimeFrameResolution,
mode: RRDMode,
2020-05-23 13:37:17 +00:00
) -> (u64, u64, Vec<Option<f64>>) {
2021-10-06 05:06:17 +00:00
let epoch = time as u64;
2020-05-23 07:29:33 +00:00
let reso = timeframe as u64;
let end = reso*(epoch/reso + 1);
2020-05-23 07:29:33 +00:00
let start = end - reso*(RRD_DATA_ENTRIES as u64);
let mut list = Vec::new();
2020-05-24 14:51:28 +00:00
let raa = match (mode, timeframe) {
(RRDMode::Average, RRDTimeFrameResolution::Hour) => &self.hour_avg,
(RRDMode::Max, RRDTimeFrameResolution::Hour) => &self.hour_max,
(RRDMode::Average, RRDTimeFrameResolution::Day) => &self.day_avg,
(RRDMode::Max, RRDTimeFrameResolution::Day) => &self.day_max,
(RRDMode::Average, RRDTimeFrameResolution::Week) => &self.week_avg,
(RRDMode::Max, RRDTimeFrameResolution::Week) => &self.week_max,
(RRDMode::Average, RRDTimeFrameResolution::Month) => &self.month_avg,
(RRDMode::Max, RRDTimeFrameResolution::Month) => &self.month_max,
(RRDMode::Average, RRDTimeFrameResolution::Year) => &self.year_avg,
(RRDMode::Max, RRDTimeFrameResolution::Year) => &self.year_max,
2020-05-23 07:29:33 +00:00
};
let rrd_end = reso*((raa.last_update as u64)/reso);
2020-05-24 14:51:28 +00:00
let rrd_start = rrd_end - reso*(RRD_DATA_ENTRIES as u64);
2020-05-23 07:29:33 +00:00
let mut t = start;
let mut index = ((t/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
for _ in 0..RRD_DATA_ENTRIES {
if t < rrd_start || t > rrd_end {
2020-05-23 13:37:17 +00:00
list.push(None);
2020-05-23 07:29:33 +00:00
} else {
2020-05-24 14:51:28 +00:00
let value = raa.data[index];
if value.is_nan() {
2020-05-23 13:37:17 +00:00
list.push(None);
2020-05-23 07:29:33 +00:00
} else {
2020-05-23 13:37:17 +00:00
list.push(Some(value));
2020-05-23 07:29:33 +00:00
}
}
t += reso; index = (index + 1) % RRD_DATA_ENTRIES;
}
(start, reso, list)
2020-05-23 07:29:33 +00:00
}
2021-10-06 10:19:54 +00:00
/// Create instance from raw data, testing data len and magic number
pub fn from_raw(mut raw: &[u8]) -> Result<Self, std::io::Error> {
2020-05-23 07:29:33 +00:00
let expected_len = std::mem::size_of::<RRD>();
if raw.len() != expected_len {
let msg = format!("wrong data size ({} != {})", raw.len(), expected_len);
return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
2020-05-23 07:29:33 +00:00
}
2020-05-23 07:29:33 +00:00
let mut rrd: RRD = unsafe { std::mem::zeroed() };
unsafe {
let rrd_slice = std::slice::from_raw_parts_mut(&mut rrd as *mut _ as *mut u8, expected_len);
raw.read_exact(rrd_slice)?;
}
if rrd.magic != PROXMOX_RRD_MAGIC_1_0 {
let msg = "wrong magic number".to_string();
return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
}
2020-05-23 07:29:33 +00:00
Ok(rrd)
}
2021-10-06 10:19:54 +00:00
/// 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)
2020-05-23 07:29:33 +00:00
}
2021-10-06 10:19:54 +00:00
/// Store data into a file (atomic replace file)
2021-10-06 05:06:17 +00:00
pub fn save(&self, filename: &Path, options: CreateOptions) -> Result<(), Error> {
2020-05-23 07:29:33 +00:00
let rrd_slice = unsafe {
std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of::<RRD>())
};
2021-10-06 05:06:17 +00:00
replace_file(filename, rrd_slice, options)
2020-05-23 07:29:33 +00:00
}
pub fn last_update(&self) -> f64 {
let mut last_update = 0.0;
{
let mut check_last_update = |rra: &RRA| {
if rra.last_update > last_update {
last_update = rra.last_update;
}
};
check_last_update(&self.hour_avg);
check_last_update(&self.hour_max);
check_last_update(&self.day_avg);
check_last_update(&self.day_max);
check_last_update(&self.week_avg);
check_last_update(&self.week_max);
check_last_update(&self.month_avg);
check_last_update(&self.month_max);
check_last_update(&self.year_avg);
check_last_update(&self.year_max);
}
last_update
}
2021-10-06 10:19:54 +00:00
/// Update the value (in memory)
///
/// Note: This does not call [Self::save].
pub fn update(&mut self, time: f64, value: f64) {
2021-10-07 06:01:12 +00:00
let mut log_error = true;
2021-10-07 06:01:12 +00:00
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;
}
}
};
2021-10-07 06:01:12 +00:00
update_rra(&mut self.hour_avg);
update_rra(&mut self.hour_max);
2021-10-07 06:01:12 +00:00
update_rra(&mut self.day_avg);
update_rra(&mut self.day_max);
2021-10-07 06:01:12 +00:00
update_rra(&mut self.week_avg);
update_rra(&mut self.week_max);
2020-05-23 07:29:33 +00:00
2021-10-07 06:01:12 +00:00
update_rra(&mut self.month_avg);
update_rra(&mut self.month_max);
2021-10-07 06:01:12 +00:00
update_rra(&mut self.year_avg);
update_rra(&mut self.year_max);
2020-05-23 07:29:33 +00:00
}
}