1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! Simulation run reproduction functionality.

use crate::{
    common::{
        error::{err, inv_arg, oe_inv_arg, Result},
        log::{tee_file::TeeFileConfiguration, LoglevelFilter},
        types::PluginType,
        util::friendly_enumerate,
    },
    host::configuration::*,
};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

mod host_call;
pub use host_call::HostCall;

/// The contents of a plugin configuration in a reproduction file.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginReproduction {
    /// Name of the plugin, used to refer to the plugin by the log system.
    pub name: String,

    /// The executable filename of the plugin.
    pub executable: PathBuf,

    /// If specified, the executable is expected to be an interpreter, which is
    /// to execute the specified script file. If not specified, the executable
    /// is a native plugin.
    pub script: Option<PathBuf>,

    /// The functional configuration of the plugin, i.e. the options
    /// configuring how the plugin behaves (besides the specification).
    #[serde(flatten)]
    pub functional: PluginProcessFunctionalConfiguration,
}

/// Represents a nonfunctional configuration modification for a previously
/// defined plugin.
///
/// This allows the nonfunctional configuration of the plugin to be modified.
/// This is particularly important when DQCsim is running in
/// --reproduce-exactly mode; it is of marginal use to reproduce a run exactly
/// if you're not looking to get additional information from it by changing
/// loglevels and such.
#[derive(Debug)]
pub struct PluginModification {
    /// Name of the referenced plugin.
    pub name: String,

    /// Specifies the verbosity of the messages sent to DQCsim. If this is
    /// `None`, the value of DQCsim's `--plugin_level` option should be used.
    pub verbosity: Option<LoglevelFilter>,

    /// Specifies the tee file configuration for this plugin.
    pub tee_files: Vec<TeeFileConfiguration>,

    /// Specifies how the stdout stream of the plugin should be connected.
    /// `None` implies default.
    pub stdout_mode: Option<StreamCaptureMode>,

    /// Specifies how the stderr stream of the plugin should be connected.
    /// `None` implies default.
    pub stderr_mode: Option<StreamCaptureMode>,

    /// Specifies the timeout for connecting to the plugin after it has been
    /// spawned.
    pub accept_timeout: Option<Timeout>,

    /// Specifies the timeout for connecting to the plugin after it has been
    /// spawned.
    pub shutdown_timeout: Option<Timeout>,
}

impl PluginModification {
    /// Applies this plugin modification to a plugin definition vector.
    ///
    /// An error is returned if the referenced plugin cannot be found in the
    /// vector, otherwise `Ok(())` is returned.
    pub fn apply(self, to: &mut Vec<PluginProcessConfiguration>) -> Result<()> {
        for plugin_config in &mut to.iter_mut() {
            if plugin_config.name == self.name {
                if let Some(verbosity) = self.verbosity {
                    plugin_config.nonfunctional.verbosity = verbosity;
                }
                plugin_config
                    .nonfunctional
                    .tee_files
                    .extend(self.tee_files.iter().cloned());
                if let Some(stdout_mode) = &self.stdout_mode {
                    plugin_config.nonfunctional.stdout_mode = stdout_mode.clone();
                }
                if let Some(stderr_mode) = &self.stderr_mode {
                    plugin_config.nonfunctional.stderr_mode = stderr_mode.clone();
                }
                if let Some(accept_timeout) = &self.accept_timeout {
                    plugin_config.nonfunctional.accept_timeout = *accept_timeout;
                }
                if let Some(shutdown_timeout) = &self.shutdown_timeout {
                    plugin_config.nonfunctional.shutdown_timeout = *shutdown_timeout;
                }
                return Ok(());
            }
        }
        inv_arg(format!(
            "There is no plugin named {}. The available plugins are {}.",
            self.name,
            friendly_enumerate(to.iter().map(|x| &x.name[..]), Some("or"))
        ))
    }
}

