Skip to main content

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