Skip to content

Commit 7cbf0f2

Browse files
committed
Refactor load aggregator with 'stress_test' module
This makes the core more readable and usable.
1 parent a2222e0 commit 7cbf0f2

File tree

10 files changed

+841
-780
lines changed

10 files changed

+841
-780
lines changed

mithril-test-lab/mithril-end-to-end/src/bin/load-aggregator/main.rs

Lines changed: 44 additions & 780 deletions
Large diffs are not rendered by default.

mithril-test-lab/mithril-end-to-end/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod devnet;
33
mod end_to_end_spec;
44
mod mithril;
55
mod run_only;
6+
pub mod stress_test;
67
mod utils;
78

89
pub use devnet::*;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use slog_scope::info;
2+
use std::time::Duration;
3+
4+
use mithril_common::{entities::Epoch, test_utils::MithrilFixture, StdResult};
5+
6+
use crate::{
7+
stress_test::{entities::AggregatorParameters, fake_chain, fake_signer, wait},
8+
Aggregator,
9+
};
10+
11+
/// Bootstrap an aggregator and make it compute its genesis certificate
12+
pub async fn bootstrap_aggregator(
13+
args: &AggregatorParameters,
14+
signers_fixture: &MithrilFixture,
15+
current_epoch: &mut Epoch,
16+
) -> StdResult<Aggregator> {
17+
info!(">> Launch Aggregator");
18+
let mut aggregator = Aggregator::new(
19+
args.server_port as u64,
20+
&args.bft_node,
21+
&args.cardano_cli_path,
22+
&args.work_dir,
23+
&args.bin_dir,
24+
&args.mithril_era,
25+
)
26+
.unwrap();
27+
28+
fake_chain::set_epoch(&args.mock_epoch_file_path(), *current_epoch);
29+
fake_chain::set_stake_distribution(&args.mock_stake_distribution_file_path(), signers_fixture);
30+
31+
// Extremely large interval since, for the two following starts, only the http_server part
32+
// of the aggregator is relevant as we need to send signer registrations.
33+
aggregator.change_run_interval(Duration::from_secs(20000));
34+
aggregator.set_mock_cardano_cli_file_path(
35+
&args.mock_stake_distribution_file_path(),
36+
&args.mock_epoch_file_path(),
37+
);
38+
aggregator.set_protocol_parameters(&signers_fixture.protocol_parameters());
39+
40+
info!(
41+
">> Starting the aggregator with a large run interval to call the http_server\
42+
without being bothered by the state machine cycles"
43+
);
44+
aggregator.serve().unwrap();
45+
wait::for_http_response(
46+
&format!("{}/epoch-settings", aggregator.endpoint()),
47+
Duration::from_secs(10),
48+
"Waiting for the aggregator to start",
49+
)
50+
.await?;
51+
52+
info!(">> Send the Signer Key Registrations payloads for the genesis signers");
53+
let errors = fake_signer::register_signers_to_aggregator(
54+
&aggregator,
55+
&signers_fixture.signers(),
56+
*current_epoch + 1,
57+
)
58+
.await?;
59+
assert_eq!(0, errors);
60+
aggregator.stop().await.unwrap();
61+
62+
info!(">> Move one epoch forward in order to issue the genesis certificate");
63+
*current_epoch += 1;
64+
fake_chain::set_epoch(&args.mock_epoch_file_path(), *current_epoch);
65+
66+
info!(">> Restarting the aggregator still with a large run interval");
67+
aggregator.serve().unwrap();
68+
wait::for_http_response(
69+
&format!("{}/epoch-settings", aggregator.endpoint()),
70+
Duration::from_secs(10),
71+
"Waiting for the aggregator to start",
72+
)
73+
.await?;
74+
75+
info!(">> Send the Signer Key Registrations payloads for next genesis signers");
76+
let errors = fake_signer::register_signers_to_aggregator(
77+
&aggregator,
78+
&signers_fixture.signers(),
79+
*current_epoch + 1,
80+
)
81+
.await?;
82+
assert_eq!(0, errors);
83+
aggregator.stop().await.unwrap();
84+
85+
{
86+
info!(">> Compute genesis certificate");
87+
let mut genesis_aggregator = Aggregator::copy_configuration(&aggregator);
88+
genesis_aggregator
89+
.bootstrap_genesis()
90+
.await
91+
.expect("Genesis aggregator should be able to bootstrap genesis");
92+
}
93+
94+
info!(">> Restart aggregator with a normal run interval");
95+
aggregator.change_run_interval(Duration::from_secs(3));
96+
aggregator.serve().unwrap();
97+
98+
wait::for_http_response(
99+
&format!("{}/epoch-settings", aggregator.endpoint()),
100+
Duration::from_secs(10),
101+
"Waiting for the aggregator to restart",
102+
)
103+
.await?;
104+
105+
info!(">> Aggregator bootrapped");
106+
107+
Ok(aggregator)
108+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
use anyhow::{anyhow, Context};
2+
use clap::Parser;
3+
use mithril_common::StdResult;
4+
use slog::Level;
5+
use std::{
6+
path::{Path, PathBuf},
7+
time::Duration,
8+
};
9+
use tokio::time::Instant;
10+
11+
use crate::BftNode;
12+
13+
#[derive(Debug, Parser)]
14+
#[command(author, version, about, long_about = None)]
15+
pub struct MainOpts {
16+
/// Location of the Cardano CLI binary
17+
#[arg(short, long)]
18+
pub cardano_cli_path: PathBuf,
19+
20+
/// Temporary location for logs, databases etc.
21+
#[arg(short, long)]
22+
pub temporary_path: Option<PathBuf>,
23+
24+
/// Path of the Aggregator binary
25+
#[arg(short, long, default_value = "./target/debug")]
26+
pub aggregator_dir: PathBuf,
27+
28+
/// Number of concurrent signers
29+
#[arg(long, default_value = "20")]
30+
pub num_signers: usize,
31+
32+
/// Mithril technical Era
33+
#[arg(long, default_value = "thales")]
34+
pub mithril_era: String,
35+
36+
/// Aggregator HTTP port
37+
#[arg(short = 'p', long, default_value = "8080")]
38+
server_port: u32,
39+
40+
/// Log level
41+
#[arg(short='v', action = clap::ArgAction::Count)]
42+
verbose: u8,
43+
}
44+
45+
impl MainOpts {
46+
/// get log level from parameters
47+
pub fn log_level(&self) -> Level {
48+
match self.verbose {
49+
0 => Level::Warning,
50+
1 => Level::Info,
51+
2 => Level::Debug,
52+
_ => Level::Trace,
53+
}
54+
}
55+
}
56+
57+
#[derive(Debug)]
58+
pub struct AggregatorParameters {
59+
pub server_port: u32,
60+
pub bft_node: BftNode,
61+
pub cardano_cli_path: PathBuf,
62+
pub work_dir: PathBuf,
63+
pub bin_dir: PathBuf,
64+
pub mithril_era: String,
65+
}
66+
67+
impl AggregatorParameters {
68+
pub fn new(opts: &MainOpts, immutable_db_path: &Path) -> StdResult<Self> {
69+
let bft_node = BftNode {
70+
db_path: immutable_db_path.to_path_buf(),
71+
socket_path: PathBuf::new(),
72+
};
73+
let tmp_dir = opts
74+
.temporary_path
75+
.as_ref()
76+
.cloned()
77+
.unwrap_or_else(|| std::env::temp_dir().join("load-aggregator"));
78+
79+
if tmp_dir.exists() {
80+
std::fs::remove_dir_all(&tmp_dir).with_context(|| {
81+
format!(
82+
"Could not remove existing temp directory '{}'.",
83+
tmp_dir.display()
84+
)
85+
})?;
86+
}
87+
std::fs::create_dir_all(&tmp_dir)
88+
.with_context(|| format!("Could not create temp directory '{}'.", tmp_dir.display()))?;
89+
90+
let cardano_cli_path = {
91+
if !opts.cardano_cli_path.exists() {
92+
Err(anyhow!(
93+
"Given cardano-cli path does not exist: '{}'",
94+
opts.cardano_cli_path.display()
95+
))?
96+
}
97+
98+
opts.cardano_cli_path.canonicalize().with_context(|| {
99+
format!(
100+
"Could not canonicalize path to the cardano-cli, path: '{}'",
101+
opts.cardano_cli_path.display()
102+
)
103+
})?
104+
};
105+
106+
let aggregator_parameters = AggregatorParameters {
107+
bft_node,
108+
bin_dir: opts.aggregator_dir.clone(),
109+
cardano_cli_path,
110+
server_port: opts.server_port,
111+
work_dir: tmp_dir,
112+
mithril_era: opts.mithril_era.clone(),
113+
};
114+
115+
Ok(aggregator_parameters)
116+
}
117+
118+
pub fn mock_stake_distribution_file_path(&self) -> PathBuf {
119+
self.work_dir.join("stake_distribution.json")
120+
}
121+
122+
pub fn mock_epoch_file_path(&self) -> PathBuf {
123+
self.work_dir.join("epoch.txt")
124+
}
125+
}
126+
127+
pub struct Timing {
128+
phase: String,
129+
duration: Duration,
130+
}
131+
132+
pub struct Reporter {
133+
number_of_signers: usize,
134+
timings: Vec<Timing>,
135+
current_timing: Option<(String, Instant)>,
136+
}
137+
138+
impl Reporter {
139+
pub fn new(number_of_signers: usize) -> Self {
140+
Self {
141+
number_of_signers,
142+
timings: vec![],
143+
current_timing: None,
144+
}
145+
}
146+
147+
pub fn start(&mut self, phase: &str) {
148+
self.current_timing = Some((phase.to_owned(), Instant::now()));
149+
}
150+
151+
pub fn stop(&mut self) {
152+
match &self.current_timing {
153+
Some((phase, instant)) => {
154+
let timing = Timing {
155+
phase: phase.clone(),
156+
duration: instant.elapsed(),
157+
};
158+
159+
self.timings.push(timing);
160+
self.current_timing = None;
161+
}
162+
None => (),
163+
}
164+
}
165+
166+
pub fn print_report(&self) {
167+
println!("number_of_signers\t{}", self.number_of_signers);
168+
println!("phase\tduration/ms");
169+
for t in &self.timings {
170+
println!("{}\t{}", t.phase, t.duration.as_millis());
171+
}
172+
}
173+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use slog_scope::debug;
2+
use std::{fs::File, io::Write, path::Path};
3+
4+
use mithril_common::{entities::Epoch, test_utils::MithrilFixture};
5+
6+
pub fn set_stake_distribution(
7+
mock_stake_distribution_file_path: &Path,
8+
signers_fixture: &MithrilFixture,
9+
) {
10+
let mock_stake_distribution_file = File::create(mock_stake_distribution_file_path).unwrap();
11+
serde_json::to_writer(
12+
&mock_stake_distribution_file,
13+
&signers_fixture.cardano_cli_stake_distribution(),
14+
)
15+
.expect("Writing the stake distribution into a file for the mock cardano cli failed");
16+
}
17+
18+
pub fn set_epoch(mock_epoch_file_path: &Path, epoch: Epoch) {
19+
let mock_epoch_file = File::create(mock_epoch_file_path).unwrap();
20+
write!(&mock_epoch_file, "{}", *epoch)
21+
.expect("Writing the epoch into a file for the mock cardano cli failed");
22+
debug!("New Epoch: {epoch}");
23+
}

0 commit comments

Comments
 (0)