2024-03-09 07:09:15 +01:00
|
|
|
use crate::error::Error;
|
|
|
|
use crate::state::*;
|
|
|
|
use crate::stats::{BasicStatistics, TestPhase};
|
|
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2024-07-30 04:11:01 +02:00
|
|
|
use rand::{Rng, SeedableRng};
|
2024-03-09 07:09:15 +01:00
|
|
|
use rand_chacha::ChaCha8Rng;
|
|
|
|
|
|
|
|
use crossbeam::queue::{ArrayQueue, SegQueue};
|
|
|
|
|
|
|
|
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
|
|
|
|
2024-08-03 02:37:49 +02:00
|
|
|
use serde::Serialize;
|
2024-03-09 07:09:15 +01:00
|
|
|
use tokio::sync::broadcast;
|
|
|
|
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
|
|
|
|
async fn actor_person(
|
2024-07-30 04:11:01 +02:00
|
|
|
main_client: KanidmClient,
|
2024-03-09 07:09:15 +01:00
|
|
|
person: Person,
|
|
|
|
stats_queue: Arc<SegQueue<EventRecord>>,
|
|
|
|
mut actor_rx: broadcast::Receiver<Signal>,
|
2024-07-30 04:11:01 +02:00
|
|
|
rng_seed: u64,
|
|
|
|
additional_clients: Vec<KanidmClient>,
|
|
|
|
warmup_time: Duration,
|
2024-03-09 07:09:15 +01:00
|
|
|
) -> Result<(), Error> {
|
2024-07-30 04:11:01 +02:00
|
|
|
let mut model =
|
|
|
|
person
|
|
|
|
.model
|
|
|
|
.as_dyn_object(rng_seed, additional_clients, &person.username, warmup_time)?;
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
while let Err(broadcast::error::TryRecvError::Empty) = actor_rx.try_recv() {
|
2024-07-30 04:11:01 +02:00
|
|
|
let events = model.transition(&main_client, &person).await?;
|
2024-06-07 02:18:14 +02:00
|
|
|
debug!("Pushed event to queue!");
|
2024-07-30 04:11:01 +02:00
|
|
|
for event in events.into_iter() {
|
|
|
|
stats_queue.push(event);
|
|
|
|
}
|
2024-03-09 07:09:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
debug!("Stopped person {}", person.username);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-07-30 04:11:01 +02:00
|
|
|
#[derive(Debug)]
|
2024-03-09 07:09:15 +01:00
|
|
|
pub struct EventRecord {
|
|
|
|
pub start: Instant,
|
|
|
|
pub duration: Duration,
|
|
|
|
pub details: EventDetail,
|
|
|
|
}
|
|
|
|
|
2024-08-03 02:37:49 +02:00
|
|
|
#[derive(Debug, Serialize, Clone)]
|
2024-03-09 07:09:15 +01:00
|
|
|
pub enum EventDetail {
|
2024-04-17 01:35:16 +02:00
|
|
|
Login,
|
2024-03-09 07:09:15 +01:00
|
|
|
Logout,
|
2024-04-17 01:35:16 +02:00
|
|
|
PersonSetSelfMail,
|
2024-04-23 02:30:38 +02:00
|
|
|
PersonGetSelfAccount,
|
|
|
|
PersonGetSelfMemberOf,
|
2024-06-21 03:47:36 +02:00
|
|
|
PersonSetSelfPassword,
|
2024-04-17 01:35:16 +02:00
|
|
|
PersonReauth,
|
2024-07-30 04:11:01 +02:00
|
|
|
PersonCreateGroup,
|
|
|
|
PersonAddGroupMembers,
|
|
|
|
GroupReplicationDelay,
|
2024-03-09 07:09:15 +01:00
|
|
|
Error,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum Signal {
|
|
|
|
Stop,
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn execute_inner(
|
|
|
|
warmup: Duration,
|
|
|
|
test_time: Option<Duration>,
|
|
|
|
mut control_rx: broadcast::Receiver<Signal>,
|
|
|
|
stat_ctrl: Arc<ArrayQueue<TestPhase>>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Delay for warmup time.
|
|
|
|
tokio::select! {
|
|
|
|
_ = tokio::time::sleep(warmup) => {
|
|
|
|
// continue.
|
|
|
|
}
|
|
|
|
_ = control_rx.recv() => {
|
2024-04-17 01:35:16 +02:00
|
|
|
// Until we add other signal types, any event is
|
2024-03-09 07:09:15 +01:00
|
|
|
// either Ok(Signal::Stop) or Err(_), both of which indicate
|
|
|
|
// we need to stop immediately.
|
2024-04-17 01:35:16 +02:00
|
|
|
return Err(Error::Interrupt);
|
2024-03-09 07:09:15 +01:00
|
|
|
}
|
|
|
|
}
|
2024-04-17 01:35:16 +02:00
|
|
|
info!("warmup time passed, statistics will now be collected ...");
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
let start = Instant::now();
|
|
|
|
if let Err(crossbeam_err) = stat_ctrl.push(TestPhase::Start(start)) {
|
|
|
|
error!(
|
|
|
|
?crossbeam_err,
|
|
|
|
"Unable to signal statistics collector to start"
|
|
|
|
);
|
|
|
|
return Err(Error::Crossbeam);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(test_time) = test_time {
|
|
|
|
let sleep = tokio::time::sleep(test_time);
|
|
|
|
tokio::pin!(sleep);
|
|
|
|
let recv = (control_rx).recv();
|
|
|
|
tokio::pin!(recv);
|
|
|
|
|
|
|
|
// Wait for some condition (signal, or time).
|
|
|
|
tokio::select! {
|
|
|
|
_ = sleep => {
|
|
|
|
// continue.
|
|
|
|
}
|
|
|
|
_ = recv => {
|
2024-04-17 01:35:16 +02:00
|
|
|
// Until we add other signal types, any event is
|
2024-03-09 07:09:15 +01:00
|
|
|
// either Ok(Signal::Stop) or Err(_), both of which indicate
|
|
|
|
// we need to stop immediately.
|
2024-04-17 01:35:16 +02:00
|
|
|
debug!("Interrupt");
|
|
|
|
return Err(Error::Interrupt);
|
2024-03-09 07:09:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let _ = control_rx.recv().await;
|
|
|
|
}
|
|
|
|
|
|
|
|
let end = Instant::now();
|
|
|
|
if let Err(crossbeam_err) = stat_ctrl.push(TestPhase::End(end)) {
|
|
|
|
error!(
|
|
|
|
?crossbeam_err,
|
2024-04-17 01:35:16 +02:00
|
|
|
"Unable to signal statistics collector to end"
|
2024-03-09 07:09:15 +01:00
|
|
|
);
|
|
|
|
return Err(Error::Crossbeam);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn execute(state: State, control_rx: broadcast::Receiver<Signal>) -> Result<(), Error> {
|
|
|
|
// Create a statistics queue.
|
|
|
|
let stats_queue = Arc::new(SegQueue::new());
|
|
|
|
let stats_ctrl = Arc::new(ArrayQueue::new(4));
|
|
|
|
|
|
|
|
// Spawn the stats aggregator
|
|
|
|
let c_stats_queue = stats_queue.clone();
|
|
|
|
let c_stats_ctrl = stats_ctrl.clone();
|
|
|
|
|
2024-06-07 02:18:14 +02:00
|
|
|
let node_count = 1 + state.profile.extra_uris().len();
|
|
|
|
let mut dyn_data_collector =
|
|
|
|
BasicStatistics::new(state.persons.len(), state.groups.len(), node_count);
|
2024-03-09 07:09:15 +01:00
|
|
|
|
2024-08-03 02:37:49 +02:00
|
|
|
let dump_raw_data = state.profile.dump_raw_data();
|
|
|
|
|
|
|
|
let stats_task = tokio::task::spawn_blocking(move || {
|
|
|
|
dyn_data_collector.run(c_stats_queue, c_stats_ctrl, dump_raw_data)
|
|
|
|
});
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
// Create clients. Note, we actually seed these deterministically too, so that
|
|
|
|
// or persons are spread over the clients that exist, in a way that is also
|
|
|
|
// deterministic.
|
|
|
|
let mut seeded_rng = ChaCha8Rng::seed_from_u64(state.profile.seed());
|
|
|
|
|
|
|
|
let clients = std::iter::once(state.profile.control_uri().to_string())
|
|
|
|
.chain(state.profile.extra_uris().iter().cloned())
|
|
|
|
.map(|uri| {
|
|
|
|
KanidmClientBuilder::new()
|
|
|
|
.address(uri)
|
|
|
|
.danger_accept_invalid_hostnames(true)
|
|
|
|
.danger_accept_invalid_certs(true)
|
|
|
|
.build()
|
|
|
|
.map_err(|err| {
|
|
|
|
error!(?err, "Unable to create kanidm client");
|
|
|
|
Error::KanidmClient
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
|
|
|
|
let (actor_tx, _actor_rx) = broadcast::channel(1);
|
|
|
|
|
|
|
|
// Start the actors
|
|
|
|
let mut tasks = Vec::with_capacity(state.persons.len());
|
|
|
|
for person in state.persons.into_iter() {
|
2024-07-30 04:11:01 +02:00
|
|
|
// this is not super efficient but we don't really care as we are not even inside the warmup time window, so we're not in a hurry
|
|
|
|
let mut cloned_clients: Vec<KanidmClient> = clients
|
|
|
|
.iter()
|
|
|
|
.map(|client| {
|
|
|
|
client.new_session().map_err(|err| {
|
|
|
|
error!(?err, "Unable to create a new kanidm client session");
|
|
|
|
Error::KanidmClient
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
let main_client_index = seeded_rng.gen_range(0..cloned_clients.len());
|
|
|
|
let main_client = cloned_clients.remove(main_client_index);
|
|
|
|
//note that cloned_clients now contains all other clients except the first one
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
let c_stats_queue = stats_queue.clone();
|
|
|
|
|
|
|
|
let c_actor_rx = actor_tx.subscribe();
|
|
|
|
|
|
|
|
tasks.push(tokio::spawn(actor_person(
|
2024-07-30 04:11:01 +02:00
|
|
|
main_client,
|
2024-03-09 07:09:15 +01:00
|
|
|
person,
|
|
|
|
c_stats_queue,
|
|
|
|
c_actor_rx,
|
2024-07-30 04:11:01 +02:00
|
|
|
state.profile.seed(),
|
|
|
|
cloned_clients,
|
|
|
|
state.profile.warmup_time(),
|
2024-03-09 07:09:15 +01:00
|
|
|
)))
|
|
|
|
}
|
|
|
|
|
|
|
|
let warmup = state.profile.warmup_time();
|
2024-04-17 01:35:16 +02:00
|
|
|
let test_time = state.profile.test_time();
|
2024-03-09 07:09:15 +01:00
|
|
|
|
2024-04-17 01:35:16 +02:00
|
|
|
// We run a separate test inner so we don't have to worry about
|
2024-03-09 07:09:15 +01:00
|
|
|
// task spawn/join within our logic.
|
|
|
|
let c_stats_ctrl = stats_ctrl.clone();
|
|
|
|
// Don't ? this, we want to stash the result so we cleanly stop all the workers
|
|
|
|
// before returning the inner test result.
|
2024-04-17 01:35:16 +02:00
|
|
|
let test_result = execute_inner(warmup, test_time, control_rx, c_stats_ctrl).await;
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
info!("stopping stats");
|
|
|
|
|
|
|
|
// The statistics collector has been working in the BG, and was likely told
|
|
|
|
// to end by now, but if not (due to an error) send a signal to stop immediately.
|
|
|
|
if let Err(crossbeam_err) = stats_ctrl.push(TestPhase::StopNow) {
|
|
|
|
error!(
|
|
|
|
?crossbeam_err,
|
|
|
|
"Unable to signal statistics collector to stop"
|
|
|
|
);
|
|
|
|
return Err(Error::Crossbeam);
|
|
|
|
}
|
|
|
|
|
|
|
|
info!("stopping workers");
|
|
|
|
|
|
|
|
// Test workers to stop
|
|
|
|
actor_tx.send(Signal::Stop).map_err(|broadcast_err| {
|
|
|
|
error!(?broadcast_err, "Unable to signal workers to stop");
|
|
|
|
Error::Tokio
|
|
|
|
})?;
|
|
|
|
|
|
|
|
info!("joining workers");
|
|
|
|
|
|
|
|
// Join all the tasks.
|
|
|
|
for task in tasks {
|
|
|
|
task.await.map_err(|tokio_err| {
|
|
|
|
error!(?tokio_err, "Failed to join task");
|
|
|
|
Error::Tokio
|
|
|
|
})??;
|
|
|
|
// The double ? isn't a mistake, it's because this is Result<Result<T, E>, E>
|
|
|
|
// and flatten is nightly.
|
|
|
|
}
|
|
|
|
|
|
|
|
// By this point the stats task should have been told to halt and rejoin.
|
|
|
|
stats_task.await.map_err(|tokio_err| {
|
|
|
|
error!(?tokio_err, "Failed to join statistics task");
|
|
|
|
Error::Tokio
|
|
|
|
})??;
|
|
|
|
// Not an error, two ? to handle the inner data collector error.
|
|
|
|
|
|
|
|
// Complete!
|
|
|
|
|
|
|
|
test_result
|
|
|
|
}
|