]> git.proxmox.com Git - proxmox-backup.git/blame - proxmox-rrd/examples/prrd.rs
use complete_file_name from proxmox-router 1.1
[proxmox-backup.git] / proxmox-rrd / examples / prrd.rs
CommitLineData
ec08247e
DM
1//! RRD toolkit - create/manage/update proxmox RRD (v2) file
2
3use std::path::PathBuf;
4
5use anyhow::{bail, Error};
6use serde::{Serialize, Deserialize};
bacc99c7 7use serde_json::json;
ec08247e
DM
8
9use proxmox_router::RpcEnvironment;
b3f279e2 10use proxmox_router::cli::{run_cli_command, complete_file_name, CliCommand, CliCommandMap, CliEnvironment};
ec08247e 11use proxmox_schema::{api, parse_property_string};
bacc99c7 12use proxmox_schema::{ApiStringFormat, ApiType, IntegerSchema, Schema, StringSchema};
ec08247e
DM
13
14use proxmox::tools::fs::CreateOptions;
15
16use proxmox_rrd::rrd::{CF, DST, RRA, RRD};
17
bacc99c7
DM
18pub const RRA_INDEX_SCHEMA: Schema = IntegerSchema::new(
19 "Index of the RRA.")
20 .minimum(0)
21 .schema();
22
ec08247e
DM
23pub const RRA_CONFIG_STRING_SCHEMA: Schema = StringSchema::new(
24 "RRA configuration")
25 .format(&ApiStringFormat::PropertyString(&RRAConfig::API_SCHEMA))
26 .schema();
27
28#[api(
29 properties: {},
30 default_key: "cf",
31)]
32#[derive(Debug, Serialize, Deserialize)]
33/// RRA configuration
34pub struct RRAConfig {
35 /// Time resolution
36 pub r: u64,
37 pub cf: CF,
38 /// Number of data points
39 pub n: u64,
40}
41
42#[api(
43 input: {
44 properties: {
45 path: {
46 description: "The filename."
47 },
48 },
49 },
50)]
bacc99c7
DM
51/// Dump the RRD file in JSON format
52pub fn dump_rrd(path: String) -> Result<(), Error> {
ec08247e
DM
53
54 let rrd = RRD::load(&PathBuf::from(path))?;
55 serde_json::to_writer_pretty(std::io::stdout(), &rrd)?;
bacc99c7
DM
56 println!("");
57 Ok(())
58}
59
60#[api(
61 input: {
62 properties: {
63 path: {
64 description: "The filename."
65 },
66 },
67 },
68)]
69/// RRD file information
70pub fn rrd_info(path: String) -> Result<(), Error> {
71
72 let rrd = RRD::load(&PathBuf::from(path))?;
73
74 println!("DST: {:?}", rrd.source.dst);
75
76 for (i, rra) in rrd.rra_list.iter().enumerate() {
77 // use RRAConfig property string format
78 println!("RRA[{}]: {:?},r={},n={}", i, rra.cf, rra.resolution, rra.data.len());
79 }
80
ec08247e
DM
81 Ok(())
82}
83
84#[api(
85 input: {
86 properties: {
87 path: {
88 description: "The filename."
89 },
90 time: {
91 description: "Update time.",
92 optional: true,
93 },
94 value: {
95 description: "Update value.",
96 },
97 },
98 },
99)]
bacc99c7
DM
100/// Update the RRD database
101pub fn update_rrd(
ec08247e
DM
102 path: String,
103 time: Option<u64>,
104 value: f64,
105) -> Result<(), Error> {
106
107 let path = PathBuf::from(path);
108
109 let time = time.map(|v| v as f64)
110 .unwrap_or_else(proxmox_time::epoch_f64);
111
112 let mut rrd = RRD::load(&path)?;
113 rrd.update(time, value);
114
115 rrd.save(&path, CreateOptions::new())?;
116
117 Ok(())
118}
119
120#[api(
121 input: {
122 properties: {
123 path: {
124 description: "The filename."
125 },
126 cf: {
127 type: CF,
128 },
129 resolution: {
130 description: "Time resulution",
131 },
132 start: {
133 description: "Start time. If not sepecified, we simply extract 10 data points.",
134 optional: true,
135 },
136 end: {
137 description: "End time (Unix Epoch). Default is the last update time.",
138 optional: true,
139 },
140 },
141 },
142)]
bacc99c7
DM
143/// Fetch data from the RRD file
144pub fn fetch_rrd(
ec08247e
DM
145 path: String,
146 cf: CF,
147 resolution: u64,
148 start: Option<u64>,
149 end: Option<u64>,
150) -> Result<(), Error> {
151
152 let rrd = RRD::load(&PathBuf::from(path))?;
153
154 let data = rrd.extract_data(cf, resolution, start, end)?;
155
156 println!("{}", serde_json::to_string_pretty(&data)?);
157
158 Ok(())
159}
160
bacc99c7
DM
161#[api(
162 input: {
163 properties: {
164 path: {
165 description: "The filename."
166 },
167 "rra-index": {
168 schema: RRA_INDEX_SCHEMA,
169 },
170 },
171 },
172)]
173/// Return the Unix timestamp of the first time slot inside the
174/// specified RRA (slot start time)
175pub fn first_update_time(
176 path: String,
177 rra_index: usize,
178) -> Result<(), Error> {
179
180 let rrd = RRD::load(&PathBuf::from(path))?;
181
182 if rra_index >= rrd.rra_list.len() {
183 bail!("rra-index is out of range");
184 }
185 let rra = &rrd.rra_list[rra_index];
186 let duration = (rra.data.len() as u64)*rra.resolution;
187 let first = rra.slot_start_time((rrd.source.last_update as u64).saturating_sub(duration));
188
189 println!("{}", first);
190 Ok(())
191}
192
193#[api(
194 input: {
195 properties: {
196 path: {
197 description: "The filename."
198 },
199 },
200 },
201)]
202/// Return the Unix timestamp of the last update
203pub fn last_update_time(path: String) -> Result<(), Error> {
204
205 let rrd = RRD::load(&PathBuf::from(path))?;
206
207 println!("{}", rrd.source.last_update);
208 Ok(())
209}
210
211#[api(
212 input: {
213 properties: {
214 path: {
215 description: "The filename."
216 },
217 },
218 },
219)]
220/// Return the time and value from the last update
221pub fn last_update(path: String) -> Result<(), Error> {
222
223 let rrd = RRD::load(&PathBuf::from(path))?;
224
225 let result = json!({
226 "time": rrd.source.last_update,
227 "value": rrd.source.last_value,
228 });
229
230 println!("{}", serde_json::to_string_pretty(&result)?);
231
232 Ok(())
233}
234
ec08247e
DM
235#[api(
236 input: {
237 properties: {
238 dst: {
239 type: DST,
240 },
241 path: {
242 description: "The filename to create."
243 },
244 rra: {
245 description: "Configuration of contained RRAs.",
246 type: Array,
247 items: {
248 schema: RRA_CONFIG_STRING_SCHEMA,
249 }
250 },
251 },
252 },
253)]
bacc99c7
DM
254/// Create a new RRD file
255pub fn create_rrd(
ec08247e
DM
256 dst: DST,
257 path: String,
258 rra: Vec<String>,
259) -> Result<(), Error> {
260
261 let mut rra_list = Vec::new();
262
263 for item in rra.iter() {
264 let rra: RRAConfig = serde_json::from_value(
265 parse_property_string(item, &RRAConfig::API_SCHEMA)?
266 )?;
267 println!("GOT {:?}", rra);
268 rra_list.push(RRA::new(rra.cf, rra.r, rra.n as usize));
269 }
270
271 let path = PathBuf::from(path);
272
273 let rrd = RRD::new(dst, rra_list);
274
275 rrd.save(&path, CreateOptions::new())?;
276
277 Ok(())
278}
279
bacc99c7
DM
280#[api(
281 input: {
282 properties: {
283 path: {
284 description: "The filename."
285 },
286 "rra-index": {
287 schema: RRA_INDEX_SCHEMA,
288 },
289 slots: {
290 description: "The number of slots you want to add or remove.",
291 type: i64,
292 },
293 },
294 },
295)]
296/// Resize. Change the number of data slots for the specified RRA.
297pub fn resize_rrd(
298 path: String,
299 rra_index: usize,
300 slots: i64,
301) -> Result<(), Error> {
302
303 let path = PathBuf::from(&path);
304
305 let mut rrd = RRD::load(&path)?;
306
307 if rra_index >= rrd.rra_list.len() {
308 bail!("rra-index is out of range");
309 }
310
311 let rra = &rrd.rra_list[rra_index];
312
313 let new_slots = (rra.data.len() as i64) + slots;
314
315 if new_slots < 1 {
316 bail!("numer of new slots is too small ('{}' < 1)", new_slots);
317 }
318
319 if new_slots > 1024*1024 {
320 bail!("numer of new slots is too big ('{}' > 1M)", new_slots);
321 }
322
323 let rra_end = rra.slot_end_time(rrd.source.last_update as u64);
324 let rra_start = rra_end - rra.resolution*(rra.data.len() as u64);
325 let (start, reso, data) = rra.extract_data(rra_start, rra_end, rrd.source.last_update);
326
327 let mut new_rra = RRA::new(rra.cf, rra.resolution, new_slots as usize);
328 new_rra.last_count = rra.last_count;
329
330 new_rra.insert_data(start, reso, data)?;
331
332 rrd.rra_list[rra_index] = new_rra;
333
334 rrd.save(&path, CreateOptions::new())?;
335
336 Ok(())
337}
ec08247e
DM
338
339fn main() -> Result<(), Error> {
340
341 let uid = nix::unistd::Uid::current();
342
343 let username = match nix::unistd::User::from_uid(uid)? {
344 Some(user) => user.name,
345 None => bail!("unable to get user name"),
346 };
347
348 let cmd_def = CliCommandMap::new()
349 .insert(
350 "create",
bacc99c7 351 CliCommand::new(&API_METHOD_CREATE_RRD)
ec08247e 352 .arg_param(&["path"])
b3f279e2 353 .completion_cb("path", complete_file_name)
ec08247e
DM
354 )
355 .insert(
bacc99c7
DM
356 "dump",
357 CliCommand::new(&API_METHOD_DUMP_RRD)
ec08247e 358 .arg_param(&["path"])
b3f279e2 359 .completion_cb("path", complete_file_name)
bacc99c7 360 )
ec08247e
DM
361 .insert(
362 "fetch",
bacc99c7 363 CliCommand::new(&API_METHOD_FETCH_RRD)
ec08247e 364 .arg_param(&["path"])
b3f279e2 365 .completion_cb("path", complete_file_name)
bacc99c7
DM
366 )
367 .insert(
368 "first",
369 CliCommand::new(&API_METHOD_FIRST_UPDATE_TIME)
370 .arg_param(&["path"])
b3f279e2 371 .completion_cb("path", complete_file_name)
ec08247e
DM
372 )
373 .insert(
bacc99c7
DM
374 "info",
375 CliCommand::new(&API_METHOD_RRD_INFO)
376 .arg_param(&["path"])
b3f279e2 377 .completion_cb("path", complete_file_name)
bacc99c7
DM
378 )
379 .insert(
380 "last",
381 CliCommand::new(&API_METHOD_LAST_UPDATE_TIME)
382 .arg_param(&["path"])
b3f279e2 383 .completion_cb("path", complete_file_name)
bacc99c7
DM
384 )
385 .insert(
386 "lastupdate",
387 CliCommand::new(&API_METHOD_LAST_UPDATE)
388 .arg_param(&["path"])
b3f279e2 389 .completion_cb("path", complete_file_name)
bacc99c7
DM
390 )
391 .insert(
392 "resize",
393 CliCommand::new(&API_METHOD_RESIZE_RRD)
394 .arg_param(&["path"])
b3f279e2 395 .completion_cb("path", complete_file_name)
bacc99c7
DM
396 )
397 .insert(
398 "update",
399 CliCommand::new(&API_METHOD_UPDATE_RRD)
ec08247e 400 .arg_param(&["path"])
b3f279e2 401 .completion_cb("path", complete_file_name)
ec08247e
DM
402 )
403 ;
404
405 let mut rpcenv = CliEnvironment::new();
406 rpcenv.set_auth_id(Some(format!("{}@pam", username)));
407
408 run_cli_command(cmd_def, rpcenv, None);
409
410 Ok(())
411
412}