2021-10-13 11:24:47 +03:00
//! RRD toolkit - create/manage/update proxmox RRD (v2) file
use std ::path ::PathBuf ;
use anyhow ::{ bail , Error } ;
2022-04-06 17:56:33 +03:00
use serde ::{ Deserialize , Serialize } ;
2021-10-13 11:24:52 +03:00
use serde_json ::json ;
2021-10-13 11:24:47 +03:00
2022-04-06 17:56:33 +03:00
use proxmox_router ::cli ::{
complete_file_name , run_cli_command , CliCommand , CliCommandMap , CliEnvironment ,
} ;
2021-10-13 11:24:47 +03:00
use proxmox_router ::RpcEnvironment ;
2021-12-16 13:02:53 +03:00
use proxmox_schema ::{ api , ApiStringFormat , ApiType , IntegerSchema , Schema , StringSchema } ;
2021-10-13 11:24:47 +03:00
2021-11-23 19:57:00 +03:00
use proxmox_sys ::fs ::CreateOptions ;
2021-10-13 11:24:47 +03:00
2024-01-31 18:26:01 +03:00
use proxmox_rrd ::rrd ::{ AggregationFn , Archive , DataSourceType , Database } ;
2021-10-13 11:24:47 +03:00
2022-04-06 17:56:33 +03:00
pub const RRA_INDEX_SCHEMA : Schema = IntegerSchema ::new ( " Index of the RRA. " ) . minimum ( 0 ) . schema ( ) ;
2021-10-13 11:24:52 +03:00
2022-04-06 17:56:33 +03:00
pub const RRA_CONFIG_STRING_SCHEMA : Schema = StringSchema ::new ( " RRA configuration " )
2021-10-13 11:24:47 +03:00
. 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 ,
2024-01-31 18:26:01 +03:00
pub cf : AggregationFn ,
2021-10-13 11:24:47 +03:00
/// Number of data points
pub n : u64 ,
}
#[ api(
input : {
properties : {
path : {
description : " The filename. "
} ,
} ,
} ,
) ]
2021-10-13 11:24:52 +03:00
/// Dump the RRD file in JSON format
pub fn dump_rrd ( path : String ) -> Result < ( ) , Error > {
2024-01-31 18:26:01 +03:00
let rrd = Database ::load ( & PathBuf ::from ( path ) , false ) ? ;
2021-10-13 11:24:47 +03:00
serde_json ::to_writer_pretty ( std ::io ::stdout ( ) , & rrd ) ? ;
2022-02-08 16:57:16 +03:00
println! ( ) ;
2021-10-13 11:24:52 +03:00
Ok ( ( ) )
}
#[ api(
input : {
properties : {
path : {
description : " The filename. "
} ,
} ,
} ,
) ]
/// RRD file information
pub fn rrd_info ( path : String ) -> Result < ( ) , Error > {
2024-01-31 18:26:01 +03:00
let rrd = Database ::load ( & PathBuf ::from ( path ) , false ) ? ;
2021-10-13 11:24:52 +03:00
println! ( " DST: {:?} " , rrd . source . dst ) ;
for ( i , rra ) in rrd . rra_list . iter ( ) . enumerate ( ) {
// use RRAConfig property string format
2022-04-06 17:56:33 +03:00
println! (
" RRA[{}]: {:?},r={},n={} " ,
i ,
rra . cf ,
rra . resolution ,
rra . data . len ( )
) ;
2021-10-13 11:24:52 +03:00
}
2021-10-13 11:24:47 +03:00
Ok ( ( ) )
}
#[ api(
input : {
properties : {
path : {
description : " The filename. "
} ,
time : {
description : " Update time. " ,
optional : true ,
} ,
value : {
description : " Update value. " ,
} ,
} ,
} ,
) ]
2021-10-13 11:24:52 +03:00
/// Update the RRD database
2022-04-06 17:56:33 +03:00
pub fn update_rrd ( path : String , time : Option < u64 > , value : f64 ) -> Result < ( ) , Error > {
2021-10-13 11:24:47 +03:00
let path = PathBuf ::from ( path ) ;
2022-04-06 17:56:33 +03:00
let time = time
. map ( | v | v as f64 )
2021-10-13 11:24:47 +03:00
. unwrap_or_else ( proxmox_time ::epoch_f64 ) ;
2024-01-31 18:26:01 +03:00
let mut rrd = Database ::load ( & path , false ) ? ;
2021-10-13 11:24:47 +03:00
rrd . update ( time , value ) ;
2021-10-19 19:41:03 +03:00
rrd . save ( & path , CreateOptions ::new ( ) , false ) ? ;
2021-10-13 11:24:47 +03:00
Ok ( ( ) )
}
#[ api(
input : {
properties : {
path : {
description : " The filename. "
} ,
cf : {
2024-02-21 14:28:09 +03:00
type : AggregationFn ,
2021-10-13 11:24:47 +03:00
} ,
resolution : {
2023-11-29 20:32:06 +03:00
description : " Time resolution " ,
2021-10-13 11:24:47 +03:00
} ,
start : {
2023-11-29 20:32:06 +03:00
description : " Start time. If not specified, we simply extract 10 data points. " ,
2021-10-13 11:24:47 +03:00
optional : true ,
} ,
end : {
description : " End time (Unix Epoch). Default is the last update time. " ,
optional : true ,
} ,
} ,
} ,
) ]
2021-10-13 11:24:52 +03:00
/// Fetch data from the RRD file
pub fn fetch_rrd (
2021-10-13 11:24:47 +03:00
path : String ,
2024-01-31 18:26:01 +03:00
cf : AggregationFn ,
2021-10-13 11:24:47 +03:00
resolution : u64 ,
start : Option < u64 > ,
end : Option < u64 > ,
) -> Result < ( ) , Error > {
2024-01-31 18:26:01 +03:00
let rrd = Database ::load ( & PathBuf ::from ( path ) , false ) ? ;
2021-10-13 11:24:47 +03:00
let data = rrd . extract_data ( cf , resolution , start , end ) ? ;
println! ( " {} " , serde_json ::to_string_pretty ( & data ) ? ) ;
Ok ( ( ) )
}
2021-10-13 11:24:52 +03:00
#[ 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)
2022-04-06 17:56:33 +03:00
pub fn first_update_time ( path : String , rra_index : usize ) -> Result < ( ) , Error > {
2024-01-31 18:26:01 +03:00
let rrd = Database ::load ( & PathBuf ::from ( path ) , false ) ? ;
2021-10-13 11:24:52 +03:00
if rra_index > = rrd . rra_list . len ( ) {
bail! ( " rra-index is out of range " ) ;
}
let rra = & rrd . rra_list [ rra_index ] ;
2022-04-06 17:56:33 +03:00
let duration = ( rra . data . len ( ) as u64 ) * rra . resolution ;
2021-10-13 11:24:52 +03:00
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 > {
2024-01-31 18:26:01 +03:00
let rrd = Database ::load ( & PathBuf ::from ( path ) , false ) ? ;
2021-10-13 11:24:52 +03:00
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 > {
2024-01-31 18:26:01 +03:00
let rrd = Database ::load ( & PathBuf ::from ( path ) , false ) ? ;
2021-10-13 11:24:52 +03:00
let result = json! ( {
" time " : rrd . source . last_update ,
" value " : rrd . source . last_value ,
} ) ;
println! ( " {} " , serde_json ::to_string_pretty ( & result ) ? ) ;
Ok ( ( ) )
}
2021-10-13 11:24:47 +03:00
#[ api(
input : {
properties : {
dst : {
2024-02-21 14:28:09 +03:00
type : DataSourceType ,
2021-10-13 11:24:47 +03:00
} ,
path : {
description : " The filename to create. "
} ,
rra : {
description : " Configuration of contained RRAs. " ,
type : Array ,
items : {
schema : RRA_CONFIG_STRING_SCHEMA ,
}
} ,
} ,
} ,
) ]
2021-10-13 11:24:52 +03:00
/// Create a new RRD file
2024-01-31 18:26:01 +03:00
pub fn create_rrd ( dst : DataSourceType , path : String , rra : Vec < String > ) -> Result < ( ) , Error > {
2021-10-13 11:24:47 +03:00
let mut rra_list = Vec ::new ( ) ;
for item in rra . iter ( ) {
2022-04-06 17:56:33 +03:00
let rra : RRAConfig =
serde_json ::from_value ( RRAConfig ::API_SCHEMA . parse_property_string ( item ) ? ) ? ;
2021-10-13 11:24:47 +03:00
println! ( " GOT {:?} " , rra ) ;
2024-01-31 18:26:01 +03:00
rra_list . push ( Archive ::new ( rra . cf , rra . r , rra . n as usize ) ) ;
2021-10-13 11:24:47 +03:00
}
let path = PathBuf ::from ( path ) ;
2024-01-31 18:26:01 +03:00
let rrd = Database ::new ( dst , rra_list ) ;
2021-10-13 11:24:47 +03:00
2021-10-19 19:41:03 +03:00
rrd . save ( & path , CreateOptions ::new ( ) , false ) ? ;
2021-10-13 11:24:47 +03:00
Ok ( ( ) )
}
2021-10-13 11:24:52 +03:00
#[ 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.
2022-04-06 17:56:33 +03:00
pub fn resize_rrd ( path : String , rra_index : usize , slots : i64 ) -> Result < ( ) , Error > {
2021-10-13 11:24:52 +03:00
let path = PathBuf ::from ( & path ) ;
2024-01-31 18:26:01 +03:00
let mut rrd = Database ::load ( & path , false ) ? ;
2021-10-13 11:24:52 +03:00
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 {
2023-11-29 20:32:06 +03:00
bail! ( " number of new slots is too small ('{}' < 1) " , new_slots ) ;
2021-10-13 11:24:52 +03:00
}
2022-04-06 17:56:33 +03:00
if new_slots > 1024 * 1024 {
2023-11-29 20:32:06 +03:00
bail! ( " number of new slots is too big ('{}' > 1M) " , new_slots ) ;
2021-10-13 11:24:52 +03:00
}
let rra_end = rra . slot_end_time ( rrd . source . last_update as u64 ) ;
2022-04-06 17:56:33 +03:00
let rra_start = rra_end - rra . resolution * ( rra . data . len ( ) as u64 ) ;
2022-07-27 14:43:04 +03:00
let ( start , reso , data ) = rra
. extract_data ( rra_start , rra_end , rrd . source . last_update )
. into ( ) ;
2021-10-13 11:24:52 +03:00
2024-01-31 18:26:01 +03:00
let mut new_rra = Archive ::new ( rra . cf , rra . resolution , new_slots as usize ) ;
2021-10-13 11:24:52 +03:00
new_rra . last_count = rra . last_count ;
new_rra . insert_data ( start , reso , data ) ? ;
rrd . rra_list [ rra_index ] = new_rra ;
2021-10-19 19:41:03 +03:00
rrd . save ( & path , CreateOptions ::new ( ) , false ) ? ;
2021-10-13 11:24:52 +03:00
Ok ( ( ) )
}
2021-10-13 11:24:47 +03:00
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 " ,
2021-10-13 11:24:52 +03:00
CliCommand ::new ( & API_METHOD_CREATE_RRD )
2021-10-13 11:24:47 +03:00
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
2021-10-13 11:24:47 +03:00
)
. insert (
2021-10-13 11:24:52 +03:00
" dump " ,
CliCommand ::new ( & API_METHOD_DUMP_RRD )
2021-10-13 11:24:47 +03:00
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
)
2021-10-13 11:24:47 +03:00
. insert (
" fetch " ,
2021-10-13 11:24:52 +03:00
CliCommand ::new ( & API_METHOD_FETCH_RRD )
2021-10-13 11:24:47 +03:00
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
)
2021-10-13 11:24:52 +03:00
. insert (
" first " ,
CliCommand ::new ( & API_METHOD_FIRST_UPDATE_TIME )
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
2021-10-13 11:24:47 +03:00
)
. insert (
2021-10-13 11:24:52 +03:00
" info " ,
CliCommand ::new ( & API_METHOD_RRD_INFO )
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
2021-10-13 11:24:52 +03:00
)
. insert (
" last " ,
CliCommand ::new ( & API_METHOD_LAST_UPDATE_TIME )
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
2021-10-13 11:24:52 +03:00
)
. insert (
" lastupdate " ,
CliCommand ::new ( & API_METHOD_LAST_UPDATE )
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
2021-10-13 11:24:52 +03:00
)
. insert (
" resize " ,
CliCommand ::new ( & API_METHOD_RESIZE_RRD )
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
2021-10-13 11:24:52 +03:00
)
. insert (
" update " ,
CliCommand ::new ( & API_METHOD_UPDATE_RRD )
2021-10-13 11:24:47 +03:00
. arg_param ( & [ " path " ] )
2022-04-06 17:56:33 +03:00
. completion_cb ( " path " , complete_file_name ) ,
) ;
2021-10-13 11:24:47 +03:00
let mut rpcenv = CliEnvironment ::new ( ) ;
rpcenv . set_auth_id ( Some ( format! ( " {} @pam " , username ) ) ) ;
run_cli_command ( cmd_def , rpcenv , None ) ;
Ok ( ( ) )
}