]> git.proxmox.com Git - proxmox-backup.git/blob - src/rrd/rrd.rs
8f542b528b550137f2c92eabbf2bc6d3a1654533
[proxmox-backup.git] / src / rrd / rrd.rs
1 use std::io::Read;
2 use std::path::Path;
3
4 use anyhow::Error;
5
6 use crate::api2::types::{RRDMode, RRDTimeFrameResolution};
7
8 pub const RRD_DATA_ENTRIES: usize = 70;
9
10 // openssl::sha::sha256(b"Proxmox Round Robin Database file v1.0")[0..8];
11 pub const PROXMOX_RRD_MAGIC_1_0: [u8; 8] = [206, 46, 26, 212, 172, 158, 5, 186];
12
13 use bitflags::bitflags;
14
15 bitflags!{
16 pub struct RRAFlags: u64 {
17 // Data Source Types
18 const DST_GAUGE = 1;
19 const DST_DERIVE = 2;
20 const DST_COUNTER = 4;
21 const DST_MASK = 255; // first 8 bits
22
23 // Consolidation Functions
24 const CF_AVERAGE = 1 << 8;
25 const CF_MAX = 2 << 8;
26 const CF_MASK = 255 << 8;
27 }
28 }
29
30 pub enum DST {
31 Gauge,
32 Derive,
33 }
34
35 #[repr(C)]
36 struct RRA {
37 flags: RRAFlags,
38 resolution: u64,
39 last_update: f64,
40 last_count: u64,
41 counter_value: f64, // used for derive/counters
42 data: [f64; RRD_DATA_ENTRIES],
43 }
44
45 impl RRA {
46 fn new(flags: RRAFlags, resolution: u64) -> Self {
47 Self {
48 flags, resolution,
49 last_update: 0.0,
50 last_count: 0,
51 counter_value: f64::NAN,
52 data: [f64::NAN; RRD_DATA_ENTRIES],
53 }
54 }
55
56 fn delete_old(&mut self, time: f64) {
57 let epoch = time as u64;
58 let last_update = self.last_update as u64;
59 let reso = self.resolution;
60
61 let min_time = epoch - (RRD_DATA_ENTRIES as u64)*reso;
62 let min_time = (min_time/reso + 1)*reso;
63 let mut t = last_update - (RRD_DATA_ENTRIES as u64)*reso;
64 let mut index = ((t/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
65 for _ in 0..RRD_DATA_ENTRIES {
66 t += reso; index = (index + 1) % RRD_DATA_ENTRIES;
67 if t < min_time {
68 self.data[index] = f64::NAN;
69 } else {
70 break;
71 }
72 }
73 }
74
75 fn compute_new_value(&mut self, time: f64, value: f64) {
76 let epoch = time as u64;
77 let last_update = self.last_update as u64;
78 let reso = self.resolution;
79
80 let index = ((epoch/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
81 let last_index = ((last_update/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
82
83 if (epoch - (last_update as u64)) > reso || index != last_index {
84 self.last_count = 0;
85 }
86
87 let last_value = self.data[index];
88 if last_value.is_nan() {
89 self.last_count = 0;
90 }
91
92 let new_count = if self.last_count < u64::MAX {
93 self.last_count + 1
94 } else {
95 u64::MAX // should never happen
96 };
97
98 if self.last_count == 0 {
99 self.data[index] = value;
100 self.last_count = 1;
101 } else {
102 let new_value = if self.flags.contains(RRAFlags::CF_MAX) {
103 if last_value > value { last_value } else { value }
104 } else if self.flags.contains(RRAFlags::CF_AVERAGE) {
105 (last_value*(self.last_count as f64))/(new_count as f64)
106 + value/(new_count as f64)
107 } else {
108 eprintln!("rrdb update failed - unknown CF");
109 return;
110 };
111 self.data[index] = new_value;
112 self.last_count = new_count;
113 }
114 self.last_update = time;
115 }
116
117 fn update(&mut self, time: f64, mut value: f64) {
118
119 if time <= self.last_update {
120 eprintln!("rrdb update failed - time in past ({} < {})", time, self.last_update);
121 }
122
123 if value.is_nan() {
124 eprintln!("rrdb update failed - new value is NAN");
125 return;
126 }
127
128 // derive counter value
129 if self.flags.intersects(RRAFlags::DST_DERIVE | RRAFlags::DST_COUNTER) {
130 let time_diff = time - self.last_update;
131 let diff = if self.counter_value.is_nan() {
132 0.0
133 } else {
134 if self.flags.contains(RRAFlags::DST_COUNTER) { // check for overflow
135 if value < 0.0 {
136 eprintln!("rrdb update failed - got negative value for counter");
137 return;
138 }
139 // Note: We do not try automatic overflow corrections
140 if value < self.counter_value { // overflow or counter reset
141 self.counter_value = value;
142 eprintln!("rrdb update failed - conter overflow/reset detected");
143 return;
144 } else {
145 value - self.counter_value
146 }
147 } else {
148 value - self.counter_value
149 }
150 };
151 self.counter_value = value;
152 value = diff/time_diff;
153 }
154
155 self.delete_old(time);
156 self.compute_new_value(time, value);
157 }
158 }
159
160 #[repr(C)]
161 // Note: Avoid alignment problems by using 8byte types only
162 pub struct RRD {
163 magic: [u8; 8],
164 hour_avg: RRA,
165 hour_max: RRA,
166 day_avg: RRA,
167 day_max: RRA,
168 week_avg: RRA,
169 week_max: RRA,
170 month_avg: RRA,
171 month_max: RRA,
172 year_avg: RRA,
173 year_max: RRA,
174 }
175
176 impl RRD {
177
178 pub fn new(dst: DST) -> Self {
179 let flags = match dst {
180 DST::Gauge => RRAFlags::DST_GAUGE,
181 DST::Derive => RRAFlags::DST_DERIVE,
182 };
183
184 Self {
185 magic: PROXMOX_RRD_MAGIC_1_0,
186 hour_avg: RRA::new(
187 flags | RRAFlags::CF_AVERAGE,
188 RRDTimeFrameResolution::Hour as u64,
189 ),
190 hour_max: RRA::new(
191 flags | RRAFlags::CF_MAX,
192 RRDTimeFrameResolution::Hour as u64,
193 ),
194 day_avg: RRA::new(
195 flags | RRAFlags::CF_AVERAGE,
196 RRDTimeFrameResolution::Day as u64,
197 ),
198 day_max: RRA::new(
199 flags | RRAFlags::CF_MAX,
200 RRDTimeFrameResolution::Day as u64,
201 ),
202 week_avg: RRA::new(
203 flags | RRAFlags::CF_AVERAGE,
204 RRDTimeFrameResolution::Week as u64,
205 ),
206 week_max: RRA::new(
207 flags | RRAFlags::CF_MAX,
208 RRDTimeFrameResolution::Week as u64,
209 ),
210 month_avg: RRA::new(
211 flags | RRAFlags::CF_AVERAGE,
212 RRDTimeFrameResolution::Month as u64,
213 ),
214 month_max: RRA::new(
215 flags | RRAFlags::CF_MAX,
216 RRDTimeFrameResolution::Month as u64,
217 ),
218 year_avg: RRA::new(
219 flags | RRAFlags::CF_AVERAGE,
220 RRDTimeFrameResolution::Year as u64,
221 ),
222 year_max: RRA::new(
223 flags | RRAFlags::CF_MAX,
224 RRDTimeFrameResolution::Year as u64,
225 ),
226 }
227 }
228
229 pub fn extract_data(
230 &self,
231 time: f64,
232 timeframe: RRDTimeFrameResolution,
233 mode: RRDMode,
234 ) -> (u64, u64, Vec<Option<f64>>) {
235 let epoch = time as u64;
236 let reso = timeframe as u64;
237
238 let end = reso*(epoch/reso + 1);
239 let start = end - reso*(RRD_DATA_ENTRIES as u64);
240
241 let mut list = Vec::new();
242
243 let raa = match (mode, timeframe) {
244 (RRDMode::Average, RRDTimeFrameResolution::Hour) => &self.hour_avg,
245 (RRDMode::Max, RRDTimeFrameResolution::Hour) => &self.hour_max,
246 (RRDMode::Average, RRDTimeFrameResolution::Day) => &self.day_avg,
247 (RRDMode::Max, RRDTimeFrameResolution::Day) => &self.day_max,
248 (RRDMode::Average, RRDTimeFrameResolution::Week) => &self.week_avg,
249 (RRDMode::Max, RRDTimeFrameResolution::Week) => &self.week_max,
250 (RRDMode::Average, RRDTimeFrameResolution::Month) => &self.month_avg,
251 (RRDMode::Max, RRDTimeFrameResolution::Month) => &self.month_max,
252 (RRDMode::Average, RRDTimeFrameResolution::Year) => &self.year_avg,
253 (RRDMode::Max, RRDTimeFrameResolution::Year) => &self.year_max,
254 };
255
256 let rrd_end = reso*((raa.last_update as u64)/reso);
257 let rrd_start = rrd_end - reso*(RRD_DATA_ENTRIES as u64);
258
259 let mut t = start;
260 let mut index = ((t/reso) % (RRD_DATA_ENTRIES as u64)) as usize;
261 for _ in 0..RRD_DATA_ENTRIES {
262 if t < rrd_start || t > rrd_end {
263 list.push(None);
264 } else {
265 let value = raa.data[index];
266 if value.is_nan() {
267 list.push(None);
268 } else {
269 list.push(Some(value));
270 }
271 }
272 t += reso; index = (index + 1) % RRD_DATA_ENTRIES;
273 }
274
275 (start, reso, list.into())
276 }
277
278 pub fn from_raw(mut raw: &[u8]) -> Result<Self, std::io::Error> {
279 let expected_len = std::mem::size_of::<RRD>();
280 if raw.len() != expected_len {
281 let msg = format!("wrong data size ({} != {})", raw.len(), expected_len);
282 return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
283 }
284
285 let mut rrd: RRD = unsafe { std::mem::zeroed() };
286 unsafe {
287 let rrd_slice = std::slice::from_raw_parts_mut(&mut rrd as *mut _ as *mut u8, expected_len);
288 raw.read_exact(rrd_slice)?;
289 }
290
291 if rrd.magic != PROXMOX_RRD_MAGIC_1_0 {
292 let msg = format!("wrong magic number");
293 return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
294 }
295
296 Ok(rrd)
297 }
298
299 pub fn load(path: &Path) -> Result<Self, std::io::Error> {
300 let raw = std::fs::read(path)?;
301 Self::from_raw(&raw)
302 }
303
304 pub fn save(&self, filename: &Path) -> Result<(), Error> {
305 use proxmox::tools::{fs::replace_file, fs::CreateOptions};
306
307 let rrd_slice = unsafe {
308 std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of::<RRD>())
309 };
310
311 let backup_user = crate::backup::backup_user()?;
312 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
313 // set the correct owner/group/permissions while saving file
314 // owner(rw) = backup, group(r)= backup
315 let options = CreateOptions::new()
316 .perm(mode)
317 .owner(backup_user.uid)
318 .group(backup_user.gid);
319
320 replace_file(filename, rrd_slice, options)?;
321
322 Ok(())
323 }
324
325
326 pub fn update(&mut self, time: f64, value: f64) {
327 self.hour_avg.update(time, value);
328 self.hour_max.update(time, value);
329
330 self.day_avg.update(time, value);
331 self.day_max.update(time, value);
332
333 self.week_avg.update(time, value);
334 self.week_max.update(time, value);
335
336 self.month_avg.update(time, value);
337 self.month_max.update(time, value);
338
339 self.year_avg.update(time, value);
340 self.year_max.update(time, value);
341 }
342 }