]> git.proxmox.com Git - proxmox-backup.git/blobdiff - proxmox-rrd/src/rrd.rs
proxmox-rrd: cleanup error handling
[proxmox-backup.git] / proxmox-rrd / src / rrd.rs
index 19a6debaa5da87cc147fde4955ba5dbbf2dbd438..f4c0890985b9427cef678f33e390e1775ea53590 100644 (file)
@@ -1,11 +1,14 @@
+//! # 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;
@@ -14,10 +17,11 @@ 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;
@@ -31,20 +35,24 @@ bitflags!{
     }
 }
 
-/// 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 {
@@ -110,7 +118,7 @@ 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;
@@ -119,15 +127,15 @@ impl RRA {
         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
@@ -138,13 +146,13 @@ impl RRA {
             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
             };
@@ -154,27 +162,42 @@ impl RRA {
 
         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,
@@ -226,6 +249,7 @@ impl RRD {
         }
     }
 
+    /// Extract data from the archive
     pub fn extract_data(
         &self,
         time: f64,
@@ -276,6 +300,7 @@ impl RRD {
         (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 {
@@ -297,11 +322,13 @@ impl RRD {
         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>())
@@ -309,21 +336,37 @@ impl 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);
     }
 }