]> git.proxmox.com Git - proxmox-backup.git/blob - proxmox-rrd/src/rrd.rs
split out RRD api types into proxmox-rrd-api-types crate
[proxmox-backup.git] / proxmox-rrd / src / rrd.rs
1 use std::io::Read;
2 use std::path::Path;
3
4 use anyhow::Error;
5
6 use proxmox::tools::{fs::replace_file, fs::CreateOptions};
7
8 use proxmox_rrd_api_types::{RRDMode, RRDTimeFrameResolution};
9
10 /// The number of data entries per RRA
11 pub const RRD_DATA_ENTRIES: usize = 70;
12
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];
16
17 use bitflags::bitflags;
18
19 bitflags!{
20 struct RRAFlags: u64 {
21 // Data Source Types
22 const DST_GAUGE = 1;
23 const DST_DERIVE = 2;
24 const DST_COUNTER = 4;
25 const DST_MASK = 255; // first 8 bits
26
27 // Consolidation Functions
28 const CF_AVERAGE = 1 << 8;
29 const CF_MAX = 2 << 8;
30 const CF_MASK = 255 << 8;
31 }
32 }
33
34 /// RRD data source tyoe
35 pub enum DST {
36 Gauge,
37 Derive,
38 }
39
40 #[repr(C)]
41 struct RRA {
42 flags: RRAFlags,
43 resolution: u64,
44 last_update: f64,
45 last_count: u64,
46 counter_value: f64, // used for derive/counters
47 data: [f64; RRD_DATA_ENTRIES],
48 }
49
50 impl RRA {
51 fn new(flags: RRAFlags, resolution: u64) -> Self {
52 Self {
53 flags, resolution,
54 last_update: 0.0,
55 last_count: 0,
56 counter_value: f64::NAN,
57 data: [f64::NAN; RRD_DATA_ENTRIES],
58 }
59 }
60
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;
65
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;
72 if t < min_time {
73 self.data[index] = f64::NAN;
74 } else {
75 break;
76 }
77 }
78 }
79
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;
84
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;
87
88 if (epoch - (last_update as u64)) > reso || index != last_index {
89 self.last_count = 0;
90 }
91
92 let last_value = self.data[index];
93 if last_value.is_nan() {
94 self.last_count = 0;
95 }
96
97 let new_count = if self.last_count < u64::MAX {
98 self.last_count + 1
99 } else {
100 u64::MAX // should never happen
101 };
102
103 if self.last_count == 0 {
104 self.data[index] = value;
105 self.last_count = 1;
106 } else {
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)
112 } else {
113 eprintln!("rrdb update failed - unknown CF");
114 return;
115 };
116 self.data[index] = new_value;
117 self.last_count = new_count;
118 }
119 self.last_update = time;
120 }
121
122 fn update(&mut self, time: f64, mut value: f64) {
123
124 if time <= self.last_update {
125 eprintln!("rrdb update failed - time in past ({} < {})", time, self.last_update);
126 }
127
128 if value.is_nan() {
129 eprintln!("rrdb update failed - new value is NAN");
130 return;
131 }
132
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);
137
138 let diff = if self.counter_value.is_nan() {
139 0.0
140 } else if is_counter && value < 0.0 {
141 eprintln!("rrdb update failed - got negative value for counter");
142 return;
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");
147 return;
148 } else {
149 value - self.counter_value
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
236 let epoch = time as u64;
237 let reso = timeframe as u64;
238
239 let end = reso*(epoch/reso + 1);
240 let start = end - reso*(RRD_DATA_ENTRIES as u64);
241
242 let mut list = Vec::new();
243
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,
255 };
256
257 let rrd_end = reso*((raa.last_update as u64)/reso);
258 let rrd_start = rrd_end - reso*(RRD_DATA_ENTRIES as u64);
259
260 let mut t = start;
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 {
264 list.push(None);
265 } else {
266 let value = raa.data[index];
267 if value.is_nan() {
268 list.push(None);
269 } else {
270 list.push(Some(value));
271 }
272 }
273 t += reso; index = (index + 1) % RRD_DATA_ENTRIES;
274 }
275
276 (start, reso, list)
277 }
278
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));
284 }
285
286 let mut rrd: RRD = unsafe { std::mem::zeroed() };
287 unsafe {
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)?;
290 }
291
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));
295 }
296
297 Ok(rrd)
298 }
299
300 pub fn load(path: &Path) -> Result<Self, std::io::Error> {
301 let raw = std::fs::read(path)?;
302 Self::from_raw(&raw)
303 }
304
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>())
308 };
309 replace_file(filename, rrd_slice, options)
310 }
311
312
313 pub fn update(&mut self, time: f64, value: f64) {
314 self.hour_avg.update(time, value);
315 self.hour_max.update(time, value);
316
317 self.day_avg.update(time, value);
318 self.day_max.update(time, value);
319
320 self.week_avg.update(time, value);
321 self.week_max.update(time, value);
322
323 self.month_avg.update(time, value);
324 self.month_max.update(time, value);
325
326 self.year_avg.update(time, value);
327 self.year_max.update(time, value);
328 }
329 }