]>
Commit | Line | Data |
---|---|---|
bf78f708 DM |
1 | //! Datastote status |
2 | ||
bda48e04 DC |
3 | use anyhow::{Error}; |
4 | use serde_json::{json, Value}; | |
5 | ||
6ef1b649 WB |
6 | use proxmox_schema::api; |
7 | use proxmox_router::{ | |
20b3094b DC |
8 | ApiMethod, |
9 | Permission, | |
10 | Router, | |
11 | RpcEnvironment, | |
12 | SubdirMap, | |
20b3094b | 13 | }; |
6ef1b649 | 14 | use proxmox_router::list_subdirs_api_method; |
20b3094b | 15 | |
8cc3760e | 16 | use pbs_api_types::{ |
d1c3bc53 DM |
17 | Authid, DATASTORE_SCHEMA, RRDMode, RRDTimeFrameResolution, |
18 | PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, | |
20b3094b | 19 | }; |
09340f28 | 20 | |
6d5d305d DM |
21 | use pbs_datastore::DataStore; |
22 | use pbs_config::CachedUserInfo; | |
bda48e04 | 23 | |
bda48e04 | 24 | use crate::tools::statistics::{linear_regression}; |
fa49d0fd | 25 | use 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 | 80 | pub 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 | ||
184 | const SUBDIRS: SubdirMap = &[ | |
185 | ("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)), | |
186 | ]; | |
187 | ||
188 | pub const ROUTER: Router = Router::new() | |
189 | .get(&list_subdirs_api_method!(SUBDIRS)) | |
190 | .subdirs(SUBDIRS); |