#[macro_use]
extern crate log;
extern crate structopt;
pub mod goose;
mod client;
#[cfg(feature = "gaggle")]
mod manager;
pub mod prelude;
mod stats;
mod util;
#[cfg(feature = "gaggle")]
mod worker;
use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeMap, HashMap};
use std::f32;
use std::fs::File;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc,
};
use std::time;
use lazy_static::lazy_static;
#[cfg(feature = "gaggle")]
use nng::Socket;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use simplelog::*;
use structopt::StructOpt;
use tokio::sync::{mpsc, Mutex, RwLock};
use url::Url;
use crate::goose::{
GooseClient, GooseClientCommand, GooseRawRequest, GooseRequest, GooseTask, GooseTaskSet,
};
const RUNNING_STATS_EVERY: usize = 15;
const DEFAULT_PORT: &str = "5115";
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
lazy_static! {
static ref CLIENT: RwLock<Vec<GooseClientState>> = RwLock::new(Vec::new());
}
struct GooseClientState {
client: Mutex<Client>,
weighted_bucket: AtomicUsize,
weighted_bucket_position: AtomicUsize,
}
impl GooseClientState {
async fn initialize(clients: usize) {
let mut goose_client_state = CLIENT.write().await;
if !goose_client_state.is_empty() {
goose_client_state.clear();
}
for _ in 0..clients {
let builder = Client::builder()
.user_agent(APP_USER_AGENT)
.cookie_store(true);
let client = match builder.build() {
Ok(c) => Mutex::new(c),
Err(e) => {
error!("failed to build client: {}", e);
std::process::exit(1);
}
};
goose_client_state.push(GooseClientState {
client,
weighted_bucket: AtomicUsize::new(0),
weighted_bucket_position: AtomicUsize::new(0),
});
}
}
}
lazy_static! {
static ref WORKER_ID: AtomicUsize = AtomicUsize::new(0);
}
pub fn get_worker_id() -> usize {
WORKER_ID.load(Ordering::Relaxed)
}
#[cfg(not(feature = "gaggle"))]
#[derive(Debug)]
pub struct Socket {}
#[derive(Clone)]
pub struct GooseAttack {
test_start_task: Option<GooseTask>,
test_stop_task: Option<GooseTask>,
task_sets: Vec<GooseTaskSet>,
task_sets_hash: u64,
weighted_clients: Vec<GooseClient>,
host: Option<String>,
configuration: GooseConfiguration,
number_of_cpus: usize,
run_time: usize,
clients: usize,
active_clients: usize,
merged_requests: HashMap<String, GooseRequest>,
}
impl GooseAttack {
pub fn initialize() -> GooseAttack {
let goose_attack = GooseAttack {
test_start_task: None,
test_stop_task: None,
task_sets: Vec::new(),
task_sets_hash: 0,
weighted_clients: Vec::new(),
host: None,
configuration: GooseConfiguration::from_args(),
number_of_cpus: num_cpus::get(),
run_time: 0,
clients: 0,
active_clients: 0,
merged_requests: HashMap::new(),
};
goose_attack.setup()
}
pub fn initialize_with_config(config: GooseConfiguration) -> GooseAttack {
GooseAttack {
test_start_task: None,
test_stop_task: None,
task_sets: Vec::new(),
task_sets_hash: 0,
weighted_clients: Vec::new(),
host: None,
configuration: config,
number_of_cpus: num_cpus::get(),
run_time: 0,
clients: 0,
active_clients: 0,
merged_requests: HashMap::new(),
}
}
pub fn initialize_logger(&self) {
let debug_level;
match self.configuration.verbose {
0 => debug_level = LevelFilter::Warn,
1 => debug_level = LevelFilter::Info,
2 => debug_level = LevelFilter::Debug,
_ => debug_level = LevelFilter::Trace,
}
let log_level;
match self.configuration.log_level {
0 => log_level = LevelFilter::Info,
1 => log_level = LevelFilter::Debug,
_ => log_level = LevelFilter::Trace,
}
let log_file = PathBuf::from(&self.configuration.log_file);
match CombinedLogger::init(vec![
match TermLogger::new(debug_level, Config::default(), TerminalMode::Mixed) {
Some(t) => t,
None => {
eprintln!("failed to initialize TermLogger");
return;
}
},
WriteLogger::new(
log_level,
Config::default(),
File::create(&log_file).unwrap(),
),
]) {
Ok(_) => (),
Err(e) => {
info!("failed to initialize CombinedLogger: {}", e);
}
}
info!("Output verbosity level: {}", debug_level);
info!("Logfile verbosity level: {}", log_level);
info!("Writing to log file: {}", log_file.display());
}
pub fn setup(mut self) -> Self {
self.initialize_logger();
if self.configuration.status_codes && self.configuration.no_stats {
error!("You must not enable --no-stats when enabling --status-codes.");
std::process::exit(1);
}
if self.configuration.only_summary && self.configuration.no_stats {
error!("You must not enable --no-stats when enabling --only-summary.");
std::process::exit(1);
}
if self.configuration.worker {
if self.configuration.run_time != "" {
error!("The --run-time option is only available to the manager.");
std::process::exit(1);
}
self.run_time = 0;
} else if self.configuration.run_time != "" {
self.run_time = util::parse_timespan(&self.configuration.run_time);
info!("run_time = {}", self.run_time);
} else {
self.run_time = 0;
}
self.clients = match self.configuration.clients {
Some(c) => {
if c == 0 {
if self.configuration.worker {
error!("At least 1 client is required.");
std::process::exit(1);
} else {
0
}
} else {
if self.configuration.worker {
error!("The --clients option is only available to the manager.");
std::process::exit(1);
}
c
}
}
None => {
let c = self.number_of_cpus;
if !self.configuration.manager && !self.configuration.worker {
info!("concurrent clients defaulted to {} (number of CPUs)", c);
}
c
}
};
if !self.configuration.manager && !self.configuration.worker {
debug!("clients = {}", self.clients);
}
self
}
pub fn register_taskset(mut self, mut taskset: GooseTaskSet) -> Self {
taskset.task_sets_index = self.task_sets.len();
self.task_sets.push(taskset);
self
}
pub fn test_start(mut self, task: GooseTask) -> Self {
self.test_start_task = Some(task);
self
}
pub fn test_stop(mut self, task: GooseTask) -> Self {
self.test_stop_task = Some(task);
self
}
pub fn set_host(mut self, host: &str) -> Self {
trace!("set_host: {}", host);
self.host = Some(host.to_string());
self
}
fn weight_task_set_clients(&mut self) -> Vec<GooseClient> {
trace!("weight_task_set_clients");
let mut u: usize = 0;
let mut v: usize;
for task_set in &self.task_sets {
if u == 0 {
u = task_set.weight;
} else {
v = task_set.weight;
trace!("calculating greatest common denominator of {} and {}", u, v);
u = util::gcd(u, v);
trace!("inner gcd: {}", u);
}
}
debug!("gcd: {}", u);
let mut weighted_task_sets = Vec::new();
for (index, task_set) in self.task_sets.iter().enumerate() {
let weight = task_set.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
index,
task_set.name,
task_set.weight,
weight
);
let mut weighted_sets = vec![index; weight];
weighted_task_sets.append(&mut weighted_sets);
}
info!("initializing client states...");
let mut weighted_clients = Vec::new();
let mut client_count = 0;
let config = self.configuration.clone();
loop {
for task_sets_index in &weighted_task_sets {
let task_set_host = self.task_sets[*task_sets_index].host.clone();
weighted_clients.push(GooseClient::new(
self.task_sets[*task_sets_index].task_sets_index,
self.host.clone(),
task_set_host,
self.task_sets[*task_sets_index].min_wait,
self.task_sets[*task_sets_index].max_wait,
&config,
self.task_sets_hash,
));
client_count += 1;
if client_count >= self.clients {
trace!("created {} weighted_clients", client_count);
return weighted_clients;
}
}
}
}
pub fn execute(mut self) {
if self.task_sets.is_empty() {
error!("No task sets defined.");
std::process::exit(1);
}
if self.configuration.list {
println!("Available tasks:");
for task_set in self.task_sets {
println!(" - {} (weight: {})", task_set.name, task_set.weight);
for task in task_set.tasks {
println!(" o {} (weight: {})", task.name, task.weight);
}
}
std::process::exit(0);
}
if self.configuration.manager {
if self.configuration.worker {
error!("You can only run in manager or worker mode, not both.");
std::process::exit(1);
}
if self.configuration.expect_workers < 1 {
error!("You must set --expect-workers to 1 or more.");
std::process::exit(1);
}
if self.configuration.expect_workers as usize > self.clients {
error!(
"You must enable at least as many clients ({}) as workers ({}).",
self.clients, self.configuration.expect_workers
);
std::process::exit(1);
}
}
if self.configuration.worker {
if self.configuration.manager {
error!("You can only run in manager or worker mode, not both.");
std::process::exit(1);
}
if self.configuration.expect_workers > 0 {
error!("The --expect-workers option is only available to the manager");
std::process::exit(1);
}
if self.configuration.host != "" {
error!("The --host option is only available to the manager");
std::process::exit(1);
}
if self.configuration.manager_bind_host != "0.0.0.0" {
error!("The --manager-bind-host option is only available to the manager");
std::process::exit(1);
}
let default_port: u16 = DEFAULT_PORT.to_string().parse().unwrap();
if self.configuration.manager_bind_port != default_port {
error!("The --manager-bind-port option is only available to the manager");
std::process::exit(1);
}
if self.configuration.no_stats {
error!("The --no-stats option is only available to the manager");
std::process::exit(1);
}
if self.configuration.only_summary {
error!("The --only-summary option is only available to the manager");
std::process::exit(1);
}
if self.configuration.status_codes {
error!("The --status-codes option is only available to the manager");
std::process::exit(1);
}
if self.configuration.no_hash_check {
error!("The --no-hash-check option is only available to the manager");
std::process::exit(1);
}
}
if !self.configuration.manager
&& !self.configuration.worker
&& self.configuration.no_hash_check
{
error!("The --no-hash-check option is only available when running in manager mode");
std::process::exit(1);
}
let hatch_rate = self.configuration.hatch_rate;
if hatch_rate < 1 {
error!("Hatch rate must be greater than 0, or no clients will launch.");
std::process::exit(1);
}
if hatch_rate > 1 && self.configuration.worker {
error!("The --hatch-rate option is only available to the manager");
std::process::exit(1);
}
debug!("hatch_rate = {}", hatch_rate);
if self.configuration.host.is_empty() {
for task_set in &self.task_sets {
match &task_set.host {
Some(h) => {
if is_valid_host(h) {
info!("host for {} configured: {}", task_set.name, h);
}
}
None => match &self.host {
Some(h) => {
if is_valid_host(h) {
info!("host for {} configured: {}", task_set.name, h);
}
}
None => {
if !self.configuration.worker {
error!("Host must be defined globally or per-TaskSet. No host defined for {}.", task_set.name);
std::process::exit(1);
}
}
},
}
}
} else if is_valid_host(&self.configuration.host) {
info!("global host configured: {}", self.configuration.host);
}
for task_set in &mut self.task_sets {
let (weighted_on_start_tasks, weighted_tasks, weighted_on_stop_tasks) =
weight_tasks(&task_set);
task_set.weighted_on_start_tasks = weighted_on_start_tasks;
task_set.weighted_tasks = weighted_tasks;
task_set.weighted_on_stop_tasks = weighted_on_stop_tasks;
debug!(
"weighted {} on_start: {:?} tasks: {:?} on_stop: {:?}",
task_set.name,
task_set.weighted_on_start_tasks,
task_set.weighted_tasks,
task_set.weighted_on_stop_tasks
);
}
if !self.configuration.worker {
self.weighted_clients = self.weight_task_set_clients();
}
let mut s = DefaultHasher::new();
self.task_sets.hash(&mut s);
self.task_sets_hash = s.finish();
debug!("task_sets_hash: {}", self.task_sets_hash);
let started = time::Instant::now();
let sleep_float = 1.0 / hatch_rate as f32;
let sleep_duration = time::Duration::from_secs_f32(sleep_float);
if self.configuration.manager {
#[cfg(feature = "gaggle")]
{
self = manager::manager_main(self);
}
#[cfg(not(feature = "gaggle"))]
{
error!(
"goose must be recompiled with `--features gaggle` to start in manager mode"
);
std::process::exit(1);
}
}
else if self.configuration.worker {
#[cfg(feature = "gaggle")]
{
worker::worker_main(&self);
}
#[cfg(not(feature = "gaggle"))]
{
error!("goose must be recompiled with `--features gaggle` to start in worker mode");
std::process::exit(1);
}
}
else {
let mut rt = tokio::runtime::Runtime::new().unwrap();
self = rt.block_on(self.launch_clients(started, sleep_duration, None));
}
if !self.configuration.no_stats && !self.configuration.worker {
stats::print_final_stats(&self, started.elapsed().as_secs() as usize);
}
}
async fn launch_clients(
mut self,
mut started: time::Instant,
sleep_duration: time::Duration,
socket: Option<Socket>,
) -> GooseAttack {
trace!(
"launch clients: started({:?}) sleep_duration({:?}) socket({:?})",
started,
sleep_duration,
socket
);
if !self.configuration.worker {
match &self.test_start_task {
Some(t) => {
info!("running test_start_task");
GooseClientState::initialize(1).await;
let mut client =
GooseClient::new(0, self.host.clone(), None, 0, 0, &self.configuration, 0);
client.weighted_clients_index = 0;
let function = t.function;
function(&client).await;
}
None => (),
}
}
GooseClientState::initialize(self.weighted_clients.len()).await;
let mut clients = vec![];
let mut client_channels = vec![];
let (all_threads_sender, mut parent_receiver): (
mpsc::UnboundedSender<GooseRawRequest>,
mpsc::UnboundedReceiver<GooseRawRequest>,
) = mpsc::unbounded_channel();
for mut thread_client in self.weighted_clients.clone() {
if util::timer_expired(started, self.run_time) {
break;
}
thread_client.weighted_tasks = self.task_sets[thread_client.task_sets_index]
.weighted_tasks
.clone();
thread_client.weighted_on_start_tasks = self.task_sets[thread_client.task_sets_index]
.weighted_on_start_tasks
.clone();
thread_client.weighted_on_stop_tasks = self.task_sets[thread_client.task_sets_index]
.weighted_on_stop_tasks
.clone();
thread_client.weighted_clients_index = self.active_clients;
let (parent_sender, thread_receiver): (
mpsc::UnboundedSender<GooseClientCommand>,
mpsc::UnboundedReceiver<GooseClientCommand>,
) = mpsc::unbounded_channel();
client_channels.push(parent_sender);
thread_client.parent = Some(all_threads_sender.clone());
let thread_task_set = self.task_sets[thread_client.task_sets_index].clone();
let thread_number = self.active_clients + 1;
let is_worker = self.configuration.worker;
let client = tokio::spawn(client::client_main(
thread_number,
thread_task_set,
thread_client,
thread_receiver,
is_worker,
));
clients.push(client);
self.active_clients += 1;
debug!("sleeping {:?} milliseconds...", sleep_duration);
tokio::time::delay_for(sleep_duration).await;
}
started = time::Instant::now();
if self.configuration.worker {
info!(
"[{}] launched {} clients...",
get_worker_id(),
self.active_clients
);
} else {
info!("launched {} clients...", self.active_clients);
}
let mut statistics_reset: bool = false;
let canceled = Arc::new(AtomicBool::new(false));
util::setup_ctrlc_handler(&canceled);
let mut statistics_timer = time::Instant::now();
let mut display_running_statistics = false;
loop {
if !self.configuration.no_stats {
if util::timer_expired(statistics_timer, RUNNING_STATS_EVERY) {
statistics_timer = time::Instant::now();
if !self.configuration.only_summary {
display_running_statistics = true;
}
}
let mut received_message = false;
let mut message = parent_receiver.try_recv();
while message.is_ok() {
received_message = true;
let raw_request = message.unwrap();
let key = format!("{:?} {}", raw_request.method, raw_request.name);
let mut merge_request = match self.merged_requests.get(&key) {
Some(m) => m.clone(),
None => GooseRequest::new(&raw_request.name, raw_request.method, 0),
};
merge_request.set_response_time(raw_request.response_time);
merge_request.set_status_code(raw_request.status_code);
if raw_request.success {
merge_request.success_count += 1;
} else {
merge_request.fail_count += 1;
}
self.merged_requests.insert(key.to_string(), merge_request);
message = parent_receiver.try_recv();
}
if self.configuration.worker && received_message {
#[cfg(feature = "gaggle")]
{
if !worker::push_stats_to_manager(
&socket.clone().unwrap(),
&self.merged_requests.clone(),
true,
) {
canceled.store(true, Ordering::SeqCst);
}
self.merged_requests = HashMap::new();
}
}
if self.configuration.reset_stats && !statistics_reset {
info!("statistics reset...");
self.merged_requests = HashMap::new();
statistics_reset = true;
}
}
if util::timer_expired(started, self.run_time) || canceled.load(Ordering::SeqCst) {
if self.configuration.worker {
info!(
"[{}] stopping after {} seconds...",
get_worker_id(),
started.elapsed().as_secs()
);
} else {
info!("stopping after {} seconds...", started.elapsed().as_secs());
}
for (index, send_to_client) in client_channels.iter().enumerate() {
match send_to_client.send(GooseClientCommand::EXIT) {
Ok(_) => {
debug!("telling client {} to exit", index);
}
Err(e) => {
info!("failed to tell client {} to exit: {}", index, e);
}
}
}
if self.configuration.worker {
info!("[{}] waiting for clients to exit", get_worker_id());
} else {
info!("waiting for clients to exit");
}
futures::future::join_all(clients).await;
debug!("all clients exited");
if !self.configuration.no_stats {
let mut message = parent_receiver.try_recv();
while message.is_ok() {
let raw_request = message.unwrap();
let key = format!("{:?} {}", raw_request.method, raw_request.name);
let mut merge_request = match self.merged_requests.get(&key) {
Some(m) => m.clone(),
None => GooseRequest::new(&raw_request.name, raw_request.method, 0),
};
merge_request.set_response_time(raw_request.response_time);
merge_request.set_status_code(raw_request.status_code);
if raw_request.success {
merge_request.success_count += 1;
} else {
merge_request.fail_count += 1;
}
self.merged_requests.insert(key.to_string(), merge_request);
message = parent_receiver.try_recv();
}
}
#[cfg(feature = "gaggle")]
{
if self.configuration.worker {
worker::push_stats_to_manager(
&socket.clone().unwrap(),
&self.merged_requests.clone(),
true,
);
}
}
break;
}
if display_running_statistics {
display_running_statistics = false;
stats::print_running_stats(&self, started.elapsed().as_secs() as usize);
}
let one_second = time::Duration::from_secs(1);
tokio::time::delay_for(one_second).await;
}
if !self.configuration.worker {
match &self.test_stop_task {
Some(t) => {
info!("running test_stop_task");
GooseClientState::initialize(1).await;
let mut client =
GooseClient::new(0, self.host.clone(), None, 0, 0, &self.configuration, 0);
client.weighted_clients_index = 0;
let function = t.function;
function(&client).await;
}
None => (),
}
}
self
}
}
#[derive(StructOpt, Debug, Default, Clone, Serialize, Deserialize)]
#[structopt(name = "client")]
pub struct GooseConfiguration {
#[structopt(short = "H", long, required = false, default_value = "")]
pub host: String,
#[structopt(short, long)]
pub clients: Option<usize>,
#[structopt(short = "r", long, required = false, default_value = "1")]
pub hatch_rate: usize,
#[structopt(short = "t", long, required = false, default_value = "")]
pub run_time: String,
#[structopt(long)]
pub no_stats: bool,
#[structopt(long)]
pub status_codes: bool,
#[structopt(long)]
pub only_summary: bool,
#[structopt(long)]
pub reset_stats: bool,
#[structopt(short, long)]
pub list: bool,
#[structopt(short = "v", long, parse(from_occurrences))]
pub verbose: u8,
#[structopt(short = "g", long, parse(from_occurrences))]
pub log_level: u8,
#[structopt(long, default_value = "goose.log")]
pub log_file: String,
#[structopt(long)]
pub manager: bool,
#[structopt(long)]
pub no_hash_check: bool,
#[structopt(long, required = false, default_value = "0")]
pub expect_workers: u16,
#[structopt(long, default_value = "0.0.0.0")]
pub manager_bind_host: String,
#[structopt(long, default_value=DEFAULT_PORT)]
pub manager_bind_port: u16,
#[structopt(long)]
pub worker: bool,
#[structopt(long, default_value = "127.0.0.1")]
pub manager_host: String,
#[structopt(long, default_value=DEFAULT_PORT)]
pub manager_port: u16,
}
fn weight_tasks(task_set: &GooseTaskSet) -> (Vec<Vec<usize>>, Vec<Vec<usize>>, Vec<Vec<usize>>) {
trace!("weight_tasks for {}", task_set.name);
let mut sequenced_tasks: BTreeMap<usize, Vec<GooseTask>> = BTreeMap::new();
let mut sequenced_on_start_tasks: BTreeMap<usize, Vec<GooseTask>> = BTreeMap::new();
let mut sequenced_on_stop_tasks: BTreeMap<usize, Vec<GooseTask>> = BTreeMap::new();
let mut unsequenced_tasks: Vec<GooseTask> = Vec::new();
let mut unsequenced_on_start_tasks: Vec<GooseTask> = Vec::new();
let mut unsequenced_on_stop_tasks: Vec<GooseTask> = Vec::new();
let mut u: usize = 0;
let mut v: usize;
for task in &task_set.tasks {
if task.sequence > 0 {
if task.on_start {
if let Some(sequence) = sequenced_on_start_tasks.get_mut(&task.sequence) {
sequence.push(task.clone());
} else {
sequenced_on_start_tasks.insert(task.sequence, vec![task.clone()]);
}
}
if task.on_stop {
if let Some(sequence) = sequenced_on_stop_tasks.get_mut(&task.sequence) {
sequence.push(task.clone());
} else {
sequenced_on_stop_tasks.insert(task.sequence, vec![task.clone()]);
}
}
if !task.on_start && !task.on_stop {
if let Some(sequence) = sequenced_tasks.get_mut(&task.sequence) {
sequence.push(task.clone());
} else {
sequenced_tasks.insert(task.sequence, vec![task.clone()]);
}
}
} else {
if task.on_start {
unsequenced_on_start_tasks.push(task.clone());
}
if task.on_stop {
unsequenced_on_stop_tasks.push(task.clone());
}
if !task.on_start && !task.on_stop {
unsequenced_tasks.push(task.clone());
}
}
if u == 0 {
u = task.weight;
} else {
v = task.weight;
trace!("calculating greatest common denominator of {} and {}", u, v);
u = util::gcd(u, v);
trace!("inner gcd: {}", u);
}
}
debug!("gcd: {}", u);
let mut weighted_tasks: Vec<Vec<usize>> = Vec::new();
for (_sequence, tasks) in sequenced_tasks.iter() {
let mut sequence_weighted_tasks = Vec::new();
for task in tasks {
let weight = task.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
task.tasks_index,
task.name,
task.weight,
weight
);
let mut tasks = vec![task.tasks_index; weight];
sequence_weighted_tasks.append(&mut tasks);
}
weighted_tasks.push(sequence_weighted_tasks);
}
trace!("created weighted_tasks: {:?}", weighted_tasks);
let mut weighted_unsequenced_tasks = Vec::new();
for task in unsequenced_tasks {
let weight = task.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
task.tasks_index,
task.name,
task.weight,
weight
);
let mut tasks = vec![task.tasks_index; weight];
weighted_unsequenced_tasks.append(&mut tasks);
}
if !weighted_unsequenced_tasks.is_empty() {
weighted_tasks.push(weighted_unsequenced_tasks);
}
let mut weighted_on_start_tasks: Vec<Vec<usize>> = Vec::new();
for (_sequence, tasks) in sequenced_on_start_tasks.iter() {
let mut sequence_on_start_weighted_tasks = Vec::new();
for task in tasks {
let weight = task.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
task.tasks_index,
task.name,
task.weight,
weight
);
let mut tasks = vec![task.tasks_index; weight];
sequence_on_start_weighted_tasks.append(&mut tasks);
}
weighted_on_start_tasks.push(sequence_on_start_weighted_tasks);
}
trace!("created weighted_on_start_tasks: {:?}", weighted_tasks);
let mut weighted_on_start_unsequenced_tasks = Vec::new();
for task in unsequenced_on_start_tasks {
let weight = task.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
task.tasks_index,
task.name,
task.weight,
weight
);
let mut tasks = vec![task.tasks_index; weight];
weighted_on_start_unsequenced_tasks.append(&mut tasks);
}
weighted_on_start_tasks.push(weighted_on_start_unsequenced_tasks);
let mut weighted_on_stop_tasks: Vec<Vec<usize>> = Vec::new();
for (_sequence, tasks) in sequenced_on_stop_tasks.iter() {
let mut sequence_on_stop_weighted_tasks = Vec::new();
for task in tasks {
let weight = task.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
task.tasks_index,
task.name,
task.weight,
weight
);
let mut tasks = vec![task.tasks_index; weight];
sequence_on_stop_weighted_tasks.append(&mut tasks);
}
weighted_on_stop_tasks.push(sequence_on_stop_weighted_tasks);
}
trace!("created weighted_on_stop_tasks: {:?}", weighted_tasks);
let mut weighted_on_stop_unsequenced_tasks = Vec::new();
for task in unsequenced_on_stop_tasks {
let weight = task.weight / u;
trace!(
"{}: {} has weight of {} (reduced with gcd to {})",
task.tasks_index,
task.name,
task.weight,
weight
);
let mut tasks = vec![task.tasks_index; weight];
weighted_on_stop_unsequenced_tasks.append(&mut tasks);
}
weighted_on_stop_tasks.push(weighted_on_stop_unsequenced_tasks);
(
weighted_on_start_tasks,
weighted_tasks,
weighted_on_stop_tasks,
)
}
fn is_valid_host(host: &str) -> bool {
match Url::parse(host) {
Ok(_) => true,
Err(e) => {
error!("invalid host '{}': {}", host, e);
std::process::exit(1);
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn valid_host() {
assert_eq!(is_valid_host("http://example.com"), true);
assert_eq!(is_valid_host("http://example.com/"), true);
assert_eq!(is_valid_host("https://www.example.com/and/with/path"), true);
assert_eq!(is_valid_host("foo://example.com"), true);
assert_eq!(is_valid_host("file:///path/to/file"), true);
}
}