2020-05-23 07:29:33 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::sync::{RwLock};
|
|
|
|
|
|
|
|
use anyhow::{format_err, Error};
|
|
|
|
use lazy_static::lazy_static;
|
2020-05-23 13:37:17 +00:00
|
|
|
use serde_json::{json, Value};
|
2020-05-23 07:29:33 +00:00
|
|
|
|
|
|
|
use proxmox::tools::fs::{create_path, CreateOptions};
|
|
|
|
|
2020-05-23 09:10:02 +00:00
|
|
|
use crate::api2::types::{RRDMode, RRDTimeFrameResolution};
|
2020-06-10 10:02:56 +00:00
|
|
|
use crate::tools::epoch_now_f64;
|
2020-05-23 09:10:02 +00:00
|
|
|
|
2020-05-23 07:29:33 +00:00
|
|
|
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 = crate::backup::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(())
|
|
|
|
}
|
|
|
|
|
2020-05-29 07:16:13 +00:00
|
|
|
pub fn update_value(rel_path: &str, value: f64, dst: DST, save: bool) -> Result<(), Error> {
|
2020-05-23 07:29:33 +00:00
|
|
|
|
|
|
|
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();
|
2020-06-10 10:02:56 +00:00
|
|
|
let now = epoch_now_f64()?;
|
2020-05-23 12:03:44 +00:00
|
|
|
|
2020-05-23 07:29:33 +00:00
|
|
|
if let Some(rrd) = map.get_mut(rel_path) {
|
|
|
|
rrd.update(now, value);
|
2020-05-29 07:16:13 +00:00
|
|
|
if save { rrd.save(&path)?; }
|
2020-05-23 07:29:33 +00:00
|
|
|
} else {
|
|
|
|
let mut rrd = match RRD::load(&path) {
|
|
|
|
Ok(rrd) => rrd,
|
2020-05-25 08:18:53 +00:00
|
|
|
Err(err) => {
|
|
|
|
if err.kind() != std::io::ErrorKind::NotFound {
|
2020-05-25 08:30:04 +00:00
|
|
|
eprintln!("overwriting RRD file {:?}, because of load error: {}", path, err);
|
2020-05-25 08:18:53 +00:00
|
|
|
}
|
|
|
|
RRD::new(dst)
|
|
|
|
},
|
2020-05-23 07:29:33 +00:00
|
|
|
};
|
|
|
|
rrd.update(now, value);
|
2020-05-29 07:16:13 +00:00
|
|
|
if save { rrd.save(&path)?; }
|
2020-05-23 07:29:33 +00:00
|
|
|
map.insert(rel_path.into(), rrd);
|
|
|
|
}
|
2020-05-23 12:03:44 +00:00
|
|
|
|
2020-05-23 07:29:33 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-09 08:01:11 +00:00
|
|
|
/// extracts the lists of the given items and a list of timestamps
|
|
|
|
pub fn extract_lists(
|
|
|
|
base: &str,
|
|
|
|
items: &[&str],
|
|
|
|
timeframe: RRDTimeFrameResolution,
|
|
|
|
mode: RRDMode,
|
|
|
|
) -> Result<(Vec<u64>, HashMap<String, Vec<Option<f64>>>), Error> {
|
|
|
|
|
|
|
|
let now = now()?;
|
|
|
|
|
|
|
|
let map = RRD_CACHE.read().unwrap();
|
|
|
|
|
|
|
|
let mut result = HashMap::new();
|
|
|
|
|
|
|
|
let mut times = Vec::new();
|
|
|
|
|
|
|
|
for name in items.iter() {
|
|
|
|
let rrd = match map.get(&format!("{}/{}", base, name)) {
|
|
|
|
Some(rrd) => rrd,
|
|
|
|
None => continue,
|
|
|
|
};
|
|
|
|
let (start, reso, list) = rrd.extract_data(now, timeframe, mode);
|
|
|
|
|
|
|
|
result.insert(name.to_string(), list);
|
|
|
|
|
|
|
|
if times.len() == 0 {
|
|
|
|
let mut t = start;
|
|
|
|
for _ in 0..RRD_DATA_ENTRIES {
|
|
|
|
times.push(t);
|
|
|
|
t += reso;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((times, result))
|
|
|
|
}
|
|
|
|
|
2020-05-23 07:29:33 +00:00
|
|
|
pub fn extract_data(
|
2020-05-23 12:03:44 +00:00
|
|
|
base: &str,
|
|
|
|
items: &[&str],
|
|
|
|
timeframe: RRDTimeFrameResolution,
|
|
|
|
mode: RRDMode,
|
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2020-06-10 10:02:56 +00:00
|
|
|
let now = epoch_now_f64()?;
|
2020-05-23 12:03:44 +00:00
|
|
|
|
|
|
|
let map = RRD_CACHE.read().unwrap();
|
|
|
|
|
2020-05-23 13:37:17 +00:00
|
|
|
let mut result = Vec::new();
|
|
|
|
|
2020-05-23 12:03:44 +00:00
|
|
|
for name in items.iter() {
|
2020-05-24 14:51:28 +00:00
|
|
|
let rrd = match map.get(&format!("{}/{}", base, name)) {
|
|
|
|
Some(rrd) => rrd,
|
|
|
|
None => continue,
|
|
|
|
};
|
2020-05-23 13:37:17 +00:00
|
|
|
let (start, reso, list) = rrd.extract_data(now, timeframe, mode);
|
|
|
|
let mut t = start;
|
|
|
|
for index in 0..RRD_DATA_ENTRIES {
|
|
|
|
if result.len() <= index {
|
|
|
|
if let Some(value) = list[index] {
|
|
|
|
result.push(json!({ "time": t, *name: value }));
|
|
|
|
} else {
|
|
|
|
result.push(json!({ "time": t }));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if let Some(value) = list[index] {
|
|
|
|
result[index][name] = value.into();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t += reso;
|
2020-05-23 12:03:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 13:37:17 +00:00
|
|
|
Ok(result.into())
|
2020-05-23 12:03:44 +00:00
|
|
|
}
|