persistence/
file_handler.rs
1use diagnostics_data::ExtendedMoniker;
6use glob::glob;
7use log::{info, warn};
8use persistence_config::{Config, ServiceName, Tag};
9use serde::ser::SerializeMap;
10use serde::{Serialize, Serializer};
11use serde_json::Value;
12use std::collections::HashMap;
13use std::fs;
14
15const CURRENT_PATH: &str = "/cache/current";
16const PREVIOUS_PATH: &str = "/cache/previous";
17
18pub(crate) struct PersistSchema {
19 pub timestamps: Timestamps,
20 pub payload: PersistPayload,
21}
22
23pub(crate) enum PersistPayload {
24 Data(PersistData),
25 Error(String),
26}
27
28pub(crate) struct PersistData {
29 pub data_length: usize,
30 pub entries: HashMap<ExtendedMoniker, Value>,
31}
32
33#[derive(Clone, Serialize)]
34pub(crate) struct Timestamps {
35 pub before_monotonic: i64,
36 pub before_utc: i64,
37 pub after_monotonic: i64,
38 pub after_utc: i64,
39}
40
41const TIMESTAMPS_KEY: &str = "@timestamps";
43const SIZE_KEY: &str = "@persist_size";
44const ERROR_KEY: &str = ":error";
45const ERROR_DESCRIPTION_KEY: &str = "description";
46
47impl Serialize for PersistSchema {
48 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49 where
50 S: Serializer,
51 {
52 match &self.payload {
53 PersistPayload::Data(data) => {
54 let mut s = serializer.serialize_map(Some(data.entries.len() + 2))?;
55 s.serialize_entry(TIMESTAMPS_KEY, &self.timestamps)?;
56 s.serialize_entry(SIZE_KEY, &data.data_length)?;
57 for (k, v) in data.entries.iter() {
58 s.serialize_entry(&k.to_string(), v)?;
59 }
60 s.end()
61 }
62 PersistPayload::Error(error) => {
63 let mut s = serializer.serialize_map(Some(2))?;
64 s.serialize_entry(TIMESTAMPS_KEY, &self.timestamps)?;
65 s.serialize_entry(ERROR_KEY, &ErrorHelper(error))?;
66 s.end()
67 }
68 }
69 }
70}
71
72impl PersistSchema {
73 pub(crate) fn error(timestamps: Timestamps, description: String) -> Self {
74 Self { timestamps, payload: PersistPayload::Error(description) }
75 }
76}
77
78struct ErrorHelper<'a>(&'a str);
79
80impl Serialize for ErrorHelper<'_> {
81 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
82 where
83 S: Serializer,
84 {
85 let mut s = serializer.serialize_map(Some(1))?;
86 s.serialize_entry(ERROR_DESCRIPTION_KEY, self.0)?;
87 s.end()
88 }
89}
90
91pub fn forget_old_data(config: &Config) {
99 info!("Forgetting persisted inspect data from two boots ago, except for tags with persist_across_boot enabled");
100
101 fs::remove_dir_all(PREVIOUS_PATH)
103 .map_err(|e| info!("Could not delete {}: {:?}", PREVIOUS_PATH, e))
104 .ok();
105 fs::rename(CURRENT_PATH, PREVIOUS_PATH)
106 .map_err(|e| info!("Could not move {} to {}: {:?}", CURRENT_PATH, PREVIOUS_PATH, e))
107 .ok();
108
109 let mut copied_count = 0;
111
112 for (service, tag) in config.iter().flat_map(|(service, tags)| {
113 tags.iter().filter(|(_, c)| c.persist_across_boot).map(move |(tag, _)| (service, tag))
114 }) {
115 match fs::read(format!("{PREVIOUS_PATH}/{service}/{tag}")) {
116 Ok(data) => {
117 match fs::create_dir(format!("{CURRENT_PATH}/{service}")) {
118 Ok(()) => {}
119 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
120 Err(e) => {
121 warn!("Error creating directory {CURRENT_PATH}/{service}: {e:?}");
122 continue;
123 }
124 }
125 match fs::write(format!("{CURRENT_PATH}/{service}/{tag}"), data) {
126 Ok(()) => {
127 copied_count += 1;
128 }
129 Err(e) => {
130 warn!("Error writing persisted data for {service}/{tag}: {e:?}");
131 }
132 }
133 }
134 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
135 }
137 Err(e) => {
138 warn!("Error reading persisted data for {service}/{tag}: {e:?}")
139 }
140 }
141 }
142
143 info!("Persisted {copied_count} tags across boot");
144}
145
146pub(crate) fn write(service_name: &ServiceName, tag: &Tag, data: &PersistSchema) {
148 let path = format!("{}/{}", CURRENT_PATH, service_name);
150 fs::create_dir_all(&path)
151 .map_err(|e| warn!("Could not create directory {}: {:?}", path, e))
152 .ok();
153 let data = match serde_json::to_string(data) {
154 Ok(data) => data,
155 Err(e) => {
156 warn!("Could not serialize data - unexpected error {e}");
157 return;
158 }
159 };
160 fs::write(format!("{}/{}", path, tag), data)
161 .map_err(|e| warn!("Could not write file {}/{}: {:?}", path, tag, e))
162 .ok();
163}
164
165pub(crate) struct ServiceEntry {
166 pub name: String,
167 pub data: Vec<TagEntry>,
168}
169
170pub(crate) struct TagEntry {
171 pub name: String,
172 pub data: String,
173}
174
175pub(crate) fn remembered_data() -> impl Iterator<Item = ServiceEntry> {
178 glob(&format!("{PREVIOUS_PATH}/*"))
181 .expect("Failed to read previous-path glob pattern")
182 .filter_map(|p| match p {
183 Ok(path) => {
184 path.file_name().map(|p| p.to_string_lossy().to_string())
185 }
186 Err(e) => {
187 warn!("Encountered GlobError; contents could not be read to determine if glob pattern was matched: {e:?}");
188 None
189 }
190 })
191 .map(|service_name| {
192 let entries: Vec<TagEntry> = glob(&format!("{PREVIOUS_PATH}/{service_name}/*"))
193 .expect("Failed to read previous service persistence pattern")
194 .filter_map(|p| match p {
195 Ok(path) => path
196 .file_name()
197 .map(|tag| (path.clone(), tag.to_string_lossy().to_string())),
198 Err(ref e) => {
199 warn!("Failed to retrieve text persisted at path {p:?}: {e:?}");
200 None
201 }
202 })
203 .filter_map(|(path, tag)| match fs::read(&path) {
204 Ok(text) => match std::str::from_utf8(&text) {
208 Ok(contents) => Some(TagEntry { name: tag, data: contents.to_owned() }),
209 Err(e) => {
210 warn!("Failed to parse persisted bytes at path: {path:?} into text: {e:?}");
211 None
212 }
213 },
214 Err(e) => {
215 warn!("Failed to retrieve text persisted at path: {path:?}: {e:?}");
216 None
217 }
218 })
219 .collect();
220
221 if entries.is_empty() {
222 info!("No data available to persist for {service_name:?}.");
223 } else {
224 info!("{} data entries available to persist for {service_name:?}.", entries.len());
225 }
226
227 ServiceEntry { name: service_name, data: entries }
228 })
229}