test_realm_helpers/
tracing.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 crate::trace_runner::{TerminationResult, TraceRunner};
6use anyhow::format_err;
7use log::{info, warn};
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::time::Duration;
10
11static NEXT_TRACING_ID: AtomicUsize = AtomicUsize::new(0);
12static DEFAULT_TRACE_TIMEOUT: Duration = Duration::from_mins(2);
13static DEFAULT_TRACE_FILE_MAX_BYTES: usize = 100 * 1024 * 1024;
14
15/// An RAII-style struct that starts tracing in the test realm upon creation via `Tracing::start`
16/// and collects and writes the trace when the struct is dropped.
17pub struct Tracing {
18    output_trace_path: std::ffi::OsString,
19    tracer: TraceRunner,
20    always_record_trace: bool,
21}
22
23pub(crate) struct HermeticityParameters {
24    pub(crate) service_prefix: String,
25}
26
27pub(crate) enum Hermeticity {
28    NonHermetic,
29    Hermetic(HermeticityParameters),
30}
31
32impl Hermeticity {
33    pub fn new_hermetic(service_prefix: &str) -> Self {
34        Self::Hermetic(HermeticityParameters { service_prefix: service_prefix.to_string() })
35    }
36}
37
38impl Tracing {
39    pub async fn start_non_hermetic(trace_file_prefix: &str) -> Result<Self, anyhow::Error> {
40        Self::start_(
41            trace_file_prefix,
42            Hermeticity::NonHermetic,
43            false,
44            DEFAULT_TRACE_TIMEOUT,
45            DEFAULT_TRACE_FILE_MAX_BYTES,
46        )
47        .await
48    }
49
50    pub async fn start_at(service_prefix: &str) -> Result<Self, anyhow::Error> {
51        Self::start_(
52            service_prefix.strip_prefix("/").ok_or_else(|| {
53                format_err!("Provided service prefix does not start with '/': {service_prefix}")
54            })?,
55            Hermeticity::new_hermetic(service_prefix),
56            false,
57            DEFAULT_TRACE_TIMEOUT,
58            DEFAULT_TRACE_FILE_MAX_BYTES,
59        )
60        .await
61    }
62
63    async fn start_<'a>(
64        trace_file_prefix: &'a str,
65        hermeticity: Hermeticity,
66        always_record_trace: bool,
67        trace_timeout: Duration,
68        trace_file_max_bytes: usize,
69    ) -> Result<Self, anyhow::Error> {
70        // The test namespace prefix ensures all generated trace files for a test suite
71        // are unique per test realm.  In case multiple trace files are generated in the
72        // same process, the tracing_id ensures their names will still be unique even
73        // when the same test namespace prefix is used.
74        let tracing_id = NEXT_TRACING_ID.fetch_add(1, Ordering::SeqCst);
75        let output_trace_path = std::path::Path::new(
76            format!("/custom_artifacts/{tracing_id:04}-{trace_file_prefix}-trace.fxt").as_str(),
77        )
78        .to_path_buf();
79
80        let tracer = TraceRunner::start(
81            hermeticity,
82            output_trace_path.clone(),
83            trace_timeout,
84            trace_file_max_bytes,
85        )
86        .await?;
87
88        Ok(Tracing { output_trace_path: output_trace_path.into(), tracer, always_record_trace })
89    }
90}
91
92impl Drop for Tracing {
93    fn drop(&mut self) {
94        let always_record_trace = self.always_record_trace;
95        let output_trace_path = self.output_trace_path.clone();
96
97        match self.tracer.terminate_trace(format!("Tracing::drop")) {
98            TerminationResult { termination_signal: None, .. } => {
99                warn!("Terminate signal sent before Tracing::drop. Keeping trace.");
100                return;
101            }
102            TerminationResult { termination_signal: Some(Err(e)), .. } => {
103                warn!("Failed to signal termination of trace-writer: {e:?}");
104                return;
105            }
106            TerminationResult { trace_writer: Some(Err(e)), .. } => {
107                warn!("Failed to terminate trace-writer: {e:?}");
108                return;
109            }
110            _ => (),
111        }
112
113        if !always_record_trace {
114            info!("Discarding trace because Tracing instance dropped before a panic.");
115            let _: Result<(), ()> = std::fs::remove_file(&output_trace_path)
116                .map_err(|e| warn!("Failed to remove {}: {e:?}", output_trace_path.display()));
117        }
118    }
119}