foundationdb_simulation/
bindings.rs

1//! Wrapper module
2//!
3//! This module defines all C and Rust structures.
4//! It also provides bindings and wrappers to map behavior from Rust to C.
5
6use std::{ffi, str::FromStr};
7
8mod raw_bindings {
9    #![allow(non_camel_case_types)]
10    #![allow(non_upper_case_globals)]
11    #![allow(non_snake_case)]
12    #![allow(dead_code)]
13    #![allow(missing_docs)]
14    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
15}
16pub use raw_bindings::{
17    FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, FDBWorkloadContext, OpaqueWorkload,
18};
19use raw_bindings::{
20    FDBMetric, FDBSeverity, FDBSeverity_FDBSeverity_Debug, FDBSeverity_FDBSeverity_Error,
21    FDBSeverity_FDBSeverity_Info, FDBSeverity_FDBSeverity_Warn, FDBSeverity_FDBSeverity_WarnAlways,
22    FDBStringPair,
23};
24
25// -----------------------------------------------------------------------------
26// String conversions
27
28#[doc(hidden)]
29#[allow(clippy::not_unsafe_ptr_arg_deref)]
30pub fn str_from_c(c_buf: *const i8) -> String {
31    let c_str = unsafe { ffi::CStr::from_ptr(c_buf) };
32    c_str.to_str().unwrap().to_string()
33}
34#[doc(hidden)]
35pub fn str_for_c<T>(buf: T) -> ffi::CString
36where
37    T: Into<Vec<u8>>,
38{
39    ffi::CString::new(buf).unwrap()
40}
41
42/// Macro that can be used to create log "details" more easily.
43#[macro_export]
44macro_rules! details {
45    ($($k:expr => $v:expr),* $(,)?) => {
46        &[
47            $((
48                &$k.to_string(), &$v.to_string()
49            )),*
50        ]
51    };
52}
53
54// -----------------------------------------------------------------------------
55// Rust Types
56
57/// Wrapper around the C FDBWorkloadContext
58pub struct WorkloadContext(FDBWorkloadContext);
59/// Wrapper around the C FDBPromise
60pub struct Promise(FDBPromise);
61/// Wrapper around the C FDBMetrics
62pub struct Metrics(FDBMetrics);
63
64/// A single metric entry
65#[derive(Clone)]
66pub struct Metric<'a> {
67    /// The name of the metric
68    pub key: &'a str,
69    /// The value of the metric
70    pub val: f64,
71    /// Indicates if the value represents an average or not
72    pub avg: bool,
73    /// C++ string formatter of the metric
74    pub fmt: Option<&'a str>,
75}
76
77/// Indicates the severity of a FoundationDB log entry
78#[derive(Clone, Copy)]
79#[repr(u32)]
80pub enum Severity {
81    /// debug
82    Debug = FDBSeverity_FDBSeverity_Debug,
83    /// info
84    Info = FDBSeverity_FDBSeverity_Info,
85    /// warn
86    Warn = FDBSeverity_FDBSeverity_Warn,
87    /// warn always
88    WarnAlways = FDBSeverity_FDBSeverity_WarnAlways,
89    /// error, this severity automatically breaks execution
90    Error = FDBSeverity_FDBSeverity_Error,
91}
92
93// -----------------------------------------------------------------------------
94// Implementations
95
96impl WorkloadContext {
97    #[doc(hidden)]
98    pub fn new(raw: FDBWorkloadContext) -> Self {
99        Self(raw)
100    }
101
102    /// Add a log entry in the FoundationDB logs
103    pub fn trace<S, S2, S3>(&self, severity: Severity, name: S, details: &[(S2, S3)])
104    where
105        S: Into<Vec<u8>>,
106        S2: AsRef<str>,
107        S3: AsRef<str>,
108    {
109        let name = str_for_c(name);
110        let details_storage = details
111            .iter()
112            .map(|(key, val)| {
113                let key = str_for_c(key.as_ref());
114                let val = str_for_c(val.as_ref());
115                (key, val)
116            })
117            .collect::<Vec<_>>();
118        let details = details_storage
119            .iter()
120            .map(|(key, val)| FDBStringPair {
121                key: key.as_ptr(),
122                val: val.as_ptr(),
123            })
124            .collect::<Vec<_>>();
125        unsafe {
126            self.0.trace.unwrap_unchecked()(
127                self.0.inner,
128                severity as FDBSeverity,
129                name.as_ptr(),
130                details.as_ptr(),
131                details.len() as i32,
132            )
133        }
134    }
135    /// Get the process id of the workload
136    pub fn get_process_id(&self) -> u64 {
137        unsafe { self.0.getProcessID.unwrap_unchecked()(self.0.inner) }
138    }
139    /// Set the process id of the workload
140    pub fn set_process_id(&self, id: u64) {
141        unsafe { self.0.setProcessID.unwrap_unchecked()(self.0.inner, id) }
142    }
143    /// Get the current simulated time in seconds (starts at zero)
144    pub fn now(&self) -> f64 {
145        unsafe { self.0.now.unwrap_unchecked()(self.0.inner) }
146    }
147    /// Get a determinist 32-bit random number
148    pub fn rnd(&self) -> u32 {
149        unsafe { self.0.rnd.unwrap_unchecked()(self.0.inner) }
150    }
151    /// Get the value of a parameter from the simulation config file
152    ///
153    /// /!\ getting an option consumes it, following call on that option will return `None`
154    pub fn get_option<T>(&self, name: &str) -> Option<T>
155    where
156        T: FromStr,
157    {
158        self.get_option_raw(name)
159            .and_then(|value| value.parse::<T>().ok())
160    }
161    fn get_option_raw(&self, name: &str) -> Option<String> {
162        let null = "";
163        let name = str_for_c(name);
164        let default_value = str_for_c(null);
165        let raw_value = unsafe {
166            self.0.getOption.unwrap_unchecked()(self.0.inner, name.as_ptr(), default_value.as_ptr())
167        };
168        let value = str_from_c(raw_value.inner);
169        unsafe { raw_value.free.unwrap_unchecked()(raw_value.inner) };
170        if value == null {
171            None
172        } else {
173            Some(value)
174        }
175    }
176    /// Get the client id of the workload
177    pub fn client_id(&self) -> i32 {
178        unsafe { self.0.clientId.unwrap_unchecked()(self.0.inner) }
179    }
180    /// Get the client id of the workload
181    pub fn client_count(&self) -> i32 {
182        unsafe { self.0.clientCount.unwrap_unchecked()(self.0.inner) }
183    }
184    /// Get a determinist 64-bit random number
185    pub fn shared_random_number(&self) -> i64 {
186        unsafe { self.0.sharedRandomNumber.unwrap_unchecked()(self.0.inner) }
187    }
188}
189
190impl Promise {
191    pub(crate) fn new(raw: FDBPromise) -> Self {
192        Self(raw)
193    }
194    /// Resolve a FoundationDB promise by setting its value to a boolean.
195    /// You can resolve a Promise only once.
196    ///
197    /// note: FoundationDB disregards the value sent, so sending `true` or `false` is equivalent
198    pub fn send(self, value: bool) {
199        unsafe { self.0.send.unwrap_unchecked()(self.0.inner, value) };
200    }
201}
202impl Drop for Promise {
203    fn drop(&mut self) {
204        unsafe { self.0.free.unwrap_unchecked()(self.0.inner) };
205    }
206}
207
208impl Metrics {
209    pub(crate) fn new(raw: FDBMetrics) -> Self {
210        Self(raw)
211    }
212    /// Call std::vector::reserve on the underlying C++ sink
213    pub fn reserve(&mut self, n: usize) {
214        unsafe { self.0.reserve.unwrap_unchecked()(self.0.inner, n as i32) }
215    }
216    /// Push a [Metric] entry in the underlying C++ sink
217    pub fn push(&mut self, metric: Metric) {
218        let key_storage = str_for_c(metric.key);
219        let fmt_storage = str_for_c(metric.fmt.unwrap_or("%.3g"));
220        unsafe {
221            self.0.push.unwrap_unchecked()(
222                self.0.inner,
223                FDBMetric {
224                    key: key_storage.as_ptr(),
225                    fmt: fmt_storage.as_ptr(),
226                    val: metric.val,
227                    avg: metric.avg,
228                },
229            )
230        }
231    }
232    /// Push several [Metric] entries in the underlying C++ sink
233    pub fn extend<'a, T>(&mut self, metrics: T)
234    where
235        T: IntoIterator<Item = Metric<'a>>,
236    {
237        let metrics = metrics.into_iter();
238        let (min, max) = metrics.size_hint();
239        self.reserve(max.unwrap_or(min));
240        for metric in metrics {
241            self.push(metric);
242        }
243    }
244}
245
246impl<'a> Metric<'a> {
247    /// Create a metric value entry
248    pub fn val<V>(key: &'a str, val: V) -> Self
249    where
250        V: TryInto<f64>,
251    {
252        Self {
253            key,
254            val: val.try_into().ok().expect("convertion failed"),
255            avg: false,
256            fmt: None,
257        }
258    }
259    /// Create a metric average entry
260    pub fn avg<V>(key: &'a str, val: V) -> Self
261    where
262        V: TryInto<f64>,
263    {
264        Self {
265            key,
266            val: val.try_into().ok().expect("convertion failed"),
267            avg: true,
268            fmt: None,
269        }
270    }
271}