]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/status.rs
RRD_CACHE: use a OnceCell instead of lazy_static
[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::{
d1c3bc53
DM
17 Authid, DATASTORE_SCHEMA, RRDMode, RRDTimeFrameResolution,
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};
fa49d0fd 25use crate::get_rrd_cache;
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
fa49d0fd
DM
93 let rrd_cache = get_rrd_cache()?;
94
bda48e04 95 for (store, (_, _)) in &config.sections {
e6dc35ac 96 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
bda48e04
DC
97 let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
98 if !allowed {
99 continue;
100 }
101
64591e73
TL
102 let datastore = match DataStore::lookup_datastore(&store) {
103 Ok(datastore) => datastore,
104 Err(err) => {
105 list.push(json!({
106 "store": store,
107 "total": -1,
108 "used": -1,
109 "avail": -1,
110 "error": err.to_string()
111 }));
112 continue;
113 }
114 };
bda48e04
DC
115 let status = crate::tools::disks::disk_usage(&datastore.base_path())?;
116
117 let mut entry = json!({
118 "store": store,
119 "total": status.total,
120 "used": status.used,
121 "avail": status.avail,
b35eb0a1 122 "gc-status": datastore.last_gc_status(),
bda48e04
DC
123 });
124
125 let rrd_dir = format!("datastore/{}", store);
6ef1b649 126 let now = proxmox_time::epoch_f64();
bda48e04 127
fa49d0fd
DM
128
129 let get_rrd = |what: &str| rrd_cache.extract_cached_data(
bda48e04 130 &rrd_dir,
64e0786a 131 what,
ec8f0424 132 now,
64e0786a
TL
133 RRDTimeFrameResolution::Month,
134 RRDMode::Average,
ec8f0424 135 );
bda48e04 136
64e0786a
TL
137 let total_res = get_rrd("total");
138 let used_res = get_rrd("used");
ec8f0424 139
b92cad09
FG
140 if let (Some((start, reso, total_list)), Some((_, _, used_list))) = (total_res, used_res) {
141 let mut usage_list: Vec<f64> = Vec::new();
142 let mut time_list: Vec<u64> = Vec::new();
143 let mut history = Vec::new();
144
145 for (idx, used) in used_list.iter().enumerate() {
146 let total = if idx < total_list.len() {
147 total_list[idx]
148 } else {
149 None
150 };
151
152 match (total, used) {
153 (Some(total), Some(used)) if total != 0.0 => {
154 time_list.push(start + (idx as u64)*reso);
155 let usage = used/total;
156 usage_list.push(usage);
157 history.push(json!(usage));
158 },
159 _ => {
160 history.push(json!(null))
ec8f0424 161 }
bda48e04 162 }
b92cad09
FG
163 }
164
165 entry["history-start"] = start.into();
166 entry["history-delta"] = reso.into();
167 entry["history"] = history.into();
168
169 // we skip the calculation for datastores with not enough data
170 if usage_list.len() >= 7 {
90761f0f
TL
171 entry["estimated-full-date"] = match linear_regression(&time_list, &usage_list) {
172 Some((a, b)) if b != 0.0 => Value::from(((1.0 - a) / b).floor() as u64),
173 _ => Value::from(0),
174 };
b92cad09 175 }
bda48e04
DC
176 }
177
178 list.push(entry);
179 }
180
bda48e04
DC
181 Ok(list.into())
182}
183
184const SUBDIRS: SubdirMap = &[
185 ("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)),
186];
187
188pub const ROUTER: Router = Router::new()
189 .get(&list_subdirs_api_method!(SUBDIRS))
190 .subdirs(SUBDIRS);