cargo_tarpaulin/
event_log.rs

1use crate::cargo::TestBinary;
2use crate::config::Config;
3#[cfg(ptrace_supported)]
4use crate::ptrace_control::*;
5#[cfg(ptrace_supported)]
6use crate::statemachine::ProcessInfo;
7#[cfg(ptrace_supported)]
8use crate::statemachine::TracerAction;
9use crate::traces::Location;
10#[cfg(ptrace_supported)]
11use crate::traces::TraceMap;
12use chrono::offset::Local;
13#[cfg(ptrace_supported)]
14use nix::libc::*;
15#[cfg(ptrace_supported)]
16use nix::sys::{signal::Signal, wait::WaitStatus};
17use serde::{Deserialize, Serialize};
18use std::cell::RefCell;
19use std::collections::HashSet;
20use std::fs::File;
21use std::path::PathBuf;
22use std::time::Instant;
23use tracing::{info, warn};
24
25#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
26pub enum Event {
27    ConfigLaunch(String),
28    BinaryLaunch(TestBinary),
29    Trace(TraceEvent),
30    Marker(Option<()>),
31}
32
33#[derive(Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
34pub struct EventWrapper {
35    #[serde(flatten)]
36    event: Event,
37    // The time this was created in seconds
38    created: f64,
39}
40
41impl EventWrapper {
42    fn new(event: Event, since: Instant) -> Self {
43        let created = Instant::now().duration_since(since).as_secs_f64();
44        Self { event, created }
45    }
46}
47
48#[derive(Clone, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
49pub struct TraceEvent {
50    pid: Option<i64>,
51    child: Option<i64>,
52    signal: Option<String>,
53    addr: Option<u64>,
54    return_val: Option<i64>,
55    location: Option<Location>,
56    description: String,
57}
58
59impl TraceEvent {
60    #[cfg(ptrace_supported)]
61    pub(crate) fn new_from_action(action: &TracerAction<ProcessInfo>) -> Self {
62        match action {
63            TracerAction::TryContinue(t) => TraceEvent {
64                pid: Some(t.pid.as_raw().into()),
65                signal: t.signal.map(|x| x.to_string()),
66                description: "Try continue child".to_string(),
67                ..Default::default()
68            },
69            TracerAction::Continue(t) => TraceEvent {
70                pid: Some(t.pid.as_raw().into()),
71                signal: t.signal.map(|x| x.to_string()),
72                description: "Continue child".to_string(),
73                ..Default::default()
74            },
75            TracerAction::Step(t) => TraceEvent {
76                pid: Some(t.pid.as_raw().into()),
77                description: "Step child".to_string(),
78                ..Default::default()
79            },
80            TracerAction::Detach(t) => TraceEvent {
81                pid: Some(t.pid.as_raw().into()),
82                description: "Detach child".to_string(),
83                ..Default::default()
84            },
85            TracerAction::Nothing => TraceEvent {
86                description: "Do nothing".to_string(),
87                ..Default::default()
88            },
89        }
90    }
91
92    #[cfg(ptrace_supported)]
93    pub(crate) fn new_from_wait(wait: &WaitStatus, offset: u64, traces: &TraceMap) -> Self {
94        let pid = wait.pid().map(|p| p.as_raw().into());
95        let mut event = TraceEvent {
96            pid,
97            ..Default::default()
98        };
99        match wait {
100            WaitStatus::Exited(_, i) => {
101                event.description = "Exited".to_string();
102                event.return_val = Some((*i).into());
103            }
104            WaitStatus::Signaled(_, sig, _) => {
105                event.signal = Some(sig.to_string());
106                event.description = "Signaled".to_string();
107            }
108            WaitStatus::Stopped(c, sig) => {
109                event.signal = Some(sig.to_string());
110                if *sig == Signal::SIGTRAP {
111                    event.description = "Stopped".to_string();
112                    event.addr = current_instruction_pointer(*c).ok().map(|x| (x - 1) as u64);
113                    if let Some(addr) = event.addr {
114                        event.location = traces.get_location(addr - offset);
115                    }
116                } else {
117                    event.description = "Non-trace stop".to_string();
118                }
119            }
120            WaitStatus::PtraceEvent(pid, sig, val) => {
121                event.signal = Some(sig.to_string());
122                match *val {
123                    PTRACE_EVENT_CLONE => {
124                        event.description = "Ptrace Clone".to_string();
125                        if *sig == Signal::SIGTRAP {
126                            event.child = get_event_data(*pid).ok();
127                        }
128                    }
129                    PTRACE_EVENT_FORK => {
130                        event.description = "Ptrace fork".to_string();
131                        if *sig == Signal::SIGTRAP {
132                            event.child = get_event_data(*pid).ok();
133                        }
134                    }
135                    PTRACE_EVENT_VFORK => {
136                        event.description = "Ptrace vfork".to_string();
137                        if *sig == Signal::SIGTRAP {
138                            event.child = get_event_data(*pid).ok();
139                        }
140                    }
141                    PTRACE_EVENT_EXEC => {
142                        event.description = "Ptrace exec".to_string();
143                    }
144                    PTRACE_EVENT_EXIT => {
145                        event.description = "Ptrace exit".to_string();
146                    }
147                    _ => {
148                        event.description = "Ptrace unknown event".to_string();
149                    }
150                }
151            }
152            WaitStatus::Continued(_) => {
153                event.description = "Continued".to_string();
154            }
155            WaitStatus::StillAlive => {
156                event.description = "StillAlive".to_string();
157            }
158            WaitStatus::PtraceSyscall(_) => {
159                event.description = "PtraceSyscall".to_string();
160            }
161        }
162        event
163    }
164}
165
166#[derive(Clone, PartialEq, Serialize, Deserialize)]
167pub struct EventLog {
168    events: RefCell<Vec<EventWrapper>>,
169    #[serde(skip)]
170    start: Option<Instant>,
171    manifest_paths: HashSet<PathBuf>,
172    #[serde(skip)]
173    output_folder: PathBuf,
174}
175
176impl EventLog {
177    pub fn new(manifest_paths: HashSet<PathBuf>, config: &Config) -> Self {
178        Self {
179            events: RefCell::new(vec![]),
180            start: Some(Instant::now()),
181            manifest_paths,
182            output_folder: config.output_dir(),
183        }
184    }
185
186    pub fn push_binary(&self, binary: TestBinary) {
187        self.events.borrow_mut().push(EventWrapper::new(
188            Event::BinaryLaunch(binary),
189            self.start.unwrap(),
190        ));
191    }
192
193    pub fn push_trace(&self, event: TraceEvent) {
194        self.events
195            .borrow_mut()
196            .push(EventWrapper::new(Event::Trace(event), self.start.unwrap()));
197    }
198
199    pub fn push_config(&self, name: String) {
200        self.events.borrow_mut().push(EventWrapper::new(
201            Event::ConfigLaunch(name),
202            self.start.unwrap(),
203        ));
204    }
205
206    pub fn push_marker(&self) {
207        // Prevent back to back markers when we spend a lot of time waiting on events
208        if self
209            .events
210            .borrow()
211            .last()
212            .filter(|x| matches!(x.event, Event::Marker(_)))
213            .is_none()
214        {
215            self.events
216                .borrow_mut()
217                .push(EventWrapper::new(Event::Marker(None), self.start.unwrap()));
218        }
219    }
220}
221
222impl Drop for EventLog {
223    fn drop(&mut self) {
224        let fname = format!("tarpaulin_{}.json", Local::now().format("%Y%m%d%H%M%S"));
225        let path = self.output_folder.join(fname);
226        info!("Serializing tarpaulin debug log to {}", path.display());
227        if let Ok(output) = File::create(path) {
228            if let Err(e) = serde_json::to_writer(output, self) {
229                warn!("Failed to serialise or write result: {e}");
230            }
231        } else {
232            warn!("Failed to create log file");
233        }
234    }
235}