/// The contents of a reproduction file.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Reproduction {
    /// The random seed for the simulation.
    pub seed: u64,

    /// The plugin configurations, from front to back.
    pub plugins: Vec<PluginReproduction>,

    /// The sequence of host calls to make.
    ///
    /// Note that `wait()` is not represented in the `HostCall` enumeration.
    /// `wait()` calls should instead be inserted automatically as late as
    /// possible, that is:
    ///
    ///  - when `HostCall::Start` is encountered while the accelerator was
    ///    already started;
    ///  - before DQCsim terminates, if the accelerator is still running.
    pub host_calls: Vec<HostCall>,

    /// The host on which the run was performed, if available.
    ///
    /// This parameter is not used by DQCsim when it runs in reproduction mode.
    /// It is only there for helping the user trace the reproduction file back
    /// to its source.
    pub hostname: String,

    /// The user that performed the run.
    ///
    /// This parameter is not used by DQCsim when it runs in reproduction mode.
    /// It is only there for helping the user trace the reproduction file back
    /// to its source.
    pub username: String,

    /// The working directory that the run was performed in.
    ///
    /// This parameter is not used by DQCsim when it runs in reproduction mode.
    /// It is only there for helping the user trace the reproduction file back
    /// to its source.
    pub workdir: PathBuf,
}

impl Reproduction {
    /// Constructs a reproduction structure for tracking a simulation.
    pub fn new_logger(config: &SimulatorConfiguration) -> Result<Reproduction> {
        Ok(Reproduction {
            seed: config.seed.value,
            host_calls: vec![],
            plugins: config
                .plugins
                .iter()
                .map(|x| x.get_reproduction(config.reproduction_path_style.ok_or_else(oe_inv_arg("cannot create reproduction logger for simulator configuration with reproduction explicitly disabled"))?))
                .collect::<Result<Vec<PluginReproduction>>>()?,
            hostname: whoami::hostname(),
            username: whoami::username(),
            workdir: std::env::current_dir()?,
        })
    }

    /// Records a host call to the reproduction log.
    pub fn record(&mut self, host_call: HostCall) {
        self.host_calls.push(host_call);
    }

    /// Turns this reproduction structure into a configuration and a list of
    /// host calls for reproduction.
    ///
    /// If exact is set, the random seed is taken from the reproduction
    /// structure. Otherwise, it is regenerated.
    pub fn to_run(
        &self,
        config: &mut SimulatorConfiguration,
        modifications: impl IntoIterator<Item = PluginModification>,
        exact: bool,
    ) -> Result<Vec<HostCall>> {
        // If this is an exact reproduction, set the seed.
        if exact {
            config.seed.value = self.seed;
        }

        // Construct the plugin configurations. The nonfunctional config is set
        // to the default value. Note that we pretend that every plugin is an
        // operator here; we change this later.
        let mut plugins = self
            .plugins
            .iter()
            .map(|x| PluginProcessConfiguration {
                name: x.name.clone(),
                specification: PluginProcessSpecification::new(
                    &x.executable,
                    x.script.clone(),
                    PluginType::Operator,
                ),
                functional: x.functional.clone(),
                nonfunctional: PluginProcessNonfunctionalConfiguration::default(),
            })
            .collect::<Vec<PluginProcessConfiguration>>();

        // Set the plugin types and make sure we have at least a frontend
        // and a backend.
        let plugin_count = plugins.len();
        if plugin_count < 2 {
            err("reproduction file corrupted: less than two plugins specified")?;
        }
        plugins[0].specification.typ = PluginType::Frontend;
        plugins[plugin_count - 1].specification.typ = PluginType::Backend;

        // Update the plugin nonfunctional configurations using the
        // modification list.
        for m in modifications {
            m.apply(&mut plugins)?;
        }

        // Wrap the PluginProcessConfigurations in a box for the simulator
        // configuration structure.
        config.plugins = plugins
            .into_iter()
            .map(|plugin| Box::new(plugin) as Box<dyn PluginConfiguration>)
            .collect();

        Ok(self.host_calls.clone())
    }

    /// Constructs a reproduction structure from a file.
    pub fn from_file(file: impl AsRef<Path>) -> Result<Reproduction> {
        Ok(serde_yaml::from_reader(&mut std::fs::File::open(
            file.as_ref(),
        )?)?)
    }

    /// Writes a reproduction structure to a file.
    pub fn to_file(&self, file: impl AsRef<Path>) -> Result<()> {
        serde_yaml::to_writer(&mut std::fs::File::create(file.as_ref())?, self)?;
        Ok(())
    }
}