proxmox-backup/proxmox-rrd/examples/prrd.rs
Thomas Lamprecht 82f5ad18f0 proxmox-rrd: move unshipped cli tool to examples
it's a rather low-level tool mostly useful for debugging and some of
it is rather "dumb" (by design) anyway, e.g., it does not
transparently applies journal but really only operates on the DB
files as is (which can conflict with daemon operations).

In summary, not (yet) a tool meant for end user consumption.
Move it to examples folder to avoid compilation on packaging (we do
not ship it anyway) which allows us to move the rather expensive
proxmox-router (pulls in hyper) to the dev-dependencies section.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00

413 lines
9.8 KiB
Rust

//! RRD toolkit - create/manage/update proxmox RRD (v2) file
use std::path::PathBuf;
use anyhow::{bail, Error};
use serde::{Serialize, Deserialize};
use serde_json::json;
use proxmox_router::RpcEnvironment;
use proxmox_router::cli::{run_cli_command, CliCommand, CliCommandMap, CliEnvironment};
use proxmox_schema::{api, parse_property_string};
use proxmox_schema::{ApiStringFormat, ApiType, IntegerSchema, Schema, StringSchema};
use proxmox::tools::fs::CreateOptions;
use proxmox_rrd::rrd::{CF, DST, RRA, RRD};
pub const RRA_INDEX_SCHEMA: Schema = IntegerSchema::new(
"Index of the RRA.")
.minimum(0)
.schema();
pub const RRA_CONFIG_STRING_SCHEMA: Schema = StringSchema::new(
"RRA configuration")
.format(&ApiStringFormat::PropertyString(&RRAConfig::API_SCHEMA))
.schema();
#[api(
properties: {},
default_key: "cf",
)]
#[derive(Debug, Serialize, Deserialize)]
/// RRA configuration
pub struct RRAConfig {
/// Time resolution
pub r: u64,
pub cf: CF,
/// Number of data points
pub n: u64,
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
},
},
)]
/// Dump the RRD file in JSON format
pub fn dump_rrd(path: String) -> Result<(), Error> {
let rrd = RRD::load(&PathBuf::from(path))?;
serde_json::to_writer_pretty(std::io::stdout(), &rrd)?;
println!("");
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
},
},
)]
/// RRD file information
pub fn rrd_info(path: String) -> Result<(), Error> {
let rrd = RRD::load(&PathBuf::from(path))?;
println!("DST: {:?}", rrd.source.dst);
for (i, rra) in rrd.rra_list.iter().enumerate() {
// use RRAConfig property string format
println!("RRA[{}]: {:?},r={},n={}", i, rra.cf, rra.resolution, rra.data.len());
}
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
time: {
description: "Update time.",
optional: true,
},
value: {
description: "Update value.",
},
},
},
)]
/// Update the RRD database
pub fn update_rrd(
path: String,
time: Option<u64>,
value: f64,
) -> Result<(), Error> {
let path = PathBuf::from(path);
let time = time.map(|v| v as f64)
.unwrap_or_else(proxmox_time::epoch_f64);
let mut rrd = RRD::load(&path)?;
rrd.update(time, value);
rrd.save(&path, CreateOptions::new())?;
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
cf: {
type: CF,
},
resolution: {
description: "Time resulution",
},
start: {
description: "Start time. If not sepecified, we simply extract 10 data points.",
optional: true,
},
end: {
description: "End time (Unix Epoch). Default is the last update time.",
optional: true,
},
},
},
)]
/// Fetch data from the RRD file
pub fn fetch_rrd(
path: String,
cf: CF,
resolution: u64,
start: Option<u64>,
end: Option<u64>,
) -> Result<(), Error> {
let rrd = RRD::load(&PathBuf::from(path))?;
let data = rrd.extract_data(cf, resolution, start, end)?;
println!("{}", serde_json::to_string_pretty(&data)?);
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
"rra-index": {
schema: RRA_INDEX_SCHEMA,
},
},
},
)]
/// Return the Unix timestamp of the first time slot inside the
/// specified RRA (slot start time)
pub fn first_update_time(
path: String,
rra_index: usize,
) -> Result<(), Error> {
let rrd = RRD::load(&PathBuf::from(path))?;
if rra_index >= rrd.rra_list.len() {
bail!("rra-index is out of range");
}
let rra = &rrd.rra_list[rra_index];
let duration = (rra.data.len() as u64)*rra.resolution;
let first = rra.slot_start_time((rrd.source.last_update as u64).saturating_sub(duration));
println!("{}", first);
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
},
},
)]
/// Return the Unix timestamp of the last update
pub fn last_update_time(path: String) -> Result<(), Error> {
let rrd = RRD::load(&PathBuf::from(path))?;
println!("{}", rrd.source.last_update);
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
},
},
)]
/// Return the time and value from the last update
pub fn last_update(path: String) -> Result<(), Error> {
let rrd = RRD::load(&PathBuf::from(path))?;
let result = json!({
"time": rrd.source.last_update,
"value": rrd.source.last_value,
});
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}
#[api(
input: {
properties: {
dst: {
type: DST,
},
path: {
description: "The filename to create."
},
rra: {
description: "Configuration of contained RRAs.",
type: Array,
items: {
schema: RRA_CONFIG_STRING_SCHEMA,
}
},
},
},
)]
/// Create a new RRD file
pub fn create_rrd(
dst: DST,
path: String,
rra: Vec<String>,
) -> Result<(), Error> {
let mut rra_list = Vec::new();
for item in rra.iter() {
let rra: RRAConfig = serde_json::from_value(
parse_property_string(item, &RRAConfig::API_SCHEMA)?
)?;
println!("GOT {:?}", rra);
rra_list.push(RRA::new(rra.cf, rra.r, rra.n as usize));
}
let path = PathBuf::from(path);
let rrd = RRD::new(dst, rra_list);
rrd.save(&path, CreateOptions::new())?;
Ok(())
}
#[api(
input: {
properties: {
path: {
description: "The filename."
},
"rra-index": {
schema: RRA_INDEX_SCHEMA,
},
slots: {
description: "The number of slots you want to add or remove.",
type: i64,
},
},
},
)]
/// Resize. Change the number of data slots for the specified RRA.
pub fn resize_rrd(
path: String,
rra_index: usize,
slots: i64,
) -> Result<(), Error> {
let path = PathBuf::from(&path);
let mut rrd = RRD::load(&path)?;
if rra_index >= rrd.rra_list.len() {
bail!("rra-index is out of range");
}
let rra = &rrd.rra_list[rra_index];
let new_slots = (rra.data.len() as i64) + slots;
if new_slots < 1 {
bail!("numer of new slots is too small ('{}' < 1)", new_slots);
}
if new_slots > 1024*1024 {
bail!("numer of new slots is too big ('{}' > 1M)", new_slots);
}
let rra_end = rra.slot_end_time(rrd.source.last_update as u64);
let rra_start = rra_end - rra.resolution*(rra.data.len() as u64);
let (start, reso, data) = rra.extract_data(rra_start, rra_end, rrd.source.last_update);
let mut new_rra = RRA::new(rra.cf, rra.resolution, new_slots as usize);
new_rra.last_count = rra.last_count;
new_rra.insert_data(start, reso, data)?;
rrd.rra_list[rra_index] = new_rra;
rrd.save(&path, CreateOptions::new())?;
Ok(())
}
fn main() -> Result<(), Error> {
let uid = nix::unistd::Uid::current();
let username = match nix::unistd::User::from_uid(uid)? {
Some(user) => user.name,
None => bail!("unable to get user name"),
};
let cmd_def = CliCommandMap::new()
.insert(
"create",
CliCommand::new(&API_METHOD_CREATE_RRD)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"dump",
CliCommand::new(&API_METHOD_DUMP_RRD)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"fetch",
CliCommand::new(&API_METHOD_FETCH_RRD)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"first",
CliCommand::new(&API_METHOD_FIRST_UPDATE_TIME)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"info",
CliCommand::new(&API_METHOD_RRD_INFO)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"last",
CliCommand::new(&API_METHOD_LAST_UPDATE_TIME)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"lastupdate",
CliCommand::new(&API_METHOD_LAST_UPDATE)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"resize",
CliCommand::new(&API_METHOD_RESIZE_RRD)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
.insert(
"update",
CliCommand::new(&API_METHOD_UPDATE_RRD)
.arg_param(&["path"])
//.completion_cb("path", pbs_tools::fs::complete_file_name)
)
;
let mut rpcenv = CliEnvironment::new();
rpcenv.set_auth_id(Some(format!("{}@pam", username)));
run_cli_command(cmd_def, rpcenv, None);
Ok(())
}