1 //! RRD toolkit - create/manage/update proxmox RRD (v2) file
3 use std
::path
::PathBuf
;
5 use anyhow
::{bail, Error}
;
6 use serde
::{Serialize, Deserialize}
;
9 use proxmox_router
::RpcEnvironment
;
10 use proxmox_router
::cli
::{run_cli_command, complete_file_name, CliCommand, CliCommandMap, CliEnvironment}
;
11 use proxmox_schema
::{api, parse_property_string}
;
12 use proxmox_schema
::{ApiStringFormat, ApiType, IntegerSchema, Schema, StringSchema}
;
14 use proxmox
::tools
::fs
::CreateOptions
;
16 use proxmox_rrd
::rrd
::{CF, DST, RRA, RRD}
;
18 pub const RRA_INDEX_SCHEMA
: Schema
= IntegerSchema
::new(
23 pub const RRA_CONFIG_STRING_SCHEMA
: Schema
= StringSchema
::new(
25 .format(&ApiStringFormat
::PropertyString(&RRAConfig
::API_SCHEMA
))
32 #[derive(Debug, Serialize, Deserialize)]
34 pub struct RRAConfig
{
38 /// Number of data points
46 description
: "The filename."
51 /// Dump the RRD file in JSON format
52 pub fn dump_rrd(path
: String
) -> Result
<(), Error
> {
54 let rrd
= RRD
::load(&PathBuf
::from(path
))?
;
55 serde_json
::to_writer_pretty(std
::io
::stdout(), &rrd
)?
;
64 description
: "The filename."
69 /// RRD file information
70 pub fn rrd_info(path
: String
) -> Result
<(), Error
> {
72 let rrd
= RRD
::load(&PathBuf
::from(path
))?
;
74 println
!("DST: {:?}", rrd
.source
.dst
);
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());
88 description
: "The filename."
91 description
: "Update time.",
95 description
: "Update value.",
100 /// Update the RRD database
105 ) -> Result
<(), Error
> {
107 let path
= PathBuf
::from(path
);
109 let time
= time
.map(|v
| v
as f64)
110 .unwrap_or_else(proxmox_time
::epoch_f64
);
112 let mut rrd
= RRD
::load(&path
)?
;
113 rrd
.update(time
, value
);
115 rrd
.save(&path
, CreateOptions
::new())?
;
124 description
: "The filename."
130 description
: "Time resulution",
133 description
: "Start time. If not sepecified, we simply extract 10 data points.",
137 description
: "End time (Unix Epoch). Default is the last update time.",
143 /// Fetch data from the RRD file
150 ) -> Result
<(), Error
> {
152 let rrd
= RRD
::load(&PathBuf
::from(path
))?
;
154 let data
= rrd
.extract_data(cf
, resolution
, start
, end
)?
;
156 println
!("{}", serde_json
::to_string_pretty(&data
)?
);
165 description
: "The filename."
168 schema
: RRA_INDEX_SCHEMA
,
173 /// Return the Unix timestamp of the first time slot inside the
174 /// specified RRA (slot start time)
175 pub fn first_update_time(
178 ) -> Result
<(), Error
> {
180 let rrd
= RRD
::load(&PathBuf
::from(path
))?
;
182 if rra_index
>= rrd
.rra_list
.len() {
183 bail
!("rra-index is out of range");
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
));
189 println
!("{}", first
);
197 description
: "The filename."
202 /// Return the Unix timestamp of the last update
203 pub fn last_update_time(path
: String
) -> Result
<(), Error
> {
205 let rrd
= RRD
::load(&PathBuf
::from(path
))?
;
207 println
!("{}", rrd
.source
.last_update
);
215 description
: "The filename."
220 /// Return the time and value from the last update
221 pub fn last_update(path
: String
) -> Result
<(), Error
> {
223 let rrd
= RRD
::load(&PathBuf
::from(path
))?
;
226 "time": rrd
.source
.last_update
,
227 "value": rrd
.source
.last_value
,
230 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
242 description
: "The filename to create."
245 description
: "Configuration of contained RRAs.",
248 schema
: RRA_CONFIG_STRING_SCHEMA
,
254 /// Create a new RRD file
259 ) -> Result
<(), Error
> {
261 let mut rra_list
= Vec
::new();
263 for item
in rra
.iter() {
264 let rra
: RRAConfig
= serde_json
::from_value(
265 parse_property_string(item
, &RRAConfig
::API_SCHEMA
)?
267 println
!("GOT {:?}", rra
);
268 rra_list
.push(RRA
::new(rra
.cf
, rra
.r
, rra
.n
as usize));
271 let path
= PathBuf
::from(path
);
273 let rrd
= RRD
::new(dst
, rra_list
);
275 rrd
.save(&path
, CreateOptions
::new())?
;
284 description
: "The filename."
287 schema
: RRA_INDEX_SCHEMA
,
290 description
: "The number of slots you want to add or remove.",
296 /// Resize. Change the number of data slots for the specified RRA.
301 ) -> Result
<(), Error
> {
303 let path
= PathBuf
::from(&path
);
305 let mut rrd
= RRD
::load(&path
)?
;
307 if rra_index
>= rrd
.rra_list
.len() {
308 bail
!("rra-index is out of range");
311 let rra
= &rrd
.rra_list
[rra_index
];
313 let new_slots
= (rra
.data
.len() as i64) + slots
;
316 bail
!("numer of new slots is too small ('{}' < 1)", new_slots
);
319 if new_slots
> 1024*1024 {
320 bail
!("numer of new slots is too big ('{}' > 1M)", new_slots
);
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
);
327 let mut new_rra
= RRA
::new(rra
.cf
, rra
.resolution
, new_slots
as usize);
328 new_rra
.last_count
= rra
.last_count
;
330 new_rra
.insert_data(start
, reso
, data
)?
;
332 rrd
.rra_list
[rra_index
] = new_rra
;
334 rrd
.save(&path
, CreateOptions
::new())?
;
339 fn main() -> Result
<(), Error
> {
341 let uid
= nix
::unistd
::Uid
::current();
343 let username
= match nix
::unistd
::User
::from_uid(uid
)?
{
344 Some(user
) => user
.name
,
345 None
=> bail
!("unable to get user name"),
348 let cmd_def
= CliCommandMap
::new()
351 CliCommand
::new(&API_METHOD_CREATE_RRD
)
352 .arg_param(&["path"])
353 .completion_cb("path", complete_file_name
)
357 CliCommand
::new(&API_METHOD_DUMP_RRD
)
358 .arg_param(&["path"])
359 .completion_cb("path", complete_file_name
)
363 CliCommand
::new(&API_METHOD_FETCH_RRD
)
364 .arg_param(&["path"])
365 .completion_cb("path", complete_file_name
)
369 CliCommand
::new(&API_METHOD_FIRST_UPDATE_TIME
)
370 .arg_param(&["path"])
371 .completion_cb("path", complete_file_name
)
375 CliCommand
::new(&API_METHOD_RRD_INFO
)
376 .arg_param(&["path"])
377 .completion_cb("path", complete_file_name
)
381 CliCommand
::new(&API_METHOD_LAST_UPDATE_TIME
)
382 .arg_param(&["path"])
383 .completion_cb("path", complete_file_name
)
387 CliCommand
::new(&API_METHOD_LAST_UPDATE
)
388 .arg_param(&["path"])
389 .completion_cb("path", complete_file_name
)
393 CliCommand
::new(&API_METHOD_RESIZE_RRD
)
394 .arg_param(&["path"])
395 .completion_cb("path", complete_file_name
)
399 CliCommand
::new(&API_METHOD_UPDATE_RRD
)
400 .arg_param(&["path"])
401 .completion_cb("path", complete_file_name
)
405 let mut rpcenv
= CliEnvironment
::new();
406 rpcenv
.set_auth_id(Some(format
!("{}@pam", username
)));
408 run_cli_command(cmd_def
, rpcenv
, None
);