mirror of
https://github.com/apple/foundationdb.git
synced 2026-01-25 04:18:18 +00:00
Rust external workload modifications (#11805)
* Rust external workload modifications - add readme - add simulation configuration file - minor Rust bindings changes - fix FDBPerfMetric::format_code default value in the C++ bindings - Add CWorkload.c to CMake - Fix cpp_workload test file --------- Signed-off-by: Eloi DEMOLIS <eloi.demolis@clever-cloud.com>
This commit is contained in:
@@ -382,21 +382,30 @@ if(NOT WIN32)
|
||||
|
||||
endif()
|
||||
|
||||
set(c_workloads_srcs
|
||||
set(cpp_workloads_srcs
|
||||
test/workloads/workloads.cpp
|
||||
test/workloads/workloads.h
|
||||
test/workloads/SimpleWorkload.cpp)
|
||||
|
||||
set(c_workloads_srcs
|
||||
test/workloads/CWorkload.c)
|
||||
|
||||
if(OPEN_FOR_IDE)
|
||||
add_library(cpp_workloads OBJECT ${cpp_workloads_srcs})
|
||||
add_library(c_workloads OBJECT ${c_workloads_srcs})
|
||||
else()
|
||||
add_library(cpp_workloads SHARED ${cpp_workloads_srcs})
|
||||
add_library(c_workloads SHARED ${c_workloads_srcs})
|
||||
endif()
|
||||
set_target_properties(cpp_workloads PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/share/foundationdb")
|
||||
set_target_properties(c_workloads PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/share/foundationdb")
|
||||
target_link_libraries(c_workloads PUBLIC fdb_c)
|
||||
target_link_libraries(cpp_workloads PUBLIC fdb_c)
|
||||
target_include_directories(c_workloads PUBLIC ${CMAKE_SOURCE_DIR}/bindings/c)
|
||||
|
||||
if(NOT WIN32 AND NOT APPLE AND NOT OPEN_FOR_IDE)
|
||||
target_link_options(cpp_workloads PRIVATE "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/external_workload.map,-z,nodelete")
|
||||
target_link_options(c_workloads PRIVATE "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/external_workload.map,-z,nodelete")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
global:
|
||||
workloadFactory;
|
||||
workloadCFactory;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ struct FDBPerfMetric {
|
||||
std::string name;
|
||||
double value;
|
||||
bool averaged;
|
||||
std::string format_code = "0.3g";
|
||||
std::string format_code = "%.3g";
|
||||
};
|
||||
|
||||
class DLLEXPORT FDBWorkload {
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef struct CWorkload {
|
||||
|
||||
#define BIND(W) CWorkload* this = (CWorkload*)W
|
||||
#define WITH(C, M, ...) (C).M((C).inner, ##__VA_ARGS__)
|
||||
#define EXPORT extern __attribute__((visibility("default")))
|
||||
|
||||
static void workload_setup(OpaqueWorkload* raw_workload, FDBDatabase* db, FDBPromise done) {
|
||||
BIND(raw_workload);
|
||||
@@ -83,7 +84,7 @@ static void workload_free(OpaqueWorkload* raw_workload) {
|
||||
free(this);
|
||||
}
|
||||
|
||||
extern FDBWorkload workloadCFactory(const char* borrow_name, FDBWorkloadContext context) {
|
||||
EXPORT FDBWorkload workloadCFactory(const char* borrow_name, FDBWorkloadContext context) {
|
||||
int len = strlen(borrow_name) + 1;
|
||||
char* name = (char*)malloc(len);
|
||||
memcpy(name, borrow_name, len);
|
||||
|
||||
31
bindings/c/test/workloads/RustWorkload/README.md
Normal file
31
bindings/c/test/workloads/RustWorkload/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# RustWorkload
|
||||
|
||||
This repository showcases an example workload written in Rust, designed to
|
||||
interact with the FoundationDB simulation environment via the C API. The
|
||||
workload provided here is not integrated into the CMake build system and serves
|
||||
purely as a reference implementation.
|
||||
|
||||
## Building the Workload
|
||||
|
||||
You can build this crate in any standard Rust environment using the following command:
|
||||
|
||||
```shell
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
This will generate a shared library that can be used as an `ExternalWorkload`
|
||||
within the FoundationDB simulation. To inject this library into the simulation,
|
||||
use the command:
|
||||
|
||||
```shell
|
||||
fdbserver -r simulation -f test_file.toml
|
||||
```
|
||||
|
||||
Its behavior should be equivalent to the `CWorkload.c` test workload.
|
||||
|
||||
## Limitations and Further Examples
|
||||
|
||||
This example workload does not include functionality for interacting with the
|
||||
database. For examples with working database bindings, refer to the
|
||||
[foundationdb-rs](https://github.com/foundationdb-rs/foundationdb-rs/tree/main/foundationdb-simulation)
|
||||
project.
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{ffi, str::FromStr};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::str::FromStr;
|
||||
|
||||
mod raw_bindings {
|
||||
#![allow(non_camel_case_types)]
|
||||
@@ -21,33 +22,37 @@ use raw_bindings::{
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn str_from_c(c_buf: *const i8) -> String {
|
||||
let c_str = unsafe { ffi::CStr::from_ptr(c_buf) };
|
||||
let c_str = unsafe { CStr::from_ptr(c_buf) };
|
||||
c_str.to_str().unwrap().to_string()
|
||||
}
|
||||
pub fn str_for_c<T>(buf: T) -> ffi::CString
|
||||
pub fn str_for_c<T>(buf: T) -> CString
|
||||
where
|
||||
T: Into<Vec<u8>>,
|
||||
{
|
||||
ffi::CString::new(buf).unwrap()
|
||||
CString::new(buf).unwrap()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rust Types
|
||||
|
||||
/// Wrapper around a FoundationDB simulation context
|
||||
pub struct WorkloadContext(FDBWorkloadContext);
|
||||
/// Wrapper around a FoundationDB promise
|
||||
pub struct Promise(FDBPromise);
|
||||
/// Wrapper around a FoundationDB metric sink
|
||||
pub struct Metrics(FDBMetrics);
|
||||
|
||||
/// A single metric entry
|
||||
pub struct Metric {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Metric<'a> {
|
||||
/// The name of the metric
|
||||
pub key: String,
|
||||
pub key: &'a str,
|
||||
/// The value of the metric
|
||||
pub val: f64,
|
||||
/// Indicates if the value represents an average or not
|
||||
/// Specifies whether the metric value should be aggregated as a sum or an average
|
||||
pub avg: bool,
|
||||
/// C++ string formatter of the metric
|
||||
pub fmt: Option<String>,
|
||||
pub fmt: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// Indicates the severity of a FoundationDB log entry
|
||||
@@ -70,10 +75,9 @@ pub enum Severity {
|
||||
// Implementations
|
||||
|
||||
impl WorkloadContext {
|
||||
pub(crate) fn new(raw: FDBWorkloadContext) -> Self {
|
||||
pub fn new(raw: FDBWorkloadContext) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
/// Add a log entry in the FoundationDB logs
|
||||
pub fn trace<S>(&self, severity: Severity, name: S, details: &[(&str, &str)])
|
||||
where
|
||||
@@ -113,7 +117,7 @@ impl WorkloadContext {
|
||||
pub fn set_process_id(&self, id: u64) {
|
||||
unsafe { self.0.setProcessID.unwrap_unchecked()(self.0.inner, id) }
|
||||
}
|
||||
/// Get the current time
|
||||
/// Get the current simulated time in seconds (starts at zero)
|
||||
pub fn now(&self) -> f64 {
|
||||
unsafe { self.0.now.unwrap_unchecked()(self.0.inner) }
|
||||
}
|
||||
@@ -150,7 +154,7 @@ impl WorkloadContext {
|
||||
pub fn client_id(&self) -> i32 {
|
||||
unsafe { self.0.clientId.unwrap_unchecked()(self.0.inner) }
|
||||
}
|
||||
/// Get the client id of the workload
|
||||
/// Get the number of clients of the workload
|
||||
pub fn client_count(&self) -> i32 {
|
||||
unsafe { self.0.clientCount.unwrap_unchecked()(self.0.inner) }
|
||||
}
|
||||
@@ -182,9 +186,11 @@ impl Metrics {
|
||||
pub(crate) fn new(raw: FDBMetrics) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
/// Call `reserve` on the underlying C++ vector of metrics
|
||||
pub fn reserve(&mut self, n: usize) {
|
||||
unsafe { self.0.reserve.unwrap_unchecked()(self.0.inner, n as i32) }
|
||||
}
|
||||
/// Add a single metric to the sink
|
||||
pub fn push(&mut self, metric: Metric) {
|
||||
let key_storage = str_for_c(metric.key);
|
||||
let fmt_storage = str_for_c(metric.fmt.as_deref().unwrap_or("%.3g"));
|
||||
@@ -200,30 +206,35 @@ impl Metrics {
|
||||
)
|
||||
}
|
||||
}
|
||||
/// Add multiple metrics, ensuring the sink reallocates at most once
|
||||
pub fn extend(&mut self, metrics: &[Metric]) {
|
||||
self.reserve(metrics.len());
|
||||
for metric in metrics {
|
||||
self.push(*metric);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Metric {
|
||||
/// Create a metric value entry
|
||||
pub fn val<S, V>(key: S, val: V) -> Self
|
||||
impl<'a> Metric<'a> {
|
||||
/// Create a summed metric entry
|
||||
pub fn val<V>(key: &'a str, val: V) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
f64: From<V>,
|
||||
V: TryInto<f64>,
|
||||
{
|
||||
Self {
|
||||
key: key.into(),
|
||||
val: val.into(),
|
||||
key,
|
||||
val: val.try_into().ok().expect("convertion failed"),
|
||||
avg: false,
|
||||
fmt: None,
|
||||
}
|
||||
}
|
||||
/// Create a metric average entry
|
||||
pub fn avg<S, V>(key: S, val: V) -> Self
|
||||
/// Create an averaged metric entry
|
||||
pub fn avg<V>(key: &'a str, val: V) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
V: TryInto<f64>,
|
||||
{
|
||||
Self {
|
||||
key: key.into(),
|
||||
key,
|
||||
val: val.try_into().ok().expect("convertion failed"),
|
||||
avg: true,
|
||||
fmt: None,
|
||||
|
||||
@@ -4,13 +4,14 @@ mod bindings;
|
||||
mod mock;
|
||||
|
||||
use bindings::{
|
||||
str_from_c, FDBMetrics, FDBPromise, FDBWorkloadContext, OpaqueWorkload,
|
||||
str_from_c, FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, FDBWorkloadContext, Metric,
|
||||
Metrics, OpaqueWorkload, Promise, Severity, WorkloadContext,
|
||||
};
|
||||
pub use bindings::{FDBDatabase, FDBWorkload, Metric, Metrics, Promise, Severity, WorkloadContext};
|
||||
|
||||
// Should be replaced by a Rust wrapper over the FDBDatabase bindings, like the one provided by
|
||||
// foundationdb-rs
|
||||
/// Should be replaced by a Rust wrapper over the `FDBDatabase` bindings, like the one provided by foundationdb-rs
|
||||
pub type MockDatabase = NonNull<FDBDatabase>;
|
||||
/// FFI-safe wrapprer around a specific `RustWorkload` implementation
|
||||
pub type WrappedWorkload = FDBWorkload;
|
||||
|
||||
/// Equivalent to the C++ abstract class `FDBWorkload`
|
||||
pub trait RustWorkload {
|
||||
@@ -48,7 +49,7 @@ pub trait RustWorkload {
|
||||
/// * `out` - A metric sink
|
||||
fn get_metrics(&self, out: Metrics);
|
||||
|
||||
/// Set the check timeout for this workload.
|
||||
/// Set the check timeout in simulated seconds for this workload.
|
||||
fn get_check_timeout(&self) -> f64;
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ pub trait RustWorkloadFactory {
|
||||
/// If the test file contains a key-value pair workloadName the value will be passed to this method (empty string otherwise).
|
||||
/// This way, a library author can implement many workloads in one library and use the test file to chose which one to run
|
||||
/// (or run multiple workloads either concurrently or serially).
|
||||
fn create(name: String, context: WorkloadContext) -> FDBWorkload;
|
||||
fn create(name: String, context: WorkloadContext) -> WrappedWorkload;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn workload_setup<W: RustWorkload + 'static>(
|
||||
@@ -108,19 +109,23 @@ unsafe extern "C" fn workload_drop<W: RustWorkload>(raw_workload: *mut OpaqueWor
|
||||
unsafe { drop(Box::from_raw(raw_workload as *mut W)) };
|
||||
}
|
||||
|
||||
pub fn wrap<W: RustWorkload + 'static>(workload: W) -> FDBWorkload {
|
||||
let workload = Box::into_raw(Box::new(workload));
|
||||
FDBWorkload {
|
||||
inner: workload as *mut _,
|
||||
setup: Some(workload_setup::<W>),
|
||||
start: Some(workload_start::<W>),
|
||||
check: Some(workload_check::<W>),
|
||||
getMetrics: Some(workload_get_metrics::<W>),
|
||||
getCheckTimeout: Some(workload_get_check_timeout::<W>),
|
||||
free: Some(workload_drop::<W>),
|
||||
impl WrappedWorkload {
|
||||
pub fn new<W: RustWorkload + 'static>(workload: W) -> Self {
|
||||
let workload = Box::into_raw(Box::new(workload));
|
||||
WrappedWorkload {
|
||||
inner: workload as *mut _,
|
||||
setup: Some(workload_setup::<W>),
|
||||
start: Some(workload_start::<W>),
|
||||
check: Some(workload_check::<W>),
|
||||
getMetrics: Some(workload_get_metrics::<W>),
|
||||
getCheckTimeout: Some(workload_get_check_timeout::<W>),
|
||||
free: Some(workload_drop::<W>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a `RustWorkloadFactory` in the FoundationDB simulation.
|
||||
/// This macro must be invoked exactly once in a program.
|
||||
#[macro_export]
|
||||
macro_rules! register_factory {
|
||||
($name:ident) => {
|
||||
@@ -128,7 +133,7 @@ macro_rules! register_factory {
|
||||
extern "C" fn workloadCFactory(
|
||||
raw_name: *const i8,
|
||||
raw_context: $crate::FDBWorkloadContext,
|
||||
) -> $crate::FDBWorkload {
|
||||
) -> $crate::WrappedWorkload {
|
||||
let name = $crate::str_from_c(raw_name);
|
||||
let context = $crate::WorkloadContext::new(raw_context);
|
||||
<$name as $crate::RustWorkloadFactory>::create(name, context)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
register_factory, wrap, FDBWorkload, Metric, Metrics, MockDatabase, Promise, RustWorkload,
|
||||
RustWorkloadFactory, Severity, WorkloadContext,
|
||||
register_factory, Metric, Metrics, MockDatabase, Promise, RustWorkload, RustWorkloadFactory,
|
||||
Severity, WorkloadContext, WrappedWorkload,
|
||||
};
|
||||
|
||||
struct MockWorkload {
|
||||
@@ -64,20 +64,20 @@ impl Drop for MockWorkload {
|
||||
|
||||
struct MockFactory;
|
||||
impl RustWorkloadFactory for MockFactory {
|
||||
fn create(name: String, context: WorkloadContext) -> FDBWorkload {
|
||||
fn create(name: String, context: WorkloadContext) -> WrappedWorkload {
|
||||
let client_id = context.client_id();
|
||||
let client_count = context.client_count();
|
||||
println!("RustWorkloadFactory::create({name})[{client_id}/{client_count}]");
|
||||
println!(
|
||||
"my_c_option: {:?}",
|
||||
context.get_option::<String>("my_c_option")
|
||||
context.get_option::<String>("my_rust_option")
|
||||
);
|
||||
println!(
|
||||
"my_c_option: {:?}",
|
||||
context.get_option::<String>("my_c_option")
|
||||
context.get_option::<String>("my_rust_option")
|
||||
);
|
||||
match name.as_str() {
|
||||
"MockWorkload" => wrap(MockWorkload::new(name, client_id, context)),
|
||||
"MockWorkload" => WrappedWorkload::new(MockWorkload::new(name, client_id, context)),
|
||||
_ => panic!("Unknown workload name: {name}"),
|
||||
}
|
||||
}
|
||||
|
||||
11
bindings/c/test/workloads/RustWorkload/test_file.toml
Normal file
11
bindings/c/test/workloads/RustWorkload/test_file.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[[test]]
|
||||
testTitle = 'Rust_CAPI_Test'
|
||||
|
||||
[[test.workload]]
|
||||
testName = 'External'
|
||||
useCAPI = true
|
||||
libraryPath = './target/release'
|
||||
libraryName = 'rust_workload'
|
||||
workloadName = 'MockWorkload'
|
||||
my_rust_option = 'my_value'
|
||||
|
||||
@@ -27,11 +27,11 @@ public class PerfMetric {
|
||||
private String formatCode;
|
||||
|
||||
public PerfMetric(String name, double value) {
|
||||
this(name, value, true, "0.3g");
|
||||
this(name, value, true, "%.3g");
|
||||
}
|
||||
|
||||
public PerfMetric(String name, double value, boolean averaged) {
|
||||
this(name, value, averaged, "0.3g");
|
||||
this(name, value, averaged, "%.3g");
|
||||
}
|
||||
|
||||
public PerfMetric(String name, double value, boolean averaged, String formatCode) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
testTitle=SimpleExternalTest
|
||||
testName=External
|
||||
libraryName=c_workloads
|
||||
libraryName=cpp_workloads
|
||||
workloadName=SimpleWorkload
|
||||
|
||||
Reference in New Issue
Block a user