Skip to main content

dvm_run/
execution.rs

1//! An execution: everything a sandbox ran, one log from its start to its
2//! end, under the sandbox's id; and its replays, each judged the same
3//! execution or not.
4
5use std::future::{Future, IntoFuture};
6use std::pin::Pin;
7
8use dvm_api::{
9    Moment, NewReplay, ReplayStop, ReplayVerdict, Run, RunId, RunState, SandboxCommand,
10    SandboxSpec, Seek, WatchKind, WatchedEvents,
11};
12use reqwest::Method;
13
14use crate::client::Client;
15use crate::error::{Error, Result};
16
17/// A sandbox's execution, from [`Client::execution`]: its spec, its state,
18/// each command it was sent, as it ran, and its log's size once it ended.
19#[derive(Clone, Debug)]
20pub struct Execution {
21    client: Client,
22    recorded: dvm_api::Execution,
23}
24
25/// A replay to start: [`Execution::replay`]; awaited, the replay.
26#[must_use = "a replay starts once this is awaited"]
27pub struct ReplayBuilder {
28    client: Client,
29    path: String,
30    new: NewReplay,
31}
32
33/// A replay of an execution.
34#[derive(Clone, Debug)]
35pub struct Replay {
36    client: Client,
37    path: String,
38    run: Run,
39}
40
41impl Execution {
42    pub(crate) fn new(client: Client, recorded: dvm_api::Execution) -> Self {
43        Self { client, recorded }
44    }
45
46    pub fn spec(&self) -> &SandboxSpec {
47        &self.recorded.spec
48    }
49
50    pub fn state(&self) -> &RunState {
51        &self.recorded.run.state
52    }
53
54    /// Its log's size, once it ended whole.
55    pub fn log_bytes(&self) -> Option<u64> {
56        self.recorded.run.log_bytes
57    }
58
59    /// Each command it was sent, as it ran, oldest first.
60    pub fn commands(&self) -> &[SandboxCommand] {
61        &self.recorded.commands
62    }
63
64    pub fn replays(&self) -> Vec<Replay> {
65        let replays = self.recorded.replays.iter().cloned();
66        replays
67            .map(|run| Replay::new(self.client.clone(), run))
68            .collect()
69    }
70
71    /// Replays it, once its sandbox ended: played to its end, or held at
72    /// its start for [`Replay::seek`].
73    pub fn replay(&self) -> ReplayBuilder {
74        ReplayBuilder {
75            client: self.client.clone(),
76            path: format!("/executions/{}/replays", self.recorded.id),
77            new: NewReplay::default(),
78        }
79    }
80
81    /// The machine at `at`: each process, its threads, files and memory.
82    pub async fn machine(&self, at: Moment) -> Result<ReplayStop> {
83        let path = format!("/executions/{}/machine", self.recorded.id);
84        let query = [("at", at.to_string())];
85        self.client
86            .json(Method::GET, &path, &query, None::<&()>)
87            .await
88    }
89}
90
91impl ReplayBuilder {
92    /// Held at its start, for [`Replay::seek`], until stopped.
93    pub fn held(mut self) -> Self {
94        self.new.held = true;
95        self
96    }
97
98    /// Watches `kind`'s events as it plays: [`Replay::events`].
99    pub fn watch(mut self, kind: WatchKind) -> Self {
100        self.new.watch = self.new.watch.with(kind);
101        self
102    }
103}
104
105impl IntoFuture for ReplayBuilder {
106    type Output = Result<Replay>;
107    type IntoFuture = Pin<Box<dyn Future<Output = Result<Replay>> + Send>>;
108
109    fn into_future(self) -> Self::IntoFuture {
110        Box::pin(async move {
111            let run = self
112                .client
113                .json(Method::POST, &self.path, &[], Some(&self.new))
114                .await?;
115            Ok(Replay::new(self.client, run))
116        })
117    }
118}
119
120impl Replay {
121    fn new(client: Client, run: Run) -> Self {
122        let path = format!("/replays/{}", run.id);
123        Self { client, path, run }
124    }
125
126    pub fn id(&self) -> RunId {
127        self.run.id
128    }
129
130    /// Its state when last read.
131    pub fn state(&self) -> &RunState {
132        &self.run.state
133    }
134
135    /// Its verdict, once it ended.
136    pub fn verdict(&self) -> Option<&ReplayVerdict> {
137        self.run.verdict.as_ref()
138    }
139
140    /// Its verdict, once it ended: the same execution, or where it parted.
141    /// A replay dvm failed, as it saw it part, is an error that says where.
142    pub async fn wait(&mut self) -> Result<ReplayVerdict> {
143        while self.run.state.is_active() {
144            let query = [("newer_than", self.run.version.to_string())];
145            self.run = self
146                .client
147                .json(Method::GET, &self.path, &query, None::<&()>)
148                .await?;
149        }
150        self.run.verdict.clone().ok_or_else(|| Error::ReplayEnded {
151            replay: self.run.id,
152            state: self.run.state.clone(),
153        })
154    }
155
156    /// Takes a held replay to `to`, and holds it there: the moment, each
157    /// thread's next operation, and the machine there.
158    pub async fn seek(&self, to: Moment) -> Result<ReplayStop> {
159        let path = format!("{}/seek", self.path);
160        self.client
161            .json(Method::POST, &path, &[], Some(&Seek { to }))
162            .await
163    }
164
165    pub async fn stop(&mut self) -> Result<()> {
166        let path = format!("{}/stop", self.path);
167        self.run = self
168            .client
169            .json(Method::POST, &path, &[], None::<&()>)
170            .await?;
171        Ok(())
172    }
173
174    /// The commands it ran again, oldest first.
175    pub async fn commands(&self) -> Result<Vec<SandboxCommand>> {
176        let path = format!("{}/commands", self.path);
177        self.client.json(Method::GET, &path, &[], None::<&()>).await
178    }
179
180    /// What it watched, and the events it saw, once it ended.
181    pub async fn events(&self) -> Result<WatchedEvents> {
182        let path = format!("{}/events", self.path);
183        self.client.json(Method::GET, &path, &[], None::<&()>).await
184    }
185}