move RRD code into proxmox-rrd crate
This commit is contained in:
@ -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,
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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()?;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
25
src/lib.rs
25
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, anyhow::Error> {
|
||||
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),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -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<HashMap<String, RRD>> = {
|
||||
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<Option<f64>>)> {
|
||||
|
||||
let map = RRD_CACHE.read().unwrap();
|
||||
|
||||
match map.get(&format!("{}/{}", base, name)) {
|
||||
Some(rrd) => Some(rrd.extract_data(now, timeframe, mode)),
|
||||
None => None,
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod rrd;
|
||||
pub use rrd::*;
|
||||
mod cache;
|
||||
pub use cache::*;
|
337
src/rrd/rrd.rs
337
src/rrd/rrd.rs
@ -1,337 +0,0 @@
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
use pbs_api_types::{RRDMode, RRDTimeFrameResolution};
|
||||
|
||||
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;
|
||||
|
||||
bitflags!{
|
||||
pub struct RRAFlags: u64 {
|
||||
// Data Source Types
|
||||
const DST_GAUGE = 1;
|
||||
const DST_DERIVE = 2;
|
||||
const DST_COUNTER = 4;
|
||||
const DST_MASK = 255; // first 8 bits
|
||||
|
||||
// Consolidation Functions
|
||||
const CF_AVERAGE = 1 << 8;
|
||||
const CF_MAX = 2 << 8;
|
||||
const CF_MASK = 255 << 8;
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DST {
|
||||
Gauge,
|
||||
Derive,
|
||||
}
|
||||
|
||||
#[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],
|
||||
}
|
||||
|
||||
impl RRA {
|
||||
fn new(flags: RRAFlags, resolution: u64) -> Self {
|
||||
Self {
|
||||
flags, resolution,
|
||||
last_update: 0.0,
|
||||
last_count: 0,
|
||||
counter_value: f64::NAN,
|
||||
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;
|
||||
let reso = self.resolution;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_new_value(&mut self, time: f64, value: f64) {
|
||||
let epoch = time as u64;
|
||||
let last_update = self.last_update as u64;
|
||||
let reso = self.resolution;
|
||||
|
||||
let index = ((epoch/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
|
||||
let last_index = ((last_update/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
|
||||
|
||||
if (epoch - (last_update as u64)) > reso || index != last_index {
|
||||
self.last_count = 0;
|
||||
}
|
||||
|
||||
let last_value = self.data[index];
|
||||
if last_value.is_nan() {
|
||||
self.last_count = 0;
|
||||
}
|
||||
|
||||
let new_count = if self.last_count < u64::MAX {
|
||||
self.last_count + 1
|
||||
} else {
|
||||
u64::MAX // should never happen
|
||||
};
|
||||
|
||||
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 {
|
||||
eprintln!("rrdb update failed - unknown CF");
|
||||
return;
|
||||
};
|
||||
self.data[index] = new_value;
|
||||
self.last_count = new_count;
|
||||
}
|
||||
self.last_update = time;
|
||||
}
|
||||
|
||||
fn update(&mut self, time: f64, mut value: f64) {
|
||||
|
||||
if time <= self.last_update {
|
||||
eprintln!("rrdb update failed - time in past ({} < {})", time, self.last_update);
|
||||
}
|
||||
|
||||
if value.is_nan() {
|
||||
eprintln!("rrdb update failed - new value is NAN");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
} else if is_counter && value < self.counter_value {
|
||||
// Note: We do not try automatic overflow corrections
|
||||
self.counter_value = value;
|
||||
eprintln!("rrdb update failed - conter overflow/reset detected");
|
||||
return;
|
||||
} else {
|
||||
value - self.counter_value
|
||||
};
|
||||
self.counter_value = value;
|
||||
value = diff/time_diff;
|
||||
}
|
||||
|
||||
self.delete_old(time);
|
||||
self.compute_new_value(time, value);
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl RRD {
|
||||
|
||||
pub fn new(dst: DST) -> Self {
|
||||
let flags = match dst {
|
||||
DST::Gauge => RRAFlags::DST_GAUGE,
|
||||
DST::Derive => RRAFlags::DST_DERIVE,
|
||||
};
|
||||
|
||||
Self {
|
||||
magic: PROXMOX_RRD_MAGIC_1_0,
|
||||
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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_data(
|
||||
&self,
|
||||
time: f64,
|
||||
timeframe: RRDTimeFrameResolution,
|
||||
mode: RRDMode,
|
||||
) -> (u64, u64, Vec<Option<f64>>) {
|
||||
let epoch = time as u64;
|
||||
let reso = timeframe as u64;
|
||||
|
||||
let end = reso*(epoch/reso + 1);
|
||||
let start = end - reso*(RRD_DATA_ENTRIES as u64);
|
||||
|
||||
let mut list = Vec::new();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let rrd_end = reso*((raa.last_update as u64)/reso);
|
||||
let rrd_start = rrd_end - reso*(RRD_DATA_ENTRIES as u64);
|
||||
|
||||
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 {
|
||||
list.push(None);
|
||||
} else {
|
||||
let value = raa.data[index];
|
||||
if value.is_nan() {
|
||||
list.push(None);
|
||||
} else {
|
||||
list.push(Some(value));
|
||||
}
|
||||
}
|
||||
t += reso; index = (index + 1) % RRD_DATA_ENTRIES;
|
||||
}
|
||||
|
||||
(start, reso, list)
|
||||
}
|
||||
|
||||
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 {
|
||||
let msg = format!("wrong data size ({} != {})", raw.len(), expected_len);
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
Ok(rrd)
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> Result<Self, std::io::Error> {
|
||||
let raw = std::fs::read(path)?;
|
||||
Self::from_raw(&raw)
|
||||
}
|
||||
|
||||
pub fn save(&self, filename: &Path) -> Result<(), Error> {
|
||||
use proxmox::tools::{fs::replace_file, fs::CreateOptions};
|
||||
|
||||
let rrd_slice = unsafe {
|
||||
std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of::<RRD>())
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
self.week_avg.update(time, value);
|
||||
self.week_max.update(time, value);
|
||||
|
||||
self.month_avg.update(time, value);
|
||||
self.month_max.update(time, value);
|
||||
|
||||
self.year_avg.update(time, value);
|
||||
self.year_max.update(time, value);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user