1#![allow(non_upper_case_globals)]
6
7use std::collections::HashMap;
10
11use serde_json::{json, Value as JsonValue};
12
13use crate::coverage::record_coverage;
14use crate::database::Database;
15use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR;
16use crate::metrics::Metric;
17use crate::Lifetime;
18
19pub(crate) const INTERNAL_STORAGE: &str = "glean_internal_info";
21
22pub struct StorageManager;
24
25fn snapshot_labeled_metrics(
31 snapshot: &mut HashMap<String, HashMap<String, JsonValue>>,
32 metric_id: &str,
33 metric: &Metric,
34) {
35 let ping_section = format!("labeled_{}", metric.ping_section());
36 let map = snapshot.entry(ping_section).or_default();
37
38 let (metric_id, label) = metric_id.split_once('/').unwrap();
40
41 let obj = map.entry(metric_id.into()).or_insert_with(|| json!({}));
42 let obj = obj.as_object_mut().unwrap(); obj.insert(label.into(), metric.as_json());
44}
45
46fn snapshot_dual_labeled_metrics(
52 snapshot: &mut HashMap<String, HashMap<String, JsonValue>>,
53 metric_id: &str,
54 metric: &Metric,
55) {
56 let ping_section = format!("dual_labeled_{}", metric.ping_section());
57 let map = snapshot.entry(ping_section).or_default();
58 let parts = metric_id.split(RECORD_SEPARATOR).collect::<Vec<&str>>();
59
60 let obj = map
61 .entry(parts[0].into())
62 .or_insert_with(|| json!({}))
63 .as_object_mut()
64 .unwrap(); let key_obj = obj.entry(parts[1].to_string()).or_insert_with(|| json!({}));
66 let key_obj = key_obj.as_object_mut().unwrap();
67 key_obj.insert(parts[2].into(), metric.as_json());
68}
69
70impl StorageManager {
71 pub fn snapshot(
84 &self,
85 storage: &Database,
86 store_name: &str,
87 clear_store: bool,
88 ) -> Option<String> {
89 self.snapshot_as_json(storage, store_name, clear_store)
90 .map(|data| ::serde_json::to_string_pretty(&data).unwrap())
91 }
92
93 pub fn snapshot_as_json(
106 &self,
107 storage: &Database,
108 store_name: &str,
109 clear_store: bool,
110 ) -> Option<JsonValue> {
111 let mut snapshot: HashMap<String, HashMap<String, JsonValue>> = HashMap::new();
112
113 let mut snapshotter = |metric_id: &[u8], metric: &Metric| {
114 let metric_id = String::from_utf8_lossy(metric_id).into_owned();
115 if metric_id.contains('/') {
116 snapshot_labeled_metrics(&mut snapshot, &metric_id, metric);
117 } else if metric_id.split(RECORD_SEPARATOR).count() == 3 {
118 snapshot_dual_labeled_metrics(&mut snapshot, &metric_id, metric);
119 } else {
120 let map = snapshot.entry(metric.ping_section().into()).or_default();
121 map.insert(metric_id, metric.as_json());
122 }
123 };
124
125 storage.iter_store_from(Lifetime::Ping, store_name, None, &mut snapshotter);
126 storage.iter_store_from(Lifetime::Application, store_name, None, &mut snapshotter);
127 storage.iter_store_from(Lifetime::User, store_name, None, &mut snapshotter);
128
129 if store_name != "glean_client_info" {
131 storage.iter_store_from(Lifetime::Application, "all-pings", None, snapshotter);
132 }
133
134 if clear_store {
135 if let Err(e) = storage.clear_ping_lifetime_storage(store_name) {
136 log::warn!("Failed to clear lifetime storage: {:?}", e);
137 }
138 }
139
140 if snapshot.is_empty() {
141 None
142 } else {
143 Some(json!(snapshot))
144 }
145 }
146
147 pub fn snapshot_metric(
159 &self,
160 storage: &Database,
161 store_name: &str,
162 metric_id: &str,
163 metric_lifetime: Lifetime,
164 ) -> Option<Metric> {
165 let mut snapshot: Option<Metric> = None;
166
167 let mut snapshotter = |id: &[u8], metric: &Metric| {
168 let id = String::from_utf8_lossy(id).into_owned();
169 if id == metric_id {
170 snapshot = Some(metric.clone())
171 }
172 };
173
174 storage.iter_store_from(metric_lifetime, store_name, None, &mut snapshotter);
175
176 snapshot
177 }
178
179 pub fn snapshot_metric_for_test(
194 &self,
195 storage: &Database,
196 store_name: &str,
197 metric_id: &str,
198 metric_lifetime: Lifetime,
199 ) -> Option<Metric> {
200 record_coverage(metric_id);
201 self.snapshot_metric(storage, store_name, metric_id, metric_lifetime)
202 }
203
204 pub fn snapshot_experiments_as_json(
229 &self,
230 storage: &Database,
231 store_name: &str,
232 ) -> Option<JsonValue> {
233 let mut snapshot: HashMap<String, JsonValue> = HashMap::new();
234
235 let mut snapshotter = |metric_id: &[u8], metric: &Metric| {
236 let metric_id = String::from_utf8_lossy(metric_id).into_owned();
237 if metric_id.ends_with("#experiment") {
238 let (name, _) = metric_id.split_once('#').unwrap(); snapshot.insert(name.to_string(), metric.as_json());
240 }
241 };
242
243 storage.iter_store_from(Lifetime::Application, store_name, None, &mut snapshotter);
244
245 if snapshot.is_empty() {
246 None
247 } else {
248 Some(json!(snapshot))
249 }
250 }
251}
252
253#[cfg(test)]
254mod test {
255 use super::*;
256 use crate::metrics::ExperimentMetric;
257 use crate::Glean;
258
259 #[test]
262 fn test_experiments_json_serialization() {
263 let t = tempfile::tempdir().unwrap();
264 let name = t.path().display().to_string();
265 let glean = Glean::with_options(&name, "org.mozilla.glean", true, true);
266
267 let extra: HashMap<String, String> = [("test-key".into(), "test-value".into())]
268 .iter()
269 .cloned()
270 .collect();
271
272 let metric = ExperimentMetric::new(&glean, "some-experiment".to_string());
273
274 metric.set_active_sync(&glean, "test-branch".to_string(), extra);
275 let snapshot = StorageManager
276 .snapshot_experiments_as_json(glean.storage(), "glean_internal_info")
277 .unwrap();
278 assert_eq!(
279 json!({"some-experiment": {"branch": "test-branch", "extra": {"test-key": "test-value"}}}),
280 snapshot
281 );
282
283 metric.set_inactive_sync(&glean);
284
285 let empty_snapshot =
286 StorageManager.snapshot_experiments_as_json(glean.storage(), "glean_internal_info");
287 assert!(empty_snapshot.is_none());
288 }
289
290 #[test]
291 fn test_experiments_json_serialization_empty() {
292 let t = tempfile::tempdir().unwrap();
293 let name = t.path().display().to_string();
294 let glean = Glean::with_options(&name, "org.mozilla.glean", true, true);
295
296 let metric = ExperimentMetric::new(&glean, "some-experiment".to_string());
297
298 metric.set_active_sync(&glean, "test-branch".to_string(), HashMap::new());
299 let snapshot = StorageManager
300 .snapshot_experiments_as_json(glean.storage(), "glean_internal_info")
301 .unwrap();
302 assert_eq!(
303 json!({"some-experiment": {"branch": "test-branch"}}),
304 snapshot
305 );
306
307 metric.set_inactive_sync(&glean);
308
309 let empty_snapshot =
310 StorageManager.snapshot_experiments_as_json(glean.storage(), "glean_internal_info");
311 assert!(empty_snapshot.is_none());
312 }
313}