From bda48e04da92b9d653fa0422f6c9c638b04d0a1c Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Tue, 9 Jun 2020 10:01:13 +0200 Subject: [PATCH] api2: add status/datastore-usages api call returns a list of the datastores and their usages, a list of usages of the past month (for the gui) and an estimation of when its full (using the linear regression) Signed-off-by: Dominik Csapak --- src/api2.rs | 1 + src/api2/status.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/api2/status.rs diff --git a/src/api2.rs b/src/api2.rs index 5bcc709c..1e5bc4a9 100644 --- a/src/api2.rs +++ b/src/api2.rs @@ -24,6 +24,7 @@ pub const SUBDIRS: SubdirMap = &[ ("nodes", &NODES_ROUTER), ("pull", &pull::ROUTER), ("reader", &reader::ROUTER), + ("status", &status::ROUTER), ("subscription", &subscription::ROUTER), ("version", &version::ROUTER), ]; diff --git a/src/api2/status.rs b/src/api2/status.rs new file mode 100644 index 00000000..5aca93fb --- /dev/null +++ b/src/api2/status.rs @@ -0,0 +1,155 @@ +use proxmox::list_subdirs_api_method; + +use anyhow::{Error}; +use serde_json::{json, Value}; + +use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, UserInformation, SubdirMap}; + +use crate::api2::types::{DATASTORE_SCHEMA, RRDMode, RRDTimeFrameResolution}; +use crate::backup::{DataStore}; +use crate::config::datastore; +use crate::tools::statistics::{linear_regression}; +use crate::config::cached_user_info::CachedUserInfo; +use crate::config::acl::{ + PRIV_DATASTORE_AUDIT, + PRIV_DATASTORE_BACKUP, +}; + +#[api( + returns: { + description: "Lists the Status of the Datastores.", + type: Array, + items: { + description: "Status of a Datastore", + type: Object, + properties: { + store: { + schema: DATASTORE_SCHEMA, + }, + total: { + type: Integer, + description: "The Size of the underlying storage in bytes", + }, + used: { + type: Integer, + description: "The used bytes of the underlying storage", + }, + avail: { + type: Integer, + description: "The available bytes of the underlying storage", + }, + history: { + type: Array, + description: "A list of usages of the past (last Month).", + items: { + type: Number, + description: "The usage of a time in the past. Either null or between 0.0 and 1.0.", + } + }, + "estimated-full-date": { + type: Integer, + optional: true, + description: "Estimation of the UNIX epoch when the storage will be full.\ + This is calculated via a simple Linear Regression (Least Squares)\ + of RRD data of the last Month. Missing if there are not enough data points yet.\ + If the estimate lies in the past, the usage is decreasing.", + }, + }, + }, + }, +)] +/// List Datastore usages and estimates +fn datastore_status( + _param: Value, + _info: &ApiMethod, + rpcenv: &mut dyn RpcEnvironment, + ) -> Result { + + let (config, _digest) = datastore::config()?; + + let username = rpcenv.get_user().unwrap(); + let user_info = CachedUserInfo::new()?; + + let mut list = Vec::new(); + + for (store, (_, _)) in &config.sections { + let user_privs = user_info.lookup_privs(&username, &["datastore", &store]); + let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0; + if !allowed { + continue; + } + + let datastore = DataStore::lookup_datastore(&store)?; + let status = crate::tools::disks::disk_usage(&datastore.base_path())?; + + let mut entry = json!({ + "store": store, + "total": status.total, + "used": status.used, + "avail": status.avail, + }); + + let rrd_dir = format!("datastore/{}", store); + + let (times, lists) = crate::rrd::extract_lists( + &rrd_dir, + &[ "total", "used", ], + RRDTimeFrameResolution::Month, + RRDMode::Average, + )?; + + if !lists.contains_key("total") || !lists.contains_key("used") { + // we do not have the info, so we can skip calculating + continue; + } + + let mut usage_list: Vec = Vec::new(); + let mut time_list: Vec = Vec::new(); + let mut history = Vec::new(); + + for (idx, used) in lists["used"].iter().enumerate() { + let total = if idx < lists["total"].len() { + lists["total"][idx] + } else { + None + }; + + match (total, used) { + (Some(total), Some(used)) if total != 0.0 => { + time_list.push(times[idx]); + let usage = used/total; + usage_list.push(usage); + history.push(json!(usage)); + }, + _ => { + history.push(json!(null)) + } + } + } + + entry["history"] = history.into(); + + // we skip the calculation for datastores with not enough data + if usage_list.len() >= 7 { + if let Some((a,b)) = linear_regression(&time_list, &usage_list) { + if b != 0.0 { + let estimate = (1.0 - a) / b; + entry["estimated-full-date"] = Value::from(estimate.floor() as u64); + } + } + } + + list.push(entry); + } + + + Ok(list.into()) +} + +const SUBDIRS: SubdirMap = &[ + ("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)), +]; + +pub const ROUTER: Router = Router::new() + .get(&list_subdirs_api_method!(SUBDIRS)) + .subdirs(SUBDIRS);