Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor reports/metrics, add JSON and markdown report #600

Merged
merged 9 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
**swp
/Cargo.lock
/src/docs/*/book
/.idea
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ url = "2"
[features]
default = ["reqwest/default-tls"]
rustls-tls = ["reqwest/rustls-tls", "tokio-tungstenite/rustls"]
gaggle = []
ctron marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
httpmock = "0.6"
Expand Down
63 changes: 40 additions & 23 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ pub struct GooseConfiguration {
/// Doesn't display an error summary
#[options(no_short)]
pub no_error_summary: bool,
/// Create an html-formatted report
/// Create a report file
#[options(no_short, meta = "NAME")]
pub report_file: String,
pub report_file: Vec<String>,
/// Disable granular graphs in report file
#[options(no_short)]
pub no_granular_report: bool,
Expand Down Expand Up @@ -282,7 +282,7 @@ pub(crate) struct GooseDefaults {
/// An optional default for not displaying an error summary.
pub no_error_summary: Option<bool>,
/// An optional default for the html-formatted report file name.
pub report_file: Option<String>,
pub report_file: Option<Vec<String>>,
/// An optional default for the flag that disables granular data in HTML report graphs.
pub no_granular_report: Option<bool>,
/// An optional default for the requests log file name.
Expand Down Expand Up @@ -569,7 +569,7 @@ impl GooseDefaultType<&str> for GooseAttack {
Some(value.to_string())
}
}
GooseDefault::ReportFile => self.defaults.report_file = Some(value.to_string()),
GooseDefault::ReportFile => self.defaults.report_file = Some(vec![value.to_string()]),
GooseDefault::RequestLog => self.defaults.request_log = Some(value.to_string()),
GooseDefault::ScenarioLog => self.defaults.scenario_log = Some(value.to_string()),
GooseDefault::Scenarios => {
Expand Down Expand Up @@ -1161,6 +1161,24 @@ impl GooseConfigure<String> for GooseConfiguration {
None
}
}
impl GooseConfigure<Vec<String>> for GooseConfiguration {
/// Use [`GooseValue`] to set a [`String`] value.
fn get_value(&self, values: Vec<GooseValue<Vec<String>>>) -> Option<Vec<String>> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {:?}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<bool> for GooseConfiguration {
/// Use [`GooseValue`] to set a [`bool`] value.
fn get_value(&self, values: Vec<GooseValue<bool>>) -> Option<bool> {
Expand Down Expand Up @@ -1563,23 +1581,22 @@ impl GooseConfiguration {
.unwrap_or(false);

// Configure `report_file`.
self.report_file = match self.get_value(vec![
// Use --report-file if set.
GooseValue {
value: Some(self.report_file.to_string()),
filter: self.report_file.is_empty(),
message: "report_file",
},
// Otherwise use GooseDefault if set.
GooseValue {
value: defaults.report_file.clone(),
filter: defaults.report_file.is_none(),
message: "report_file",
},
]) {
Some(v) => v,
None => "".to_string(),
};
self.report_file = self
.get_value(vec![
// Use --report-file if set.
GooseValue {
value: Some(self.report_file.clone()),
filter: self.report_file.is_empty(),
message: "report_file",
},
// Otherwise use GooseDefault if set.
GooseValue {
value: defaults.report_file.clone(),
filter: defaults.report_file.is_none(),
message: "report_file",
},
])
.unwrap_or_default();

// Configure `no_granular_report`.
self.no_debug_body = self
Expand Down Expand Up @@ -2013,7 +2030,7 @@ impl GooseConfiguration {
} else if !self.report_file.is_empty() {
return Err(GooseError::InvalidOption {
option: "`configuration.report_file`".to_string(),
value: self.report_file.to_string(),
value: format!("{:?}", self.report_file),
detail:
"`configuration.report_file` can not be set with `configuration.no_metrics`."
.to_string(),
Expand Down Expand Up @@ -2273,7 +2290,7 @@ mod test {
assert!(goose_attack.defaults.no_autostart == Some(true));
assert!(goose_attack.defaults.timeout == Some(timeout));
assert!(goose_attack.defaults.no_gzip == Some(true));
assert!(goose_attack.defaults.report_file == Some(report_file));
assert!(goose_attack.defaults.report_file == Some(vec![report_file]));
assert!(goose_attack.defaults.request_log == Some(request_log));
assert!(goose_attack.defaults.request_format == Some(GooseLogFormat::Raw));
assert!(goose_attack.defaults.error_log == Some(error_log));
Expand Down
24 changes: 8 additions & 16 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ use tokio_tungstenite::tungstenite::Message;
/// - Commands will be displayed in the help screen in the order defined here, so
/// they should be logically grouped.
/// 2. Add the new command to `ControllerCommand::details` and populate all
/// `ControllerCommandDetails`, using other commands as an implementation reference.
/// - The `regex` is used to identify the command, and optionally to extract a
/// value (for example see `Hatchrate` and `Users`)
/// - If additional validation is required beyond the regular expression, add
/// the necessary logic to `ControllerCommand::validate_value`.
/// `ControllerCommandDetails`, using other commands as an implementation reference.
/// - The `regex` is used to identify the command, and optionally to extract a
/// value (for example see `Hatchrate` and `Users`)
/// - If additional validation is required beyond the regular expression, add
/// the necessary logic to `ControllerCommand::validate_value`.
/// 3. Add any necessary parent process logic for the command to
/// `GooseAttack::handle_controller_requests` (also in this file).
/// `GooseAttack::handle_controller_requests` (also in this file).
/// 4. Add a test for the new command in tests/controller.rs.
#[derive(Clone, Debug, EnumIter, PartialEq, Eq)]
pub enum ControllerCommand {
Expand Down Expand Up @@ -642,10 +642,8 @@ impl GooseAttack {
AttackPhase::Idle => {
let current_users = if !self.test_plan.steps.is_empty() {
self.test_plan.steps[self.test_plan.current].0
} else if let Some(users) = self.configuration.users {
users
} else {
0
self.configuration.users.unwrap_or_default()
};
info!(
"changing users from {:?} to {}",
Expand Down Expand Up @@ -1410,13 +1408,7 @@ impl Controller<ControllerTelnetMessage> for ControllerState {
raw_value: ControllerTelnetMessage,
) -> Result<String, String> {
let command_string = match str::from_utf8(&raw_value) {
Ok(m) => {
if let Some(c) = m.lines().next() {
c
} else {
""
}
}
Ok(m) => m.lines().next().unwrap_or_default(),
Err(e) => {
let error = format!("ignoring unexpected input from telnet controller: {}", e);
info!("{}", error);
Expand Down
30 changes: 15 additions & 15 deletions src/goose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ impl Scenario {
Scenario {
name: name.to_string(),
machine_name: Scenario::get_machine_name(name),
scenarios_index: usize::max_value(),
scenarios_index: usize::MAX,
weight: 1,
transaction_wait: None,
transactions: Vec::new(),
Expand Down Expand Up @@ -961,7 +961,7 @@ impl GooseUser {
metrics_channel: None,
shutdown_channel: None,
// A value of max_value() indicates this user isn't fully initialized yet.
weighted_users_index: usize::max_value(),
weighted_users_index: usize::MAX,
load_test_hash,
request_cadence: GooseRequestCadence::new(),
slept: 0,
Expand Down Expand Up @@ -1144,9 +1144,9 @@ impl GooseUser {
/// of precedence:
/// 1. `--host` (host specified on the command line when running load test)
/// 2. [`Scenario`](./struct.Scenario.html)`.host` (default host defined for the
/// current scenario)
/// current scenario)
/// 3. [`GooseDefault::Host`](../config/enum.GooseDefault.html#variant.Host) (default host
/// defined for the current load test)
/// defined for the current load test)
pub fn build_url(&self, path: &str) -> Result<String, Box<TransactionError>> {
// If URL includes a host, simply use it.
if let Ok(parsed_path) = Url::parse(path) {
Expand Down Expand Up @@ -2177,7 +2177,7 @@ impl GooseUser {
/// - A manually built client is specific to a single Goose thread -- if you are
/// generating a large load test with many users, each will need to manually build their
/// own client (typically you'd do this in a Transaction that is registered with
/// [`Transaction::set_on_start()`] in each Scenario requiring a custom client;
/// [`Transaction::set_on_start()`] in each Scenario requiring a custom client;
/// - Manually building a client will completely replace the automatically built client
/// with a brand new one, so any configuration, cookies or headers set in the previously
/// built client will be gone;
Expand Down Expand Up @@ -2811,7 +2811,7 @@ impl Transaction {
pub fn new(function: TransactionFunction) -> Self {
trace!("new transaction");
Transaction {
transactions_index: usize::max_value(),
transactions_index: usize::MAX,
name: "".to_string(),
weight: 1,
sequence: 0,
Expand Down Expand Up @@ -3081,7 +3081,7 @@ mod tests {

let mut scenario = scenario!("foo");
assert_eq!(scenario.name, "foo");
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);
assert_eq!(scenario.weight, 1);
assert_eq!(scenario.transaction_wait, None);
assert!(scenario.host.is_none());
Expand All @@ -3094,7 +3094,7 @@ mod tests {
scenario = scenario.register_transaction(transaction!(test_function_a));
assert_eq!(scenario.transactions.len(), 1);
assert_eq!(scenario.weighted_transactions.len(), 0);
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);
assert_eq!(scenario.weight, 1);
assert_eq!(scenario.transaction_wait, None);
assert!(scenario.host.is_none());
Expand All @@ -3103,7 +3103,7 @@ mod tests {
scenario = scenario.register_transaction(transaction!(test_function_b));
assert_eq!(scenario.transactions.len(), 2);
assert_eq!(scenario.weighted_transactions.len(), 0);
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);
assert_eq!(scenario.weight, 1);
assert_eq!(scenario.transaction_wait, None);
assert!(scenario.host.is_none());
Expand All @@ -3112,7 +3112,7 @@ mod tests {
scenario = scenario.register_transaction(transaction!(test_function_a));
assert_eq!(scenario.transactions.len(), 3);
assert_eq!(scenario.weighted_transactions.len(), 0);
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);
assert_eq!(scenario.weight, 1);
assert_eq!(scenario.transaction_wait, None);
assert!(scenario.host.is_none());
Expand All @@ -3122,7 +3122,7 @@ mod tests {
assert_eq!(scenario.weight, 50);
assert_eq!(scenario.transactions.len(), 3);
assert_eq!(scenario.weighted_transactions.len(), 0);
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);
assert_eq!(scenario.transaction_wait, None);
assert!(scenario.host.is_none());

Expand All @@ -3136,7 +3136,7 @@ mod tests {
assert_eq!(scenario.weight, 5);
assert_eq!(scenario.transactions.len(), 3);
assert_eq!(scenario.weighted_transactions.len(), 0);
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);
assert_eq!(scenario.transaction_wait, None);

// Host field can be changed.
Expand All @@ -3155,7 +3155,7 @@ mod tests {
assert_eq!(scenario.weight, 5);
assert_eq!(scenario.transactions.len(), 3);
assert_eq!(scenario.weighted_transactions.len(), 0);
assert_eq!(scenario.scenarios_index, usize::max_value());
assert_eq!(scenario.scenarios_index, usize::MAX);

// Wait time can be changed.
scenario = scenario
Expand All @@ -3178,7 +3178,7 @@ mod tests {

// Initialize scenario.
let mut transaction = transaction!(test_function_a);
assert_eq!(transaction.transactions_index, usize::max_value());
assert_eq!(transaction.transactions_index, usize::MAX);
assert_eq!(transaction.name, "".to_string());
assert_eq!(transaction.weight, 1);
assert_eq!(transaction.sequence, 0);
Expand Down Expand Up @@ -3254,7 +3254,7 @@ mod tests {
let base_url = get_base_url(Some(HOST.to_string()), None, None).unwrap();
let user = GooseUser::new(0, "".to_string(), base_url, &configuration, 0, None).unwrap();
assert_eq!(user.scenarios_index, 0);
assert_eq!(user.weighted_users_index, usize::max_value());
assert_eq!(user.weighted_users_index, usize::MAX);

// Confirm the URLs are correctly built using the default_host.
let url = user.build_url("/foo").unwrap();
Expand Down
Loading
Loading