]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/status.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / src / api2 / status.rs
CommitLineData
bf78f708
DM
1//! Datastote status
2
bda48e04
DC
3use anyhow::{Error};
4use serde_json::{json, Value};
5
6ef1b649
WB
6use proxmox_schema::api;
7use proxmox_router::{
20b3094b
DC
8 ApiMethod,
9 Permission,
10 Router,
11 RpcEnvironment,
12 SubdirMap,
20b3094b 13};
6ef1b649 14use proxmox_router::list_subdirs_api_method;
20b3094b 15
8cc3760e 16use pbs_api_types::{
c68fa58a 17 Authid, DATASTORE_SCHEMA, RRDMode, RRDTimeFrame,
d1c3bc53 18 PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
20b3094b 19};
09340f28 20
6d5d305d
DM
21use pbs_datastore::DataStore;
22use pbs_config::CachedUserInfo;
bda48e04 23
bda48e04 24use crate::tools::statistics::{linear_regression};
fae4f6c5 25use crate::rrd_cache::extract_rrd_data;
bda48e04
DC
26
27#[api(
28 returns: {
29 description: "Lists the Status of the Datastores.",
30 type: Array,
31 items: {
32 description: "Status of a Datastore",
33 type: Object,
34 properties: {
35 store: {
36 schema: DATASTORE_SCHEMA,
37 },
38 total: {
39 type: Integer,
40 description: "The Size of the underlying storage in bytes",
41 },
42 used: {
43 type: Integer,
44 description: "The used bytes of the underlying storage",
45 },
46 avail: {
47 type: Integer,
48 description: "The available bytes of the underlying storage",
49 },
50 history: {
51 type: Array,
64591e73 52 optional: true,
bda48e04
DC
53 description: "A list of usages of the past (last Month).",
54 items: {
55 type: Number,
56 description: "The usage of a time in the past. Either null or between 0.0 and 1.0.",
57 }
58 },
59 "estimated-full-date": {
60 type: Integer,
61 optional: true,
62 description: "Estimation of the UNIX epoch when the storage will be full.\
63 This is calculated via a simple Linear Regression (Least Squares)\
64 of RRD data of the last Month. Missing if there are not enough data points yet.\
65 If the estimate lies in the past, the usage is decreasing.",
66 },
64591e73
TL
67 "error": {
68 type: String,
69 optional: true,
70 description: "An error description, for example, when the datastore could not be looked up.",
71 },
bda48e04
DC
72 },
73 },
74 },
ecd55041
FG
75 access: {
76 permission: &Permission::Anybody,
77 },
bda48e04
DC
78)]
79/// List Datastore usages and estimates
bf78f708 80pub fn datastore_status(
bda48e04
DC
81 _param: Value,
82 _info: &ApiMethod,
83 rpcenv: &mut dyn RpcEnvironment,
84 ) -> Result<Value, Error> {
85
e7d4be9d 86 let (config, _digest) = pbs_config::datastore::config()?;
bda48e04 87
e6dc35ac 88 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
bda48e04
DC
89 let user_info = CachedUserInfo::new()?;
90
91 let mut list = Vec::new();
92
93 for (store, (_, _)) in &config.sections {
9a37bd6c 94 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", store]);
bda48e04
DC
95 let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
96 if !allowed {
97 continue;
98 }
99
9a37bd6c 100 let datastore = match DataStore::lookup_datastore(store) {
64591e73
TL
101 Ok(datastore) => datastore,
102 Err(err) => {
103 list.push(json!({
104 "store": store,
105 "total": -1,
106 "used": -1,
107 "avail": -1,
108 "error": err.to_string()
109 }));
110 continue;
111 }
112 };
bda48e04
DC
113 let status = crate::tools::disks::disk_usage(&datastore.base_path())?;
114
115 let mut entry = json!({
116 "store": store,
117 "total": status.total,
118 "used": status.used,
119 "avail": status.avail,
b35eb0a1 120 "gc-status": datastore.last_gc_status(),
bda48e04
DC
121 });
122
123 let rrd_dir = format!("datastore/{}", store);
fa49d0fd 124
eb37d4ec 125 let get_rrd = |what: &str| extract_rrd_data(
bda48e04 126 &rrd_dir,
64e0786a 127 what,
c68fa58a 128 RRDTimeFrame::Month,
64e0786a 129 RRDMode::Average,
ec8f0424 130 );
bda48e04 131
1198f8d4
DM
132 let total_res = get_rrd("total")?;
133 let used_res = get_rrd("used")?;
ec8f0424 134
b92cad09
FG
135 if let (Some((start, reso, total_list)), Some((_, _, used_list))) = (total_res, used_res) {
136 let mut usage_list: Vec<f64> = Vec::new();
137 let mut time_list: Vec<u64> = Vec::new();
138 let mut history = Vec::new();
139
140 for (idx, used) in used_list.iter().enumerate() {
141 let total = if idx < total_list.len() {
142 total_list[idx]
143 } else {
144 None
145 };
146
147 match (total, used) {
148 (Some(total), Some(used)) if total != 0.0 => {
149 time_list.push(start + (idx as u64)*reso);
150 let usage = used/total;
151 usage_list.push(usage);
152 history.push(json!(usage));
153 },
154 _ => {
155 history.push(json!(null))
ec8f0424 156 }
bda48e04 157 }
b92cad09
FG
158 }
159
160 entry["history-start"] = start.into();
161 entry["history-delta"] = reso.into();
162 entry["history"] = history.into();
163
164 // we skip the calculation for datastores with not enough data
165 if usage_list.len() >= 7 {
90761f0f
TL
166 entry["estimated-full-date"] = match linear_regression(&time_list, &usage_list) {
167 Some((a, b)) if b != 0.0 => Value::from(((1.0 - a) / b).floor() as u64),
168 _ => Value::from(0),
169 };
b92cad09 170 }
bda48e04
DC
171 }
172
173 list.push(entry);
174 }
175
bda48e04
DC
176 Ok(list.into())
177}
178
179const SUBDIRS: SubdirMap = &[
180 ("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)),
181];
182
183pub const ROUTER: Router = Router::new()
184 .get(&list_subdirs_api_method!(SUBDIRS))
185 .subdirs(SUBDIRS);