rrd/
data.rs

1//! Support for navigating fetched data sets.
2
3use crate::Timestamp;
4use rrd_sys::rrd_double;
5use std::{fmt, ops::Deref, time::Duration};
6
7/// Provides a safe abstraction for traversing the dataset produced by `fetch()`.
8///
9/// Contains both the data and the metadata (e.g. start, end, step and data sources).
10pub struct Data<T> {
11    start: Timestamp,
12    end: Timestamp,
13    step: Duration,
14    names: Vec<String>,
15    data: T,
16    row_count: usize,
17}
18
19impl<T> Data<T>
20where
21    T: Deref<Target = [rrd_double]>,
22{
23    pub(crate) fn new(
24        start: Timestamp,
25        end: Timestamp,
26        step: Duration,
27        names: Vec<String>,
28        data: T,
29    ) -> Self {
30        assert_eq!(data.len() % names.len(), 0);
31        let row_count = data.len() / names.len();
32        Self {
33            start,
34            end,
35            step,
36            names,
37            data,
38            row_count,
39        }
40    }
41
42    /// Timestamp for the first row of data.
43    pub fn start(&self) -> Timestamp {
44        self.start
45    }
46
47    /// Timestamp for the last row of data.
48    pub fn end(&self) -> Timestamp {
49        self.end
50    }
51
52    /// Time step between rows.
53    pub fn step(&self) -> Duration {
54        self.step
55    }
56
57    /// The number of rows in the dataset.
58    pub fn row_count(&self) -> usize {
59        self.row_count
60    }
61
62    /// The data source names in the dataset.
63    ///
64    /// Data sources are conceptually the "columns".
65    pub fn ds_names(&self) -> &[String] {
66        &self.names
67    }
68
69    /// The rows of data in the dataset.
70    pub fn rows(&self) -> Rows<T> {
71        Rows { data: self }
72    }
73}
74
75/// An iterator over the [`Row`]s in [`Data`].
76pub struct Rows<'data, T> {
77    data: &'data Data<T>,
78}
79
80impl<'data, T> Rows<'data, T>
81where
82    T: Deref<Target = [rrd_double]>,
83{
84    /// The number of rows.
85    pub fn len(&self) -> usize {
86        self.data.row_count()
87    }
88
89    /// True _iff_ there are 0 rows.
90    pub fn is_empty(&self) -> bool {
91        self.data.row_count() == 0
92    }
93
94    /// Iterate over the rows.
95    pub fn iter(&self) -> RowsIter<'data, T> {
96        RowsIter::new(self.data)
97    }
98}
99
100impl<'data, T> IntoIterator for Rows<'data, T>
101where
102    T: Deref<Target = [rrd_double]>,
103{
104    type Item = Row<'data, T>;
105
106    type IntoIter = RowsIter<'data, T>;
107
108    fn into_iter(self) -> Self::IntoIter {
109        RowsIter::new(self.data)
110    }
111}
112
113impl<T> fmt::Debug for Rows<'_, T>
114where
115    T: Deref<Target = [rrd_double]> + fmt::Debug,
116{
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.debug_list().entries(self.iter()).finish()
119    }
120}
121
122/// Iterate over [`Row`]s in [`Data`].
123///
124/// See [`Rows::iter`].
125pub struct RowsIter<'data, T> {
126    data: &'data Data<T>,
127    max_index: usize,
128    next_index: usize,
129}
130
131impl<'data, T> RowsIter<'data, T>
132where
133    T: Deref<Target = [rrd_double]>,
134{
135    fn new(data: &'data Data<T>) -> Self {
136        Self {
137            data,
138            max_index: data.row_count(),
139            next_index: 0,
140        }
141    }
142}
143
144impl<'data, T> Iterator for RowsIter<'data, T>
145where
146    T: Deref<Target = [rrd_double]>,
147{
148    type Item = Row<'data, T>;
149
150    fn next(&mut self) -> Option<Self::Item> {
151        if self.next_index < self.max_index {
152            let index = self.next_index;
153            self.next_index += 1;
154            Some(Row::new(self.data, index))
155        } else {
156            None
157        }
158    }
159
160    fn size_hint(&self) -> (usize, Option<usize>) {
161        (self.max_index, Some(self.max_index))
162    }
163}
164
165impl<T> ExactSizeIterator for RowsIter<'_, T> where T: Deref<Target = [rrd_double]> {}
166
167/// A sequence of values for a particular timestamp.
168pub struct Row<'data, T> {
169    data: &'data Data<T>,
170    data_offset: usize,
171    timestamp: Timestamp,
172}
173
174impl<'data, T> Row<'data, T>
175where
176    T: Deref<Target = [rrd_double]>,
177{
178    fn new(data: &'data Data<T>, row_index: usize) -> Self {
179        Self {
180            data,
181            data_offset: data.names.len() * row_index,
182            timestamp: data.start()
183                + data.step() * row_index.try_into().expect("Row index exceeds u32"),
184        }
185    }
186
187    /// The timestamp for this row of data.
188    pub fn timestamp(&self) -> Timestamp {
189        self.timestamp
190    }
191
192    /// The values for this row, in the order of the data source names in the encompassing [`Data`].
193    ///
194    /// To access values and DS names together, see [`Self::iter_cells`].
195    pub fn as_slice(&self) -> &[f64] {
196        &self.data.data.as_ref()[self.data_offset..self.data_offset + self.data.names.len()]
197    }
198
199    /// Iterate over the [`Cell`]s for this row's values.
200    pub fn iter_cells(&self) -> impl Iterator<Item = Cell> {
201        self.data
202            .names
203            .iter()
204            .zip(self.as_slice())
205            .map(|(name, value)| Cell {
206                name,
207                value: *value,
208            })
209    }
210}
211
212impl<T> Deref for Row<'_, T>
213where
214    T: Deref<Target = [rrd_double]>,
215{
216    type Target = [rrd_double];
217
218    fn deref(&self) -> &Self::Target {
219        self.as_slice()
220    }
221}
222
223impl<T> fmt::Debug for Row<'_, T>
224where
225    T: Deref<Target = [rrd_double]>,
226{
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        struct RowDataDebug<'d> {
229            row_data: &'d [rrd_double],
230        }
231        impl fmt::Debug for RowDataDebug<'_> {
232            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233                f.debug_list().entries(self.row_data.iter()).finish()
234            }
235        }
236
237        f.debug_struct("Row")
238            .field("ts", &self.timestamp)
239            .field("ts_int", &self.timestamp.timestamp())
240            .field(
241                "data",
242                &RowDataDebug {
243                    row_data: self.as_slice(),
244                },
245            )
246            .finish()
247    }
248}
249
250/// Contains a value in a [`Row`] along with the corresponding DS name.
251#[derive(Debug)]
252pub struct Cell<'data> {
253    /// The data source name for this value
254    pub name: &'data str,
255    /// A value in a [`Row`]
256    pub value: f64,
257}