1use crate::common::{EventCode, MetricId, MetricType, ProjectId};
6use fidl_fuchsia_diagnostics::Selector;
7use selectors::SelectorDisplayOptions;
8use serde::ser::{Error, SerializeSeq};
9use serde::{Deserialize, Serialize, Serializer};
10
11#[derive(Serialize, Deserialize, Debug, PartialEq)]
13pub struct ProjectConfig {
14 pub project_id: ProjectId,
16
17 pub data_sets: Vec<DataSetConfig>,
19}
20
21#[derive(Serialize, Deserialize, Debug, PartialEq)]
24pub struct DataSetConfig {
25 #[serde(deserialize_with = "crate::utils::greater_than_zero")]
27 pub poll_rate_sec: i64,
28
29 pub metrics: Vec<MetricConfig>,
31}
32
33#[derive(Serialize, Deserialize, Debug, PartialEq)]
35pub struct MetricConfig {
36 #[serde(
38 rename = "selector",
39 deserialize_with = "crate::utils::one_or_many_selectors",
40 serialize_with = "selectors_to_string"
41 )]
42 pub selectors: Vec<Selector>,
43
44 pub metric_id: MetricId,
46
47 pub metric_type: MetricType,
49
50 #[serde(default)]
54 pub event_codes: Vec<EventCode>,
55
56 #[serde(default)]
59 pub upload_once: bool,
60}
61
62pub fn selectors_to_string<S>(selectors: &[Selector], serializer: S) -> Result<S::Ok, S::Error>
63where
64 S: Serializer,
65{
66 let mut seq = serializer.serialize_seq(Some(selectors.len()))?;
67 for selector in selectors {
68 let selector_string =
69 selectors::selector_to_string(selector, SelectorDisplayOptions::never_wrap_in_quotes())
70 .map_err(S::Error::custom)?;
71 seq.serialize_element(&selector_string)?;
72 }
73 seq.end()
74}
75
76#[cfg(test)]
77mod test {
78 use super::*;
79
80 #[fuchsia::test]
81 fn parse_valid_sampler_config() {
82 let ok_json = r#"{
83 "project_id": 5,
84 "data_sets": [{
85 "poll_rate_sec": 60,
86 "metrics": [
87 {
88 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
89 "metric_id": 1,
90 "metric_type": "Occurrence",
91 "event_codes": [0, 0]
92 }
93 ]
94 }]
95 }"#;
96
97 let config: ProjectConfig = serde_json5::from_str(ok_json).expect("parse json");
98 assert_eq!(config, ProjectConfig {
99 project_id: ProjectId(5),
100 data_sets: vec![DataSetConfig {
101 poll_rate_sec: 60,
102 metrics: vec![
103 MetricConfig {
104 selectors: vec![
105 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
106 ],
107 metric_id: MetricId(1),
108 metric_type: MetricType::Occurrence,
109 event_codes: vec![EventCode(0), EventCode(0)],
110 upload_once: false,
111 }
112 ]
113 }]
114 });
115 }
116
117 #[fuchsia::test]
118 fn parse_valid_sampler_config_json5() {
119 let ok_json = r#"{
120 project_id: 5,
121 data_sets: [{
122 poll_rate_sec: 3,
123 metrics: [
124 {
125 // Test comment for json5 portability.
126 selector: "single_counter_test_component:root:counter",
127 metric_id: 1,
128 metric_type: "Occurrence",
129 event_codes: [0, 0]
130 }
131 ]
132 }]
133 }"#;
134
135 let config: ProjectConfig = serde_json5::from_str(ok_json).expect("parse json");
136 assert_eq!(
137 config,
138 ProjectConfig {
139 project_id: ProjectId(5),
140 data_sets: vec![DataSetConfig {
141 poll_rate_sec: 3,
142 metrics: vec![MetricConfig {
143 selectors: vec![
144 selectors::parse_verbose("single_counter_test_component:root:counter")
145 .unwrap(),
146 ],
147 metric_id: MetricId(1),
148 metric_type: MetricType::Occurrence,
149 upload_once: false,
150 event_codes: vec![EventCode(0), EventCode(0)],
151 }]
152 }]
153 }
154 );
155 }
156
157 #[fuchsia::test]
158 fn parse_valid_sampler_config_multiple_selectors() {
159 let ok_json = r#"{
160 project_id: 5,
161 data_sets: [{
162 poll_rate_sec: 3,
163 metrics: [
164 {
165 selector: ["component:root:one", "component:root:two"],
166 metric_id: 1,
167 metric_type: "Occurrence",
168 event_codes: [0, 0]
169 }
170 ]
171 }]
172 }"#;
173
174 let config: ProjectConfig = serde_json5::from_str(ok_json).expect("parse json");
175 assert_eq!(
176 config,
177 ProjectConfig {
178 project_id: ProjectId(5),
179 data_sets: vec![DataSetConfig {
180 poll_rate_sec: 3,
181 metrics: vec![MetricConfig {
182 selectors: vec![
183 selectors::parse_verbose("component:root:one").unwrap(),
184 selectors::parse_verbose("component:root:two").unwrap(),
185 ],
186 metric_id: MetricId(1),
187 metric_type: MetricType::Occurrence,
188 upload_once: false,
189 event_codes: vec![EventCode(0), EventCode(0)],
190 }]
191 }]
192 }
193 );
194 }
195
196 #[fuchsia::test]
197 fn parse_invalid_config() {
198 let invalid_json = r#"{
199 "project_id": 5,
200 "poll_rate_sec": 3,
201 "invalid_field": "bad bad bad"
202 }"#;
203
204 serde_json5::from_str::<ProjectConfig>(invalid_json)
205 .expect_err("fail to load invalid config");
206 }
207
208 #[fuchsia::test]
209 fn parse_optional_args() {
210 let true_json = r#"{
211 "project_id": 5,
212 "data_sets": [{
213 "poll_rate_sec": 60,
214 "metrics": [
215 {
216 // Test comment for json5 portability.
217 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
218 "metric_id": 1,
219 "metric_type": "Occurrence",
220 "event_codes": [0, 0],
221 "upload_once": true,
222 }
223 ]
224 }]
225 }"#;
226
227 let config: ProjectConfig = serde_json5::from_str(true_json).expect("parse json");
228 assert_eq!(
229 config,
230 ProjectConfig {
231 project_id: ProjectId(5),
232 data_sets: vec![DataSetConfig {
233 poll_rate_sec: 60,
234 metrics: vec![MetricConfig {
235 selectors: vec![
236 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
237 ],
238 metric_id: MetricId(1),
239 metric_type: MetricType::Occurrence,
240 upload_once: true,
241 event_codes: vec![EventCode(0), EventCode(0)],
242 }]
243 }]
244 }
245 );
246
247 let false_json = r#"{
248 "project_id": 5,
249 "data_sets": [{
250 "poll_rate_sec": 60,
251 "metrics": [
252 {
253 // Test comment for json5 portability.
254 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
255 "metric_id": 1,
256 "metric_type": "Occurrence",
257 "event_codes": [0, 0],
258 "upload_once": false,
259 }
260 ]
261 }]
262 }"#;
263 let config: ProjectConfig = serde_json5::from_str(false_json).expect("parse json");
264 assert_eq!(
265 config,
266 ProjectConfig {
267 project_id: ProjectId(5),
268 data_sets: vec![DataSetConfig {
269 poll_rate_sec: 60,
270 metrics: vec![MetricConfig {
271 selectors: vec![
272 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
273 ],
274 metric_id: MetricId(1),
275 metric_type: MetricType::Occurrence,
276 upload_once: false,
277 event_codes: vec![EventCode(0), EventCode(0)],
278 }]
279 }]
280 }
281 );
282 }
283 #[fuchsia::test]
284 fn default_customer_id() {
285 let default_json = r#"{
286 "project_id": 5,
287 "data_sets": [{
288 "poll_rate_sec": 60,
289 "metrics": [
290 {
291 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
292 "metric_id": 1,
293 "metric_type": "Occurrence",
294 "event_codes": [0, 0]
295 }
296 ]
297 }]
298 }"#;
299
300 let config: ProjectConfig = serde_json5::from_str(default_json).expect("deserialize");
301 assert_eq!(
302 config,
303 ProjectConfig {
304 project_id: ProjectId(5),
305 data_sets: vec![DataSetConfig {
306 poll_rate_sec: 60,
307 metrics: vec![MetricConfig {
308 selectors: vec![
309 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
310 ],
311 metric_id: MetricId(1),
312 metric_type: MetricType::Occurrence,
313 upload_once: false,
314 event_codes: vec![EventCode(0), EventCode(0)],
315 }],
316 }]
317 }
318 );
319 }
320
321 #[fuchsia::test]
322 fn missing_event_codes_ok() {
323 let default_json = r#"{
324 "project_id": 5,
325 "data_sets": [{
326 "poll_rate_sec": 60,
327 "metrics": [
328 {
329 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
330 "metric_id": 1,
331 "metric_type": "Occurrence",
332 }
333 ]
334 }]
335 }"#;
336
337 let config: ProjectConfig = serde_json5::from_str(default_json).expect("deserialize");
338 assert_eq!(
339 config,
340 ProjectConfig {
341 project_id: ProjectId(5),
342 data_sets: vec![DataSetConfig {
343 poll_rate_sec: 60,
344 metrics: vec![MetricConfig {
345 selectors: vec![
346 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
347 ],
348 metric_id: MetricId(1),
349 metric_type: MetricType::Occurrence,
350 upload_once: false,
351 event_codes: vec![],
352 }]
353 }]
354 }
355 );
356 }
357}