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