ic_logging/history/
mod.rs1use alloc::collections::VecDeque;
4use core::fmt;
5
6#[cfg(test)]
7mod tests;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct HistoryLimits {
17 pub entries: usize,
19 pub payload_bytes: usize,
21}
22
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub struct HistoryAppend {
26 pub evicted: usize,
28 pub evicted_payload_bytes: usize,
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum HistoryRejectionReason {
35 Disabled,
37 PayloadTooLarge {
39 supplied: usize,
41 maximum: usize,
43 },
44}
45
46impl fmt::Display for HistoryRejectionReason {
47 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::Disabled => formatter.write_str("history admission is disabled"),
50 Self::PayloadTooLarge { supplied, maximum } => write!(
51 formatter,
52 "history payload charge {supplied} exceeds {maximum}"
53 ),
54 }
55 }
56}
57
58impl core::error::Error for HistoryRejectionReason {}
59
60#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct HistoryRejection<T> {
63 pub value: T,
65 pub reason: HistoryRejectionReason,
67}
68
69impl<T> fmt::Display for HistoryRejection<T> {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 self.reason.fmt(formatter)
72 }
73}
74
75impl<T: fmt::Debug> core::error::Error for HistoryRejection<T> {}
76
77#[derive(Clone, Debug)]
78struct Row<T> {
79 value: T,
80 payload_bytes: usize,
81}
82
83#[derive(Clone, Debug)]
92pub struct BoundedHistory<T> {
93 limits: HistoryLimits,
94 rows: VecDeque<Row<T>>,
95 payload_bytes: usize,
96}
97
98impl<T> BoundedHistory<T> {
99 #[must_use]
101 pub const fn new(limits: HistoryLimits) -> Self {
102 Self {
103 limits,
104 rows: VecDeque::new(),
105 payload_bytes: 0,
106 }
107 }
108
109 pub fn append(
118 &mut self,
119 value: T,
120 payload_bytes: usize,
121 ) -> Result<HistoryAppend, HistoryRejection<T>> {
122 let reason = if self.limits.entries == 0 {
123 Some(HistoryRejectionReason::Disabled)
124 } else if payload_bytes > self.limits.payload_bytes {
125 Some(HistoryRejectionReason::PayloadTooLarge {
126 supplied: payload_bytes,
127 maximum: self.limits.payload_bytes,
128 })
129 } else {
130 None
131 };
132 if let Some(reason) = reason {
133 return Err(HistoryRejection { value, reason });
134 }
135
136 let mut receipt = HistoryAppend::default();
137 while self.rows.len() >= self.limits.entries
138 || self.payload_bytes > self.limits.payload_bytes - payload_bytes
139 {
140 if let Some(row) = self.rows.pop_front() {
141 self.payload_bytes -= row.payload_bytes;
142 receipt.evicted += 1;
143 receipt.evicted_payload_bytes += row.payload_bytes;
144 }
145 }
146 self.rows.push_back(Row {
147 value,
148 payload_bytes,
149 });
150 self.payload_bytes += payload_bytes;
151 Ok(receipt)
152 }
153
154 pub fn entries(&self) -> impl DoubleEndedIterator<Item = &T> + ExactSizeIterator + '_ {
156 self.rows.iter().map(|row| &row.value)
157 }
158
159 #[must_use]
161 pub const fn retained_payload_bytes(&self) -> usize {
162 self.payload_bytes
163 }
164
165 #[must_use]
167 pub const fn limits(&self) -> HistoryLimits {
168 self.limits
169 }
170
171 pub fn clear(&mut self) {
174 self.rows.clear();
175 self.payload_bytes = 0;
176 }
177}