]> git.proxmox.com Git - proxmox.git/blob - proxmox-rrd/src/rrd_v1.rs
3ae39bcfac54b07fae7b83fba8748fd98444a167
[proxmox.git] / proxmox-rrd / src / rrd_v1.rs
1 use std::io::Read;
2
3 use anyhow::Error;
4 use bitflags::bitflags;
5
6 /// The number of data entries per RRA
7 pub const RRD_DATA_ENTRIES: usize = 70;
8
9 /// Proxmox RRD file magic number
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 crate::rrd::{DataSource, CF, DST, RRA, RRD};
14
15 bitflags! {
16 /// Flags to specify the data source type and consolidation function
17 pub struct RRAFlags: u64 {
18 // Data Source Types
19 const DST_GAUGE = 1;
20 const DST_DERIVE = 2;
21 const DST_COUNTER = 4;
22 const DST_MASK = 255; // first 8 bits
23
24 // Consolidation Functions
25 const CF_AVERAGE = 1 << 8;
26 const CF_MAX = 2 << 8;
27 const CF_MASK = 255 << 8;
28 }
29 }
30
31 /// Round Robin Archive with [RRD_DATA_ENTRIES] data slots.
32 ///
33 /// This data structure is used inside [RRD] and directly written to the
34 /// RRD files.
35 #[repr(C)]
36 pub struct RRAv1 {
37 /// Defined the data source type and consolidation function
38 pub flags: RRAFlags,
39 /// Resolution (seconds)
40 pub resolution: u64,
41 /// Last update time (epoch)
42 pub last_update: f64,
43 /// Count values computed inside this update interval
44 pub last_count: u64,
45 /// Stores the last value, used to compute differential value for derive/counters
46 pub counter_value: f64,
47 /// Data slots
48 pub data: [f64; RRD_DATA_ENTRIES],
49 }
50
51 impl RRAv1 {
52 fn extract_data(&self) -> (u64, u64, Vec<Option<f64>>) {
53 let reso = self.resolution;
54
55 let mut list = Vec::new();
56
57 let rra_end = reso * ((self.last_update as u64) / reso);
58 let rra_start = rra_end - reso * (RRD_DATA_ENTRIES as u64);
59
60 let mut t = rra_start;
61 let mut index = ((t / reso) % (RRD_DATA_ENTRIES as u64)) as usize;
62 for _ in 0..RRD_DATA_ENTRIES {
63 let value = self.data[index];
64 if value.is_nan() {
65 list.push(None);
66 } else {
67 list.push(Some(value));
68 }
69
70 t += reso;
71 index = (index + 1) % RRD_DATA_ENTRIES;
72 }
73
74 (rra_start, reso, list)
75 }
76 }
77
78 /// Round Robin Database file format with fixed number of [RRA]s
79 #[repr(C)]
80 // Note: Avoid alignment problems by using 8byte types only
81 pub struct RRDv1 {
82 /// The magic number to identify the file type
83 pub magic: [u8; 8],
84 /// Hourly data (average values)
85 pub hour_avg: RRAv1,
86 /// Hourly data (maximum values)
87 pub hour_max: RRAv1,
88 /// Daily data (average values)
89 pub day_avg: RRAv1,
90 /// Daily data (maximum values)
91 pub day_max: RRAv1,
92 /// Weekly data (average values)
93 pub week_avg: RRAv1,
94 /// Weekly data (maximum values)
95 pub week_max: RRAv1,
96 /// Monthly data (average values)
97 pub month_avg: RRAv1,
98 /// Monthly data (maximum values)
99 pub month_max: RRAv1,
100 /// Yearly data (average values)
101 pub year_avg: RRAv1,
102 /// Yearly data (maximum values)
103 pub year_max: RRAv1,
104 }
105
106 impl RRDv1 {
107 pub fn from_raw(mut raw: &[u8]) -> Result<Self, std::io::Error> {
108 let expected_len = std::mem::size_of::<RRDv1>();
109
110 if raw.len() != expected_len {
111 let msg = format!("wrong data size ({} != {})", raw.len(), expected_len);
112 return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
113 }
114
115 let mut rrd: RRDv1 = unsafe { std::mem::zeroed() };
116 unsafe {
117 let rrd_slice =
118 std::slice::from_raw_parts_mut(&mut rrd as *mut _ as *mut u8, expected_len);
119 raw.read_exact(rrd_slice)?;
120 }
121
122 if rrd.magic != PROXMOX_RRD_MAGIC_1_0 {
123 let msg = "wrong magic number".to_string();
124 return Err(std::io::Error::new(std::io::ErrorKind::Other, msg));
125 }
126
127 Ok(rrd)
128 }
129
130 pub fn to_rrd_v2(&self) -> Result<RRD, Error> {
131 let mut rra_list = Vec::new();
132
133 // old format v1:
134 //
135 // hour 1 min, 70 points
136 // day 30 min, 70 points
137 // week 3 hours, 70 points
138 // month 12 hours, 70 points
139 // year 1 week, 70 points
140 //
141 // new default for RRD v2:
142 //
143 // day 1 min, 1440 points
144 // month 30 min, 1440 points
145 // year 365 min (6h), 1440 points
146 // decade 1 week, 570 points
147
148 // Linear extrapolation
149 fn extrapolate_data(
150 start: u64,
151 reso: u64,
152 factor: u64,
153 data: Vec<Option<f64>>,
154 ) -> (u64, u64, Vec<Option<f64>>) {
155 let mut new = Vec::new();
156
157 for i in 0..data.len() {
158 let mut next = i + 1;
159 if next >= data.len() {
160 next = 0
161 };
162 let v = data[i];
163 let v1 = data[next];
164 match (v, v1) {
165 (Some(v), Some(v1)) => {
166 let diff = (v1 - v) / (factor as f64);
167 for j in 0..factor {
168 new.push(Some(v + diff * (j as f64)));
169 }
170 }
171 (Some(v), None) => {
172 new.push(Some(v));
173 for _ in 0..factor - 1 {
174 new.push(None);
175 }
176 }
177 (None, Some(v1)) => {
178 for _ in 0..factor - 1 {
179 new.push(None);
180 }
181 new.push(Some(v1));
182 }
183 (None, None) => {
184 for _ in 0..factor {
185 new.push(None);
186 }
187 }
188 }
189 }
190
191 (start, reso / factor, new)
192 }
193
194 // Try to convert to new, higher capacity format
195
196 // compute daily average (merge old self.day_avg and self.hour_avg
197 let mut day_avg = RRA::new(CF::Average, 60, 1440);
198
199 let (start, reso, data) = self.day_avg.extract_data();
200 let (start, reso, data) = extrapolate_data(start, reso, 30, data);
201 day_avg.insert_data(start, reso, data)?;
202
203 let (start, reso, data) = self.hour_avg.extract_data();
204 day_avg.insert_data(start, reso, data)?;
205
206 // compute daily maximum (merge old self.day_max and self.hour_max
207 let mut day_max = RRA::new(CF::Maximum, 60, 1440);
208
209 let (start, reso, data) = self.day_max.extract_data();
210 let (start, reso, data) = extrapolate_data(start, reso, 30, data);
211 day_max.insert_data(start, reso, data)?;
212
213 let (start, reso, data) = self.hour_max.extract_data();
214 day_max.insert_data(start, reso, data)?;
215
216 // compute monthly average (merge old self.month_avg,
217 // self.week_avg and self.day_avg)
218 let mut month_avg = RRA::new(CF::Average, 30 * 60, 1440);
219
220 let (start, reso, data) = self.month_avg.extract_data();
221 let (start, reso, data) = extrapolate_data(start, reso, 24, data);
222 month_avg.insert_data(start, reso, data)?;
223
224 let (start, reso, data) = self.week_avg.extract_data();
225 let (start, reso, data) = extrapolate_data(start, reso, 6, data);
226 month_avg.insert_data(start, reso, data)?;
227
228 let (start, reso, data) = self.day_avg.extract_data();
229 month_avg.insert_data(start, reso, data)?;
230
231 // compute monthly maximum (merge old self.month_max,
232 // self.week_max and self.day_max)
233 let mut month_max = RRA::new(CF::Maximum, 30 * 60, 1440);
234
235 let (start, reso, data) = self.month_max.extract_data();
236 let (start, reso, data) = extrapolate_data(start, reso, 24, data);
237 month_max.insert_data(start, reso, data)?;
238
239 let (start, reso, data) = self.week_max.extract_data();
240 let (start, reso, data) = extrapolate_data(start, reso, 6, data);
241 month_max.insert_data(start, reso, data)?;
242
243 let (start, reso, data) = self.day_max.extract_data();
244 month_max.insert_data(start, reso, data)?;
245
246 // compute yearly average (merge old self.year_avg)
247 let mut year_avg = RRA::new(CF::Average, 6 * 3600, 1440);
248
249 let (start, reso, data) = self.year_avg.extract_data();
250 let (start, reso, data) = extrapolate_data(start, reso, 28, data);
251 year_avg.insert_data(start, reso, data)?;
252
253 // compute yearly maximum (merge old self.year_avg)
254 let mut year_max = RRA::new(CF::Maximum, 6 * 3600, 1440);
255
256 let (start, reso, data) = self.year_max.extract_data();
257 let (start, reso, data) = extrapolate_data(start, reso, 28, data);
258 year_max.insert_data(start, reso, data)?;
259
260 // compute decade average (merge old self.year_avg)
261 let mut decade_avg = RRA::new(CF::Average, 7 * 86400, 570);
262 let (start, reso, data) = self.year_avg.extract_data();
263 decade_avg.insert_data(start, reso, data)?;
264
265 // compute decade maximum (merge old self.year_max)
266 let mut decade_max = RRA::new(CF::Maximum, 7 * 86400, 570);
267 let (start, reso, data) = self.year_max.extract_data();
268 decade_max.insert_data(start, reso, data)?;
269
270 rra_list.push(day_avg);
271 rra_list.push(day_max);
272 rra_list.push(month_avg);
273 rra_list.push(month_max);
274 rra_list.push(year_avg);
275 rra_list.push(year_max);
276 rra_list.push(decade_avg);
277 rra_list.push(decade_max);
278
279 // use values from hour_avg for source (all RRAv1 must have the same config)
280 let dst = if self.hour_avg.flags.contains(RRAFlags::DST_COUNTER) {
281 DST::Counter
282 } else if self.hour_avg.flags.contains(RRAFlags::DST_DERIVE) {
283 DST::Derive
284 } else {
285 DST::Gauge
286 };
287
288 let source = DataSource {
289 dst,
290 last_value: f64::NAN,
291 last_update: self.hour_avg.last_update, // IMPORTANT!
292 };
293 Ok(RRD { source, rra_list })
294 }
295 }