1 //! # Round Robin Database file format
7 use bitflags
::bitflags
;
9 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
11 use proxmox_rrd_api_types
::{RRDMode, RRDTimeFrameResolution}
;
13 /// The number of data entries per RRA
14 pub const RRD_DATA_ENTRIES
: usize = 70;
16 /// Proxmox RRD file magic number
17 // openssl::sha::sha256(b"Proxmox Round Robin Database file v1.0")[0..8];
18 pub const PROXMOX_RRD_MAGIC_1_0
: [u8; 8] = [206, 46, 26, 212, 172, 158, 5, 186];
23 /// Flags to specify the data soure type and consolidation function
24 pub struct RRAFlags
: u64 {
28 const DST_COUNTER
= 4;
29 const DST_MASK
= 255; // first 8 bits
31 // Consolidation Functions
32 const CF_AVERAGE
= 1 << 8;
33 const CF_MAX
= 2 << 8;
34 const CF_MASK
= 255 << 8;
38 /// Round Robin Archive with [RRD_DATA_ENTRIES] data slots.
40 /// This data structure is used inside [RRD] and directly written to the
44 /// Defined the data soure type and consolidation function
46 /// Resulution (seconds) from [RRDTimeFrameResolution]
48 /// Last update time (epoch)
50 /// Count values computed inside this update interval
52 /// Stores the last value, used to compute differential value for derive/counters
53 pub counter_value
: f64,
55 pub data
: [f64; RRD_DATA_ENTRIES
],
59 fn new(flags
: RRAFlags
, resolution
: u64) -> Self {
64 counter_value
: f64::NAN
,
65 data
: [f64::NAN
; RRD_DATA_ENTRIES
],
69 fn delete_old(&mut self, time
: f64) {
70 let epoch
= time
as u64;
71 let last_update
= self.last_update
as u64;
72 let reso
= self.resolution
;
74 let min_time
= epoch
- (RRD_DATA_ENTRIES
as u64)*reso
;
75 let min_time
= (min_time
/reso
+ 1)*reso
;
76 let mut t
= last_update
.saturating_sub((RRD_DATA_ENTRIES
as u64)*reso
);
77 let mut index
= ((t
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
78 for _
in 0..RRD_DATA_ENTRIES
{
79 t
+= reso
; index
= (index
+ 1) % RRD_DATA_ENTRIES
;
81 self.data
[index
] = f64::NAN
;
88 fn compute_new_value(&mut self, time
: f64, value
: f64) {
89 let epoch
= time
as u64;
90 let last_update
= self.last_update
as u64;
91 let reso
= self.resolution
;
93 let index
= ((epoch
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
94 let last_index
= ((last_update
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
96 if (epoch
- (last_update
as u64)) > reso
|| index
!= last_index
{
100 let last_value
= self.data
[index
];
101 if last_value
.is_nan() {
105 let new_count
= if self.last_count
< u64::MAX
{
108 u64::MAX
// should never happen
111 if self.last_count
== 0 {
112 self.data
[index
] = value
;
115 let new_value
= if self.flags
.contains(RRAFlags
::CF_MAX
) {
116 if last_value
> value { last_value }
else { value }
117 } else if self.flags
.contains(RRAFlags
::CF_AVERAGE
) {
118 (last_value
*(self.last_count
as f64))/(new_count
as f64)
119 + value
/(new_count
as f64)
121 log
::error
!("rrdb update failed - unknown CF");
124 self.data
[index
] = new_value
;
125 self.last_count
= new_count
;
127 self.last_update
= time
;
130 fn update(&mut self, time
: f64, mut value
: f64, log_time_in_past
: &mut bool
) {
132 if time
<= self.last_update
{
133 if *log_time_in_past
{
134 log
::warn
!("rrdb update failed - time in past ({} < {})", time
, self.last_update
);
135 *log_time_in_past
= false; // avoid logging this multiple times inside a RRD
141 log
::warn
!("rrdb update failed - new value is NAN");
145 // derive counter value
146 if self.flags
.intersects(RRAFlags
::DST_DERIVE
| RRAFlags
::DST_COUNTER
) {
147 let time_diff
= time
- self.last_update
;
148 let is_counter
= self.flags
.contains(RRAFlags
::DST_COUNTER
);
150 let diff
= if self.counter_value
.is_nan() {
152 } else if is_counter
&& value
< 0.0 {
153 log
::warn
!("rrdb update failed - got negative value for counter");
155 } else if is_counter
&& value
< self.counter_value
{
156 // Note: We do not try automatic overflow corrections
157 self.counter_value
= value
;
158 log
::warn
!("rrdb update failed - conter overflow/reset detected");
161 value
- self.counter_value
163 self.counter_value
= value
;
164 value
= diff
/time_diff
;
167 self.delete_old(time
);
168 self.compute_new_value(time
, value
);
172 /// Round Robin Database file format with fixed number of [RRA]s
174 // Note: Avoid alignment problems by using 8byte types only
176 /// The magic number to identify the file type
178 /// Hourly data (average values)
180 /// Hourly data (maximum values)
182 /// Dayly data (average values)
184 /// Dayly data (maximum values)
186 /// Weekly data (average values)
188 /// Weekly data (maximum values)
190 /// Monthly data (average values)
192 /// Monthly data (maximum values)
194 /// Yearly data (average values)
196 /// Yearly data (maximum values)
202 /// Create a new empty instance
203 pub fn new(dst
: DST
) -> Self {
204 let flags
= match dst
{
205 DST
::Gauge
=> RRAFlags
::DST_GAUGE
,
206 DST
::Derive
=> RRAFlags
::DST_DERIVE
,
210 magic
: PROXMOX_RRD_MAGIC_1_0
,
212 flags
| RRAFlags
::CF_AVERAGE
,
213 RRDTimeFrameResolution
::Hour
as u64,
216 flags
| RRAFlags
::CF_MAX
,
217 RRDTimeFrameResolution
::Hour
as u64,
220 flags
| RRAFlags
::CF_AVERAGE
,
221 RRDTimeFrameResolution
::Day
as u64,
224 flags
| RRAFlags
::CF_MAX
,
225 RRDTimeFrameResolution
::Day
as u64,
228 flags
| RRAFlags
::CF_AVERAGE
,
229 RRDTimeFrameResolution
::Week
as u64,
232 flags
| RRAFlags
::CF_MAX
,
233 RRDTimeFrameResolution
::Week
as u64,
236 flags
| RRAFlags
::CF_AVERAGE
,
237 RRDTimeFrameResolution
::Month
as u64,
240 flags
| RRAFlags
::CF_MAX
,
241 RRDTimeFrameResolution
::Month
as u64,
244 flags
| RRAFlags
::CF_AVERAGE
,
245 RRDTimeFrameResolution
::Year
as u64,
248 flags
| RRAFlags
::CF_MAX
,
249 RRDTimeFrameResolution
::Year
as u64,
254 /// Extract data from the archive
258 timeframe
: RRDTimeFrameResolution
,
260 ) -> (u64, u64, Vec
<Option
<f64>>) {
262 let epoch
= time
as u64;
263 let reso
= timeframe
as u64;
265 let end
= reso
*(epoch
/reso
+ 1);
266 let start
= end
- reso
*(RRD_DATA_ENTRIES
as u64);
268 let mut list
= Vec
::new();
270 let raa
= match (mode
, timeframe
) {
271 (RRDMode
::Average
, RRDTimeFrameResolution
::Hour
) => &self.hour_avg
,
272 (RRDMode
::Max
, RRDTimeFrameResolution
::Hour
) => &self.hour_max
,
273 (RRDMode
::Average
, RRDTimeFrameResolution
::Day
) => &self.day_avg
,
274 (RRDMode
::Max
, RRDTimeFrameResolution
::Day
) => &self.day_max
,
275 (RRDMode
::Average
, RRDTimeFrameResolution
::Week
) => &self.week_avg
,
276 (RRDMode
::Max
, RRDTimeFrameResolution
::Week
) => &self.week_max
,
277 (RRDMode
::Average
, RRDTimeFrameResolution
::Month
) => &self.month_avg
,
278 (RRDMode
::Max
, RRDTimeFrameResolution
::Month
) => &self.month_max
,
279 (RRDMode
::Average
, RRDTimeFrameResolution
::Year
) => &self.year_avg
,
280 (RRDMode
::Max
, RRDTimeFrameResolution
::Year
) => &self.year_max
,
283 let rrd_end
= reso
*((raa
.last_update
as u64)/reso
);
284 let rrd_start
= rrd_end
- reso
*(RRD_DATA_ENTRIES
as u64);
287 let mut index
= ((t
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
288 for _
in 0..RRD_DATA_ENTRIES
{
289 if t
< rrd_start
|| t
> rrd_end
{
292 let value
= raa
.data
[index
];
296 list
.push(Some(value
));
299 t
+= reso
; index
= (index
+ 1) % RRD_DATA_ENTRIES
;
305 /// Create instance from raw data, testing data len and magic number
306 pub fn from_raw(mut raw
: &[u8]) -> Result
<Self, std
::io
::Error
> {
307 let expected_len
= std
::mem
::size_of
::<RRD
>();
308 if raw
.len() != expected_len
{
309 let msg
= format
!("wrong data size ({} != {})", raw
.len(), expected_len
);
310 return Err(std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, msg
));
313 let mut rrd
: RRD
= unsafe { std::mem::zeroed() }
;
315 let rrd_slice
= std
::slice
::from_raw_parts_mut(&mut rrd
as *mut _
as *mut u8, expected_len
);
316 raw
.read_exact(rrd_slice
)?
;
319 if rrd
.magic
!= PROXMOX_RRD_MAGIC_1_0
{
320 let msg
= "wrong magic number".to_string();
321 return Err(std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, msg
));
327 /// Load data from a file
328 pub fn load(path
: &Path
) -> Result
<Self, std
::io
::Error
> {
329 let raw
= std
::fs
::read(path
)?
;
333 /// Store data into a file (atomic replace file)
334 pub fn save(&self, filename
: &Path
, options
: CreateOptions
) -> Result
<(), Error
> {
335 let rrd_slice
= unsafe {
336 std
::slice
::from_raw_parts(self as *const _
as *const u8, std
::mem
::size_of
::<RRD
>())
338 replace_file(filename
, rrd_slice
, options
)
341 /// Update the value (in memory)
343 /// Note: This does not call [Self::save].
344 pub fn update(&mut self, time
: f64, value
: f64) {
347 log
::warn
!("rrdb update failed - new value is NAN");
351 let mut log_time_in_past
= true;
353 self.hour_avg
.update(time
, value
, &mut log_time_in_past
);
354 self.hour_max
.update(time
, value
, &mut log_time_in_past
);
356 self.day_avg
.update(time
, value
, &mut log_time_in_past
);
357 self.day_max
.update(time
, value
, &mut log_time_in_past
);
359 self.week_avg
.update(time
, value
, &mut log_time_in_past
);
360 self.week_max
.update(time
, value
, &mut log_time_in_past
);
362 self.month_avg
.update(time
, value
, &mut log_time_in_past
);
363 self.month_max
.update(time
, value
, &mut log_time_in_past
);
365 self.year_avg
.update(time
, value
, &mut log_time_in_past
);
366 self.year_max
.update(time
, value
, &mut log_time_in_past
);