trace_task/
triggers.rs

1// Copyright 2025 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 fidl_fuchsia_tracing_controller as trace;
6use futures::FutureExt;
7use std::collections::BTreeSet;
8use std::future::Future;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11
12#[derive(Debug, Clone, Eq, PartialEq)]
13pub enum TriggerAction {
14    Terminate,
15}
16
17impl From<trace::Action> for TriggerAction {
18    fn from(t: trace::Action) -> Self {
19        match t {
20            trace::Action::Terminate => Self::Terminate,
21            _ => panic!("Unknown trigger action: {t:?}"),
22        }
23    }
24}
25
26#[derive(Debug, Clone)]
27pub struct Trigger {
28    /// See fuchsia.tracing.controller.Controller.WatchAlert for more info.
29    pub alert: Option<String>,
30    pub action: Option<TriggerAction>,
31}
32
33impl From<trace::Trigger> for Trigger {
34    fn from(t: trace::Trigger) -> Self {
35        Self { alert: t.alert, action: t.action.map(Into::into) }
36    }
37}
38
39impl From<&trace::Trigger> for Trigger {
40    fn from(t: &trace::Trigger) -> Self {
41        Self { alert: t.alert.clone(), action: t.action.map(Into::into) }
42    }
43}
44// A wrapper type for ffx::Trigger that does some unwrapping on allocation.
45#[derive(Debug, Clone, PartialEq, Eq)]
46struct TriggerSetItem {
47    alert: String,
48    action: TriggerAction,
49}
50
51impl TriggerSetItem {
52    fn new(t: Trigger) -> Option<Self> {
53        let alert = t.alert?;
54        let action = t.action?;
55        Some(Self { alert, action })
56    }
57
58    /// Convenience constructor for doing a lookup.
59    fn lookup(alert: String) -> Self {
60        Self { alert: alert, action: TriggerAction::Terminate }
61    }
62}
63
64impl std::cmp::PartialOrd for TriggerSetItem {
65    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66        Some(self.cmp(other))
67    }
68}
69
70impl std::cmp::Ord for TriggerSetItem {
71    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
72        self.alert.cmp(&other.alert)
73    }
74}
75
76type TriggersFut<'a> = Pin<Box<dyn Future<Output = Option<TriggerAction>> + Send + 'a>>;
77
78pub struct TriggersWatcher<'a> {
79    inner: TriggersFut<'a>,
80}
81
82impl<'a> TriggersWatcher<'a> {
83    pub fn new(
84        controller: trace::SessionProxy,
85        triggers: Vec<Trigger>,
86        shutdown: async_channel::Receiver<()>,
87    ) -> Self {
88        Self {
89            inner: Box::pin(async move {
90                let items: Vec<_> =
91                    triggers.into_iter().filter_map(|i| TriggerSetItem::new(i)).collect();
92                let set: BTreeSet<TriggerSetItem> = items.iter().map(|t| t).cloned().collect();
93                let mut shutdown_fut = shutdown.recv().fuse();
94                loop {
95                    let mut watch_alert = controller.watch_alert().fuse();
96                    futures::select! {
97                        _ = shutdown_fut => {
98                            log::info!("received shutdown alert");
99                            break;
100                        }
101                        alert = watch_alert => {
102                            let Ok(alert) = alert else { break };
103                            log::info!("alert received: {}", alert);
104                            let lookup_item = TriggerSetItem::lookup(alert);
105                            if set.contains(&lookup_item) {
106                                return set.get(&lookup_item).map(|s| s.action.clone());
107                            }
108                        }
109                    }
110                }
111                None
112            }),
113        }
114    }
115}
116
117impl Future for TriggersWatcher<'_> {
118    type Output = Option<TriggerAction>;
119
120    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
121        Pin::new(&mut self.inner).poll(cx)
122    }
123}