1use super::{InterfaceIdentifier, InterfaceTimeSeriesGrouping};
6
7use fuchsia_inspect::Node as InspectNode;
8use fuchsia_inspect_contrib::nodes::{DedupeLogNode, LruCacheNode};
9use fuchsia_inspect_derive::Unit;
10use std::collections::HashMap;
11use windowed_stats::experimental::inspect::{InspectSender, InspectedTimeMatrix, TimeMatrixClient};
12use windowed_stats::experimental::series::interpolation::ConstantSample;
13use windowed_stats::experimental::series::metadata::BitSetNode;
14use windowed_stats::experimental::series::statistic::Union;
15use windowed_stats::experimental::series::{SamplingProfile, TimeMatrix};
16
17use crate::fetch::{FetchError, fetch_result_short_name};
18use crate::ping::{PingError, ping_result_short_name};
19use crate::{FetchParameters, IpVersions, PingParameters};
20
21const INSPECT_LOG_WINDOW_SIZE: usize = 50;
23
24#[derive(PartialEq, Eq, Unit)]
25struct PingResult {
26 address: String,
27 interface_name: String,
28 result: String,
29}
30
31#[derive(PartialEq, Eq, Unit)]
32struct FetchResult {
33 host_and_path: String,
34 resolved_address: String,
35 interface_name: String,
36 result: String,
37}
38
39struct PerInterfaceEvents {
40 gateway_ping_results: IpVersions<DedupeLogNode<PingResult>>,
42 internet_ping_results: IpVersions<DedupeLogNode<PingResult>>,
43 fetch_results: IpVersions<DedupeLogNode<FetchResult>>,
44 _interface_node: fuchsia_inspect::Node,
45 _v4_node: fuchsia_inspect::Node,
46 _v6_node: fuchsia_inspect::Node,
47}
48
49impl PerInterfaceEvents {
50 pub fn new(parent_node: &fuchsia_inspect::Node, identifier: InterfaceIdentifier) -> Self {
51 let interface_node = parent_node.create_child(format!("{}", identifier));
52 let v4_node = interface_node.create_child("v4");
53 let v6_node = interface_node.create_child("v6");
54
55 Self {
56 gateway_ping_results: IpVersions {
57 ipv4: DedupeLogNode::new(
58 v4_node.create_child("gateway_ping_results"),
59 INSPECT_LOG_WINDOW_SIZE,
60 ),
61 ipv6: DedupeLogNode::new(
62 v6_node.create_child("gateway_ping_results"),
63 INSPECT_LOG_WINDOW_SIZE,
64 ),
65 },
66 internet_ping_results: IpVersions {
67 ipv4: DedupeLogNode::new(
68 v4_node.create_child("internet_ping_results"),
69 INSPECT_LOG_WINDOW_SIZE,
70 ),
71 ipv6: DedupeLogNode::new(
72 v6_node.create_child("internet_ping_results"),
73 INSPECT_LOG_WINDOW_SIZE,
74 ),
75 },
76 fetch_results: IpVersions {
77 ipv4: DedupeLogNode::new(
78 v4_node.create_child("fetch_results"),
79 INSPECT_LOG_WINDOW_SIZE,
80 ),
81 ipv6: DedupeLogNode::new(
82 v6_node.create_child("fetch_results"),
83 INSPECT_LOG_WINDOW_SIZE,
84 ),
85 },
86 _interface_node: interface_node,
87 _v4_node: v4_node,
88 _v6_node: v6_node,
89 }
90 }
91
92 pub fn log_gateway_ping_result(
105 &mut self,
106 ping_parameters: &PingParameters,
107 result: &Result<(), PingError>,
108 ) {
109 Self::log_ping_result(&mut self.gateway_ping_results, ping_parameters, result);
110 }
111
112 pub fn log_internet_ping_result(
125 &mut self,
126 ping_parameters: &PingParameters,
127 result: &Result<(), PingError>,
128 ) {
129 Self::log_ping_result(&mut self.internet_ping_results, ping_parameters, result);
130 }
131
132 fn log_ping_result(
133 ping_results: &mut IpVersions<DedupeLogNode<PingResult>>,
134 ping_parameters: &PingParameters,
135 result: &Result<(), PingError>,
136 ) {
137 let results_node = match ping_parameters.addr.is_ipv4() {
138 true => &mut ping_results.ipv4,
139 false => &mut ping_results.ipv6,
140 };
141 let ping_result = PingResult {
142 address: format!("{}", ping_parameters.addr),
143 interface_name: ping_parameters.interface_name.clone(),
144 result: ping_result_short_name(result),
145 };
146 results_node.insert(ping_result);
147 }
148
149 pub fn log_fetch_result(
163 &mut self,
164 fetch_parameters: &FetchParameters,
165 result: &Result<u16, FetchError>,
166 ) {
167 let results_node = match fetch_parameters.ip.is_ipv4() {
168 true => &mut self.fetch_results.ipv4,
169 false => &mut self.fetch_results.ipv6,
170 };
171 let fetch_result = FetchResult {
172 host_and_path: format!("{}{}", fetch_parameters.domain, fetch_parameters.path),
173 resolved_address: format!("{}", fetch_parameters.ip),
174 interface_name: fetch_parameters.interface_name.clone(),
175 result: fetch_result_short_name(result),
176 };
177 results_node.insert(fetch_result);
178 }
179}
180
181fn bitset_constant_sample_time_matrix(
182 client: &TimeMatrixClient,
183 time_series_name: &str,
184 bitset_node: BitSetNode,
185) -> InspectedTimeMatrix<u64> {
186 client.inspect_time_matrix_with_metadata(
187 time_series_name,
188 TimeMatrix::<Union<u64>, ConstantSample>::new(
189 SamplingProfile::highly_granular(),
190 ConstantSample::default(),
191 ),
192 bitset_node,
193 )
194}
195
196struct PerInterfaceTimeSeries {
197 gateway_ping_result_time_matrix: IpVersions<InspectedTimeMatrix<u64>>,
198 internet_ping_result_time_matrix: IpVersions<InspectedTimeMatrix<u64>>,
199 fetch_result_time_matrix: IpVersions<InspectedTimeMatrix<u64>>,
200 _interface_node: InspectNode,
201 _v4_node: InspectNode,
202 _v6_node: InspectNode,
203}
204
205impl PerInterfaceTimeSeries {
206 pub fn new(
207 parent_node: &InspectNode,
208 inspect_metadata_path: &str,
209 identifier: InterfaceIdentifier,
210 ) -> Self {
211 let interface_node = parent_node.create_child(format!("{}", identifier));
212 let v4_node = interface_node.create_child("v4");
213 let v6_node = interface_node.create_child("v6");
214
215 let client_v4 = TimeMatrixClient::new(v4_node.clone_weak());
216 let client_v6 = TimeMatrixClient::new(v6_node.clone_weak());
217
218 let bitset_constant_sample_time_matrices = |name: &str, metadata_node_name: &str| {
219 let bitset_node =
220 BitSetNode::from_path(format!("{}/{}", inspect_metadata_path, metadata_node_name));
221 IpVersions {
222 ipv4: bitset_constant_sample_time_matrix(&client_v4, name, bitset_node.clone()),
223 ipv6: bitset_constant_sample_time_matrix(&client_v6, name, bitset_node),
224 }
225 };
226
227 Self {
228 gateway_ping_result_time_matrix: bitset_constant_sample_time_matrices(
229 "gateway_ping_results",
230 InspectMetadataNode::PING_RESULTS,
231 ),
232 internet_ping_result_time_matrix: bitset_constant_sample_time_matrices(
233 "internet_ping_results",
234 InspectMetadataNode::PING_RESULTS,
235 ),
236 fetch_result_time_matrix: bitset_constant_sample_time_matrices(
237 "fetch_results",
238 InspectMetadataNode::FETCH_RESULTS,
239 ),
240 _interface_node: interface_node,
241 _v4_node: v4_node,
242 _v6_node: v6_node,
243 }
244 }
245
246 fn log_gateway_ping_result(&self, ping_parameters: &PingParameters, result_bitmask: u64) {
247 if ping_parameters.addr.is_ipv4() {
248 self.gateway_ping_result_time_matrix.ipv4.fold_or_log_error(result_bitmask);
249 } else {
250 self.gateway_ping_result_time_matrix.ipv6.fold_or_log_error(result_bitmask);
251 }
252 }
253
254 fn log_internet_ping_result(&self, ping_parameters: &PingParameters, result_bitmask: u64) {
255 if ping_parameters.addr.is_ipv4() {
256 self.internet_ping_result_time_matrix.ipv4.fold_or_log_error(result_bitmask);
257 } else {
258 self.internet_ping_result_time_matrix.ipv6.fold_or_log_error(result_bitmask);
259 }
260 }
261
262 fn log_fetch_result(&self, fetch_parameters: &FetchParameters, result_bitmask: u64) {
263 if fetch_parameters.ip.is_ipv4() {
264 self.fetch_result_time_matrix.ipv4.fold_or_log_error(result_bitmask);
265 } else {
266 self.fetch_result_time_matrix.ipv6.fold_or_log_error(result_bitmask);
267 }
268 }
269}
270
271pub struct InterfaceAwareLogger {
273 events: HashMap<InterfaceIdentifier, PerInterfaceEvents>,
274 time_series_stats: HashMap<InterfaceIdentifier, PerInterfaceTimeSeries>,
277 inspect_metadata_node: InspectMetadataNode,
278 _events_node: InspectNode,
279 _time_series_node: InspectNode,
280}
281
282impl InterfaceAwareLogger {
283 pub fn new(
284 inspect_metadata_node: &InspectNode,
285 inspect_metadata_path: &str,
286 interface_grouping: InterfaceTimeSeriesGrouping,
287 events_node: InspectNode,
288 time_series_node: InspectNode,
289 ) -> Self {
290 let (events, time_series_stats) = match interface_grouping {
291 InterfaceTimeSeriesGrouping::Type(tys) => {
292 let events = tys
293 .iter()
294 .map(|ty| {
295 let identifier = InterfaceIdentifier::Type(*ty);
296 (identifier.clone(), PerInterfaceEvents::new(&events_node, identifier))
297 })
298 .collect();
299 let time_series_stats = tys
300 .into_iter()
301 .map(|ty| {
302 let identifier = InterfaceIdentifier::Type(ty);
303 (
304 identifier.clone(),
305 PerInterfaceTimeSeries::new(
306 &time_series_node,
307 inspect_metadata_path,
308 identifier,
309 ),
310 )
311 })
312 .collect();
313 (events, time_series_stats)
314 }
315 };
316
317 Self {
318 events,
319 time_series_stats,
320 inspect_metadata_node: InspectMetadataNode::new(inspect_metadata_node),
321 _events_node: events_node,
322 _time_series_node: time_series_node,
323 }
324 }
325
326 pub fn log_gateway_ping_result(
327 &mut self,
328 interface_identifiers: Vec<InterfaceIdentifier>,
329 ping_parameters: &PingParameters,
330 gateway_ping_result: &Result<(), PingError>,
331 ) {
332 self.log_ping_result(
333 interface_identifiers,
334 ping_parameters,
335 gateway_ping_result,
336 PerInterfaceTimeSeries::log_gateway_ping_result,
337 PerInterfaceEvents::log_gateway_ping_result,
338 );
339 }
340
341 pub fn log_internet_ping_result(
342 &mut self,
343 interface_identifiers: Vec<InterfaceIdentifier>,
344 ping_parameters: &PingParameters,
345 internet_ping_result: &Result<(), PingError>,
346 ) {
347 self.log_ping_result(
348 interface_identifiers,
349 ping_parameters,
350 internet_ping_result,
351 PerInterfaceTimeSeries::log_internet_ping_result,
352 PerInterfaceEvents::log_internet_ping_result,
353 );
354 }
355
356 fn log_ping_result(
357 &mut self,
358 interface_identifiers: Vec<InterfaceIdentifier>,
359 ping_parameters: &PingParameters,
360 ping_result: &Result<(), PingError>,
361 time_series_log_fn: fn(&PerInterfaceTimeSeries, &PingParameters, u64),
362 event_log_fn: fn(&mut PerInterfaceEvents, &PingParameters, &Result<(), PingError>),
363 ) {
364 let result = ping_result_short_name(ping_result);
365 let result_id = self.inspect_metadata_node.ping_result.insert(result);
366 interface_identifiers.iter().for_each(|identifier| {
367 if let Some(events) = self.events.get_mut(identifier) {
368 event_log_fn(events, ping_parameters, ping_result);
369 }
370
371 if let Some(time_series) = self.time_series_stats.get(identifier) {
372 time_series_log_fn(time_series, ping_parameters, 1 << result_id);
373 }
374 });
375 }
376
377 pub fn log_fetch_result(
378 &mut self,
379 interface_identifiers: Vec<InterfaceIdentifier>,
380 fetch_parameters: &FetchParameters,
381 fetch_result: &Result<u16, FetchError>,
382 ) {
383 let result = fetch_result_short_name(fetch_result);
384 let result_id = self.inspect_metadata_node.fetch_result.insert(result);
385 interface_identifiers.iter().for_each(|identifier| {
386 if let Some(events) = self.events.get_mut(identifier) {
387 events.log_fetch_result(fetch_parameters, fetch_result);
388 }
389
390 if let Some(time_series) = self.time_series_stats.get(identifier) {
391 time_series.log_fetch_result(fetch_parameters, 1 << result_id);
392 }
393 });
394 }
395}
396
397const PING_RESULT_METADATA_CACHE_SIZE: usize = 32;
398const FETCH_RESULT_METADATA_CACHE_SIZE: usize = 32;
399
400struct InspectMetadataNode {
403 ping_result: LruCacheNode<String>,
404 fetch_result: LruCacheNode<String>,
405}
406
407impl InspectMetadataNode {
408 const PING_RESULTS: &'static str = "ping_results";
409 const FETCH_RESULTS: &'static str = "fetch_results";
410
411 fn new(inspect_node: &InspectNode) -> Self {
412 let ping_result = LruCacheNode::new(
413 inspect_node.create_child(Self::PING_RESULTS),
414 PING_RESULT_METADATA_CACHE_SIZE,
415 );
416 let fetch_result = LruCacheNode::new(
417 inspect_node.create_child(Self::FETCH_RESULTS),
418 FETCH_RESULT_METADATA_CACHE_SIZE,
419 );
420
421 Self { ping_result, fetch_result }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use diagnostics_assertions::{
429 AnyBytesProperty, AnyNumericProperty, AnyProperty, assert_data_tree,
430 };
431
432 use crate::telemetry::processors::InterfaceType;
433 use crate::telemetry::testing::setup_test;
434
435 const IPV4_ADDR: std::net::IpAddr = std::net::IpAddr::V4(std::net::Ipv4Addr::new(8, 8, 8, 8));
436 const IPV6_ADDR: std::net::IpAddr =
437 std::net::IpAddr::V6(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
438
439 #[fuchsia::test]
440 fn test_log_time_series_metadata_to_inspect() {
441 let mut harness = setup_test();
442 let logger_node = harness.inspect_node.create_child("interfaces");
443 let events_node = logger_node.create_child("events");
444 let time_series_node = logger_node.create_child("time_series");
445
446 let _interface_aware_logger = InterfaceAwareLogger::new(
447 &harness.inspect_metadata_node,
448 &harness.inspect_metadata_path,
449 InterfaceTimeSeriesGrouping::Type(vec![InterfaceType::Ethernet]),
450 events_node,
451 time_series_node,
452 );
453
454 let tree = harness.get_inspect_data_tree();
455 assert_data_tree!(
456 @executor harness.exec,
457 tree,
458 root: contains {
459 test_stats: contains {
460 metadata: {
461 ping_results: contains {},
462 fetch_results: contains {},
463 },
464 interfaces: contains {
465 time_series: contains {
466 TYPE_ethernet: {
467 v4: {
468 gateway_ping_results: {
469 "type": "bitset",
470 "data": AnyBytesProperty,
471 metadata: {
472 index_node_path: "root/test_stats/metadata/ping_results",
473 }
474 },
475 internet_ping_results: {
476 "type": "bitset",
477 "data": AnyBytesProperty,
478 metadata: {
479 index_node_path: "root/test_stats/metadata/ping_results",
480 }
481 },
482 fetch_results: {
483 "type": "bitset",
484 "data": AnyBytesProperty,
485 metadata: {
486 index_node_path: "root/test_stats/metadata/fetch_results",
487 }
488 }
489 },
490 v6: {
491 gateway_ping_results: {
492 "type": "bitset",
493 "data": AnyBytesProperty,
494 metadata: {
495 index_node_path: "root/test_stats/metadata/ping_results",
496 }
497 },
498 internet_ping_results: {
499 "type": "bitset",
500 "data": AnyBytesProperty,
501 metadata: {
502 index_node_path: "root/test_stats/metadata/ping_results",
503 }
504 },
505 fetch_results: {
506 "type": "bitset",
507 "data": AnyBytesProperty,
508 metadata: {
509 index_node_path: "root/test_stats/metadata/fetch_results",
510 }
511 }
512 }
513 }
514 }
515 }
516 }
517 }
518 )
519 }
520
521 #[fuchsia::test]
522 fn test_log_gateway_ping_result() {
523 let mut harness = setup_test();
524 let logger_node = harness.inspect_node.create_child("interfaces");
525 let events_node = logger_node.create_child("events");
526 let time_series_node = logger_node.create_child("time_series");
527
528 let mut interface_aware_logger = InterfaceAwareLogger::new(
529 &harness.inspect_metadata_node,
530 &harness.inspect_metadata_path,
531 InterfaceTimeSeriesGrouping::Type(vec![InterfaceType::Ethernet]),
532 events_node,
533 time_series_node,
534 );
535
536 let ping_parameters = crate::PingParameters {
537 addr: std::net::SocketAddr::new(IPV4_ADDR.into(), 80),
538 interface_name: "eth0".to_string(),
539 };
540
541 interface_aware_logger.log_gateway_ping_result(
542 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
543 &ping_parameters,
544 &Ok(()),
545 );
546 interface_aware_logger.log_gateway_ping_result(
547 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
548 &ping_parameters,
549 &Err(PingError::NoReply),
550 );
551
552 let tree = harness.get_inspect_data_tree();
553 assert_data_tree!(
554 @executor harness.exec,
555 tree,
556 root: contains {
557 test_stats: contains {
558 metadata: contains {
559 ping_results: {
560 "0": {
561 "@time": AnyNumericProperty,
562 data: "Success",
563 },
564 "1": {
565 "@time": AnyNumericProperty,
566 data: "e_NoReply",
567 },
568 },
569 },
570 interfaces: contains {
571 time_series: contains {
572 TYPE_ethernet: contains {
573 v4: contains {
574 gateway_ping_results: {
575 "type": "bitset",
576 "data": AnyBytesProperty,
577 metadata: {
578 index_node_path: "root/test_stats/metadata/ping_results",
579 }
580 },
581 }
582 }
583 }
584 }
585 }
586 }
587 )
588 }
589
590 #[fuchsia::test]
591 fn test_log_internet_ping_result() {
592 let mut harness = setup_test();
593 let logger_node = harness.inspect_node.create_child("interfaces");
594 let events_node = logger_node.create_child("events");
595 let time_series_node = logger_node.create_child("time_series");
596
597 let mut interface_aware_logger = InterfaceAwareLogger::new(
598 &harness.inspect_metadata_node,
599 &harness.inspect_metadata_path,
600 InterfaceTimeSeriesGrouping::Type(vec![InterfaceType::Ethernet]),
601 events_node,
602 time_series_node,
603 );
604
605 let ping_parameters = crate::PingParameters {
606 addr: std::net::SocketAddr::new(IPV6_ADDR.into(), 80),
607 interface_name: "eth0".to_string(),
608 };
609
610 interface_aware_logger.log_internet_ping_result(
611 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
612 &ping_parameters,
613 &Ok(()),
614 );
615 interface_aware_logger.log_internet_ping_result(
616 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
617 &ping_parameters,
618 &Err(PingError::NoReply),
619 );
620
621 let tree = harness.get_inspect_data_tree();
622 assert_data_tree!(
623 @executor harness.exec,
624 tree,
625 root: contains {
626 test_stats: contains {
627 metadata: contains {
628 ping_results: {
629 "0": {
630 "@time": AnyNumericProperty,
631 data: "Success",
632 },
633 "1": {
634 "@time": AnyNumericProperty,
635 data: "e_NoReply",
636 },
637 },
638 },
639 interfaces: contains {
640 time_series: contains {
641 TYPE_ethernet: contains {
642 v6: contains {
643 internet_ping_results: {
644 "type": "bitset",
645 "data": AnyBytesProperty,
646 metadata: {
647 index_node_path: "root/test_stats/metadata/ping_results",
648 }
649 },
650 }
651 }
652 }
653 }
654 }
655 }
656 )
657 }
658
659 #[fuchsia::test]
660 fn test_log_fetch_result() {
661 let mut harness = setup_test();
662 let logger_node = harness.inspect_node.create_child("interfaces");
663 let events_node = logger_node.create_child("events");
664 let time_series_node = logger_node.create_child("time_series");
665
666 let mut interface_aware_logger = InterfaceAwareLogger::new(
667 &harness.inspect_metadata_node,
668 &harness.inspect_metadata_path,
669 InterfaceTimeSeriesGrouping::Type(vec![InterfaceType::Ethernet]),
670 events_node,
671 time_series_node,
672 );
673
674 let fetch_parameters = crate::FetchParameters {
675 interface_name: "eth0".to_string(),
676 domain: "example.com".to_string(),
677 ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(8, 8, 8, 8)),
678 path: "".to_string(),
679 expected_statuses: vec![204],
680 };
681
682 interface_aware_logger.log_fetch_result(
683 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
684 &fetch_parameters,
685 &Ok(204),
686 );
687 interface_aware_logger.log_fetch_result(
688 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
689 &fetch_parameters,
690 &Err(FetchError::ReadTcpStreamTimeout),
691 );
692
693 let tree = harness.get_inspect_data_tree();
694 assert_data_tree!(
695 @executor harness.exec,
696 tree,
697 root: contains {
698 test_stats: contains {
699 metadata: contains {
700 fetch_results: {
701 "0": {
702 "@time": AnyNumericProperty,
703 data: "Completed_204",
704 },
705 "1": {
706 "@time": AnyNumericProperty,
707 data: "e_ReadTcpTimeout",
708 },
709 },
710 },
711 interfaces: contains {
712 time_series: contains {
713 TYPE_ethernet: contains {
714 v4: contains {
715 fetch_results: {
716 "type": "bitset",
717 "data": AnyBytesProperty,
718 metadata: {
719 index_node_path: "root/test_stats/metadata/fetch_results",
720 }
721 },
722 }
723 }
724 }
725 }
726 }
727 }
728 )
729 }
730
731 #[fuchsia::test]
732 fn test_log_ping_result_events() {
733 let mut harness = setup_test();
734 let logger_node = harness.inspect_node.create_child("interfaces");
735 let events_node = logger_node.create_child("events");
736 let time_series_node = logger_node.create_child("time_series");
737
738 let mut interface_aware_logger = InterfaceAwareLogger::new(
739 &harness.inspect_metadata_node,
740 &harness.inspect_metadata_path,
741 InterfaceTimeSeriesGrouping::Type(vec![InterfaceType::Ethernet]),
742 events_node,
743 time_series_node,
744 );
745
746 let ping_parameters_v4 = crate::PingParameters {
747 addr: std::net::SocketAddr::new(IPV4_ADDR.into(), 80),
748 interface_name: "eth0".to_string(),
749 };
750 let ping_parameters_v6 = crate::PingParameters {
751 addr: std::net::SocketAddr::new(IPV6_ADDR.into(), 80),
752 interface_name: "eth0".to_string(),
753 };
754
755 interface_aware_logger.log_gateway_ping_result(
756 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
757 &ping_parameters_v4,
758 &Ok(()),
759 );
760 interface_aware_logger.log_internet_ping_result(
761 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
762 &ping_parameters_v6,
763 &Err(PingError::NoReply),
764 );
765
766 let tree = harness.get_inspect_data_tree();
767 assert_data_tree!(
768 @executor harness.exec,
769 tree,
770 root: contains {
771 test_stats: contains {
772 interfaces: contains {
773 events: contains {
774 TYPE_ethernet: {
775 v4: {
776 gateway_ping_results: {
777 "0": contains {
778 "Created@time": AnyProperty,
779 count: 1u64,
780 log: {
781 address: "8.8.8.8:80",
782 interface_name: "eth0",
783 result: "Success",
784 }
785 }
786 },
787 internet_ping_results: {},
788 fetch_results: {}
789 },
790 v6: {
791 gateway_ping_results: {},
792 internet_ping_results: {
793 "0": contains {
794 "Created@time": AnyProperty,
795 count: 1u64,
796 log: {
797 address: "[2001:db8::]:80",
798 interface_name: "eth0",
799 result: "e_NoReply",
800 }
801 }
802 },
803 fetch_results: {}
804 }
805 }
806 }
807 }
808 }
809 }
810 )
811 }
812
813 #[fuchsia::test]
814 fn test_log_fetch_result_events() {
815 let mut harness = setup_test();
816 let logger_node = harness.inspect_node.create_child("interfaces");
817 let events_node = logger_node.create_child("events");
818 let time_series_node = logger_node.create_child("time_series");
819
820 let mut interface_aware_logger = InterfaceAwareLogger::new(
821 &harness.inspect_metadata_node,
822 &harness.inspect_metadata_path,
823 InterfaceTimeSeriesGrouping::Type(vec![InterfaceType::Ethernet]),
824 events_node,
825 time_series_node,
826 );
827
828 let fetch_parameters = crate::FetchParameters {
829 interface_name: "eth0".to_string(),
830 domain: "example.com".to_string(),
831 ip: IPV4_ADDR.into(),
832 path: "/".to_string(),
833 expected_statuses: vec![204],
834 };
835
836 interface_aware_logger.log_fetch_result(
837 vec![InterfaceIdentifier::Type(InterfaceType::Ethernet)],
838 &fetch_parameters,
839 &Ok(204),
840 );
841
842 let tree = harness.get_inspect_data_tree();
843 assert_data_tree!(
844 @executor harness.exec,
845 tree,
846 root: contains {
847 test_stats: contains {
848 interfaces: contains {
849 events: contains {
850 TYPE_ethernet: {
851 v4: {
852 gateway_ping_results: {},
853 internet_ping_results: {},
854 fetch_results: {
855 "0": contains {
856 "Created@time": AnyProperty,
857 count: 1u64,
858 log: {
859 host_and_path: "example.com/",
860 interface_name: "eth0",
861 resolved_address: "8.8.8.8",
862 result: "Completed_204",
863 }
864 }
865 }
866 },
867 v6: {
868 gateway_ping_results: {},
869 internet_ping_results: {},
870 fetch_results: {}
871 }
872 }
873 }
874 }
875 }
876 }
877 )
878 }
879}