1 //! # Round Robin Database file format
6 use anyhow
::{bail, Error}
;
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 // Note: This may update the state even in case of errors (see counter overflow)
131 fn update(&mut self, time
: f64, mut value
: f64) -> Result
<(), Error
> {
133 if time
<= self.last_update
{
134 bail
!("time in past ({} < {})", time
, self.last_update
);
138 bail
!("new value is NAN");
141 // derive counter value
142 if self.flags
.intersects(RRAFlags
::DST_DERIVE
| RRAFlags
::DST_COUNTER
) {
143 let time_diff
= time
- self.last_update
;
144 let is_counter
= self.flags
.contains(RRAFlags
::DST_COUNTER
);
146 let diff
= if self.counter_value
.is_nan() {
148 } else if is_counter
&& value
< 0.0 {
149 bail
!("got negative value for counter");
150 } else if is_counter
&& value
< self.counter_value
{
151 // Note: We do not try automatic overflow corrections, but
152 // we update counter_value anyways, so that we can compute the diff
154 self.counter_value
= value
;
155 bail
!("conter overflow/reset detected");
157 value
- self.counter_value
159 self.counter_value
= value
;
160 value
= diff
/time_diff
;
163 self.delete_old(time
);
164 self.compute_new_value(time
, value
);
170 /// Round Robin Database file format with fixed number of [RRA]s
172 // Note: Avoid alignment problems by using 8byte types only
174 /// The magic number to identify the file type
176 /// Hourly data (average values)
178 /// Hourly data (maximum values)
180 /// Dayly data (average values)
182 /// Dayly data (maximum values)
184 /// Weekly data (average values)
186 /// Weekly data (maximum values)
188 /// Monthly data (average values)
190 /// Monthly data (maximum values)
192 /// Yearly data (average values)
194 /// Yearly data (maximum values)
200 /// Create a new empty instance
201 pub fn new(dst
: DST
) -> Self {
202 let flags
= match dst
{
203 DST
::Gauge
=> RRAFlags
::DST_GAUGE
,
204 DST
::Derive
=> RRAFlags
::DST_DERIVE
,
208 magic
: PROXMOX_RRD_MAGIC_1_0
,
210 flags
| RRAFlags
::CF_AVERAGE
,
211 RRDTimeFrameResolution
::Hour
as u64,
214 flags
| RRAFlags
::CF_MAX
,
215 RRDTimeFrameResolution
::Hour
as u64,
218 flags
| RRAFlags
::CF_AVERAGE
,
219 RRDTimeFrameResolution
::Day
as u64,
222 flags
| RRAFlags
::CF_MAX
,
223 RRDTimeFrameResolution
::Day
as u64,
226 flags
| RRAFlags
::CF_AVERAGE
,
227 RRDTimeFrameResolution
::Week
as u64,
230 flags
| RRAFlags
::CF_MAX
,
231 RRDTimeFrameResolution
::Week
as u64,
234 flags
| RRAFlags
::CF_AVERAGE
,
235 RRDTimeFrameResolution
::Month
as u64,
238 flags
| RRAFlags
::CF_MAX
,
239 RRDTimeFrameResolution
::Month
as u64,
242 flags
| RRAFlags
::CF_AVERAGE
,
243 RRDTimeFrameResolution
::Year
as u64,
246 flags
| RRAFlags
::CF_MAX
,
247 RRDTimeFrameResolution
::Year
as u64,
252 /// Extract data from the archive
256 timeframe
: RRDTimeFrameResolution
,
258 ) -> (u64, u64, Vec
<Option
<f64>>) {
260 let epoch
= time
as u64;
261 let reso
= timeframe
as u64;
263 let end
= reso
*(epoch
/reso
+ 1);
264 let start
= end
- reso
*(RRD_DATA_ENTRIES
as u64);
266 let mut list
= Vec
::new();
268 let raa
= match (mode
, timeframe
) {
269 (RRDMode
::Average
, RRDTimeFrameResolution
::Hour
) => &self.hour_avg
,
270 (RRDMode
::Max
, RRDTimeFrameResolution
::Hour
) => &self.hour_max
,
271 (RRDMode
::Average
, RRDTimeFrameResolution
::Day
) => &self.day_avg
,
272 (RRDMode
::Max
, RRDTimeFrameResolution
::Day
) => &self.day_max
,
273 (RRDMode
::Average
, RRDTimeFrameResolution
::Week
) => &self.week_avg
,
274 (RRDMode
::Max
, RRDTimeFrameResolution
::Week
) => &self.week_max
,
275 (RRDMode
::Average
, RRDTimeFrameResolution
::Month
) => &self.month_avg
,
276 (RRDMode
::Max
, RRDTimeFrameResolution
::Month
) => &self.month_max
,
277 (RRDMode
::Average
, RRDTimeFrameResolution
::Year
) => &self.year_avg
,
278 (RRDMode
::Max
, RRDTimeFrameResolution
::Year
) => &self.year_max
,
281 let rrd_end
= reso
*((raa
.last_update
as u64)/reso
);
282 let rrd_start
= rrd_end
- reso
*(RRD_DATA_ENTRIES
as u64);
285 let mut index
= ((t
/reso
) % (RRD_DATA_ENTRIES
as u64)) as usize;
286 for _
in 0..RRD_DATA_ENTRIES
{
287 if t
< rrd_start
|| t
> rrd_end
{
290 let value
= raa
.data
[index
];
294 list
.push(Some(value
));
297 t
+= reso
; index
= (index
+ 1) % RRD_DATA_ENTRIES
;
303 /// Create instance from raw data, testing data len and magic number
304 pub fn from_raw(mut raw
: &[u8]) -> Result
<Self, std
::io
::Error
> {
305 let expected_len
= std
::mem
::size_of
::<RRD
>();
306 if raw
.len() != expected_len
{
307 let msg
= format
!("wrong data size ({} != {})", raw
.len(), expected_len
);
308 return Err(std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, msg
));
311 let mut rrd
: RRD
= unsafe { std::mem::zeroed() }
;
313 let rrd_slice
= std
::slice
::from_raw_parts_mut(&mut rrd
as *mut _
as *mut u8, expected_len
);
314 raw
.read_exact(rrd_slice
)?
;
317 if rrd
.magic
!= PROXMOX_RRD_MAGIC_1_0
{
318 let msg
= "wrong magic number".to_string();
319 return Err(std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, msg
));
325 /// Load data from a file
326 pub fn load(path
: &Path
) -> Result
<Self, std
::io
::Error
> {
327 let raw
= std
::fs
::read(path
)?
;
331 /// Store data into a file (atomic replace file)
332 pub fn save(&self, filename
: &Path
, options
: CreateOptions
) -> Result
<(), Error
> {
333 let rrd_slice
= unsafe {
334 std
::slice
::from_raw_parts(self as *const _
as *const u8, std
::mem
::size_of
::<RRD
>())
336 replace_file(filename
, rrd_slice
, options
)
339 /// Update the value (in memory)
341 /// Note: This does not call [Self::save].
342 pub fn update(&mut self, time
: f64, value
: f64) {
344 let mut log_error
= true;
346 let mut update_rra
= |rra
: &mut RRA
| {
347 if let Err(err
) = rra
.update(time
, value
) {
349 log
::error
!("rrd update failed: {}", err
);
350 // we only log the first error, because it is very
351 // likely other calls produce the same error
357 update_rra(&mut self.hour_avg
);
358 update_rra(&mut self.hour_max
);
360 update_rra(&mut self.day_avg
);
361 update_rra(&mut self.day_max
);
363 update_rra(&mut self.week_avg
);
364 update_rra(&mut self.week_max
);
366 update_rra(&mut self.month_avg
);
367 update_rra(&mut self.month_max
);
369 update_rra(&mut self.year_avg
);
370 update_rra(&mut self.year_max
);