6 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
8 use proxmox_rrd_api_types
::{RRDMode, RRDTimeFrameResolution}
;
10 /// The number of data entries per RRA
11 pub const RRD_DATA_ENTRIES
: usize = 70;
13 /// Proxmox RRD file magic number
14 // openssl::sha::sha256(b"Proxmox Round Robin Database file v1.0")[0..8];
15 pub const PROXMOX_RRD_MAGIC_1_0
: [u8; 8] = [206, 46, 26, 212, 172, 158, 5, 186];
17 use bitflags
::bitflags
;
20 struct RRAFlags
: u64 {
24 const DST_COUNTER
= 4;
25 const DST_MASK
= 255; // first 8 bits
27 // Consolidation Functions
28 const CF_AVERAGE
= 1 << 8;
29 const CF_MAX
= 2 << 8;
30 const CF_MASK
= 255 << 8;
34 /// RRD data source tyoe
46 counter_value
: f64, // used for derive/counters
47 data
: [f64; RRD_DATA_ENTRIES
],
51 fn new(flags
: RRAFlags
, resolution
: u64) -> Self {
56 counter_value
: f64::NAN
,
57 data
: [f64::NAN
; RRD_DATA_ENTRIES
],
61 fn delete_old(&mut self, time
: f64) {
62 let epoch
= time
as u64;
63 let last_update
= self.last_update
as u64;
64 let reso
= self.resolution
;
66 let min_time
= epoch
- (RRD_DATA_ENTRIES
as u64)*reso
;
67 let min_time
= (min_time
/reso
+ 1)*reso
;
68 let mut t
= last_update
.saturating_sub((RRD_DATA_ENTRIES
as u64)*reso
);
69 let mut index
= ((t
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
70 for _
in 0..RRD_DATA_ENTRIES
{
71 t
+= reso
; index
= (index
+ 1) % RRD_DATA_ENTRIES
;
73 self.data
[index
] = f64::NAN
;
80 fn compute_new_value(&mut self, time
: f64, value
: f64) {
81 let epoch
= time
as u64;
82 let last_update
= self.last_update
as u64;
83 let reso
= self.resolution
;
85 let index
= ((epoch
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
86 let last_index
= ((last_update
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
88 if (epoch
- (last_update
as u64)) > reso
|| index
!= last_index
{
92 let last_value
= self.data
[index
];
93 if last_value
.is_nan() {
97 let new_count
= if self.last_count
< u64::MAX
{
100 u64::MAX
// should never happen
103 if self.last_count
== 0 {
104 self.data
[index
] = value
;
107 let new_value
= if self.flags
.contains(RRAFlags
::CF_MAX
) {
108 if last_value
> value { last_value }
else { value }
109 } else if self.flags
.contains(RRAFlags
::CF_AVERAGE
) {
110 (last_value
*(self.last_count
as f64))/(new_count
as f64)
111 + value
/(new_count
as f64)
113 eprintln
!("rrdb update failed - unknown CF");
116 self.data
[index
] = new_value
;
117 self.last_count
= new_count
;
119 self.last_update
= time
;
122 fn update(&mut self, time
: f64, mut value
: f64) {
124 if time
<= self.last_update
{
125 eprintln
!("rrdb update failed - time in past ({} < {})", time
, self.last_update
);
129 eprintln
!("rrdb update failed - new value is NAN");
133 // derive counter value
134 if self.flags
.intersects(RRAFlags
::DST_DERIVE
| RRAFlags
::DST_COUNTER
) {
135 let time_diff
= time
- self.last_update
;
136 let is_counter
= self.flags
.contains(RRAFlags
::DST_COUNTER
);
138 let diff
= if self.counter_value
.is_nan() {
140 } else if is_counter
&& value
< 0.0 {
141 eprintln
!("rrdb update failed - got negative value for counter");
143 } else if is_counter
&& value
< self.counter_value
{
144 // Note: We do not try automatic overflow corrections
145 self.counter_value
= value
;
146 eprintln
!("rrdb update failed - conter overflow/reset detected");
149 value
- self.counter_value
151 self.counter_value
= value
;
152 value
= diff
/time_diff
;
155 self.delete_old(time
);
156 self.compute_new_value(time
, value
);
161 // Note: Avoid alignment problems by using 8byte types only
178 pub fn new(dst
: DST
) -> Self {
179 let flags
= match dst
{
180 DST
::Gauge
=> RRAFlags
::DST_GAUGE
,
181 DST
::Derive
=> RRAFlags
::DST_DERIVE
,
185 magic
: PROXMOX_RRD_MAGIC_1_0
,
187 flags
| RRAFlags
::CF_AVERAGE
,
188 RRDTimeFrameResolution
::Hour
as u64,
191 flags
| RRAFlags
::CF_MAX
,
192 RRDTimeFrameResolution
::Hour
as u64,
195 flags
| RRAFlags
::CF_AVERAGE
,
196 RRDTimeFrameResolution
::Day
as u64,
199 flags
| RRAFlags
::CF_MAX
,
200 RRDTimeFrameResolution
::Day
as u64,
203 flags
| RRAFlags
::CF_AVERAGE
,
204 RRDTimeFrameResolution
::Week
as u64,
207 flags
| RRAFlags
::CF_MAX
,
208 RRDTimeFrameResolution
::Week
as u64,
211 flags
| RRAFlags
::CF_AVERAGE
,
212 RRDTimeFrameResolution
::Month
as u64,
215 flags
| RRAFlags
::CF_MAX
,
216 RRDTimeFrameResolution
::Month
as u64,
219 flags
| RRAFlags
::CF_AVERAGE
,
220 RRDTimeFrameResolution
::Year
as u64,
223 flags
| RRAFlags
::CF_MAX
,
224 RRDTimeFrameResolution
::Year
as u64,
232 timeframe
: RRDTimeFrameResolution
,
234 ) -> (u64, u64, Vec
<Option
<f64>>) {
236 let epoch
= time
as u64;
237 let reso
= timeframe
as u64;
239 let end
= reso
*(epoch
/reso
+ 1);
240 let start
= end
- reso
*(RRD_DATA_ENTRIES
as u64);
242 let mut list
= Vec
::new();
244 let raa
= match (mode
, timeframe
) {
245 (RRDMode
::Average
, RRDTimeFrameResolution
::Hour
) => &self.hour_avg
,
246 (RRDMode
::Max
, RRDTimeFrameResolution
::Hour
) => &self.hour_max
,
247 (RRDMode
::Average
, RRDTimeFrameResolution
::Day
) => &self.day_avg
,
248 (RRDMode
::Max
, RRDTimeFrameResolution
::Day
) => &self.day_max
,
249 (RRDMode
::Average
, RRDTimeFrameResolution
::Week
) => &self.week_avg
,
250 (RRDMode
::Max
, RRDTimeFrameResolution
::Week
) => &self.week_max
,
251 (RRDMode
::Average
, RRDTimeFrameResolution
::Month
) => &self.month_avg
,
252 (RRDMode
::Max
, RRDTimeFrameResolution
::Month
) => &self.month_max
,
253 (RRDMode
::Average
, RRDTimeFrameResolution
::Year
) => &self.year_avg
,
254 (RRDMode
::Max
, RRDTimeFrameResolution
::Year
) => &self.year_max
,
257 let rrd_end
= reso
*((raa
.last_update
as u64)/reso
);
258 let rrd_start
= rrd_end
- reso
*(RRD_DATA_ENTRIES
as u64);
261 let mut index
= ((t
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
262 for _
in 0..RRD_DATA_ENTRIES
{
263 if t
< rrd_start
|| t
> rrd_end
{
266 let value
= raa
.data
[index
];
270 list
.push(Some(value
));
273 t
+= reso
; index
= (index
+ 1) % RRD_DATA_ENTRIES
;
279 pub fn from_raw(mut raw
: &[u8]) -> Result
<Self, std
::io
::Error
> {
280 let expected_len
= std
::mem
::size_of
::<RRD
>();
281 if raw
.len() != expected_len
{
282 let msg
= format
!("wrong data size ({} != {})", raw
.len(), expected_len
);
283 return Err(std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, msg
));
286 let mut rrd
: RRD
= unsafe { std::mem::zeroed() }
;
288 let rrd_slice
= std
::slice
::from_raw_parts_mut(&mut rrd
as *mut _
as *mut u8, expected_len
);
289 raw
.read_exact(rrd_slice
)?
;
292 if rrd
.magic
!= PROXMOX_RRD_MAGIC_1_0
{
293 let msg
= "wrong magic number".to_string();
294 return Err(std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, msg
));
300 pub fn load(path
: &Path
) -> Result
<Self, std
::io
::Error
> {
301 let raw
= std
::fs
::read(path
)?
;
305 pub fn save(&self, filename
: &Path
, options
: CreateOptions
) -> Result
<(), Error
> {
306 let rrd_slice
= unsafe {
307 std
::slice
::from_raw_parts(self as *const _
as *const u8, std
::mem
::size_of
::<RRD
>())
309 replace_file(filename
, rrd_slice
, options
)
313 pub fn update(&mut self, time
: f64, value
: f64) {
314 self.hour_avg
.update(time
, value
);
315 self.hour_max
.update(time
, value
);
317 self.day_avg
.update(time
, value
);
318 self.day_max
.update(time
, value
);
320 self.week_avg
.update(time
, value
);
321 self.week_max
.update(time
, value
);
323 self.month_avg
.update(time
, value
);
324 self.month_max
.update(time
, value
);
326 self.year_avg
.update(time
, value
);
327 self.year_max
.update(time
, value
);