1use std::{borrow::Cow, fmt::Write, time::Duration};
2
3use crate::{
4 data::DatastoreModifier, executor::DataExecutor, runner::ExecutionRuntimeCtx,
5 user::AsyncUserBuilder,
6};
7
8#[derive(Debug, Clone, Copy)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11pub struct Rate(
12 pub usize,
14 pub Duration,
16);
17
18impl From<Rate> for (usize, Duration) {
19 fn from(value: Rate) -> Self {
20 (value.0, value.1)
21 }
22}
23
24impl std::fmt::Display for Rate {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.write_fmt(format_args!("{}", self.0))?;
27 f.write_char('/')?;
28 f.write_fmt(format_args!("{:?}", self.1))?;
29 Ok(())
30 }
31}
32
33#[derive(Debug, Clone)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36#[cfg_attr(feature = "serde", serde(rename_all_fields = "camelCase"))]
37#[cfg_attr(feature = "serde", serde(tag = "type"))]
38pub enum Executor {
39 Once,
41 Constant {
43 users: usize,
45 duration: Duration,
47 },
48 Shared {
52 users: usize,
54 iterations: usize,
56 duration: Duration,
58 },
59 PerUser {
62 users: usize,
64 iterations: usize,
66 },
67 ConstantArrivalRate {
73 pre_allocate_users: usize,
75 rate: Rate,
77 max_users: usize,
79 duration: Duration,
81 },
82 RampingUser {
85 pre_allocate_users: usize,
87 stages: Vec<(usize, Duration)>,
89 },
90 RampingArrivalRate {
95 pre_allocate_users: usize,
97 max_users: usize,
99 stages: Vec<(Rate, Duration)>,
101 },
102}
103
104impl std::fmt::Display for Executor {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 match self {
107 Executor::Once => f.write_str("Once"),
108 Executor::Constant { users, duration } => {
109 write!(f, "Constant ({} users) {:?}", users, duration)
110 }
111 Executor::Shared {
112 users, iterations, ..
113 } => write!(f, "Shared ({} users) {}", users, iterations),
114 Executor::PerUser { users, iterations } => {
115 write!(f, "PerUser ({} users) {}", users, iterations)
116 }
117 Executor::ConstantArrivalRate { rate, duration, .. } => {
118 write!(f, "ConstantArrivalRate {} for {:?}", rate, duration)
119 }
120 Executor::RampingUser { stages, .. } => {
121 write!(f, "RampingUser ({} stages)", stages.len())
122 }
123 Executor::RampingArrivalRate { stages, .. } => {
124 write!(f, "RampingArrivalRate ({}, stages)", stages.len())
125 }
126 }
127 }
128}
129
130#[async_trait::async_trait]
131pub(crate) trait ExecutionProvider {
132 fn config(&self) -> &Executor;
133 async fn execution<'a>(
134 &'a self,
135 ctx: &'a mut ExecutionRuntimeCtx,
136 ) -> Box<dyn crate::executor::Executor + 'a>;
137}
138
139pub struct Scenario<'env> {
144 pub(crate) label: Cow<'static, str>,
145 pub(crate) execution_provider: Vec<Box<dyn ExecutionProvider + 'env>>,
146}
147
148impl<'env> Scenario<'env> {
149 pub fn new<Ub>(label: impl Into<Cow<'static, str>>, execution: Execution<'env, Ub>) -> Self
151 where
152 Ub: for<'a> AsyncUserBuilder<'a> + 'env,
153 {
154 Self {
155 label: label.into(),
156 execution_provider: vec![Box::new(execution)],
157 }
158 }
159
160 pub fn with_executor<Ub>(mut self, execution: Execution<'env, Ub>) -> Self
162 where
163 Ub: for<'a> AsyncUserBuilder<'a> + 'env,
164 {
165 self.execution_provider.push(Box::new(execution));
166 self
167 }
168}
169
170pub struct Execution<'env, Ub> {
174 user_builder: Ub,
175 datastore_modifiers: Vec<Box<dyn DatastoreModifier + 'env>>,
176 executor: Executor,
177}
178
179impl<'env, Ub> Execution<'env, Ub> {
180 pub fn new(user_builder: Ub, executor: Executor) -> Self {
182 Self {
183 user_builder,
184 datastore_modifiers: vec![],
185 executor,
186 }
187 }
188}
189
190impl Execution<'static, ()> {
191 pub fn builder() -> Execution<'static, ()> {
193 Self {
194 user_builder: (),
195 datastore_modifiers: Vec::new(),
196 executor: Executor::Once,
197 }
198 }
199
200 pub fn with_user_builder<'env, F>(self, user_builder: F) -> Execution<'env, F>
202 where
203 F: for<'a> AsyncUserBuilder<'a> + 'env,
204 {
205 Execution::<'env, _> {
206 user_builder,
207 executor: self.executor,
208 datastore_modifiers: self.datastore_modifiers,
209 }
210 }
211}
212
213impl<'env, Ub> Execution<'env, Ub>
214where
215 Ub: for<'a> AsyncUserBuilder<'a> + 'env,
216{
217 pub fn with_data<T: DatastoreModifier + 'env>(mut self, f: T) -> Self {
219 self.datastore_modifiers
220 .push(Box::new(f) as Box<dyn DatastoreModifier + 'env>);
221 self
222 }
223
224 pub fn with_executor(mut self, executor: Executor) -> Self {
226 self.executor = executor;
227 self
228 }
229
230 pub fn to_scenario(self, label: impl Into<Cow<'static, str>>) -> Scenario<'env> {
232 Scenario::new(label, self)
233 }
234}
235
236#[async_trait::async_trait]
237impl<'env, Ub> ExecutionProvider for Execution<'env, Ub>
238where
239 Ub: for<'a> AsyncUserBuilder<'a>,
240{
241 fn config(&self) -> &Executor {
242 &self.executor
243 }
244
245 async fn execution<'a>(
246 &'a self,
247 ctx: &'a mut ExecutionRuntimeCtx,
248 ) -> Box<dyn crate::executor::Executor + 'a> {
249 for modifiers in self.datastore_modifiers.iter() {
250 ctx.modify(&**modifiers).await;
251 }
252 let user_builder = &self.user_builder;
253 let executor = self.executor.clone();
254 Box::new(
255 DataExecutor::<Ub>::new(ctx.datastore_mut(), user_builder, executor)
256 .await
257 .unwrap(),
258 ) as Box<dyn crate::executor::Executor + '_>
259 }
260}