diff --git a/Cargo.toml b/Cargo.toml index 4c5d2947..a582a0b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "pbs-fuse-loop", "pbs-runtime", "proxmox-rest-server", + "proxmox-rrd", "proxmox-systemd", "pbs-tape", "pbs-tools", @@ -107,6 +108,7 @@ pbs-config = { path = "pbs-config" } pbs-datastore = { path = "pbs-datastore" } pbs-runtime = { path = "pbs-runtime" } proxmox-rest-server = { path = "proxmox-rest-server" } +proxmox-rrd = { path = "proxmox-rrd" } proxmox-systemd = { path = "proxmox-systemd" } pbs-tools = { path = "pbs-tools" } pbs-tape = { path = "pbs-tape" } diff --git a/Makefile b/Makefile index 7e8dad37..d6951c9a 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ SUBCRATES := \ pbs-fuse-loop \ pbs-runtime \ proxmox-rest-server \ + proxmox-rrd \ proxmox-systemd \ pbs-tape \ pbs-tools \ diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs index f7521b02..df068b1b 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -358,33 +358,6 @@ pub struct APTUpdateInfo { pub extra_info: Option, } -#[api()] -#[derive(Copy, Clone, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum RRDMode { - /// Maximum - Max, - /// Average - Average, -} - - -#[api()] -#[repr(u64)] -#[derive(Copy, Clone, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum RRDTimeFrameResolution { - /// 1 min => last 70 minutes - Hour = 60, - /// 30 min => last 35 hours - Day = 60*30, - /// 3 hours => about 8 days - Week = 60*180, - /// 12 hours => last 35 days - Month = 60*720, - /// 1 week => last 490 days - Year = 60*10080, -} #[api()] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] diff --git a/proxmox-rrd/Cargo.toml b/proxmox-rrd/Cargo.toml new file mode 100644 index 00000000..c2b2d213 --- /dev/null +++ b/proxmox-rrd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "proxmox-rrd" +version = "0.1.0" +authors = ["Dietmar Maurer "] +edition = "2018" +description = "Simple RRD database implementation." + +[dependencies] +anyhow = "1.0" +bitflags = "1.2.1" +serde = { version = "1.0", features = [] } + +proxmox = { version = "0.13.5", features = ["api-macro"] } diff --git a/proxmox-rrd/src/cache.rs b/proxmox-rrd/src/cache.rs new file mode 100644 index 00000000..c87e49fd --- /dev/null +++ b/proxmox-rrd/src/cache.rs @@ -0,0 +1,111 @@ +use std::path::{Path, PathBuf}; +use std::collections::HashMap; +use std::sync::{RwLock}; + +use anyhow::{format_err, Error}; + +use proxmox::tools::fs::{create_path, CreateOptions}; + +use crate::{RRDMode, RRDTimeFrameResolution}; + +use super::*; + +/// RRD cache - keep RRD data in RAM, but write updates to disk +/// +/// This cache is designed to run as single instance (no concurrent +/// access from other processes). +pub struct RRDCache { + basedir: PathBuf, + file_options: CreateOptions, + dir_options: CreateOptions, + cache: RwLock>, +} + +impl RRDCache { + + /// Creates a new instance + pub fn new>( + basedir: P, + file_options: Option, + dir_options: Option, + ) -> Self { + let basedir = basedir.as_ref().to_owned(); + Self { + basedir, + file_options: file_options.unwrap_or_else(|| CreateOptions::new()), + dir_options: dir_options.unwrap_or_else(|| CreateOptions::new()), + cache: RwLock::new(HashMap::new()), + } + } +} + +impl RRDCache { + + /// Create rrdd stat dir with correct permission + pub fn create_rrdb_dir(&self) -> Result<(), Error> { + + create_path(&self.basedir, Some(self.dir_options.clone()), Some(self.file_options.clone())) + .map_err(|err: Error| format_err!("unable to create rrdb stat dir - {}", err))?; + + Ok(()) + } + + /// Update data in RAM and write file back to disk (if `save` is set) + pub fn update_value( + &self, + rel_path: &str, + value: f64, + dst: DST, + save: bool, + ) -> Result<(), Error> { + + let mut path = self.basedir.clone(); + path.push(rel_path); + + std::fs::create_dir_all(path.parent().unwrap())?; // fixme?? + + let mut map = self.cache.write().unwrap(); + let now = proxmox::tools::time::epoch_f64(); + + if let Some(rrd) = map.get_mut(rel_path) { + rrd.update(now, value); + if save { rrd.save(&path, self.file_options.clone())?; } + } else { + let mut rrd = match RRD::load(&path) { + Ok(rrd) => rrd, + Err(err) => { + if err.kind() != std::io::ErrorKind::NotFound { + eprintln!("overwriting RRD file {:?}, because of load error: {}", path, err); + } + RRD::new(dst) + }, + }; + rrd.update(now, value); + if save { + rrd.save(&path, self.file_options.clone())?; + } + map.insert(rel_path.into(), rrd); + } + + Ok(()) + } + + /// Extract data from cached RRD + pub fn extract_cached_data( + &self, + base: &str, + name: &str, + now: f64, + timeframe: RRDTimeFrameResolution, + mode: RRDMode, + ) -> Option<(u64, u64, Vec>)> { + + let map = self.cache.read().unwrap(); + + match map.get(&format!("{}/{}", base, name)) { + Some(rrd) => Some(rrd.extract_data(now, timeframe, mode)), + None => None, + } + } + +} diff --git a/proxmox-rrd/src/lib.rs b/proxmox-rrd/src/lib.rs new file mode 100644 index 00000000..d6ba54c9 --- /dev/null +++ b/proxmox-rrd/src/lib.rs @@ -0,0 +1,37 @@ +mod rrd; +pub use rrd::*; + +mod cache; +pub use cache::*; + +use serde::{Deserialize, Serialize}; +use proxmox::api::api; + +#[api()] +#[derive(Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +/// RRD consolidation mode +pub enum RRDMode { + /// Maximum + Max, + /// Average + Average, +} + +#[api()] +#[repr(u64)] +#[derive(Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +/// RRD time frame resolution +pub enum RRDTimeFrameResolution { + /// 1 min => last 70 minutes + Hour = 60, + /// 30 min => last 35 hours + Day = 60*30, + /// 3 hours => about 8 days + Week = 60*180, + /// 12 hours => last 35 days + Month = 60*720, + /// 1 week => last 490 days + Year = 60*10080, +} diff --git a/src/rrd/rrd.rs b/proxmox-rrd/src/rrd.rs similarity index 93% rename from src/rrd/rrd.rs rename to proxmox-rrd/src/rrd.rs index b1780307..19a6deba 100644 --- a/src/rrd/rrd.rs +++ b/proxmox-rrd/src/rrd.rs @@ -3,17 +3,21 @@ use std::path::Path; use anyhow::Error; -use pbs_api_types::{RRDMode, RRDTimeFrameResolution}; +use proxmox::tools::{fs::replace_file, fs::CreateOptions}; +use crate::{RRDMode, RRDTimeFrameResolution}; + +/// The number of data entries per RRA pub const RRD_DATA_ENTRIES: usize = 70; +/// 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]; use bitflags::bitflags; bitflags!{ - pub struct RRAFlags: u64 { + struct RRAFlags: u64 { // Data Source Types const DST_GAUGE = 1; const DST_DERIVE = 2; @@ -27,6 +31,7 @@ bitflags!{ } } +/// RRD data source tyoe pub enum DST { Gauge, Derive, @@ -227,6 +232,7 @@ impl RRD { timeframe: RRDTimeFrameResolution, mode: RRDMode, ) -> (u64, u64, Vec>) { + let epoch = time as u64; let reso = timeframe as u64; @@ -296,25 +302,11 @@ impl RRD { Self::from_raw(&raw) } - pub fn save(&self, filename: &Path) -> Result<(), Error> { - use proxmox::tools::{fs::replace_file, fs::CreateOptions}; - + 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::()) }; - - let backup_user = pbs_config::backup_user()?; - let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644); - // set the correct owner/group/permissions while saving file - // owner(rw) = backup, group(r)= backup - let options = CreateOptions::new() - .perm(mode) - .owner(backup_user.uid) - .group(backup_user.gid); - - replace_file(filename, rrd_slice, options)?; - - Ok(()) + replace_file(filename, rrd_slice, options) } diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs index 7e9a0ee0..628f11de 100644 --- a/src/api2/admin/datastore.rs +++ b/src/api2/admin/datastore.rs @@ -26,10 +26,11 @@ use proxmox::{http_err, identity, list_subdirs_api_method, sortable}; use pxar::accessor::aio::Accessor; use pxar::EntryKind; +use proxmox_rrd::{RRDMode, RRDTimeFrameResolution}; use pbs_api_types::{ Authid, BackupContent, Counts, CryptMode, DataStoreListItem, GarbageCollectionStatus, GroupListItem, SnapshotListItem, SnapshotVerifyState, PruneOptions, - DataStoreStatus, RRDMode, RRDTimeFrameResolution, + DataStoreStatus, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, UPID_SCHEMA, diff --git a/src/api2/node/rrd.rs b/src/api2/node/rrd.rs index f689232d..744e7c1c 100644 --- a/src/api2/node/rrd.rs +++ b/src/api2/node/rrd.rs @@ -3,9 +3,10 @@ use serde_json::{Value, json}; use proxmox::api::{api, Permission, Router}; -use pbs_api_types::{RRDMode, RRDTimeFrameResolution, NODE_SCHEMA, PRIV_SYS_AUDIT}; +use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT}; +use proxmox_rrd::{RRDMode, RRDTimeFrameResolution, RRD_DATA_ENTRIES}; -use crate::rrd::{extract_cached_data, RRD_DATA_ENTRIES}; +use crate::RRD_CACHE; pub fn create_value_from_rrd( basedir: &str, @@ -18,7 +19,7 @@ pub fn create_value_from_rrd( let now = proxmox::tools::time::epoch_f64(); for name in list { - let (start, reso, list) = match extract_cached_data(basedir, name, now, timeframe, cf) { + let (start, reso, list) = match RRD_CACHE.extract_cached_data(basedir, name, now, timeframe, cf) { Some(result) => result, None => continue, }; diff --git a/src/api2/status.rs b/src/api2/status.rs index 13d1f74e..995e12cc 100644 --- a/src/api2/status.rs +++ b/src/api2/status.rs @@ -15,13 +15,15 @@ use proxmox::api::{ }; use pbs_api_types::{ - DATASTORE_SCHEMA, RRDMode, RRDTimeFrameResolution, Authid, - PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, + Authid, DATASTORE_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, }; +use proxmox_rrd::{RRDMode, RRDTimeFrameResolution}; + use pbs_datastore::DataStore; use pbs_config::CachedUserInfo; use crate::tools::statistics::{linear_regression}; +use crate::RRD_CACHE; #[api( returns: { @@ -122,7 +124,7 @@ pub fn datastore_status( let rrd_dir = format!("datastore/{}", store); let now = proxmox::tools::time::epoch_f64(); - let get_rrd = |what: &str| crate::rrd::extract_cached_data( + let get_rrd = |what: &str| RRD_CACHE.extract_cached_data( &rrd_dir, what, now, diff --git a/src/bin/proxmox-backup-api.rs b/src/bin/proxmox-backup-api.rs index a0eef382..da9ae5dd 100644 --- a/src/bin/proxmox-backup-api.rs +++ b/src/bin/proxmox-backup-api.rs @@ -17,6 +17,7 @@ use proxmox_rest_server::{daemon, AuthError, ApiConfig, RestServer, RestEnvironm use proxmox_backup::server::auth::check_pbs_auth; use proxmox_backup::auth_helpers::*; +use proxmox_backup::RRD_CACHE; use proxmox_backup::config; fn main() { @@ -74,7 +75,8 @@ async fn run() -> Result<(), Error> { proxmox_backup::server::create_run_dir()?; - proxmox_backup::rrd::create_rrdb_dir()?; + RRD_CACHE.create_rrdb_dir()?; + proxmox_backup::server::jobstate::create_jobstate_dir()?; proxmox_backup::tape::create_tape_status_dir()?; proxmox_backup::tape::create_drive_state_dir()?; diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs index 8e4bdcac..9199ebae 100644 --- a/src/bin/proxmox-backup-proxy.rs +++ b/src/bin/proxmox-backup-proxy.rs @@ -24,12 +24,15 @@ use proxmox::tools::fs::CreateOptions; use pbs_tools::task_log; use pbs_datastore::DataStore; +use proxmox_rrd::DST; + use proxmox_rest_server::{ rotate_task_log_archive, extract_cookie , AuthError, ApiConfig, RestServer, RestEnvironment, ServerAdapter, WorkerTask, }; use proxmox_backup::{ + RRD_CACHE, server::{ auth::check_pbs_auth, jobstate::{ @@ -895,15 +898,13 @@ async fn run_stat_generator() { } fn rrd_update_gauge(name: &str, value: f64, save: bool) { - use proxmox_backup::rrd; - if let Err(err) = rrd::update_value(name, value, rrd::DST::Gauge, save) { + if let Err(err) = RRD_CACHE.update_value(name, value, DST::Gauge, save) { eprintln!("rrd::update_value '{}' failed - {}", name, err); } } fn rrd_update_derive(name: &str, value: f64, save: bool) { - use proxmox_backup::rrd; - if let Err(err) = rrd::update_value(name, value, rrd::DST::Derive, save) { + if let Err(err) = RRD_CACHE.update_value(name, value, DST::Derive, save) { eprintln!("rrd::update_value '{}' failed - {}", name, err); } } diff --git a/src/lib.rs b/src/lib.rs index 75661ab8..98b6b987 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,11 @@ use std::path::PathBuf; +use proxmox::tools::fs::CreateOptions; + use pbs_buildcfg::configdir; use pbs_tools::cert::CertInfo; +use proxmox_rrd::RRDCache; #[macro_use] pub mod tools; @@ -25,8 +28,6 @@ pub mod auth_helpers; pub mod auth; -pub mod rrd; - pub mod tape; pub mod acme; @@ -37,3 +38,23 @@ pub mod client_helpers; pub fn cert_info() -> Result { CertInfo::from_path(PathBuf::from(configdir!("/proxy.pem"))) } + +lazy_static::lazy_static!{ + /// Proxmox Backup Server RRD cache instance + pub static ref RRD_CACHE: RRDCache = { + let backup_user = pbs_config::backup_user().unwrap(); + let file_options = CreateOptions::new() + .owner(backup_user.uid) + .group(backup_user.gid); + + let dir_options = CreateOptions::new() + .owner(backup_user.uid) + .group(backup_user.gid); + + RRDCache::new( + "/var/lib/proxmox-backup/rrdb", + Some(file_options), + Some(dir_options), + ) + }; +} diff --git a/src/rrd/cache.rs b/src/rrd/cache.rs deleted file mode 100644 index d6b79ac0..00000000 --- a/src/rrd/cache.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::path::PathBuf; -use std::collections::HashMap; -use std::sync::{RwLock}; - -use anyhow::{format_err, Error}; -use lazy_static::lazy_static; - -use proxmox::tools::fs::{create_path, CreateOptions}; - -use pbs_api_types::{RRDMode, RRDTimeFrameResolution}; - -use super::*; - -const PBS_RRD_BASEDIR: &str = "/var/lib/proxmox-backup/rrdb"; - -lazy_static!{ - static ref RRD_CACHE: RwLock> = { - RwLock::new(HashMap::new()) - }; -} - -/// Create rrdd stat dir with correct permission -pub fn create_rrdb_dir() -> Result<(), Error> { - - let backup_user = pbs_config::backup_user()?; - let opts = CreateOptions::new() - .owner(backup_user.uid) - .group(backup_user.gid); - - create_path(PBS_RRD_BASEDIR, None, Some(opts)) - .map_err(|err: Error| format_err!("unable to create rrdb stat dir - {}", err))?; - - Ok(()) -} - -pub fn update_value(rel_path: &str, value: f64, dst: DST, save: bool) -> Result<(), Error> { - - let mut path = PathBuf::from(PBS_RRD_BASEDIR); - path.push(rel_path); - - std::fs::create_dir_all(path.parent().unwrap())?; - - let mut map = RRD_CACHE.write().unwrap(); - let now = proxmox::tools::time::epoch_f64(); - - if let Some(rrd) = map.get_mut(rel_path) { - rrd.update(now, value); - if save { rrd.save(&path)?; } - } else { - let mut rrd = match RRD::load(&path) { - Ok(rrd) => rrd, - Err(err) => { - if err.kind() != std::io::ErrorKind::NotFound { - eprintln!("overwriting RRD file {:?}, because of load error: {}", path, err); - } - RRD::new(dst) - }, - }; - rrd.update(now, value); - if save { rrd.save(&path)?; } - map.insert(rel_path.into(), rrd); - } - - Ok(()) -} - -pub fn extract_cached_data( - base: &str, - name: &str, - now: f64, - timeframe: RRDTimeFrameResolution, - mode: RRDMode, -) -> Option<(u64, u64, Vec>)> { - - let map = RRD_CACHE.read().unwrap(); - - match map.get(&format!("{}/{}", base, name)) { - Some(rrd) => Some(rrd.extract_data(now, timeframe, mode)), - None => None, - } -} diff --git a/src/rrd/mod.rs b/src/rrd/mod.rs deleted file mode 100644 index 03e4c9de..00000000 --- a/src/rrd/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[allow(clippy::module_inception)] -mod rrd; -pub use rrd::*; -mod cache; -pub use cache::*;