Skip to main content

input_pipeline/metrics/
mod.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{Dispatcher, Incoming, Transport, dispatcher};
6use anyhow::{Context as _, Error};
7use cobalt_client::traits::AsEventCode;
8use derivative::Derivative;
9use fidl_next_fuchsia_metrics as metrics;
10use log::warn;
11use metrics_registry::*;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::{Arc, Mutex};
14
15/// Connects to the MetricEventLoggerFactory service to create a
16/// MetricEventLoggerProxy for the caller.
17fn create_metrics_logger(
18    incoming: &Incoming,
19) -> Result<
20    (
21        fidl_next::Client<metrics::MetricEventLogger, Transport>,
22        dispatcher::TaskHandle<()>,
23        Arc<AtomicBool>,
24    ),
25    Error,
26> {
27    let factory_proxy = incoming
28        .connect_protocol_next::<metrics::MetricEventLoggerFactory>()
29        .context("connecting to metrics")?;
30    let factory_proxy = factory_proxy.spawn();
31
32    let (cobalt_proxy, cobalt_server) =
33        fidl_next::fuchsia::create_channel::<metrics::MetricEventLogger>();
34    let cobalt_proxy = Dispatcher::client_from_zx_channel(cobalt_proxy).spawn();
35
36    let project_spec = metrics::ProjectSpec {
37        customer_id: None, // defaults to fuchsia
38        project_id: Some(PROJECT_ID),
39        ..Default::default()
40    };
41
42    let completed = Arc::new(AtomicBool::new(false));
43    let completed_clone = completed.clone();
44    let task = Dispatcher::spawn_local(async move {
45        match factory_proxy.create_metric_event_logger(&project_spec, cobalt_server).await {
46            Err(e) => warn!("FIDL failure setting up event logger: {e:?}"),
47            Ok(Err(e)) => warn!("CreateMetricEventLogger failure: {e:?}"),
48            Ok(Ok(_)) => {}
49        }
50        completed_clone.store(true, Ordering::SeqCst);
51    });
52
53    Ok((cobalt_proxy, task, completed))
54}
55
56fn log_on_failure<T: std::fmt::Debug>(result: Result<Result<T, metrics::Error>, fidl_next::Error>) {
57    match result {
58        Ok(Ok(_)) => (),
59        e => warn!("failed to log metrics: {:?}", e),
60    };
61}
62
63/// A client connection to the Cobalt logging service.
64#[derive(Clone, Derivative, Default)]
65#[derivative(Debug)]
66pub struct MetricsLogger {
67    #[derivative(Debug = "ignore")]
68    logger: Option<fidl_next::Client<metrics::MetricEventLogger, Transport>>,
69    tasks: Arc<Mutex<Vec<(dispatcher::TaskHandle<()>, Arc<AtomicBool>)>>>,
70}
71
72impl MetricsLogger {
73    pub fn new(incoming: &Incoming) -> Self {
74        let tasks = Arc::new(Mutex::new(vec![]));
75        let logger = match create_metrics_logger(incoming) {
76            Ok((logger, init_task, completed)) => {
77                tasks.lock().unwrap().push((init_task, completed));
78                Some(logger)
79            }
80            Err(e) => {
81                warn!("Failed to create metrics logger: {e}");
82                None
83            }
84        };
85        Self { logger, tasks }
86    }
87
88    /// Logs an warning occurrence metric using the Cobalt logger. Does not block execution.
89    pub fn log_warn<E: AsEventCode, S: Into<String>>(&self, event_code: E, message: S) {
90        log::warn!("{}", message.into());
91        self.send_metric(event_code);
92    }
93
94    /// Logs an error occurrence metric using the Cobalt logger. Does not block execution.
95    pub fn log_error<E: AsEventCode, S: Into<String>>(&self, event_code: E, message: S) {
96        log::error!("{}", message.into());
97        self.send_metric(event_code);
98    }
99
100    // send metric, does not block the execution.
101    fn send_metric<E: AsEventCode>(&self, event_code: E) {
102        let Some(c) = self.logger.clone() else { return };
103        let code = event_code.as_event_code();
104        let completed = Arc::new(AtomicBool::new(false));
105        let completed_clone = completed.clone();
106        let task = Dispatcher::spawn_local(async move {
107            log_on_failure(c.log_occurrence(INPUT_PIPELINE_ERROR_METRIC_ID, 1, &[code]).await);
108            completed_clone.store(true, Ordering::SeqCst);
109        });
110        let mut tasks = self.tasks.lock().unwrap();
111        tasks.retain(|(_, completed)| !completed.load(Ordering::SeqCst));
112        tasks.push((task, completed));
113    }
114}