use std::collections::{HashMap, BTreeMap};
use std::time::Instant;
use http::StatusCode;
use http::method::Method;
use reqwest::blocking::{Client, Response, RequestBuilder};
use reqwest::Error;
use url::Url;
use crate::GooseConfiguration;
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
#[derive(Clone)]
pub struct GooseTaskSet {
pub name: String,
pub task_sets_index: usize,
pub weight: usize,
pub min_wait: usize,
pub max_wait: usize,
pub tasks: Vec<GooseTask>,
pub weighted_tasks: Vec<Vec<usize>>,
pub weighted_on_start_tasks: Vec<Vec<usize>>,
pub weighted_on_stop_tasks: Vec<Vec<usize>>,
pub host: Option<String>,
}
impl GooseTaskSet {
pub fn new(name: &str) -> Self {
trace!("new taskset: name: {}", &name);
let task_set = GooseTaskSet {
name: name.to_string(),
task_sets_index: usize::max_value(),
weight: 1,
min_wait: 0,
max_wait: 0,
tasks: Vec::new(),
weighted_tasks: Vec::new(),
weighted_on_start_tasks: Vec::new(),
weighted_on_stop_tasks: Vec::new(),
host: None,
};
task_set
}
pub fn register_task(mut self, mut task: GooseTask) -> Self {
trace!("{} register_task: {}", self.name, task.name);
task.tasks_index = self.tasks.len();
self.tasks.push(task);
self
}
pub fn set_weight(mut self, weight: usize) -> Self {
trace!("{} set_weight: {}", self.name, weight);
if weight < 1 {
error!("{} weight of {} not allowed", self.name, weight);
std::process::exit(1);
}
else {
self.weight = weight;
}
self
}
pub fn set_host(mut self, host: &str) -> Self {
trace!("{} set_host: {}", self.name, host);
self.host = Some(host.to_string());
self
}
pub fn set_wait_time(mut self, min_wait: usize, max_wait: usize) -> Self {
trace!("{} set_wait time: min: {} max: {}", self.name, min_wait, max_wait);
if min_wait > max_wait {
error!("min_wait({}) can't be larger than max_weight({})", min_wait, max_wait);
std::process::exit(1);
}
self.min_wait = min_wait;
self.max_wait = max_wait;
self
}
}
#[derive(Debug, Clone)]
pub enum GooseClientMode {
INIT,
HATCHING,
RUNNING,
EXITING,
}
#[derive(Debug, Clone)]
pub enum GooseClientCommand {
SYNC,
EXIT,
}
#[derive(Debug, Clone)]
pub struct GooseRequest {
pub path: String,
pub method: Method,
pub response_times: BTreeMap<usize, usize>,
pub min_response_time: usize,
pub max_response_time: usize,
pub total_response_time: usize,
pub response_time_counter: usize,
pub status_code_counts: HashMap<u16, usize>,
pub success_count: usize,
pub fail_count: usize,
}
impl GooseRequest {
fn new(path: &str, method: Method) -> Self {
trace!("new request");
GooseRequest {
path: path.to_string(),
method: method,
response_times: BTreeMap::new(),
min_response_time: 0,
max_response_time: 0,
total_response_time: 0,
response_time_counter: 0,
status_code_counts: HashMap::new(),
success_count: 0,
fail_count: 0,
}
}
fn set_response_time(&mut self, response_time: u128) {
let response_time_usize = response_time as usize;
if self.min_response_time == 0 || response_time_usize < self.min_response_time {
self.min_response_time = response_time_usize;
}
if response_time_usize > self.max_response_time {
self.max_response_time = response_time_usize;
}
self.total_response_time += response_time_usize;
self.response_time_counter += 1;
let rounded_response_time: usize;
if response_time < 10 {
rounded_response_time = response_time_usize;
}
else if response_time < 100 {
rounded_response_time = ((response_time as f64 / 10.0).round() * 10.0) as usize;
}
else if response_time < 1000 {
rounded_response_time = ((response_time as f64 / 100.0).round() * 100.0) as usize;
}
else {
rounded_response_time = ((response_time as f64 / 1000.0).round() * 1000.0) as usize;
}
let counter = match self.response_times.get(&rounded_response_time) {
Some(c) => {
debug!("got {:?} counter: {}", rounded_response_time, c);
*c + 1
}
None => {
debug!("no match for counter: {}", rounded_response_time);
1
}
};
self.response_times.insert(rounded_response_time, counter);
debug!("incremented {} counter: {}", rounded_response_time, counter);
}
fn set_status_code(&mut self, status_code: Option<StatusCode>) {
let status_code_u16 = match status_code {
Some(s) => s.as_u16(),
_ => 0,
};
let counter = match self.status_code_counts.get(&status_code_u16) {
Some(c) => {
debug!("got {:?} counter: {}", status_code, c);
*c + 1
}
None => {
debug!("no match for counter: {}", status_code_u16);
1
}
};
self.status_code_counts.insert(status_code_u16, counter);
debug!("incremented {} counter: {}", status_code_u16, counter);
}
}
#[derive(Debug, Clone)]
pub struct GooseClient {
pub task_sets_index: usize,
pub client: Client,
pub default_host: Option<String>,
pub task_set_host: Option<String>,
pub min_wait: usize,
pub max_wait: usize,
pub config: GooseConfiguration,
pub weighted_clients_index: usize,
pub mode: GooseClientMode,
pub weighted_on_start_tasks: Vec<Vec<usize>>,
pub weighted_tasks: Vec<Vec<usize>>,
pub weighted_bucket: usize,
pub weighted_bucket_position: usize,
pub weighted_on_stop_tasks: Vec<Vec<usize>>,
pub task_request_name: Option<String>,
pub request_name: Option<String>,
pub previous_path: Option<String>,
pub previous_method: Option<Method>,
pub previous_request_name: Option<String>,
pub was_success: bool,
pub requests: HashMap<String, GooseRequest>,
}
impl GooseClient {
pub fn new(counter: usize, task_sets_index: usize, default_host: Option<String>, task_set_host: Option<String>, min_wait: usize, max_wait: usize, configuration: &GooseConfiguration) -> Self {
trace!("new client");
let builder = Client::builder()
.user_agent(APP_USER_AGENT)
.cookie_store(true);
let client = match builder.build() {
Ok(c) => c,
Err(e) => {
error!("failed to build client {} for task {}: {}", counter, task_sets_index, e);
std::process::exit(1);
}
};
GooseClient {
task_sets_index: task_sets_index,
default_host: default_host,
task_set_host: task_set_host,
client: client,
config: configuration.clone(),
min_wait: min_wait,
max_wait: max_wait,
weighted_clients_index: usize::max_value(),
mode: GooseClientMode::INIT,
weighted_on_start_tasks: Vec::new(),
weighted_tasks: Vec::new(),
weighted_bucket: 0,
weighted_bucket_position: 0,
weighted_on_stop_tasks: Vec::new(),
task_request_name: None,
request_name: None,
previous_path: None,
previous_method: None,
previous_request_name: None,
was_success: false,
requests: HashMap::new(),
}
}
pub fn set_request_name(&mut self, name: &str) -> &mut Self {
if name != "" {
self.request_name = Some(name.to_string());
}
else {
self.request_name = None;
}
self
}
pub fn set_mode(&mut self, mode: GooseClientMode) {
self.mode = mode;
}
fn get_request(&mut self, path: &str, method: &Method) -> GooseRequest {
let key = format!("{:?} {}", method, path);
trace!("get key: {}", &key);
match self.requests.get(&key) {
Some(r) => r.clone(),
None => GooseRequest::new(path, method.clone()),
}
}
fn set_request(&mut self, path: &str, method: &Method, request: GooseRequest) {
let key = format!("{:?} {}", method, path);
trace!("set key: {}", &key);
self.requests.insert(key, request.clone());
}
pub fn build_url(&mut self, path: &str) -> String {
if let Ok(parsed_path) = Url::parse(path) {
if let Some(_uri) = parsed_path.host() {
return path.to_string()
}
}
let base_url;
if self.config.host.len() > 0 {
base_url = Url::parse(&self.config.host).unwrap();
}
else {
base_url = match &self.task_set_host {
Some(host) => Url::parse(host).unwrap(),
None => Url::parse(&self.default_host.clone().unwrap()).unwrap(),
};
}
match base_url.join(path) {
Ok(url) => url.to_string(),
Err(e) => {
error!("failed to build url from base {} and path {} for task {}: {}", &base_url, &path, self.task_sets_index, e);
std::process::exit(1);
}
}
}
pub fn get(&mut self, path: &str) -> Result<Response, Error> {
let request_builder = self.goose_get(path);
let response = self.goose_send(request_builder);
response
}
pub fn post(&mut self, path: &str, body: String) -> Result<Response, Error> {
let request_builder = self.goose_post(path).body(body);
let response = self.goose_send(request_builder);
response
}
pub fn head(&mut self, path: &str) -> Result<Response, Error> {
let request_builder = self.goose_head(path);
let response = self.goose_send(request_builder);
response
}
pub fn delete(&mut self, path: &str) -> Result<Response, Error> {
let request_builder = self.goose_delete(path);
let response = self.goose_send(request_builder);
response
}
pub fn goose_get(&mut self, path: &str) -> RequestBuilder {
let url = self.build_url(path);
self.client.get(&url)
}
pub fn goose_post(&mut self, path: &str) -> RequestBuilder {
let url = self.build_url(path);
self.client.post(&url)
}
pub fn goose_head(&mut self, path: &str) -> RequestBuilder {
let url = self.build_url(path);
self.client.head(&url)
}
pub fn goose_put(&mut self, path: &str) -> RequestBuilder {
let url = self.build_url(path);
self.client.put(&url)
}
pub fn goose_patch(&mut self, path: &str) -> RequestBuilder {
let url = self.build_url(path);
self.client.patch(&url)
}
pub fn goose_delete(&mut self, path: &str) -> RequestBuilder {
let url = self.build_url(path);
self.client.delete(&url)
}
pub fn goose_send(&mut self, request_builder: RequestBuilder) -> Result<Response, Error> {
let started = Instant::now();
let request = match request_builder.build() {
Ok(r) => r,
Err(e) => {
error!("goose_send failed to build request: {}", e);
std::process::exit(1);
}
};
self.previous_method = Some(request.method().clone());
self.previous_path = match Url::parse(&request.url().to_string()) {
Ok(u) => Some(u.path().to_string()),
Err(e) => {
error!("failed to parse url: {}", e);
None
}
};
self.previous_request_name = self.request_name.clone();
let response = self.client.execute(request);
let elapsed = started.elapsed() * 100;
if self.config.print_stats {
let path = self.previous_path.clone().expect("failed to unwrap previous_path").to_string();
let method = self.previous_method.clone().expect("failed to unwrap previous_method");
let request_name = match &self.request_name {
Some(rn) => rn.to_string(),
None => match &self.task_request_name {
Some(trn) => trn.to_string(),
None => path.to_string(),
}
};
let mut goose_request = self.get_request(&request_name, &method);
goose_request.set_response_time(elapsed.as_millis());
match &response {
Ok(r) => {
let status_code = r.status();
if self.config.status_codes {
goose_request.set_status_code(Some(status_code));
}
debug!("{:?}: status_code {}", &path, status_code);
if status_code.is_success() {
goose_request.success_count += 1;
self.was_success = true;
}
else {
debug!("{:?}: non-success status_code: {:?}", &path, status_code);
goose_request.fail_count += 1;
self.was_success = false;
}
}
Err(e) => {
warn!("{:?}: {}", &path, e);
goose_request.fail_count += 1;
self.was_success = false;
if self.config.status_codes {
goose_request.set_status_code(None);
}
}
};
self.set_request(&request_name, &method, goose_request);
}
match self.request_name {
Some(_) => self.request_name = None,
None => (),
};
response
}
fn get_previous_request_name(&mut self) -> String {
match &self.previous_request_name {
Some(prn) => prn.to_string(),
None => match &self.task_request_name {
Some(trn) => trn.to_string(),
None => self.previous_path.clone().expect("failed to unwrap previous_path").to_string(),
}
}
}
pub fn set_success(&mut self) {
if !self.was_success {
let request_name = self.get_previous_request_name();
let previous_method = self.previous_method.clone().expect("failed to unwrap previous_method");
let mut goose_request = self.get_request(&request_name.to_string(), &previous_method.clone());
goose_request.success_count += 1;
goose_request.fail_count -= 1;
self.set_request(&request_name, &previous_method, goose_request);
}
}
pub fn set_failure(&mut self) {
if self.was_success {
let request_name = self.get_previous_request_name();
let previous_method = self.previous_method.clone().expect("failed to unwrap previous_method");
let mut goose_request = self.get_request(&request_name.to_string(), &previous_method.clone());
goose_request.success_count -= 1;
goose_request.fail_count += 1;
self.set_request(&request_name, &previous_method, goose_request);
}
}
}
#[derive(Clone)]
pub struct GooseTask {
pub tasks_index: usize,
pub name: String,
pub weight: usize,
pub sequence: usize,
pub on_start: bool,
pub on_stop: bool,
pub function: fn(&mut GooseClient),
}
impl GooseTask {
pub fn new(function: fn(&mut GooseClient)) -> Self {
trace!("new task");
let task = GooseTask {
tasks_index: usize::max_value(),
name: "".to_string(),
weight: 1,
sequence: 0,
on_start: false,
on_stop: false,
function: function,
};
task
}
pub fn set_name(mut self, name: &str) -> Self {
trace!("[{}] set_name: {}", self.tasks_index, self.name);
self.name = name.to_string();
self
}
pub fn set_on_start(mut self) -> Self {
trace!("{} [{}] set_on_start task", self.name, self.tasks_index);
self.on_start = true;
self
}
pub fn set_on_stop(mut self) -> Self {
trace!("{} [{}] set_on_stop task", self.name, self.tasks_index);
self.on_stop = true;
self
}
pub fn set_weight(mut self, weight: usize) -> Self {
trace!("{} [{}] set_weight: {}", self.name, self.tasks_index, weight);
if weight < 1 {
error!("{} weight of {} not allowed", self.name, weight);
std::process::exit(1);
}
else {
self.weight = weight;
}
self
}
pub fn set_sequence(mut self, sequence: usize) -> Self {
trace!("{} [{}] set_sequence: {}", self.name, self.tasks_index, sequence);
if sequence < 1 {
info!("setting sequence to 0 for task {} is unnecessary, sequence disabled", self.name);
}
self.sequence = sequence;
self
}
}