#[cfg(feature = "rt")]
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
#[cfg(feature = "rt")]
use serde_with::{serde_as, DurationSeconds};
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
mod goal_tps;
mod saturate;
use goal_tps::run_goal_tps;
use saturate::run_saturate;
pub const DEFAULT_SATURATE_ERROR_RATE: f64 = 0.03;
pub const DEFAULT_OVERLOAD_ERROR_RATE: f64 = 0.80;
pub(crate) type BoxedFut = Pin<Box<dyn Future<Output = ()> + Send>>;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "rt", cfg_eval::cfg_eval, serde_as)]
#[cfg_attr(feature = "rt", derive(Serialize, Deserialize))]
pub(crate) struct ScenarioConfig {
pub name: String,
#[cfg_attr(feature = "rt", serde_as(as = "DurationSeconds"))]
pub duration: Duration,
pub kind: ScenarioKind,
}
impl ScenarioConfig {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
duration: Default::default(),
kind: Default::default(),
}
}
}
#[derive(Default, Clone, Copy, Debug)]
#[cfg_attr(feature = "rt", derive(Serialize, Deserialize))]
pub(crate) enum ScenarioKind {
#[default]
Once,
Tps(u32),
Saturate(f64),
}
#[pin_project::pin_project]
pub struct Scenario {
fut: fn() -> BoxedFut,
runner_fut: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
config: ScenarioConfig,
}
impl Scenario {
#[doc(hidden)]
pub fn new(name: &str, fut: fn() -> BoxedFut) -> Self {
Self {
fut,
runner_fut: None,
config: ScenarioConfig::new(name),
}
}
pub fn saturate(mut self) -> Self {
self.config.kind = ScenarioKind::Saturate(DEFAULT_SATURATE_ERROR_RATE);
self
}
pub fn overload(mut self) -> Self {
self.config.kind = ScenarioKind::Saturate(DEFAULT_OVERLOAD_ERROR_RATE);
self
}
pub fn error_rate(mut self, error_rate: f64) -> Self {
self.config.kind = ScenarioKind::Saturate(error_rate);
self
}
pub fn tps(mut self, tps: u32) -> Self {
self.config.kind = ScenarioKind::Tps(tps);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.config.duration = duration;
self
}
#[cfg(feature = "rt")]
pub(crate) fn set_config(mut self, config: ScenarioConfig) -> Self {
self.config = config;
self
}
}
impl Future for Scenario {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.runner_fut.is_none() {
let fut = self.fut;
let config = self.config.clone();
self.runner_fut = Some(Box::pin(async move { run_scenario(fut, config).await }));
}
if let Some(runner) = &mut self.runner_fut {
runner.as_mut().poll(cx)
} else {
unreachable!()
}
}
}
async fn run_scenario(scenario: fn() -> BoxedFut, config: ScenarioConfig) {
match config.kind {
ScenarioKind::Once => scenario().await,
ScenarioKind::Tps(goal_tps) => run_goal_tps(scenario, config, goal_tps).await,
ScenarioKind::Saturate(error_rate) => run_saturate(scenario, config, error_rate).await,
}
}