1 //! RRD toolkit - create/manage/update proxmox RRD (v2) file
3 use std
::path
::PathBuf
;
5 use anyhow
::{bail, Error}
;
6 use serde
::{Deserialize, Serialize}
;
9 use proxmox_router
::cli
::{
10 complete_file_name
, run_cli_command
, CliCommand
, CliCommandMap
, CliEnvironment
,
12 use proxmox_router
::RpcEnvironment
;
13 use proxmox_schema
::{api, ApiStringFormat, ApiType, IntegerSchema, Schema, StringSchema}
;
15 use proxmox_sys
::fs
::CreateOptions
;
17 use proxmox_rrd
::rrd
::{AggregationFn, Archive, DataSourceType, Database}
;
19 pub const RRA_INDEX_SCHEMA
: Schema
= IntegerSchema
::new("Index of the RRA.").minimum(0).schema();
21 pub const RRA_CONFIG_STRING_SCHEMA
: Schema
= StringSchema
::new("RRA configuration")
22 .format(&ApiStringFormat
::PropertyString(&RRAConfig
::API_SCHEMA
))
29 #[derive(Debug, Serialize, Deserialize)]
31 pub struct RRAConfig
{
34 pub cf
: AggregationFn
,
35 /// Number of data points
43 description
: "The filename."
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
)?
;
60 description
: "The filename."
65 /// RRD file information
66 pub fn rrd_info(path
: String
) -> Result
<(), Error
> {
67 let rrd
= Database
::load(&PathBuf
::from(path
), false)?
;
69 println
!("DST: {:?}", rrd
.source
.dst
);
71 for (i
, rra
) in rrd
.rra_list
.iter().enumerate() {
72 // use RRAConfig property string format
74 "RRA[{}]: {:?},r={},n={}",
89 description
: "The filename."
92 description
: "Update time.",
96 description
: "Update value.",
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
);
107 .unwrap_or_else(proxmox_time
::epoch_f64
);
109 let mut rrd
= Database
::load(&path
, false)?
;
110 rrd
.update(time
, value
);
112 rrd
.save(&path
, CreateOptions
::new(), false)?
;
121 description
: "The filename."
127 description
: "Time resolution",
130 description
: "Start time. If not specified, we simply extract 10 data points.",
134 description
: "End time (Unix Epoch). Default is the last update time.",
140 /// Fetch data from the RRD file
147 ) -> Result
<(), Error
> {
148 let rrd
= Database
::load(&PathBuf
::from(path
), false)?
;
150 let data
= rrd
.extract_data(cf
, resolution
, start
, end
)?
;
152 println
!("{}", serde_json
::to_string_pretty(&data
)?
);
161 description
: "The filename."
164 schema
: RRA_INDEX_SCHEMA
,
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)?
;
174 if rra_index
>= rrd
.rra_list
.len() {
175 bail
!("rra-index is out of range");
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
));
181 println
!("{}", first
);
189 description
: "The filename."
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)?
;
198 println
!("{}", rrd
.source
.last_update
);
206 description
: "The filename."
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)?
;
216 "time": rrd
.source
.last_update
,
217 "value": rrd
.source
.last_value
,
220 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
229 type: DataSourceType
,
232 description
: "The filename to create."
235 description
: "Configuration of contained RRAs.",
238 schema
: RRA_CONFIG_STRING_SCHEMA
,
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();
248 for item
in rra
.iter() {
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));
255 let path
= PathBuf
::from(path
);
257 let rrd
= Database
::new(dst
, rra_list
);
259 rrd
.save(&path
, CreateOptions
::new(), false)?
;
268 description
: "The filename."
271 schema
: RRA_INDEX_SCHEMA
,
274 description
: "The number of slots you want to add or remove.",
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
);
284 let mut rrd
= Database
::load(&path
, false)?
;
286 if rra_index
>= rrd
.rra_list
.len() {
287 bail
!("rra-index is out of range");
290 let rra
= &rrd
.rra_list
[rra_index
];
292 let new_slots
= (rra
.data
.len() as i64) + slots
;
295 bail
!("number of new slots is too small ('{}' < 1)", new_slots
);
298 if new_slots
> 1024 * 1024 {
299 bail
!("number of new slots is too big ('{}' > 1M)", new_slots
);
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
)
308 let mut new_rra
= Archive
::new(rra
.cf
, rra
.resolution
, new_slots
as usize);
309 new_rra
.last_count
= rra
.last_count
;
311 new_rra
.insert_data(start
, reso
, data
)?
;
313 rrd
.rra_list
[rra_index
] = new_rra
;
315 rrd
.save(&path
, CreateOptions
::new(), false)?
;
320 fn main() -> Result
<(), Error
> {
321 let uid
= nix
::unistd
::Uid
::current();
323 let username
= match nix
::unistd
::User
::from_uid(uid
)?
{
324 Some(user
) => user
.name
,
325 None
=> bail
!("unable to get user name"),
328 let cmd_def
= CliCommandMap
::new()
331 CliCommand
::new(&API_METHOD_CREATE_RRD
)
332 .arg_param(&["path"])
333 .completion_cb("path", complete_file_name
),
337 CliCommand
::new(&API_METHOD_DUMP_RRD
)
338 .arg_param(&["path"])
339 .completion_cb("path", complete_file_name
),
343 CliCommand
::new(&API_METHOD_FETCH_RRD
)
344 .arg_param(&["path"])
345 .completion_cb("path", complete_file_name
),
349 CliCommand
::new(&API_METHOD_FIRST_UPDATE_TIME
)
350 .arg_param(&["path"])
351 .completion_cb("path", complete_file_name
),
355 CliCommand
::new(&API_METHOD_RRD_INFO
)
356 .arg_param(&["path"])
357 .completion_cb("path", complete_file_name
),
361 CliCommand
::new(&API_METHOD_LAST_UPDATE_TIME
)
362 .arg_param(&["path"])
363 .completion_cb("path", complete_file_name
),
367 CliCommand
::new(&API_METHOD_LAST_UPDATE
)
368 .arg_param(&["path"])
369 .completion_cb("path", complete_file_name
),
373 CliCommand
::new(&API_METHOD_RESIZE_RRD
)
374 .arg_param(&["path"])
375 .completion_cb("path", complete_file_name
),
379 CliCommand
::new(&API_METHOD_UPDATE_RRD
)
380 .arg_param(&["path"])
381 .completion_cb("path", complete_file_name
),
384 let mut rpcenv
= CliEnvironment
::new();
385 rpcenv
.set_auth_id(Some(format
!("{}@pam", username
)));
387 run_cli_command(cmd_def
, rpcenv
, None
);