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 <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2020-06-09 10:01:13 +02:00 committed by Dietmar Maurer
parent ba97479848
commit bda48e04da
2 changed files with 156 additions and 0 deletions

View File

@ -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),
];

155
src/api2/status.rs Normal file
View File

@ -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<Value, Error> {
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<f64> = Vec::new();
let mut time_list: Vec<u64> = 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);