swanling/lib.rs
1//! # Swanling
2//!
3//! Have you ever been attacked by a swanling?
4//!
5//! Swanling is a load testing framework inspired by [Locust](https://locust.io/).
6//! User behavior is defined with standard Rust code.
7//!
8//! Swanling load tests, called Swanling Attacks, are built by creating an application
9//! with Cargo, and declaring a dependency on the Swanling library.
10//!
11//! Swanling uses [`reqwest`](https://docs.rs/reqwest/) to provide a convenient HTTP
12//! client.
13//!
14//! ## Documentation
15//!
16//! - [README](https://github.com/begleybrothers/swanling/blob/main/README.md)
17//! - [Developer documentation](https://docs.rs/swanling/)
18//! ## Creating and running a Swanling load test
19//!
20//! ### Creating a simple Swanling load test
21//!
22//! First create a new empty cargo application, for example:
23//!
24//! ```bash
25//! $ cargo new loadtest
26//! Created binary (application) `loadtest` package
27//! $ cd loadtest/
28//! ```
29//!
30//! Add Swanling as a dependency in `Cargo.toml`:
31//!
32//! ```toml
33//! [dependencies]
34//! swanling = "0.12"
35//! ```
36//!
37//! Add the following boilerplate `use` declaration at the top of your `src/main.rs`:
38//!
39//! ```rust
40//! use swanling::prelude::*;
41//! ```
42//!
43//! Using the above prelude will automatically add the following `use` statements
44//! necessary for your load test, so you don't need to manually add them:
45//!
46//! ```rust
47//! use swanling::swanling::{
48//! SwanlingTask, SwanlingTaskError, SwanlingTaskFunction, SwanlingTaskResult, SwanlingTaskSet, SwanlingUser,
49//! };
50//! use swanling::metrics::SwanlingMetrics;
51//! use swanling::{
52//! task, taskset, SwanlingAttack, SwanlingDefault, SwanlingDefaultType, SwanlingError, SwanlingScheduler,
53//! };
54//! ```
55//!
56//! Below your `main` function (which currently is the default `Hello, world!`), add
57//! one or more load test functions. The names of these functions are arbitrary, but it is
58//! recommended you use self-documenting names. Load test functions must be async. Each load
59//! test function must accept a reference to a [`SwanlingUser`](./swanling/struct.SwanlingUser.html) object
60//! and return a [`SwanlingTaskResult`](./swanling/type.SwanlingTaskResult.html). For example:
61//!
62//! ```rust
63//! use swanling::prelude::*;
64//!
65//! async fn loadtest_foo(user: &SwanlingUser) -> SwanlingTaskResult {
66//! let _swanling = user.get("/path/to/foo").await?;
67//!
68//! Ok(())
69//! }
70//! ```
71//!
72//! In the above example, we're using the [`SwanlingUser`](./swanling/struct.SwanlingUser.html) helper
73//! [`get`](./swanling/struct.SwanlingUser.html#method.get) to load a path on the website we are load
74//! testing. This helper creates a
75//! [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
76//! object and uses it to build and execute a request for the above path. If you want access
77//! to the [`RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
78//! object, you can instead use the [`swanling_get`](./swanling/struct.SwanlingUser.html#method.swanling_get)
79//! helper, for example to set a timeout on this specific request:
80//!
81//! ```rust
82//! use std::time;
83//!
84//! use swanling::prelude::*;
85//!
86//! async fn loadtest_bar(user: &SwanlingUser) -> SwanlingTaskResult {
87//! let request_builder = user.swanling_get("/path/to/bar").await?;
88//! let _swanling = user.swanling_send(request_builder.timeout(time::Duration::from_secs(3)), None).await?;
89//!
90//! Ok(())
91//! }
92//! ```
93//!
94//! We pass the [`RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
95//! object to [`swanling_send`](./swanling/struct.SwanlingUser.html#method.swanling_send) which builds and
96//! executes it, also collecting useful metrics. The
97//! [`.await`](https://doc.rust-lang.org/std/keyword.await.html) at the end is necessary as
98//! [`swanling_send`](./swanling/struct.SwanlingUser.html#method.swanling_send) is an async function.
99//!
100//! Once all our tasks are created, we edit the main function to initialize swanling and register
101//! the tasks. In this very simple example we only have two tasks to register, while in a real
102//! load test you can have any number of task sets with any number of individual tasks.
103//!
104//! ```rust
105//! use swanling::prelude::*;
106//!
107//! fn main() -> Result<(), SwanlingError> {
108//! let _swanling_metrics = SwanlingAttack::initialize()?
109//! .register_taskset(taskset!("LoadtestTasks")
110//! // Register the foo task, assigning it a weight of 10.
111//! .register_task(task!(loadtest_foo).set_weight(10)?)
112//! // Register the bar task, assigning it a weight of 2 (so it
113//! // runs 1/5 as often as bar). Apply a task name which shows up
114//! // in metrics.
115//! .register_task(task!(loadtest_bar).set_name("bar").set_weight(2)?)
116//! )
117//! // You could also set a default host here, for example:
118//! .set_default(SwanlingDefault::Host, "http://dev.local/")?
119//! // We set a default run time so this test runs to completion.
120//! .set_default(SwanlingDefault::RunTime, 1)?
121//! .execute()?;
122//!
123//! Ok(())
124//! }
125//!
126//! // A task function that loads `/path/to/foo`.
127//! async fn loadtest_foo(user: &SwanlingUser) -> SwanlingTaskResult {
128//! let _swanling = user.get("/path/to/foo").await?;
129//!
130//! Ok(())
131//! }
132//!
133//! // A task function that loads `/path/to/bar`.
134//! async fn loadtest_bar(user: &SwanlingUser) -> SwanlingTaskResult {
135//! let _swanling = user.get("/path/to/bar").await?;
136//!
137//! Ok(())
138//! }
139//! ```
140//!
141//! Swanling now spins up a configurable number of users, each simulating a user on your
142//! website. Thanks to [`reqwest`](https://docs.rs/reqwest/), each user maintains its own
143//! web client state, handling cookies and more so your "users" can log in, fill out forms,
144//! and more, as real users on your sites would do.
145//!
146//! ### Running the Swanling load test
147//!
148//! Attempts to run our example will result in an error, as we have not yet defined the
149//! host against which this load test should be run. We intentionally do not hard code the
150//! host in the individual tasks, as this allows us to run the test against different
151//! environments, such as local development, staging, and production.
152//!
153//! ```bash
154//! $ cargo run --release
155//! Compiling loadtest v0.1.0 (~/loadtest)
156//! Finished release [optimized] target(s) in 1.52s
157//! Running `target/release/loadtest`
158//! Error: InvalidOption { option: "--host", value: "", detail: "A host must be defined via the --host option, the SwanlingAttack.set_default() function, or the SwanlingTaskSet.set_host() function (no host defined for WebsiteUser)." }
159//! ```
160//! Pass in the `-h` flag to see all available run-time options. For now, we'll use a few
161//! options to customize our load test.
162//!
163//! ```bash
164//! $ cargo run --release -- --host http://dev.local -t 30s -v
165//! ```
166//!
167//! The first option we specified is `--host`, and in this case tells Swanling to run the load test
168//! against a VM on my local network. The `-t 30s` option tells Swanling to end the load test after 30
169//! seconds (for real load tests you'll certainly want to run it longer, you can use `h`, `m`, and
170//! `s` to specify hours, minutes and seconds respectively. For example, `-t1h30m` would run the
171//! load test for 1 hour 30 minutes). Finally, the `-v` flag tells swanling to display INFO and higher
172//! level logs to stdout, giving more insight into what is happening. (Additional `-v` flags will
173//! result in considerably more debug output, and are not recommended for running actual load tests;
174//! they're only useful if you're trying to debug Swanling itself.)
175//!
176//! Running the test results in the following output (broken up to explain it as it goes):
177//!
178//! ```bash
179//! Finished release [optimized] target(s) in 0.05s
180//! Running `target/release/loadtest --host 'http://dev.local' -t 30s -v`
181//! 15:42:23 [ INFO] Output verbosity level: INFO
182//! 15:42:23 [ INFO] Logfile verbosity level: WARN
183//! ```
184//!
185//! If we set the `--log-file` flag, Swanling will write a log file with WARN and higher level logs
186//! as you run the test from (add a `-g` flag to log all INFO and higher level logs).
187//!
188//! ```bash
189//! 15:42:23 [ INFO] concurrent users defaulted to 8 (number of CPUs)
190//! 15:42:23 [ INFO] run_time = 30
191//! 15:42:23 [ INFO] hatch_rate = 1
192//! ```
193//!
194//! Swanling will default to launching 1 user per available CPU core, and will launch them all in
195//! one second. You can change how many users are launched with the `-u` option, and you can
196//! change how many users are launched per second with the `-r` option. For example, `-u30 -r2`
197//! would launch 30 users over 15 seconds (two users per second).
198//!
199//! ```bash
200//! 15:42:23 [ INFO] global host configured: http://dev.local/
201//! 15:42:23 [ INFO] initializing user states...
202//! 15:42:23 [ INFO] launching user 1 from LoadtestTasks...
203//! 15:42:24 [ INFO] launching user 2 from LoadtestTasks...
204//! 15:42:25 [ INFO] launching user 3 from LoadtestTasks...
205//! 15:42:26 [ INFO] launching user 4 from LoadtestTasks...
206//! 15:42:27 [ INFO] launching user 5 from LoadtestTasks...
207//! 15:42:28 [ INFO] launching user 6 from LoadtestTasks...
208//! 15:42:29 [ INFO] launching user 7 from LoadtestTasks...
209//! 15:42:30 [ INFO] launching user 8 from LoadtestTasks...
210//! 15:42:31 [ INFO] launched 8 users...
211//! 15:42:31 [ INFO] printing running metrics after 8 seconds...
212//! ```
213//!
214//! Each user is launched in its own thread with its own user state. Swanling is able to make
215//! very efficient use of server resources. By default Swanling resets the metrics after all
216//! users are launched, but first it outputs the metrics collected while ramping up:
217//!
218//! ```bash
219//! 15:42:31 [ INFO] printing running metrics after 8 seconds...
220//!
221//! === PER TASK METRICS ===
222//! ------------------------------------------------------------------------------
223//! Name | # times run | # fails | task/s | fail/s
224//! ------------------------------------------------------------------------------
225//! 1: LoadtestTasks |
226//! 1: | 2,033 | 0 (0%) | 254.12 | 0.00
227//! 2: bar | 407 | 0 (0%) | 50.88 | 0.00
228//! -------------------------+---------------+----------------+----------+--------
229//! Aggregated | 2,440 | 0 (0%) | 305.00 | 0.00
230//! ------------------------------------------------------------------------------
231//! Name | Avg (ms) | Min | Max | Median
232//! ------------------------------------------------------------------------------
233//! 1: LoadtestTasks |
234//! 1: | 14.23 | 6 | 32 | 14
235//! 2: bar | 14.13 | 6 | 30 | 14
236//! -------------------------+-------------+------------+-------------+-----------
237//! Aggregated | 14.21 | 6 | 32 | 14
238//!
239//! === PER REQUEST METRICS ===
240//! ------------------------------------------------------------------------------
241//! Name | # reqs | # fails | req/s | fail/s
242//! ------------------------------------------------------------------------------
243//! GET / | 2,033 | 0 (0%) | 254.12 | 0.00
244//! GET bar | 407 | 0 (0%) | 50.88 | 0.00
245//! -------------------------+---------------+----------------+----------+--------
246//! Aggregated | 2,440 | 0 (0%) | 305.00 | 0.00
247//! ------------------------------------------------------------------------------
248//! Name | Avg (ms) | Min | Max | Median
249//! ------------------------------------------------------------------------------
250//! GET / | 14.18 | 6 | 32 | 14
251//! GET bar | 14.08 | 6 | 30 | 14
252//! -------------------------+-------------+------------+-------------+-----------
253//! Aggregated | 14.16 | 6 | 32 | 14
254//!
255//! All 8 users hatched, resetting metrics (disable with --no-reset-metrics).
256//! ```
257//!
258//! Swanling can optionally display running metrics if started with `--running-metrics INT`
259//! where INT is an integer value in seconds. For example, if Swanling is started with
260//! `--running-metrics 15` it will display running values approximately every 15 seconds.
261//! Running metrics are broken into several tables. First are the per-task metrics which
262//! are further split into two sections. The first section shows how many requests have
263//! been made, how many of them failed (non-2xx response), and the corresponding per-second
264//! rates.
265//!
266//! This table shows details for all Task Sets and all Tasks defined by your load test,
267//! regardless of if they actually run. This can be useful to ensure that you have set
268//! up weighting as intended, and that you are simulating enough users. As our first
269//! task wasn't named, it just showed up as "1:". Our second task was named, so it shows
270//! up as the name we gave it, "bar".
271//!
272//! ```bash
273//! 15:42:46 [ INFO] printing running metrics after 15 seconds...
274//!
275//! === PER TASK METRICS ===
276//! ------------------------------------------------------------------------------
277//! Name | # times run | # fails | task/s | fail/s
278//! ------------------------------------------------------------------------------
279//! 1: LoadtestTasks |
280//! 1: | 4,618 | 0 (0%) | 307.87 | 0.00
281//! 2: bar | 924 | 0 (0%) | 61.60 | 0.00
282//! -------------------------+---------------+----------------+----------+--------
283//! Aggregated | 5,542 | 0 (0%) | 369.47 | 0.00
284//! ------------------------------------------------------------------------------
285//! Name | Avg (ms) | Min | Max | Median
286//! ------------------------------------------------------------------------------
287//! 1: LoadtestTasks |
288//! 1: | 21.17 | 8 | 151 | 19
289//! 2: bar | 21.62 | 9 | 156 | 19
290//! -------------------------+-------------+------------+-------------+-----------
291//! Aggregated | 21.24 | 8 | 156 | 19
292//! ```
293//!
294//! The second table breaks down the same metrics by request instead of by Task. For
295//! our simple load test, each Task only makes a single request, so the metrics are
296//! the same. There are two main differences. First, metrics are listed by request
297//! type and path or name. The first request shows up as `GET /path/to/foo` as the
298//! request was not named. The second request shows up as `GET bar` as the request
299//! was named. The times to complete each are slightly smaller as this is only the
300//! time to make the request, not the time for Swanling to execute the entire task.
301//!
302//! ```bash
303//! === PER REQUEST METRICS ===
304//! ------------------------------------------------------------------------------
305//! Name | # reqs | # fails | req/s | fail/s
306//! ------------------------------------------------------------------------------
307//! GET /path/to/foo | 4,618 | 0 (0%) | 307.87 | 0.00
308//! GET bar | 924 | 0 (0%) | 61.60 | 0.00
309//! -------------------------+---------------+----------------+----------+--------
310//! Aggregated | 5,542 | 0 (0%) | 369.47 | 0.00
311//! ------------------------------------------------------------------------------
312//! Name | Avg (ms) | Min | Max | Median
313//! ------------------------------------------------------------------------------
314//! GET /path/to/foo | 21.13 | 8 | 151 | 19
315//! GET bar | 21.58 | 9 | 156 | 19
316//! -------------------------+-------------+------------+-------------+-----------
317//! Aggregated | 21.20 | 8 | 156 | 19
318//! ```
319//!
320//! Note that Swanling respected the per-task weights we set, and `foo` (with a weight of 10)
321//! is being loaded five times as often as `bar` (with a weight of 2). On average
322//! each page is returning within `21.2` milliseconds. The quickest page response was
323//! for `foo` in `8` milliseconds. The slowest page response was for `bar` in `156`
324//! milliseconds.
325//!
326//! ```bash
327//! 15:43:02 [ INFO] stopping after 30 seconds...
328//! 15:43:02 [ INFO] waiting for users to exit
329//! 15:43:02 [ INFO] exiting user 3 from LoadtestTasks...
330//! 15:43:02 [ INFO] exiting user 4 from LoadtestTasks...
331//! 15:43:02 [ INFO] exiting user 5 from LoadtestTasks...
332//! 15:43:02 [ INFO] exiting user 8 from LoadtestTasks...
333//! 15:43:02 [ INFO] exiting user 2 from LoadtestTasks...
334//! 15:43:02 [ INFO] exiting user 7 from LoadtestTasks...
335//! 15:43:02 [ INFO] exiting user 6 from LoadtestTasks...
336//! 15:43:02 [ INFO] exiting user 1 from LoadtestTasks...
337//! 15:43:02 [ INFO] printing metrics after 30 seconds...
338//! ```
339//!
340//! Our example only runs for 30 seconds, so we only see running metrics once. When
341//! the test completes, we get more detail in the final summary. The first two tables
342//! are the same as what we saw earlier, however now they include all metrics for the
343//! entire length of the load test:
344//!
345//! ```bash
346//! === PER TASK METRICS ===
347//! ------------------------------------------------------------------------------
348//! Name | # times run | # fails | task/s | fail/s
349//! ------------------------------------------------------------------------------
350//! 1: LoadtestTasks |
351//! 1: | 9,974 | 0 (0%) | 332.47 | 0.00
352//! 2: bar | 1,995 | 0 (0%) | 66.50 | 0.00
353//! -------------------------+---------------+----------------+----------+--------
354//! Aggregated | 11,969 | 0 (0%) | 398.97 | 0.00
355//! ------------------------------------------------------------------------------
356//! Name | Avg (ms) | Min | Max | Median
357//! ------------------------------------------------------------------------------
358//! 1: LoadtestTasks |
359//! 1: | 19.65 | 8 | 151 | 18
360//! 2: bar | 19.92 | 9 | 156 | 18
361//! -------------------------+-------------+------------+-------------+-----------
362//! Aggregated | 19.69 | 8 | 156 | 18
363//!
364//! === PER REQUEST METRICS ===
365//! ------------------------------------------------------------------------------
366//! Name | # reqs | # fails | req/s | fail/s
367//! ------------------------------------------------------------------------------
368//! GET / | 9,974 | 0 (0%) | 332.47 | 0.00
369//! GET bar | 1,995 | 0 (0%) | 66.50 | 0.00
370//! -------------------------+---------------+----------------+----------+--------
371//! Aggregated | 11,969 | 0 (0%) | 398.97 | 0.00
372//! ------------------------------------------------------------------------------
373//! Name | Avg (ms) | Min | Max | Median
374//! ------------------------------------------------------------------------------
375//! GET / | 19.61 | 8 | 151 | 18
376//! GET bar | 19.88 | 9 | 156 | 18
377//! -------------------------+-------------+------------+-------------+-----------
378//! Aggregated | 19.66 | 8 | 156 | 18
379//! ------------------------------------------------------------------------------
380//! ```
381//!
382//! The ratio between `foo` and `bar` remained 5:2 as expected.
383//!
384//! ```bash
385//! ------------------------------------------------------------------------------
386//! Slowest page load within specified percentile of requests (in ms):
387//! ------------------------------------------------------------------------------
388//! Name | 50% | 75% | 98% | 99% | 99.9% | 99.99%
389//! ------------------------------------------------------------------------------
390//! GET / | 18 | 21 | 29 | 79 | 140 | 140
391//! GET bar | 18 | 21 | 29 | 120 | 150 | 150
392//! -------------------------+--------+--------+--------+--------+--------+-------
393//! Aggregated | 18 | 21 | 29 | 84 | 140 | 156
394//! ```
395//!
396//! A new table shows additional information, breaking down response-time by
397//! percentile. This shows that the slowest page loads only happened in the
398//! slowest 1% of page loads, so were an edge case. 98% of the time page loads
399//! happened in 29 milliseconds or less.
400//!
401//! ## License
402//!
403//! Copyright 2020-21 Jeremy Andrews
404//!
405//! Licensed under the Apache License, Version 2.0 (the "License");
406//! you may not use this file except in compliance with the License.
407//! You may obtain a copy of the License at
408//!
409//! [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
410//!
411//! Unless required by applicable law or agreed to in writing, software
412//! distributed under the License is distributed on an "AS IS" BASIS,
413//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
414//! See the License for the specific language governing permissions and
415//! limitations under the License.
416
417#[macro_use]
418extern crate log;
419
420pub mod controller;
421pub mod logger;
422#[cfg(feature = "gaggle")]
423mod manager;
424pub mod metrics;
425pub mod prelude;
426mod report;
427pub mod swanling;
428mod throttle;
429mod user;
430pub mod util;
431#[cfg(feature = "gaggle")]
432mod worker;
433
434use chrono::prelude::*;
435use gumdrop::Options;
436use lazy_static::lazy_static;
437#[cfg(feature = "gaggle")]
438use nng::Socket;
439use rand::seq::SliceRandom;
440use rand::thread_rng;
441use serde::{Deserialize, Serialize};
442use simplelog::*;
443use std::collections::hash_map::DefaultHasher;
444use std::collections::BTreeMap;
445use std::hash::{Hash, Hasher};
446use std::path::PathBuf;
447use std::sync::{
448 atomic::{AtomicBool, AtomicUsize, Ordering},
449 Arc,
450};
451use std::{fmt, io, time};
452use tokio::fs::File;
453use tokio::runtime::Runtime;
454
455use crate::controller::{SwanlingControllerProtocol, SwanlingControllerRequest};
456use crate::logger::{SwanlingLogFormat, SwanlingLoggerJoinHandle, SwanlingLoggerTx};
457use crate::metrics::{SwanlingCoordinatedOmissionMitigation, SwanlingMetric, SwanlingMetrics};
458use crate::swanling::{
459 GaggleUser, SwanlingTask, SwanlingTaskSet, SwanlingUser, SwanlingUserCommand,
460};
461#[cfg(feature = "gaggle")]
462use crate::worker::{register_shutdown_pipe_handler, GaggleMetrics};
463
464/// Constant defining Swanling's default port when running a Regatta.
465const DEFAULT_PORT: &str = "5115";
466
467/// Constant defining Swanling's default telnet Controller port.
468const DEFAULT_TELNET_PORT: &str = "5116";
469
470/// Constant defining Swanling's default WebSocket Controller port.
471const DEFAULT_WEBSOCKET_PORT: &str = "5117";
472
473// WORKER_ID is only used when running a gaggle (a distributed load test).
474lazy_static! {
475 static ref WORKER_ID: AtomicUsize = AtomicUsize::new(0);
476}
477
478/// Internal representation of a weighted task list.
479type WeightedSwanlingTasks = Vec<(usize, String)>;
480
481/// Internal representation of unsequenced tasks.
482type UnsequencedSwanlingTasks = Vec<SwanlingTask>;
483/// Internal representation of sequenced tasks.
484type SequencedSwanlingTasks = BTreeMap<usize, Vec<SwanlingTask>>;
485
486/// Returns the unique identifier of the running Worker when running in Regatta mode.
487///
488/// The first Worker to connect to the Manager is assigned an ID of 1. For each
489/// subsequent Worker to connect to the Manager the ID is incremented by 1. This
490/// identifier is primarily an aid in tracing logs.
491pub fn get_worker_id() -> usize {
492 WORKER_ID.load(Ordering::Relaxed)
493}
494
495#[cfg(not(feature = "gaggle"))]
496#[derive(Debug, Clone)]
497/// Socket used for coordinating a Regatta distributed load test.
498pub(crate) struct Socket {}
499
500/// An enumeration of all errors a [`SwanlingAttack`](./struct.SwanlingAttack.html) can return.
501#[derive(Debug)]
502pub enum SwanlingError {
503 /// Wraps a [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html).
504 Io(io::Error),
505 /// Wraps a [`reqwest::Error`](https://docs.rs/reqwest/*/reqwest/struct.Error.html).
506 Reqwest(reqwest::Error),
507 /// Wraps a ['tokio::task::JoinError'](https://tokio-rs.github.io/tokio/doc/tokio/task/struct.JoinError.html).
508 TokioJoin(tokio::task::JoinError),
509 //std::convert::From<tokio::task::JoinError>
510 /// Failed attempt to use code that requires a compile-time feature be enabled.
511 FeatureNotEnabled {
512 /// The missing compile-time feature.
513 feature: String,
514 /// An optional explanation of the error.
515 detail: String,
516 },
517 /// Failed to parse a hostname.
518 InvalidHost {
519 /// The invalid hostname that caused this error.
520 host: String,
521 /// An optional explanation of the error.
522 detail: String,
523 /// Wraps a [`url::ParseError`](https://docs.rs/url/*/url/enum.ParseError.html).
524 parse_error: url::ParseError,
525 },
526 /// Invalid option or value specified, may only be invalid in context.
527 InvalidOption {
528 /// The invalid option that caused this error, may be only invalid in context.
529 option: String,
530 /// The invalid value that caused this error, may be only invalid in context.
531 value: String,
532 /// An optional explanation of the error.
533 detail: String,
534 },
535 /// Invalid wait time specified.
536 InvalidWaitTime {
537 // The specified minimum wait time.
538 min_wait: usize,
539 // The specified maximum wait time.
540 max_wait: usize,
541 /// An optional explanation of the error.
542 detail: String,
543 },
544 /// Invalid weight specified.
545 InvalidWeight {
546 // The specified weight.
547 weight: usize,
548 /// An optional explanation of the error.
549 detail: String,
550 },
551 /// [`SwanlingAttack`](./struct.SwanlingAttack.html) has no [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html) defined.
552 NoTaskSets {
553 /// An optional explanation of the error.
554 detail: String,
555 },
556}
557/// Implement a helper to provide a text description of all possible types of errors.
558impl SwanlingError {
559 fn describe(&self) -> &str {
560 match *self {
561 SwanlingError::Io(_) => "io::Error",
562 SwanlingError::Reqwest(_) => "reqwest::Error",
563 SwanlingError::TokioJoin(_) => "tokio::task::JoinError",
564 SwanlingError::FeatureNotEnabled { .. } => "required compile-time feature not enabled",
565 SwanlingError::InvalidHost { .. } => "failed to parse hostname",
566 SwanlingError::InvalidOption { .. } => "invalid option or value specified",
567 SwanlingError::InvalidWaitTime { .. } => "invalid wait_time specified",
568 SwanlingError::InvalidWeight { .. } => "invalid weight specified",
569 SwanlingError::NoTaskSets { .. } => "no task sets defined",
570 }
571 }
572}
573
574/// Implement format trait to allow displaying errors.
575impl fmt::Display for SwanlingError {
576 // Implement display of error with `{}` marker.
577 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
578 match *self {
579 SwanlingError::Io(ref source) => {
580 write!(f, "SwanlingError: {} ({})", self.describe(), source)
581 }
582 SwanlingError::Reqwest(ref source) => {
583 write!(f, "SwanlingError: {} ({})", self.describe(), source)
584 }
585 SwanlingError::TokioJoin(ref source) => {
586 write!(f, "SwanlingError: {} ({})", self.describe(), source)
587 }
588 SwanlingError::InvalidHost {
589 ref parse_error, ..
590 } => write!(f, "SwanlingError: {} ({})", self.describe(), parse_error),
591 _ => write!(f, "SwanlingError: {}", self.describe()),
592 }
593 }
594}
595
596// Define the lower level source of this error, if any.
597impl std::error::Error for SwanlingError {
598 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
599 match *self {
600 SwanlingError::Io(ref source) => Some(source),
601 SwanlingError::Reqwest(ref source) => Some(source),
602 SwanlingError::TokioJoin(ref source) => Some(source),
603 SwanlingError::InvalidHost {
604 ref parse_error, ..
605 } => Some(parse_error),
606 _ => None,
607 }
608 }
609}
610
611/// Auto-convert Reqwest errors.
612impl From<reqwest::Error> for SwanlingError {
613 fn from(err: reqwest::Error) -> SwanlingError {
614 SwanlingError::Reqwest(err)
615 }
616}
617
618/// Auto-convert IO errors.
619impl From<io::Error> for SwanlingError {
620 fn from(err: io::Error) -> SwanlingError {
621 SwanlingError::Io(err)
622 }
623}
624
625/// Auto-convert TokioJoin errors.
626impl From<tokio::task::JoinError> for SwanlingError {
627 fn from(err: tokio::task::JoinError) -> SwanlingError {
628 SwanlingError::TokioJoin(err)
629 }
630}
631
632#[derive(Clone, Debug, PartialEq)]
633/// A [`SwanlingAttack`](./struct.SwanlingAttack.html) load test operates in one (and only one)
634/// of the following modes.
635pub enum AttackMode {
636 /// During early startup before one of the following modes gets assigned.
637 Undefined,
638 /// A single standalone process performing a load test.
639 StandAlone,
640 /// The controlling process in a Regatta distributed load test.
641 Manager,
642 /// One of one or more working processes in a Regatta distributed load test.
643 Worker,
644}
645
646#[derive(Clone, Debug, PartialEq)]
647/// A [`SwanlingAttack`](./struct.SwanlingAttack.html) load test moves through each of the following
648/// phases during a complete load test.
649pub enum AttackPhase {
650 /// No load test is running, configuration can be changed by a Controller.
651 Idle,
652 /// [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s are launching and beginning to generate
653 /// load.
654 Starting,
655 /// All [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s have launched and are generating load.
656 Running,
657 /// [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s are stopping.
658 Stopping,
659 /// Exiting the load test.
660 Shutdown,
661}
662
663#[derive(Clone, Debug, PartialEq)]
664/// Used to define the order [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s and
665/// [`SwanlingTask`](./swanling/struct.SwanlingTask.html)s are allocated.
666///
667/// In order to configure the scheduler, and to see examples of the different scheduler
668/// variants, review the
669/// [`SwanlingAttack::set_scheduler`](./struct.SwanlingAttack.html#method.set_scheduler)
670/// documentation.
671pub enum SwanlingScheduler {
672 /// Allocate one of each available type at a time (default).
673 RoundRobin,
674 /// Allocate in the order and weighting defined.
675 Serial,
676 /// Allocate in a random order.
677 Random,
678}
679
680/// Optional default values for Swanling run-time options.
681#[derive(Clone, Debug, Default)]
682pub struct SwanlingDefaults {
683 /// An optional default host to run this load test against.
684 host: Option<String>,
685 /// An optional default number of users to simulate.
686 users: Option<usize>,
687 /// An optional default number of clients to start per second.
688 hatch_rate: Option<String>,
689 /// An optional default number of seconds for the test to run.
690 run_time: Option<usize>,
691 /// An optional default log level.
692 log_level: Option<u8>,
693 /// An optional default for the swanling log file name.
694 swanling_log: Option<String>,
695 /// An optional default value for verbosity level.
696 verbose: Option<u8>,
697 /// An optional default for printing running metrics.
698 running_metrics: Option<usize>,
699 /// An optional default for not resetting metrics after all users started.
700 no_reset_metrics: Option<bool>,
701 /// An optional default for not tracking metrics.
702 no_metrics: Option<bool>,
703 /// An optional default for not tracking task metrics.
704 no_task_metrics: Option<bool>,
705 /// An optional default for not displaying an error summary.
706 no_error_summary: Option<bool>,
707 /// An optional default for the html-formatted report file name.
708 report_file: Option<String>,
709 /// An optional default for the requests log file name.
710 request_log: Option<String>,
711 /// An optional default for the requests log file format.
712 request_format: Option<SwanlingLogFormat>,
713 /// An optional default for the tasks log file name.
714 task_log: Option<String>,
715 /// An optional default for the tasks log file format.
716 task_format: Option<SwanlingLogFormat>,
717 /// An optional default for the error log file name.
718 error_log: Option<String>,
719 /// An optional default for the error log format.
720 error_format: Option<SwanlingLogFormat>,
721 /// An optional default for the debug log file name.
722 debug_log: Option<String>,
723 /// An optional default for the debug log format.
724 debug_format: Option<SwanlingLogFormat>,
725 /// An optional default for not logging response body in debug log.
726 no_debug_body: Option<bool>,
727 /// An optional default for not enabling telnet Controller thread.
728 no_telnet: Option<bool>,
729 /// An optional default for not enabling WebSocket Controller thread.
730 no_websocket: Option<bool>,
731 /// An optional default for not auto-starting the load test.
732 no_autostart: Option<bool>,
733 /// An optional default for coordinated omission mitigation.
734 co_mitigation: Option<SwanlingCoordinatedOmissionMitigation>,
735 /// An optional default to track additional status code metrics.
736 status_codes: Option<bool>,
737 /// An optional default maximum requests per second.
738 throttle_requests: Option<usize>,
739 /// An optional default to follows base_url redirect with subsequent request.
740 sticky_follow: Option<bool>,
741 /// An optional default to enable Manager mode.
742 manager: Option<bool>,
743 /// An optional default for number of Workers to expect.
744 expect_workers: Option<u16>,
745 /// An optional default for Manager to ignore load test checksum.
746 no_hash_check: Option<bool>,
747 /// An optional default for host telnet Controller listens on.
748 telnet_host: Option<String>,
749 /// An optional default for port telnet Controller listens on.
750 telnet_port: Option<u16>,
751 /// An optional default for host WebSocket Controller listens on.
752 websocket_host: Option<String>,
753 /// An optional default for port WebSocket Controller listens on.
754 websocket_port: Option<u16>,
755 /// An optional default for host Manager listens on.
756 manager_bind_host: Option<String>,
757 /// An optional default for port Manager listens on.
758 manager_bind_port: Option<u16>,
759 /// An optional default to enable Worker mode.
760 worker: Option<bool>,
761 /// An optional default for host Worker connects to.
762 manager_host: Option<String>,
763 /// An optional default for port Worker connects to.
764 manager_port: Option<u16>,
765}
766
767/// Allows the optional configuration of Swanling's defaults.
768#[derive(Debug)]
769pub enum SwanlingDefault {
770 /// An optional default host to run this load test against.
771 Host,
772 /// An optional default number of users to simulate.
773 Users,
774 /// An optional default number of clients to start per second.
775 HatchRate,
776 /// An optional default number of seconds for the test to run.
777 RunTime,
778 /// An optional default log level.
779 LogLevel,
780 /// An optional default for the log file name.
781 SwanlingLog,
782 /// An optional default value for verbosity level.
783 Verbose,
784 /// An optional default for printing running metrics.
785 RunningMetrics,
786 /// An optional default for not resetting metrics after all users started.
787 NoResetMetrics,
788 /// An optional default for not tracking metrics.
789 NoMetrics,
790 /// An optional default for not tracking task metrics.
791 NoTaskMetrics,
792 /// An optional default for not displaying an error summary.
793 NoErrorSummary,
794 /// An optional default for the report file name.
795 ReportFile,
796 /// An optional default for the request log file name.
797 RequestLog,
798 /// An optional default for the request log file format.
799 RequestFormat,
800 /// An optional default for the task log file name.
801 TaskLog,
802 /// An optional default for the task log file format.
803 TaskFormat,
804 /// An optional default for the error log file name.
805 ErrorLog,
806 /// An optional default for the error log format.
807 ErrorFormat,
808 /// An optional default for the debug log file name.
809 DebugLog,
810 /// An optional default for the debug log format.
811 DebugFormat,
812 /// An optional default for not logging the response body in the debug log.
813 NoDebugBody,
814 /// An optional default for not enabling telnet Controller thread.
815 NoTelnet,
816 /// An optional default for not enabling WebSocket Controller thread.
817 NoWebSocket,
818 /// An optional default for coordinated omission mitigation.
819 CoordinatedOmissionMitigation,
820 /// An optional default for not automatically starting load test.
821 NoAutoStart,
822 /// An optional default to track additional status code metrics.
823 StatusCodes,
824 /// An optional default maximum requests per second.
825 ThrottleRequests,
826 /// An optional default to follows base_url redirect with subsequent request.
827 StickyFollow,
828 /// An optional default to enable Manager mode.
829 Manager,
830 /// An optional default for number of Workers to expect.
831 ExpectWorkers,
832 /// An optional default for Manager to ignore load test checksum.
833 NoHashCheck,
834 /// An optional default for host telnet Controller listens on.
835 TelnetHost,
836 /// An optional default for port telnet Controller listens on.
837 TelnetPort,
838 /// An optional default for host Websocket Controller listens on.
839 WebSocketHost,
840 /// An optional default for port WebSocket Controller listens on.
841 WebSocketPort,
842 /// An optional default for host Manager listens on.
843 ManagerBindHost,
844 /// An optional default for port Manager listens on.
845 ManagerBindPort,
846 /// An optional default to enable Worker mode.
847 Worker,
848 /// An optional default for host Worker connects to.
849 ManagerHost,
850 /// An optional default for port Worker connects to.
851 ManagerPort,
852}
853
854#[derive(Debug)]
855/// Internal global run state for load test.
856struct SwanlingAttackRunState {
857 /// A timestamp tracking when the previous [`SwanlingUser`](./swanling/struct.SwanlingUser.html)
858 /// was launched.
859 spawn_user_timer: std::time::Instant,
860 /// How many milliseconds until the next [`SwanlingUser`](./swanling/struct.SwanlingUser.html)
861 /// should be spawned.
862 spawn_user_in_ms: usize,
863 /// A counter tracking which [`SwanlingUser`](./swanling/struct.SwanlingUser.html) is being
864 /// spawned.
865 spawn_user_counter: usize,
866 /// This variable accounts for time spent doing things which is then subtracted from
867 /// the time sleeping to avoid an unintentional drift in events that are supposed to
868 /// happen regularly.
869 drift_timer: tokio::time::Instant,
870 /// Unbounded sender used by all [`SwanlingUser`](./swanling/struct.SwanlingUser.html)
871 /// threads to send metrics to parent.
872 all_threads_metrics_tx: flume::Sender<SwanlingMetric>,
873 /// Unbounded receiver used by Swanling parent to receive metrics from
874 /// [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s.
875 metrics_rx: flume::Receiver<SwanlingMetric>,
876 /// Optional unbounded receiver for logger thread, if enabled.
877 logger_handle: SwanlingLoggerJoinHandle,
878 /// Optional unbounded sender from all [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s
879 /// to logger thread, if enabled.
880 all_threads_logger_tx: SwanlingLoggerTx,
881 /// Optional receiver for all [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s from
882 /// throttle thread, if enabled.
883 throttle_threads_tx: Option<flume::Sender<bool>>,
884 /// Optional sender for throttle thread, if enabled.
885 parent_to_throttle_tx: Option<flume::Sender<bool>>,
886 /// Optional channel allowing controller thread to make requests, if not disabled.
887 controller_channel_rx: Option<flume::Receiver<SwanlingControllerRequest>>,
888 /// Optional unbuffered writer for html-formatted report file, if enabled.
889 report_file: Option<File>,
890 /// A flag tracking whether or not the header has been written when the metrics
891 /// log is enabled.
892 metrics_header_displayed: bool,
893 /// When entering the idle phase use this flag to only display a message one time.
894 idle_status_displayed: bool,
895 /// Collection of all [`SwanlingUser`](./swanling/struct.SwanlingUser.html) threads so they
896 /// can be stopped later.
897 users: Vec<tokio::task::JoinHandle<()>>,
898 /// All unbounded senders to allow communication with
899 /// [`SwanlingUser`](./swanling/struct.SwanlingUser.html) threads.
900 user_channels: Vec<flume::Sender<SwanlingUserCommand>>,
901 /// Timer tracking when to display running metrics, if enabled.
902 running_metrics_timer: std::time::Instant,
903 /// Boolean flag indicating if running metrics should be displayed.
904 display_running_metrics: bool,
905 /// Boolean flag indicating if all [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s
906 /// have been spawned.
907 all_users_spawned: bool,
908 /// Boolean flag indicating of Swanling should shutdown after stopping a running load test.
909 shutdown_after_stop: bool,
910 /// Thread-safe boolean flag indicating if the [`SwanlingAttack`](./struct.SwanlingAttack.html)
911 /// has been canceled.
912 canceled: Arc<AtomicBool>,
913 /// Optional socket used to coordinate a distributed Regatta.
914 socket: Option<Socket>,
915}
916
917/// Global internal state for the load test.
918#[derive(Clone)]
919pub struct SwanlingAttack {
920 /// An optional task that is run one time before starting SwanlingUsers and running SwanlingTaskSets.
921 test_start_task: Option<SwanlingTask>,
922 /// An optional task that is run one time after all SwanlingUsers have finished.
923 test_stop_task: Option<SwanlingTask>,
924 /// A vector containing one copy of each SwanlingTaskSet defined by this load test.
925 task_sets: Vec<SwanlingTaskSet>,
926 /// A weighted vector containing a SwanlingUser object for each SwanlingUser that will run during this load test.
927 weighted_users: Vec<SwanlingUser>,
928 /// A weighted vector containing a lightweight GaggleUser object that is sent to all Workers if running in Regatta mode.
929 weighted_gaggle_users: Vec<GaggleUser>,
930 /// Optional default values for Swanling run-time options.
931 defaults: SwanlingDefaults,
932 /// Configuration object holding options set when launching the load test.
933 configuration: SwanlingConfiguration,
934 /// How long (in seconds) the load test should run.
935 run_time: usize,
936 /// The load test operates in only one of the following modes: StandAlone, Manager, or Worker.
937 attack_mode: AttackMode,
938 /// Which phase the load test is currently operating in.
939 attack_phase: AttackPhase,
940 /// Defines the order [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s and
941 /// [`SwanlingTask`](./swanling/struct.SwanlingTask.html)s are allocated.
942 scheduler: SwanlingScheduler,
943 /// When the load test started.
944 started: Option<time::Instant>,
945 /// All metrics merged together.
946 metrics: SwanlingMetrics,
947}
948/// Swanling's internal global state.
949impl SwanlingAttack {
950 /// Load configuration and initialize a [`SwanlingAttack`](./struct.SwanlingAttack.html).
951 ///
952 /// # Example
953 /// ```rust
954 /// use swanling::prelude::*;
955 ///
956 /// let mut swanling_attack = SwanlingAttack::initialize();
957 /// ```
958 pub fn initialize() -> Result<SwanlingAttack, SwanlingError> {
959 Ok(SwanlingAttack {
960 test_start_task: None,
961 test_stop_task: None,
962 task_sets: Vec::new(),
963 weighted_users: Vec::new(),
964 weighted_gaggle_users: Vec::new(),
965 defaults: SwanlingDefaults::default(),
966 configuration: SwanlingConfiguration::parse_args_default_or_exit(),
967 run_time: 0,
968 attack_mode: AttackMode::Undefined,
969 attack_phase: AttackPhase::Idle,
970 scheduler: SwanlingScheduler::RoundRobin,
971 started: None,
972 metrics: SwanlingMetrics::default(),
973 })
974 }
975
976 /// Initialize a [`SwanlingAttack`](./struct.SwanlingAttack.html) with an already loaded
977 /// configuration.
978 ///
979 /// This is generally used by Worker instances and tests.
980 ///
981 /// # Example
982 /// ```rust
983 /// use swanling::{SwanlingAttack, SwanlingConfiguration};
984 /// use gumdrop::Options;
985 ///
986 /// let configuration = SwanlingConfiguration::parse_args_default_or_exit();
987 /// let mut swanling_attack = SwanlingAttack::initialize_with_config(configuration);
988 /// ```
989 pub fn initialize_with_config(
990 configuration: SwanlingConfiguration,
991 ) -> Result<SwanlingAttack, SwanlingError> {
992 Ok(SwanlingAttack {
993 test_start_task: None,
994 test_stop_task: None,
995 task_sets: Vec::new(),
996 weighted_users: Vec::new(),
997 weighted_gaggle_users: Vec::new(),
998 defaults: SwanlingDefaults::default(),
999 configuration,
1000 run_time: 0,
1001 attack_mode: AttackMode::Undefined,
1002 attack_phase: AttackPhase::Idle,
1003 scheduler: SwanlingScheduler::RoundRobin,
1004 started: None,
1005 metrics: SwanlingMetrics::default(),
1006 })
1007 }
1008
1009 /// Optionally initialize the logger which writes to standard out and/or to
1010 /// a configurable log file.
1011 ///
1012 /// This method is invoked by
1013 /// [`SwanlingAttack.execute()`](./struct.SwanlingAttack.html#method.execute).
1014 pub(crate) fn initialize_logger(&self) {
1015 // Allow optionally controlling debug output level
1016 let debug_level;
1017 match self.configuration.verbose {
1018 0 => debug_level = LevelFilter::Warn,
1019 1 => debug_level = LevelFilter::Info,
1020 2 => debug_level = LevelFilter::Debug,
1021 _ => debug_level = LevelFilter::Trace,
1022 }
1023
1024 // Set log level based on run-time option or default if set.
1025 let log_level_value = if self.configuration.log_level > 0 {
1026 self.configuration.log_level
1027 } else if let Some(default_log_level) = self.defaults.log_level {
1028 default_log_level
1029 } else {
1030 0
1031 };
1032 let log_level = match log_level_value {
1033 0 => LevelFilter::Warn,
1034 1 => LevelFilter::Info,
1035 2 => LevelFilter::Debug,
1036 _ => LevelFilter::Trace,
1037 };
1038
1039 let swanling_log: Option<PathBuf>;
1040 // Use --log-file if set.
1041 if !self.configuration.swanling_log.is_empty() {
1042 swanling_log = Some(PathBuf::from(&self.configuration.swanling_log));
1043 }
1044 // Otherwise use swanling_attack.defaults.swanling_log if set.
1045 else if let Some(default_swanling_log) = &self.defaults.swanling_log {
1046 swanling_log = Some(PathBuf::from(default_swanling_log));
1047 }
1048 // Otherwise disable the log.
1049 else {
1050 swanling_log = None;
1051 }
1052
1053 if let Some(log_to_file) = swanling_log {
1054 match CombinedLogger::init(vec![
1055 SimpleLogger::new(debug_level, Config::default()),
1056 WriteLogger::new(
1057 log_level,
1058 Config::default(),
1059 std::fs::File::create(&log_to_file).unwrap(),
1060 ),
1061 ]) {
1062 Ok(_) => (),
1063 Err(e) => {
1064 info!("failed to initialize CombinedLogger: {}", e);
1065 }
1066 }
1067 info!("Writing to log file: {}", log_to_file.display());
1068 } else {
1069 match CombinedLogger::init(vec![SimpleLogger::new(debug_level, Config::default())]) {
1070 Ok(_) => (),
1071 Err(e) => {
1072 info!("failed to initialize CombinedLogger: {}", e);
1073 }
1074 }
1075 }
1076
1077 info!("Output verbosity level: {}", debug_level);
1078 info!("Logfile verbosity level: {}", log_level);
1079 }
1080
1081 /// Define the order [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s are
1082 /// allocated to new [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s as they are
1083 /// launched.
1084 ///
1085 /// By default, [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s are allocated
1086 /// to new [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s in a round robin style.
1087 /// For example, if TaskSet A has a weight of 5, TaskSet B has a weight of 3, and
1088 /// you launch 20 users, they will be launched in the following order:
1089 /// A, B, A, B, A, B, A, A, A, B, A, B, A, B, A, A, A, B, A, B
1090 ///
1091 /// Note that the following pattern is repeated:
1092 /// A, B, A, B, A, B, A, A
1093 ///
1094 /// If reconfigured to schedule serially, then they will instead be allocated in
1095 /// the following order:
1096 /// A, A, A, A, A, B, B, B, A, A, A, A, A, B, B, B, A, A, A, A
1097 ///
1098 /// In the serial case, the following pattern is repeated:
1099 /// A, A, A, A, A, B, B, B
1100 ///
1101 /// In the following example, [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s
1102 /// are allocated to launching [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s in a
1103 /// random order. This means running the test multiple times can generate
1104 /// different amounts of load, as depending on your weighting rules you may
1105 /// have a different number of [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s
1106 /// running each [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html) each time.
1107 ///
1108 /// # Example
1109 /// ```rust
1110 /// use swanling::prelude::*;
1111 ///
1112 /// fn main() -> Result<(), SwanlingError> {
1113 /// SwanlingAttack::initialize()?
1114 /// .set_scheduler(SwanlingScheduler::Random)
1115 /// .register_taskset(taskset!("A Tasks")
1116 /// .set_weight(5)?
1117 /// .register_task(task!(a_task_1))
1118 /// )
1119 /// .register_taskset(taskset!("B Tasks")
1120 /// .set_weight(3)?
1121 /// .register_task(task!(b_task_1))
1122 /// );
1123 ///
1124 /// Ok(())
1125 /// }
1126 ///
1127 /// async fn a_task_1(user: &SwanlingUser) -> SwanlingTaskResult {
1128 /// let _swanling = user.get("/foo").await?;
1129 ///
1130 /// Ok(())
1131 /// }
1132 ///
1133 /// async fn b_task_1(user: &SwanlingUser) -> SwanlingTaskResult {
1134 /// let _swanling = user.get("/bar").await?;
1135 ///
1136 /// Ok(())
1137 /// }
1138 /// ```
1139 pub fn set_scheduler(mut self, scheduler: SwanlingScheduler) -> Self {
1140 self.scheduler = scheduler;
1141 self
1142 }
1143
1144 /// A load test must contain one or more [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s
1145 /// be registered into Swanling's global state with this method for it to run.
1146 ///
1147 /// # Example
1148 /// ```rust
1149 /// use swanling::prelude::*;
1150 ///
1151 /// fn main() -> Result<(), SwanlingError> {
1152 /// SwanlingAttack::initialize()?
1153 /// .register_taskset(taskset!("ExampleTasks")
1154 /// .register_task(task!(example_task))
1155 /// )
1156 /// .register_taskset(taskset!("OtherTasks")
1157 /// .register_task(task!(other_task))
1158 /// );
1159 ///
1160 /// Ok(())
1161 /// }
1162 ///
1163 /// async fn example_task(user: &SwanlingUser) -> SwanlingTaskResult {
1164 /// let _swanling = user.get("/foo").await?;
1165 ///
1166 /// Ok(())
1167 /// }
1168 ///
1169 /// async fn other_task(user: &SwanlingUser) -> SwanlingTaskResult {
1170 /// let _swanling = user.get("/bar").await?;
1171 ///
1172 /// Ok(())
1173 /// }
1174 /// ```
1175 pub fn register_taskset(mut self, mut taskset: SwanlingTaskSet) -> Self {
1176 taskset.task_sets_index = self.task_sets.len();
1177 self.task_sets.push(taskset);
1178 self
1179 }
1180
1181 /// Optionally define a task to run before users are started and all task sets
1182 /// start running. This is would generally be used to set up anything required
1183 /// for the load test.
1184 ///
1185 /// The [`SwanlingUser`](./swanling/struct.SwanlingUser.html) used to run the `test_start`
1186 /// tasks is not preserved and does not otherwise affect the subsequent
1187 /// [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s that run the rest of the load
1188 /// test. For example, if the [`SwanlingUser`](./swanling/struct.SwanlingUser.html)
1189 /// logs in during `test_start`, subsequent [`SwanlingUser`](./swanling/struct.SwanlingUser.html)
1190 /// do not retain this session and are therefor not already logged in.
1191 ///
1192 /// When running in a distributed Regatta, this task is only run one time by the
1193 /// Manager.
1194 ///
1195 /// # Example
1196 /// ```rust
1197 /// use swanling::prelude::*;
1198 ///
1199 /// fn main() -> Result<(), SwanlingError> {
1200 /// SwanlingAttack::initialize()?
1201 /// .test_start(task!(setup));
1202 ///
1203 /// Ok(())
1204 /// }
1205 ///
1206 /// async fn setup(user: &SwanlingUser) -> SwanlingTaskResult {
1207 /// // do stuff to set up load test ...
1208 ///
1209 /// Ok(())
1210 /// }
1211 /// ```
1212 pub fn test_start(mut self, task: SwanlingTask) -> Self {
1213 self.test_start_task = Some(task);
1214 self
1215 }
1216
1217 /// Optionally define a task to run after all users have finished running
1218 /// all defined task sets. This would generally be used to clean up anything
1219 /// that was specifically set up for the load test.
1220 ///
1221 /// When running in a distributed Regatta, this task is only run one time by the
1222 /// Manager.
1223 ///
1224 /// # Example
1225 /// ```rust
1226 /// use swanling::prelude::*;
1227 ///
1228 /// fn main() -> Result<(), SwanlingError> {
1229 /// SwanlingAttack::initialize()?
1230 /// .test_stop(task!(teardown));
1231 ///
1232 /// Ok(())
1233 /// }
1234 ///
1235 /// async fn teardown(user: &SwanlingUser) -> SwanlingTaskResult {
1236 /// // do stuff to tear down the load test ...
1237 ///
1238 /// Ok(())
1239 /// }
1240 /// ```
1241 pub fn test_stop(mut self, task: SwanlingTask) -> Self {
1242 self.test_stop_task = Some(task);
1243 self
1244 }
1245
1246 /// Use configured SwanlingScheduler to build out a properly weighted list of
1247 /// [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html)s to be assigned to
1248 /// [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s
1249 fn allocate_task_sets(&mut self) -> Vec<usize> {
1250 trace!("allocate_task_sets");
1251
1252 let mut u: usize = 0;
1253 let mut v: usize;
1254 for task_set in &self.task_sets {
1255 if u == 0 {
1256 u = task_set.weight;
1257 } else {
1258 v = task_set.weight;
1259 trace!("calculating greatest common denominator of {} and {}", u, v);
1260 u = util::gcd(u, v);
1261 trace!("inner gcd: {}", u);
1262 }
1263 }
1264 // 'u' will always be the greatest common divisor
1265 debug!("gcd: {}", u);
1266
1267 // Build a vector of vectors to be used to schedule users.
1268 let mut available_task_sets = Vec::with_capacity(self.task_sets.len());
1269 let mut total_task_sets = 0;
1270 for (index, task_set) in self.task_sets.iter().enumerate() {
1271 // divide by greatest common divisor so vector is as short as possible
1272 let weight = task_set.weight / u;
1273 trace!(
1274 "{}: {} has weight of {} (reduced with gcd to {})",
1275 index,
1276 task_set.name,
1277 task_set.weight,
1278 weight
1279 );
1280 let weighted_sets = vec![index; weight];
1281 total_task_sets += weight;
1282 available_task_sets.push(weighted_sets);
1283 }
1284
1285 info!(
1286 "allocating tasks and task sets with {:?} scheduler",
1287 self.scheduler
1288 );
1289
1290 // Now build the weighted list with the appropriate scheduler.
1291 let mut weighted_task_sets = Vec::new();
1292 match self.scheduler {
1293 SwanlingScheduler::RoundRobin => {
1294 // Allocate task sets round robin.
1295 let task_sets_len = available_task_sets.len();
1296 loop {
1297 for (task_set_index, task_sets) in available_task_sets
1298 .iter_mut()
1299 .enumerate()
1300 .take(task_sets_len)
1301 {
1302 if let Some(task_set) = task_sets.pop() {
1303 debug!("allocating 1 user from TaskSet {}", task_set_index);
1304 weighted_task_sets.push(task_set);
1305 }
1306 }
1307 if weighted_task_sets.len() >= total_task_sets {
1308 break;
1309 }
1310 }
1311 }
1312 SwanlingScheduler::Serial => {
1313 // Allocate task sets serially in the weighted order defined.
1314 for (task_set_index, task_sets) in available_task_sets.iter().enumerate() {
1315 debug!(
1316 "allocating all {} users from TaskSet {}",
1317 task_sets.len(),
1318 task_set_index
1319 );
1320 weighted_task_sets.append(&mut task_sets.clone());
1321 }
1322 }
1323 SwanlingScheduler::Random => {
1324 // Allocate task sets randomly.
1325 loop {
1326 let task_set = available_task_sets.choose_mut(&mut rand::thread_rng());
1327 match task_set {
1328 Some(set) => {
1329 if let Some(s) = set.pop() {
1330 weighted_task_sets.push(s);
1331 }
1332 }
1333 None => warn!("randomly allocating a SwanlingTaskSet failed, trying again"),
1334 }
1335 if weighted_task_sets.len() >= total_task_sets {
1336 break;
1337 }
1338 }
1339 }
1340 }
1341 weighted_task_sets
1342 }
1343
1344 /// Allocate a vector of weighted [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s.
1345 fn weight_task_set_users(&mut self) -> Result<Vec<SwanlingUser>, SwanlingError> {
1346 trace!("weight_task_set_users");
1347
1348 let weighted_task_sets = self.allocate_task_sets();
1349
1350 // Allocate a state for each user that will be hatched.
1351 info!("initializing user states...");
1352 let mut weighted_users = Vec::new();
1353 let mut user_count = 0;
1354 loop {
1355 for task_sets_index in &weighted_task_sets {
1356 debug!(
1357 "creating user state: {} ({})",
1358 weighted_users.len(),
1359 task_sets_index
1360 );
1361 let base_url = swanling::get_base_url(
1362 self.get_configuration_host(),
1363 self.task_sets[*task_sets_index].host.clone(),
1364 self.defaults.host.clone(),
1365 )?;
1366 weighted_users.push(SwanlingUser::new(
1367 self.task_sets[*task_sets_index].task_sets_index,
1368 base_url,
1369 self.task_sets[*task_sets_index].min_wait,
1370 self.task_sets[*task_sets_index].max_wait,
1371 &self.configuration,
1372 self.metrics.hash,
1373 )?);
1374 user_count += 1;
1375 // Users are required here so unwrap() is safe.
1376 if user_count >= self.configuration.users.unwrap() {
1377 debug!("created {} weighted_users", user_count);
1378 return Ok(weighted_users);
1379 }
1380 }
1381 }
1382 }
1383
1384 /// Allocate a vector of weighted [`GaggleUser`](./swanling/struct.GaggleUser.html).
1385 fn prepare_worker_task_set_users(&mut self) -> Result<Vec<GaggleUser>, SwanlingError> {
1386 trace!("prepare_worker_task_set_users");
1387
1388 let weighted_task_sets = self.allocate_task_sets();
1389
1390 // Determine the users sent to each Worker.
1391 info!("preparing users for Workers...");
1392 let mut weighted_users = Vec::new();
1393 let mut user_count = 0;
1394 loop {
1395 for task_sets_index in &weighted_task_sets {
1396 let base_url = swanling::get_base_url(
1397 self.get_configuration_host(),
1398 self.task_sets[*task_sets_index].host.clone(),
1399 self.defaults.host.clone(),
1400 )?;
1401 weighted_users.push(GaggleUser::new(
1402 self.task_sets[*task_sets_index].task_sets_index,
1403 base_url,
1404 self.task_sets[*task_sets_index].min_wait,
1405 self.task_sets[*task_sets_index].max_wait,
1406 &self.configuration,
1407 self.metrics.hash,
1408 ));
1409 user_count += 1;
1410 // Users are required here so unwrap() is safe.
1411 if user_count >= self.configuration.users.unwrap() {
1412 debug!("prepared {} weighted_gaggle_users", user_count);
1413 return Ok(weighted_users);
1414 }
1415 }
1416 }
1417 }
1418
1419 // Configure which mode this [`SwanlingAttack`](./struct.SwanlingAttack.html)
1420 // will run in.
1421 fn set_attack_mode(&mut self) -> Result<(), SwanlingError> {
1422 // Determine if Manager is enabled by default.
1423 let manager_is_default = if let Some(value) = self.defaults.manager {
1424 value
1425 } else {
1426 false
1427 };
1428
1429 // Determine if Worker is enabled by default.
1430 let worker_is_default = if let Some(value) = self.defaults.worker {
1431 value
1432 } else {
1433 false
1434 };
1435
1436 // Don't allow Manager and Worker to both be the default.
1437 if manager_is_default && worker_is_default {
1438 return Err(SwanlingError::InvalidOption {
1439 option: "SwanlingDefault::Worker".to_string(),
1440 value: "true".to_string(),
1441 detail: "The SwanlingDefault::Worker default can not be set together with the SwanlingDefault::Manager default"
1442 .to_string(),
1443 });
1444 }
1445
1446 // Manager mode if --manager is set, or --worker is not set and Manager is default.
1447 if self.configuration.manager || (!self.configuration.worker && manager_is_default) {
1448 self.attack_mode = AttackMode::Manager;
1449 if self.configuration.worker {
1450 return Err(SwanlingError::InvalidOption {
1451 option: "--worker".to_string(),
1452 value: "true".to_string(),
1453 detail: "The --worker flag can not be set together with the --manager flag"
1454 .to_string(),
1455 });
1456 }
1457
1458 if !self.configuration.debug_log.is_empty() {
1459 return Err(SwanlingError::InvalidOption {
1460 option: "--debug-file".to_string(),
1461 value: self.configuration.debug_log.clone(),
1462 detail:
1463 "The --debug-file option can not be set together with the --manager flag."
1464 .to_string(),
1465 });
1466 }
1467 }
1468
1469 // Worker mode if --worker is set, or --manager is not set and Worker is default.
1470 if self.configuration.worker || (!self.configuration.manager && worker_is_default) {
1471 self.attack_mode = AttackMode::Worker;
1472 if self.configuration.manager {
1473 return Err(SwanlingError::InvalidOption {
1474 option: "--manager".to_string(),
1475 value: "true".to_string(),
1476 detail: "The --manager flag can not be set together with the --worker flag."
1477 .to_string(),
1478 });
1479 }
1480
1481 if !self.configuration.host.is_empty() {
1482 return Err(SwanlingError::InvalidOption {
1483 option: "--host".to_string(),
1484 value: self.configuration.host.clone(),
1485 detail: "The --host option can not be set together with the --worker flag."
1486 .to_string(),
1487 });
1488 }
1489 }
1490
1491 // Otherwise run in standalone attack mode.
1492 if self.attack_mode == AttackMode::Undefined {
1493 self.attack_mode = AttackMode::StandAlone;
1494
1495 if self.configuration.no_hash_check {
1496 return Err(SwanlingError::InvalidOption {
1497 option: "--no-hash-check".to_string(),
1498 value: self.configuration.no_hash_check.to_string(),
1499 detail: "The --no-hash-check flag can not be set without also setting the --manager flag.".to_string(),
1500 });
1501 }
1502 }
1503
1504 Ok(())
1505 }
1506
1507 // Change from one attack_phase to another.
1508 fn set_attack_phase(
1509 &mut self,
1510 swanling_attack_run_state: &mut SwanlingAttackRunState,
1511 phase: AttackPhase,
1512 ) {
1513 // There's nothing to do if already in the specified phase.
1514 if self.attack_phase == phase {
1515 return;
1516 }
1517
1518 // The drift timer starts at 0 any time the phase is changed.
1519 swanling_attack_run_state.drift_timer = tokio::time::Instant::now();
1520
1521 // Optional debug output.
1522 info!("entering SwanlingAttack phase: {:?}", &phase);
1523
1524 // Update the current phase.
1525 self.attack_phase = phase;
1526 }
1527
1528 // Determine how many Workers to expect.
1529 fn set_expect_workers(&mut self) -> Result<(), SwanlingError> {
1530 // Track how value gets set so we can return a meaningful error if necessary.
1531 let mut key = "configuration.expect_workers";
1532
1533 // Check if --expect-workers was set.
1534 if self.configuration.expect_workers.is_some() {
1535 key = "--expect-workers";
1536 // Otherwise check if a custom default is set.
1537 } else if let Some(default_expect_workers) = self.defaults.expect_workers {
1538 if self.attack_mode == AttackMode::Manager {
1539 key = "set_default(SwanlingDefault::ExpectWorkers)";
1540
1541 self.configuration.expect_workers = Some(default_expect_workers);
1542 }
1543 }
1544
1545 if let Some(expect_workers) = self.configuration.expect_workers {
1546 // Disallow --expect-workers without --manager.
1547 if self.attack_mode != AttackMode::Manager {
1548 return Err(SwanlingError::InvalidOption {
1549 option: key.to_string(),
1550 value: expect_workers.to_string(),
1551 detail: format!(
1552 "{} can not be set without also setting the --manager flag.",
1553 key
1554 ),
1555 });
1556 } else {
1557 // Must expect at least 1 Worker when running as Manager.
1558 if expect_workers < 1 {
1559 return Err(SwanlingError::InvalidOption {
1560 option: key.to_string(),
1561 value: expect_workers.to_string(),
1562 detail: format!("{} must be set to at least 1.", key),
1563 });
1564 }
1565
1566 // Must not expect more Workers than Users. Users are required at this point so
1567 // using unwrap() is safe.
1568 if expect_workers as usize > self.configuration.users.unwrap() {
1569 return Err(SwanlingError::InvalidOption {
1570 option: key.to_string(),
1571 value: expect_workers.to_string(),
1572 detail: format!(
1573 "{} can not be set to a value larger than --users option.",
1574 key
1575 ),
1576 });
1577 }
1578 }
1579 }
1580
1581 Ok(())
1582 }
1583
1584 // Configure the host and port the Manager listens on.
1585 fn set_gaggle_host_and_port(&mut self) -> Result<(), SwanlingError> {
1586 // Configure manager_bind_host and manager_bind_port.
1587 if self.attack_mode == AttackMode::Manager {
1588 // Use default if run-time option not set.
1589 if self.configuration.manager_bind_host.is_empty() {
1590 self.configuration.manager_bind_host =
1591 if let Some(host) = self.defaults.manager_bind_host.clone() {
1592 host
1593 } else {
1594 "0.0.0.0".to_string()
1595 }
1596 }
1597
1598 // Use default if run-time option not set.
1599 if self.configuration.manager_bind_port == 0 {
1600 self.configuration.manager_bind_port =
1601 if let Some(port) = self.defaults.manager_bind_port {
1602 port
1603 } else {
1604 DEFAULT_PORT.to_string().parse().unwrap()
1605 };
1606 }
1607 } else {
1608 if !self.configuration.manager_bind_host.is_empty() {
1609 return Err(SwanlingError::InvalidOption {
1610 option: "--manager-bind-host".to_string(),
1611 value: self.configuration.manager_bind_host.clone(),
1612 detail: "The --manager-bind-host option can not be set together with the --worker flag.".to_string(),
1613 });
1614 }
1615
1616 if self.configuration.manager_bind_port != 0 {
1617 return Err(SwanlingError::InvalidOption {
1618 option: "--manager-bind-port".to_string(),
1619 value: self.configuration.manager_bind_port.to_string(),
1620 detail: "The --manager-bind-port option can not be set together with the --worker flag.".to_string(),
1621 });
1622 }
1623 }
1624
1625 // Configure manager_host and manager_port.
1626 if self.attack_mode == AttackMode::Worker {
1627 // Use default if run-time option not set.
1628 if self.configuration.manager_host.is_empty() {
1629 self.configuration.manager_host =
1630 if let Some(host) = self.defaults.manager_host.clone() {
1631 host
1632 } else {
1633 "127.0.0.1".to_string()
1634 }
1635 }
1636
1637 // Use default if run-time option not set.
1638 if self.configuration.manager_port == 0 {
1639 self.configuration.manager_port = if let Some(port) = self.defaults.manager_port {
1640 port
1641 } else {
1642 DEFAULT_PORT.to_string().parse().unwrap()
1643 };
1644 }
1645 } else {
1646 if !self.configuration.manager_host.is_empty() {
1647 return Err(SwanlingError::InvalidOption {
1648 option: "--manager-host".to_string(),
1649 value: self.configuration.manager_host.clone(),
1650 detail:
1651 "The --manager-host option must be set together with the --worker flag."
1652 .to_string(),
1653 });
1654 }
1655
1656 if self.configuration.manager_port != 0 {
1657 return Err(SwanlingError::InvalidOption {
1658 option: "--manager-port".to_string(),
1659 value: self.configuration.manager_port.to_string(),
1660 detail:
1661 "The --manager-port option must be set together with the --worker flag."
1662 .to_string(),
1663 });
1664 }
1665 }
1666
1667 Ok(())
1668 }
1669
1670 // Configure how many [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s to hatch.
1671 fn set_users(&mut self) -> Result<(), SwanlingError> {
1672 // Track how value gets set so we can return a meaningful error if necessary.
1673 let mut key = "configuration.users";
1674 let mut value = 0;
1675
1676 // Check if --users is set.
1677 if let Some(users) = self.configuration.users {
1678 key = "--users";
1679 value = users;
1680 // If not, check if a default for users is set.
1681 } else if let Some(default_users) = self.defaults.users {
1682 // On Worker users comes from the Manager.
1683 if self.attack_mode == AttackMode::Worker {
1684 self.configuration.users = None;
1685 // Otherwise use default.
1686 } else {
1687 key = "set_default(SwanlingDefault::Users)";
1688 value = default_users;
1689
1690 self.configuration.users = Some(default_users);
1691 }
1692 // If not and if not running on Worker, default to 1.
1693 } else if self.attack_mode != AttackMode::Worker {
1694 // This should not be able to fail, but setting up debug in case the number
1695 // of cpus library returns an invalid number.
1696 key = "num_cpus::get()";
1697 value = num_cpus::get();
1698
1699 info!("concurrent users defaulted to {} (number of CPUs)", value);
1700
1701 self.configuration.users = Some(value);
1702 }
1703
1704 // Perform bounds checking.
1705 if let Some(users) = self.configuration.users {
1706 // Setting --users with --worker is not allowed.
1707 if self.attack_mode == AttackMode::Worker {
1708 return Err(SwanlingError::InvalidOption {
1709 option: key.to_string(),
1710 value: value.to_string(),
1711 detail: format!("{} can not be set together with the --worker flag.", key),
1712 });
1713 }
1714
1715 // Setting users to 0 is not allowed.
1716 if users == 0 {
1717 return Err(SwanlingError::InvalidOption {
1718 option: key.to_string(),
1719 value: "0".to_string(),
1720 detail: "The --users option must be set to at least 1.".to_string(),
1721 });
1722 }
1723
1724 // Debug output.
1725 info!("users = {}", users);
1726 }
1727
1728 Ok(())
1729 }
1730
1731 // Configure maximum run time if specified, otherwise run until canceled.
1732 fn set_run_time(&mut self) -> Result<(), SwanlingError> {
1733 // Track how value gets set so we can return a meaningful error if necessary.
1734 let mut key = "configuration.run_time";
1735 let mut value = 0;
1736
1737 // Use --run-time if set, don't allow on Worker.
1738 self.run_time = if !self.configuration.run_time.is_empty() {
1739 key = "--run-time";
1740 value = util::parse_timespan(&self.configuration.run_time);
1741 value
1742 // Otherwise, use default if set, but not on Worker.
1743 } else if let Some(default_run_time) = self.defaults.run_time {
1744 if self.attack_mode == AttackMode::Worker {
1745 0
1746 } else {
1747 key = "set_default(SwanlingDefault::RunTime)";
1748 value = default_run_time;
1749 default_run_time
1750 }
1751 }
1752 // Otherwise the test runs until canceled.
1753 else {
1754 0
1755 };
1756
1757 if self.run_time > 0 {
1758 if self.attack_mode == AttackMode::Worker {
1759 return Err(SwanlingError::InvalidOption {
1760 option: key.to_string(),
1761 value: value.to_string(),
1762 detail: format!("{} can not be set together with the --worker flag.", key),
1763 });
1764 }
1765
1766 // Debug output.
1767 info!("run_time = {}", self.run_time);
1768 }
1769
1770 Ok(())
1771 }
1772
1773 // Configure how quickly to hatch [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s.
1774 fn set_hatch_rate(&mut self) -> Result<(), SwanlingError> {
1775 // Track how value gets set so we can return a meaningful error if necessary.
1776 let mut key = "configuration.hatch_rate";
1777 let mut value = "".to_string();
1778
1779 // Check if --hash-rate is set.
1780 if let Some(hatch_rate) = &self.configuration.hatch_rate {
1781 key = "--hatch_rate";
1782 value = hatch_rate.to_string();
1783 // If not, check if a default hatch_rate is set.
1784 } else if let Some(default_hatch_rate) = &self.defaults.hatch_rate {
1785 // On Worker hatch_rate comes from the Manager.
1786 if self.attack_mode == AttackMode::Worker {
1787 self.configuration.hatch_rate = None;
1788 // Otherwise use default.
1789 } else {
1790 key = "set_default(SwanlingDefault::HatchRate)";
1791 value = default_hatch_rate.to_string();
1792 self.configuration.hatch_rate = Some(default_hatch_rate.to_string());
1793 }
1794 // If not and if not running on Worker, default to 1.
1795 } else if self.attack_mode != AttackMode::Worker {
1796 // This should not be able to fail, but setting up debug in case a later
1797 // change introduces the potential for failure.
1798 key = "Swanling default";
1799 value = "1".to_string();
1800 self.configuration.hatch_rate = Some(value.to_string());
1801 }
1802
1803 // Verbose output.
1804 if let Some(hatch_rate) = &self.configuration.hatch_rate {
1805 // Setting --hatch-rate with --worker is not allowed.
1806 if self.attack_mode == AttackMode::Worker {
1807 return Err(SwanlingError::InvalidOption {
1808 option: key.to_string(),
1809 value,
1810 detail: format!("{} can not be set together with the --worker flag.", key),
1811 });
1812 }
1813
1814 // Setting --hatch-rate of 0 is not allowed.
1815 if hatch_rate.is_empty() {
1816 return Err(SwanlingError::InvalidOption {
1817 option: key.to_string(),
1818 value,
1819 detail: format!("{} must be set to at least 1.", key),
1820 });
1821 }
1822
1823 // Debug output.
1824 info!("hatch_rate = {}", hatch_rate);
1825 }
1826
1827 Ok(())
1828 }
1829
1830 // Configure the coordinated omission mitigation strategy.
1831 fn set_coordinated_omission(&mut self) -> Result<(), SwanlingError> {
1832 // Track how value gets set so we can return a meaningful error if necessary.
1833 let mut key = "configuration.coordinated_omission";
1834 let mut value = Some(SwanlingCoordinatedOmissionMitigation::Disabled);
1835
1836 if self.configuration.co_mitigation.is_some() {
1837 key = "--co-mitigation";
1838 value = self.configuration.co_mitigation.clone();
1839 }
1840
1841 // Use default for co_mitigation if set and not on Worker.
1842 if self.configuration.co_mitigation.is_none() {
1843 if let Some(default_co_mitigation) = self.defaults.co_mitigation.as_ref() {
1844 // In Gaggles, co_mitigation is only set on Manager.
1845 if self.attack_mode != AttackMode::Worker {
1846 key = "set_default(SwanlingDefault::CoordinatedOmissionMitigation)";
1847 value = Some(default_co_mitigation.clone());
1848
1849 self.configuration.co_mitigation = Some(default_co_mitigation.clone());
1850 }
1851 }
1852 }
1853
1854 // Otherwise default to SwanlingCoordinaatedOmissionMitigation::Average.
1855 if self.configuration.co_mitigation.is_none() && self.attack_mode != AttackMode::Worker {
1856 self.configuration.co_mitigation = value.clone();
1857 }
1858
1859 if let Some(co_mitigation) = self.configuration.co_mitigation.as_ref() {
1860 // Setting --co-mitigation with --worker is not allowed.
1861 if self.attack_mode == AttackMode::Worker {
1862 return Err(SwanlingError::InvalidOption {
1863 option: key.to_string(),
1864 value: format!("{:?}", value),
1865 detail: format!("{} can not be set together with the --worker flag.", key),
1866 });
1867 }
1868
1869 // Setting --co-mitigation with --no-metrics is not allowed.
1870 if self.configuration.no_metrics {
1871 return Err(SwanlingError::InvalidOption {
1872 option: key.to_string(),
1873 value: format!("{:?}", value),
1874 detail: format!(
1875 "{} can not be set together with the --no-metrics flag.",
1876 key
1877 ),
1878 });
1879 }
1880
1881 if co_mitigation != &SwanlingCoordinatedOmissionMitigation::Disabled
1882 && self.scheduler == SwanlingScheduler::Random
1883 {
1884 // Coordinated Omission Mitigation is not possible together with the random scheduler,
1885 // as it's impossible to calculate an accurate request cadence.
1886 return Err(SwanlingError::InvalidOption {
1887 option: key.to_string(),
1888 value: format!("{:?}", value),
1889 detail: format!(
1890 "{} can not be set together with SwanlingScheduler::Random.",
1891 key
1892 ),
1893 });
1894 }
1895
1896 info!(
1897 "co_mitigation = {:?}",
1898 self.configuration.co_mitigation.as_ref().unwrap()
1899 );
1900 }
1901
1902 Ok(())
1903 }
1904
1905 // Configure maximum requests per second if throttle enabled.
1906 fn set_throttle_requests(&mut self) -> Result<(), SwanlingError> {
1907 // Track how value gets set so we can return a meaningful error if necessary.
1908 let mut key = "configuration.throttle_requests";
1909 let mut value = 0;
1910
1911 if self.configuration.throttle_requests > 0 {
1912 key = "--throttle-requests";
1913 value = self.configuration.throttle_requests;
1914 }
1915
1916 // Use default for throttle_requests if set and not on Worker.
1917 if self.configuration.throttle_requests == 0 {
1918 if let Some(default_throttle_requests) = self.defaults.throttle_requests {
1919 // In Gaggles, throttle_requests is only set on Worker.
1920 if self.attack_mode != AttackMode::Manager {
1921 key = "set_default(SwanlingDefault::ThrottleRequests)";
1922 value = default_throttle_requests;
1923
1924 self.configuration.throttle_requests = default_throttle_requests;
1925 }
1926 }
1927 }
1928
1929 if self.configuration.throttle_requests > 0 {
1930 // Setting --throttle-requests with --worker is not allowed.
1931 if self.attack_mode == AttackMode::Manager {
1932 return Err(SwanlingError::InvalidOption {
1933 option: key.to_string(),
1934 value: value.to_string(),
1935 detail: format!("{} can not be set together with the --manager flag.", key),
1936 });
1937 }
1938
1939 // Be sure throttle_requests is in allowed range.
1940 if self.configuration.throttle_requests == 0 {
1941 return Err(SwanlingError::InvalidOption {
1942 option: key.to_string(),
1943 value: value.to_string(),
1944 detail: format!("{} must be set to at least 1 request per second.", key),
1945 });
1946 } else if self.configuration.throttle_requests > 1_000_000 {
1947 return Err(SwanlingError::InvalidOption {
1948 option: key.to_string(),
1949 value: value.to_string(),
1950 detail: format!(
1951 "{} can not be set to more than 1,000,000 requests per second.",
1952 key
1953 ),
1954 });
1955 }
1956
1957 info!(
1958 "throttle_requests = {}",
1959 self.configuration.throttle_requests
1960 );
1961 }
1962
1963 Ok(())
1964 }
1965
1966 // Determine if `no_reset_statics` is enabled.
1967 fn set_no_reset_metrics(&mut self) -> Result<(), SwanlingError> {
1968 // Track how value gets set so we can return a meaningful error if necessary.
1969 let mut key = "configuration.no_reset_metrics";
1970 let mut value = false;
1971
1972 if self.configuration.no_reset_metrics {
1973 key = "--no-reset-metrics";
1974 value = true;
1975 // If not otherwise set and not Worker, check if there's a default.
1976 } else if self.attack_mode != AttackMode::Worker {
1977 if let Some(default_no_reset_metrics) = self.defaults.no_reset_metrics {
1978 key = "set_default(SwanlingDefault::NoResetMetrics)";
1979 value = default_no_reset_metrics;
1980
1981 // Optionally set default.
1982 self.configuration.no_reset_metrics = default_no_reset_metrics;
1983 }
1984 }
1985
1986 // Setting --no-reset-metrics with --worker is not allowed.
1987 if self.configuration.no_reset_metrics && self.attack_mode == AttackMode::Worker {
1988 return Err(SwanlingError::InvalidOption {
1989 option: key.to_string(),
1990 value: value.to_string(),
1991 detail: format!("{} can not be set together with the --worker flag.", key),
1992 });
1993 }
1994
1995 Ok(())
1996 }
1997
1998 // Determine if the `--status-codes` flag is enabled.
1999 fn set_status_codes(&mut self) -> Result<(), SwanlingError> {
2000 // Track how value gets set so we can return a meaningful error if necessary.
2001 let mut key = "configuration.status_codes";
2002 let mut value = false;
2003
2004 if self.configuration.status_codes {
2005 key = "--status-codes";
2006 value = true;
2007 // If not otherwise set and not Worker, check if there's a default.
2008 } else if self.attack_mode != AttackMode::Worker {
2009 if let Some(default_status_codes) = self.defaults.status_codes {
2010 key = "set_default(SwanlingDefault::StatusCodes)";
2011 value = default_status_codes;
2012
2013 // Optionally set default.
2014 self.configuration.status_codes = default_status_codes;
2015 }
2016 }
2017
2018 // Setting --status-codes with --worker is not allowed.
2019 if self.configuration.status_codes && self.attack_mode == AttackMode::Worker {
2020 return Err(SwanlingError::InvalidOption {
2021 option: key.to_string(),
2022 value: value.to_string(),
2023 detail: format!("{} can not be set together with the --worker flag.", key),
2024 });
2025 }
2026
2027 Ok(())
2028 }
2029
2030 // Determine if the `--running-metrics` flag is enabled.
2031 fn set_running_metrics(&mut self) -> Result<(), SwanlingError> {
2032 // Track how value gets set so we can return a meaningful error if necessary.
2033 let mut key = "configuration.running_metrics";
2034 let mut value = 0;
2035
2036 if let Some(running_metrics) = self.configuration.running_metrics {
2037 key = "--running-metrics";
2038 value = running_metrics;
2039 // If not otherwise set and not Worker, check if there's a default.
2040 } else if self.attack_mode != AttackMode::Worker {
2041 // Optionally set default.
2042 if let Some(default_running_metrics) = self.defaults.running_metrics {
2043 key = "set_default(SwanlingDefault::RunningMetrics)";
2044 value = default_running_metrics;
2045
2046 self.configuration.running_metrics = Some(default_running_metrics);
2047 }
2048 }
2049
2050 // Setting --running-metrics with --worker is not allowed.
2051 if let Some(running_metrics) = self.configuration.running_metrics {
2052 if self.attack_mode == AttackMode::Worker {
2053 return Err(SwanlingError::InvalidOption {
2054 option: key.to_string(),
2055 value: value.to_string(),
2056 detail: format!("{} can not be set together with the --worker flag.", key),
2057 });
2058 }
2059
2060 if running_metrics > 0 {
2061 info!("running_metrics = {}", running_metrics);
2062 }
2063 }
2064
2065 Ok(())
2066 }
2067
2068 // Determine if the `--no-task-metrics` flag is enabled.
2069 fn set_no_task_metrics(&mut self) -> Result<(), SwanlingError> {
2070 // Track how value gets set so we can return a meaningful error if necessary.
2071 let mut key = "configuration.no_task_metrics";
2072 let mut value = false;
2073
2074 if self.configuration.no_task_metrics {
2075 key = "--no-task-metrics";
2076 value = true;
2077 // If not otherwise set and not Worker, check if there's a default.
2078 } else if self.attack_mode != AttackMode::Worker {
2079 // Optionally set default.
2080 if let Some(default_no_task_metrics) = self.defaults.no_task_metrics {
2081 key = "set_default(SwanlingDefault::NoTaskMetrics)";
2082 value = default_no_task_metrics;
2083
2084 self.configuration.no_task_metrics = default_no_task_metrics;
2085 }
2086 }
2087
2088 // Setting --no-task-metrics with --worker is not allowed.
2089 if self.configuration.no_task_metrics && self.attack_mode == AttackMode::Worker {
2090 return Err(SwanlingError::InvalidOption {
2091 option: key.to_string(),
2092 value: value.to_string(),
2093 detail: format!("{} can not be set together with the --worker flag.", key),
2094 });
2095 }
2096
2097 Ok(())
2098 }
2099
2100 // Determine if the `--no-error-summary` flag is enabled.
2101 fn set_no_error_summary(&mut self) -> Result<(), SwanlingError> {
2102 // Track how value gets set so we can return a meaningful error if necessary.
2103 let mut key = "configuration.no_error_summary";
2104 let mut value = false;
2105
2106 if self.configuration.no_error_summary {
2107 key = "--no-error-summary";
2108 value = true;
2109 // If not otherwise set and not Worker, check if there's a default.
2110 } else if self.attack_mode != AttackMode::Worker {
2111 // Optionally set default.
2112 if let Some(default_no_error_summary) = self.defaults.no_error_summary {
2113 key = "set_default(SwanlingDefault::NoErrorSummary)";
2114 value = default_no_error_summary;
2115
2116 self.configuration.no_error_summary = default_no_error_summary;
2117 }
2118 }
2119
2120 // Setting --no-error-summary with --worker is not allowed.
2121 if self.configuration.no_error_summary && self.attack_mode == AttackMode::Worker {
2122 return Err(SwanlingError::InvalidOption {
2123 option: key.to_string(),
2124 value: value.to_string(),
2125 detail: format!("{} can not be set together with the --worker flag.", key),
2126 });
2127 }
2128
2129 Ok(())
2130 }
2131
2132 // Determine if the `--no-metrics` flag is enabled.
2133 fn set_no_metrics(&mut self) -> Result<(), SwanlingError> {
2134 // Track how value gets set so we can return a meaningful error if necessary.
2135 let mut key = "configuration.no_metrics";
2136 let mut value = false;
2137
2138 if self.configuration.no_metrics {
2139 key = "--no-metrics";
2140 value = true;
2141 // If not otherwise set and not Worker, check if there's a default.
2142 } else if self.attack_mode != AttackMode::Worker {
2143 // Optionally set default.
2144 if let Some(default_no_metrics) = self.defaults.no_metrics {
2145 key = "set_default(SwanlingDefault::NoMetrics)";
2146 value = default_no_metrics;
2147
2148 self.configuration.no_metrics = default_no_metrics;
2149 }
2150 }
2151
2152 // Setting --no-metrics with --worker is not allowed.
2153 if self.configuration.no_metrics && self.attack_mode == AttackMode::Worker {
2154 return Err(SwanlingError::InvalidOption {
2155 option: key.to_string(),
2156 value: value.to_string(),
2157 detail: format!("{} can not be set together with the --worker flag.", key),
2158 });
2159 }
2160
2161 // Don't allow overhead of collecting metrics unless we're printing them.
2162 if self.configuration.no_metrics {
2163 if self.configuration.status_codes {
2164 return Err(SwanlingError::InvalidOption {
2165 option: key.to_string(),
2166 value: value.to_string(),
2167 detail: format!(
2168 "{} can not be set together with the --status-codes flag.",
2169 key
2170 ),
2171 });
2172 }
2173
2174 // Don't allow overhead of collecting metrics unless we're printing them.
2175 if self.configuration.running_metrics.is_some() {
2176 return Err(SwanlingError::InvalidOption {
2177 option: key.to_string(),
2178 value: value.to_string(),
2179 detail: format!(
2180 "{} can not be set together with the --running_metrics option.",
2181 key
2182 ),
2183 });
2184 }
2185
2186 // There is nothing to log if metrics are disabled.
2187 if !self.configuration.request_log.is_empty() {
2188 return Err(SwanlingError::InvalidOption {
2189 option: key.to_string(),
2190 value: value.to_string(),
2191 detail: format!(
2192 "{} can not be set together with the --requests-file option.",
2193 key
2194 ),
2195 });
2196 }
2197 }
2198
2199 Ok(())
2200 }
2201
2202 // Determine if the `--sticky-follow` flag is enabled.
2203 fn set_sticky_follow(&mut self) -> Result<(), SwanlingError> {
2204 // Track how value gets set so we can return a meaningful error if necessary.
2205 let mut key = "configuration.sticky_follow";
2206 let mut value = false;
2207
2208 if self.configuration.sticky_follow {
2209 key = "--sticky-follow";
2210 value = true;
2211 // If not otherwise set and not Worker, check if there's a default.
2212 } else if self.attack_mode != AttackMode::Worker {
2213 // Optionally set default.
2214 if let Some(default_sticky_follow) = self.defaults.sticky_follow {
2215 key = "set_default(SwanlingDefault::StickyFollow)";
2216 value = default_sticky_follow;
2217
2218 self.configuration.sticky_follow = default_sticky_follow;
2219 }
2220 }
2221
2222 if self.configuration.sticky_follow && self.attack_mode == AttackMode::Worker {
2223 return Err(SwanlingError::InvalidOption {
2224 option: key.to_string(),
2225 value: value.to_string(),
2226 detail: format!("{} can not be set together with the --worker flag.", key),
2227 });
2228 }
2229
2230 Ok(())
2231 }
2232
2233 #[cfg(feature = "gaggle")]
2234 // Determine if `--no-hash-check` flag is enabled.
2235 fn set_no_hash_check(&mut self) -> Result<(), SwanlingError> {
2236 // Track how value gets set so we can return a meaningful error if necessary.
2237 let mut key = "configuration.no_hash_check";
2238 let mut value = false;
2239
2240 if self.configuration.no_hash_check {
2241 key = "--no-hash-check";
2242 value = true;
2243 // If not otherwise set and not Worker, check if there's a default.
2244 } else if self.attack_mode != AttackMode::Worker {
2245 // Optionally set default.
2246 if let Some(default_no_hash_check) = self.defaults.no_hash_check {
2247 key = "set_default(SwanlingDefault::NoHashCheck)";
2248 value = default_no_hash_check;
2249
2250 self.configuration.no_hash_check = default_no_hash_check;
2251 }
2252 }
2253
2254 if self.configuration.no_hash_check && self.attack_mode == AttackMode::Worker {
2255 return Err(SwanlingError::InvalidOption {
2256 option: key.to_string(),
2257 value: value.to_string(),
2258 detail: format!("{} can not be set together with the --worker flag.", key),
2259 });
2260 }
2261
2262 Ok(())
2263 }
2264
2265 // If enabled, returns the path of the report_file, otherwise returns None.
2266 fn get_report_file_path(&mut self) -> Option<String> {
2267 // If metrics are disabled, or running in Manager mode, there is no
2268 // report file, exit immediately.
2269 if self.configuration.no_metrics || self.attack_mode == AttackMode::Manager {
2270 return None;
2271 }
2272
2273 // If --report-file is set, return it.
2274 if !self.configuration.report_file.is_empty() {
2275 return Some(self.configuration.report_file.to_string());
2276 }
2277
2278 // If SwanlingDefault::ReportFile is set, return it.
2279 if let Some(default_report_file) = &self.defaults.report_file {
2280 return Some(default_report_file.to_string());
2281 }
2282
2283 // Otherwise there is no report file.
2284 None
2285 }
2286
2287 // Configure requests log format.
2288 fn set_request_format(&mut self) -> Result<(), SwanlingError> {
2289 // Track how value gets set so we can return a meaningful error if necessary.
2290 let mut key = "configuration.request_format";
2291 let mut value = Some(SwanlingLogFormat::Json);
2292
2293 if self.configuration.request_format.is_some() {
2294 key = "--requests-format";
2295 value = self.configuration.request_format.clone();
2296 } else if let Some(default_request_format) = self.defaults.request_format.as_ref() {
2297 // In Gaggles, request_format is only set on Worker.
2298 if self.attack_mode != AttackMode::Manager {
2299 key = "set_default(SwanlingDefault::RequestFormat)";
2300 value = Some(default_request_format.clone());
2301 self.configuration.request_format = Some(default_request_format.clone());
2302 }
2303 }
2304
2305 // Otherwise default to SwanlingLogFormat::Json.
2306 if !self.configuration.request_log.is_empty()
2307 && self.configuration.request_format.is_none()
2308 && self.attack_mode != AttackMode::Manager
2309 {
2310 self.configuration.request_format = value.clone();
2311 }
2312
2313 if self.configuration.request_format.is_some() {
2314 // Log format isn't relevant if metrics aren't enabled.
2315 if self.configuration.no_metrics {
2316 return Err(SwanlingError::InvalidOption {
2317 option: "--no-metrics".to_string(),
2318 value: "true".to_string(),
2319 detail: "The --no-metrics flag can not be set together with the --requests-format option.".to_string(),
2320 });
2321 }
2322 // Log format isn't relevant if log not enabled.
2323 else if self.configuration.request_log.is_empty() {
2324 return Err(SwanlingError::InvalidOption {
2325 option: key.to_string(),
2326 value: format!("{:?}", value),
2327 detail: "The --requests-file option must be set together with the --requests-format option.".to_string(),
2328 });
2329 }
2330 }
2331
2332 Ok(())
2333 }
2334
2335 // Configure tasks log format.
2336 fn set_task_format(&mut self) -> Result<(), SwanlingError> {
2337 // Track how value gets set so we can return a meaningful error if necessary.
2338 let mut key = "configuration.task_format";
2339 let mut value = Some(SwanlingLogFormat::Json);
2340
2341 if self.configuration.task_format.is_some() {
2342 key = "--tasks-format";
2343 value = self.configuration.task_format.clone();
2344 } else if let Some(default_task_format) = self.defaults.task_format.as_ref() {
2345 // In Gaggles, task_format is only set on Worker.
2346 if self.attack_mode != AttackMode::Manager {
2347 key = "set_default(SwanlingDefault::TaskFormat)";
2348 value = Some(default_task_format.clone());
2349 self.configuration.task_format = Some(default_task_format.clone());
2350 }
2351 }
2352
2353 // Otherwise default to SwanlingLogFormat::Json.
2354 if !self.configuration.task_log.is_empty()
2355 && self.configuration.task_format.is_none()
2356 && self.attack_mode != AttackMode::Manager
2357 {
2358 self.configuration.task_format = value.clone();
2359 }
2360
2361 if self.configuration.task_format.is_some() {
2362 // Log format isn't relevant if metrics aren't enabled.
2363 if self.configuration.no_metrics {
2364 return Err(SwanlingError::InvalidOption {
2365 option: "--no-metrics".to_string(),
2366 value: "true".to_string(),
2367 detail: "The --no-metrics flag can not be set together with the --tasks-format option.".to_string(),
2368 });
2369 }
2370 // Log format isn't relevant if log not enabled.
2371 else if self.configuration.task_log.is_empty() {
2372 return Err(SwanlingError::InvalidOption {
2373 option: key.to_string(),
2374 value: format!("{:?}", value),
2375 detail: "The --tasks-file option must be set together with the --tasks-format option.".to_string(),
2376 });
2377 }
2378 }
2379
2380 Ok(())
2381 }
2382
2383 // Configure tasks log format.
2384 fn set_error_format(&mut self) -> Result<(), SwanlingError> {
2385 // Track how value gets set so we can return a meaningful error if necessary.
2386 let mut key = "configuration.error_format";
2387 let mut value = Some(SwanlingLogFormat::Json);
2388
2389 if self.configuration.error_format.is_some() {
2390 key = "--error-format";
2391 value = self.configuration.error_format.clone();
2392 } else if let Some(default_error_format) = self.defaults.error_format.as_ref() {
2393 // In Gaggles, error_format is only set on Worker.
2394 if self.attack_mode != AttackMode::Manager {
2395 key = "set_default(SwanlingDefault::ErrorFormat)";
2396 value = Some(default_error_format.clone());
2397 self.configuration.error_format = Some(default_error_format.clone());
2398 }
2399 }
2400
2401 // Otherwise default to SwanlingLogFormat::Json.
2402 if !self.configuration.error_log.is_empty()
2403 && self.configuration.error_format.is_none()
2404 && self.attack_mode != AttackMode::Manager
2405 {
2406 self.configuration.error_format = value.clone();
2407 }
2408
2409 if self.configuration.error_format.is_some() {
2410 // Log format isn't relevant if metrics aren't enabled.
2411 if self.configuration.no_metrics {
2412 return Err(SwanlingError::InvalidOption {
2413 option: "--no-metrics".to_string(),
2414 value: "true".to_string(),
2415 detail: "The --no-metrics flag can not be set together with the --error-format option.".to_string(),
2416 });
2417 }
2418 // Log format isn't relevant if log not enabled.
2419 else if self.configuration.error_log.is_empty() {
2420 return Err(SwanlingError::InvalidOption {
2421 option: key.to_string(),
2422 value: format!("{:?}", value),
2423 detail: "The --error-file option must be set together with the --error-format option.".to_string(),
2424 });
2425 }
2426 }
2427
2428 Ok(())
2429 }
2430
2431 // Configure debug log format.
2432 fn set_debug_format(&mut self) -> Result<(), SwanlingError> {
2433 // Track how value gets set so we can return a meaningful error if necessary.
2434 let mut key = "configuration.debug_format";
2435 let mut value = Some(SwanlingLogFormat::Json);
2436
2437 if self.configuration.debug_format.is_some() {
2438 key = "--debug-format";
2439 value = self.configuration.debug_format.clone();
2440 } else if let Some(default_debug_format) = self.defaults.debug_format.as_ref() {
2441 // In Gaggles, debug_format is only set on Worker.
2442 if self.attack_mode != AttackMode::Manager {
2443 key = "set_default(SwanlingDefault::DebugFormat)";
2444 value = Some(default_debug_format.clone());
2445 self.configuration.debug_format = Some(default_debug_format.clone());
2446 }
2447 }
2448
2449 // Otherwise default to SwanlingLogFormat::Json.
2450 if !self.configuration.debug_log.is_empty()
2451 && self.configuration.debug_format.is_none()
2452 && self.attack_mode != AttackMode::Manager
2453 {
2454 self.configuration.debug_format = value.clone();
2455 }
2456
2457 if self.configuration.debug_format.is_some() {
2458 // Log format isn't relevant if log not enabled.
2459 if self.configuration.debug_log.is_empty() {
2460 return Err(SwanlingError::InvalidOption {
2461 option: key.to_string(),
2462 value: format!("{:?}", value),
2463 detail: "The --debug-file option must be set together with the --debug-format option.".to_string(),
2464 });
2465 }
2466 }
2467
2468 Ok(())
2469 }
2470
2471 // Configure whether or not to enable the telnet Controller. Always disable when in Regatta mode.
2472 fn set_no_telnet(&mut self) {
2473 // Currently Gaggles are not Controller-aware, force disable.
2474 if [AttackMode::Manager, AttackMode::Worker].contains(&self.attack_mode) {
2475 self.configuration.no_telnet = true;
2476 // Otherwise, if --no-telnet flag not set, respect default if configured.
2477 } else if !self.configuration.no_telnet {
2478 if let Some(default_no_telnet) = self.defaults.no_telnet {
2479 self.configuration.no_telnet = default_no_telnet;
2480 }
2481 }
2482 }
2483
2484 // Configure whether or not to enable the WebSocket Controller. Always disable when in Regatta mode.
2485 fn set_no_websocket(&mut self) {
2486 // Currently Gaggles are not Controller-aware, force disable.
2487 if [AttackMode::Manager, AttackMode::Worker].contains(&self.attack_mode) {
2488 self.configuration.no_websocket = true;
2489 // Otherwise, if --no-websocket flag not set, respect default if configured.
2490 } else if !self.configuration.no_websocket {
2491 if let Some(default_no_telnet) = self.defaults.no_telnet {
2492 self.configuration.no_websocket = default_no_telnet;
2493 }
2494 }
2495 }
2496
2497 // Configure whether or not to autostart the load test.
2498 fn set_no_autostart(&mut self) -> Result<(), SwanlingError> {
2499 // Track how value gets set so we can return a meaningful error if necessary.
2500 let mut key = "configuration.no_autostart";
2501 let mut value = false;
2502
2503 // Currently Gaggles are not Controller-aware.
2504 if self.configuration.no_autostart {
2505 key = "--no-autostart";
2506 value = true;
2507 // Otherwise set default if configured.
2508 } else if let Some(default_no_autostart) = self.defaults.no_autostart {
2509 key = "set_default(SwanlingDefault::NoAutoStart)";
2510 value = default_no_autostart;
2511
2512 self.configuration.no_autostart = default_no_autostart;
2513 }
2514
2515 if self.configuration.no_autostart {
2516 // Can't disable autostart in Regatta mode.
2517 if [AttackMode::Manager, AttackMode::Worker].contains(&self.attack_mode) {
2518 return Err(SwanlingError::InvalidOption {
2519 option: key.to_string(),
2520 value: value.to_string(),
2521 detail: format!(
2522 "{} can not be set together with the --manager or --worker flags.",
2523 key
2524 ),
2525 });
2526 }
2527
2528 // Can't disable autostart if there's no Controller enabled.
2529 if self.configuration.no_telnet && self.configuration.no_websocket {
2530 return Err(SwanlingError::InvalidOption {
2531 option: key.to_string(),
2532 value: value.to_string(),
2533 detail: format!("{} can not be set together with both the --no-telnet and --no-websocket flags.", key),
2534 });
2535 }
2536 }
2537
2538 Ok(())
2539 }
2540
2541 // Configure whether to log response body.
2542 fn set_no_debug_body(&mut self) -> Result<(), SwanlingError> {
2543 // Track how value gets set so we can return a meaningful error if necessary.
2544 let mut key = "configuration.no_debug_body";
2545 let mut value = false;
2546
2547 if self.configuration.no_debug_body {
2548 key = "--no-debug-body";
2549 value = true;
2550 // If not otherwise set and not Manager, check if there's a default.
2551 } else if self.attack_mode != AttackMode::Manager {
2552 // Optionally set default.
2553 if let Some(default_no_debug_body) = self.defaults.no_debug_body {
2554 key = "set_default(SwanlingDefault::NoDebugBody)";
2555 value = default_no_debug_body;
2556
2557 self.configuration.no_debug_body = default_no_debug_body;
2558 }
2559 }
2560
2561 if self.configuration.no_debug_body && self.attack_mode == AttackMode::Manager {
2562 return Err(SwanlingError::InvalidOption {
2563 option: key.to_string(),
2564 value: value.to_string(),
2565 detail: format!("{} can not be set together with the --manager flag.", key),
2566 });
2567 }
2568
2569 Ok(())
2570 }
2571
2572 /// Execute the [`SwanlingAttack`](./struct.SwanlingAttack.html) load test.
2573 ///
2574 /// # Example
2575 /// ```rust
2576 /// use swanling::prelude::*;
2577 ///
2578 /// fn main() -> Result<(), SwanlingError> {
2579 /// let _swanling_metrics = SwanlingAttack::initialize()?
2580 /// .register_taskset(taskset!("ExampleTasks")
2581 /// .register_task(task!(example_task).set_weight(2)?)
2582 /// .register_task(task!(another_example_task).set_weight(3)?)
2583 /// // Swanling must run against a host, point to localhost so test starts.
2584 /// .set_host("http://localhost")
2585 /// )
2586 /// // Exit after one second so test doesn't run forever.
2587 /// .set_default(SwanlingDefault::RunTime, 1)?
2588 /// .execute()?;
2589 ///
2590 /// Ok(())
2591 /// }
2592 ///
2593 /// async fn example_task(user: &SwanlingUser) -> SwanlingTaskResult {
2594 /// let _swanling = user.get("/foo").await?;
2595 ///
2596 /// Ok(())
2597 /// }
2598 ///
2599 /// async fn another_example_task(user: &SwanlingUser) -> SwanlingTaskResult {
2600 /// let _swanling = user.get("/bar").await?;
2601 ///
2602 /// Ok(())
2603 /// }
2604 /// ```
2605 pub fn execute(mut self) -> Result<SwanlingMetrics, SwanlingError> {
2606 // If version flag is set, display package name and version and exit.
2607 if self.configuration.version {
2608 println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
2609 std::process::exit(0);
2610 }
2611
2612 // At least one task set is required.
2613 if self.task_sets.is_empty() {
2614 return Err(SwanlingError::NoTaskSets {
2615 detail: "No task sets are defined.".to_string(),
2616 });
2617 }
2618
2619 // Display task sets and tasks, then exit.
2620 if self.configuration.list {
2621 println!("Available tasks:");
2622 for task_set in self.task_sets {
2623 println!(" - {} (weight: {})", task_set.name, task_set.weight);
2624 for task in task_set.tasks {
2625 println!(" o {} (weight: {})", task.name, task.weight);
2626 }
2627 }
2628 std::process::exit(0);
2629 }
2630
2631 // Configure loggers.
2632 self.configuration.configure_loggers(&self.defaults);
2633
2634 // Initialize logger.
2635 self.initialize_logger();
2636
2637 // Configure run mode (StandAlone, Worker, Manager).
2638 self.set_attack_mode()?;
2639
2640 // Determine whether or not to enable the telnet Controller.
2641 self.set_no_telnet();
2642
2643 // Determine whether or not to enable the WebSocket Controller.
2644 self.set_no_websocket();
2645
2646 // Determine whether or not to autostart load test.
2647 self.set_no_autostart()?;
2648
2649 // Configure number of users to simulate.
2650 self.set_users()?;
2651
2652 // Configure expect_workers if running in Manager attack mode.
2653 self.set_expect_workers()?;
2654
2655 // Configure host and ports if running in a Regatta distributed load test.
2656 self.set_gaggle_host_and_port()?;
2657
2658 // Configure how long to run.
2659 self.set_run_time()?;
2660
2661 // Configure how many users to hatch per second.
2662 self.set_hatch_rate()?;
2663
2664 // Configure the requests log format.
2665 self.set_request_format()?;
2666
2667 // Configure the tasks log format.
2668 self.set_task_format()?;
2669
2670 // Configure the tasks log format.
2671 self.set_error_format()?;
2672
2673 // Configure the debug log format.
2674 self.set_debug_format()?;
2675
2676 // Determine whether or not to log response body.
2677 self.set_no_debug_body()?;
2678
2679 // Configure coordinated ommission mitigation strategy.
2680 self.set_coordinated_omission()?;
2681
2682 // Configure throttle if enabled.
2683 self.set_throttle_requests()?;
2684
2685 // Configure status_codes flag.
2686 self.set_status_codes()?;
2687
2688 // Configure running_metrics flag.
2689 self.set_running_metrics()?;
2690
2691 // Configure no_reset_metrics flag.
2692 self.set_no_reset_metrics()?;
2693
2694 // Configure no_task_metrics flag.
2695 self.set_no_task_metrics()?;
2696
2697 // Configure no_error_summary flag.
2698 self.set_no_error_summary()?;
2699
2700 // Configure no_metrics flag.
2701 self.set_no_metrics()?;
2702
2703 // Configure sticky_follow flag.
2704 self.set_sticky_follow()?;
2705
2706 // Configure no_hash_check flag.
2707 #[cfg(feature = "gaggle")]
2708 self.set_no_hash_check()?;
2709
2710 // Confirm there's either a global host, or each task set has a host defined.
2711 if let Err(e) = self.validate_host() {
2712 if self.configuration.no_autostart {
2713 info!("host must be configured via Controller before starting load test");
2714 } else {
2715 // If auto-starting, host must be valid.
2716 return Err(e);
2717 }
2718 } else {
2719 info!("global host configured: {}", self.configuration.host);
2720 self.prepare_load_test()?;
2721 }
2722
2723 // Calculate a unique hash for the current load test.
2724 let mut s = DefaultHasher::new();
2725 self.task_sets.hash(&mut s);
2726 self.metrics.hash = s.finish();
2727 debug!("hash: {}", self.metrics.hash);
2728
2729 // Start swanling in manager mode.
2730 if self.attack_mode == AttackMode::Manager {
2731 #[cfg(feature = "gaggle")]
2732 {
2733 let rt = Runtime::new().unwrap();
2734 self = rt.block_on(manager::manager_main(self));
2735 }
2736
2737 #[cfg(not(feature = "gaggle"))]
2738 {
2739 return Err(SwanlingError::FeatureNotEnabled {
2740 feature: "gaggle".to_string(), detail: "Load test must be recompiled with `--features gaggle` to start in manager mode.".to_string()
2741 });
2742 }
2743 }
2744 // Start swanling in worker mode.
2745 else if self.attack_mode == AttackMode::Worker {
2746 #[cfg(feature = "gaggle")]
2747 {
2748 let rt = Runtime::new().unwrap();
2749 self = rt.block_on(worker::worker_main(&self));
2750 }
2751
2752 #[cfg(not(feature = "gaggle"))]
2753 {
2754 return Err(SwanlingError::FeatureNotEnabled {
2755 feature: "gaggle".to_string(),
2756 detail: "Load test must be recompiled with `--features gaggle` to start in worker mode.".to_string(),
2757 });
2758 }
2759 }
2760 // Start swanling in single-process mode.
2761 else {
2762 let rt = Runtime::new().unwrap();
2763 self = rt.block_on(self.start_attack(None))?;
2764 }
2765
2766 Ok(self.metrics)
2767 }
2768
2769 // Returns OK(()) if there's a valid host, SwanlingError with details if not.
2770 fn validate_host(&mut self) -> Result<(), SwanlingError> {
2771 if self.configuration.host.is_empty() {
2772 for task_set in &self.task_sets {
2773 match &task_set.host {
2774 Some(h) => {
2775 if util::is_valid_host(h).is_ok() {
2776 info!("host for {} configured: {}", task_set.name, h);
2777 }
2778 }
2779 None => match &self.defaults.host {
2780 Some(h) => {
2781 if util::is_valid_host(h).is_ok() {
2782 info!("host for {} configured: {}", task_set.name, h);
2783 }
2784 }
2785 None => {
2786 if self.attack_mode != AttackMode::Worker {
2787 return Err(SwanlingError::InvalidOption {
2788 option: "--host".to_string(),
2789 value: "".to_string(),
2790 detail: format!("A host must be defined via the --host option, the SwanlingAttack.set_default() function, or the SwanlingTaskSet.set_host() function (no host defined for {}).", task_set.name)
2791 });
2792 }
2793 }
2794 },
2795 }
2796 }
2797 }
2798 Ok(())
2799 }
2800
2801 // Create and schedule SwanlingUsers. This requires that the host that will be load tested
2802 // has been configured.
2803 fn prepare_load_test(&mut self) -> Result<(), SwanlingError> {
2804 // If not on a Worker, be sure a valid host has been defined before building configuration.
2805 if self.attack_mode != AttackMode::Worker {
2806 self.validate_host()?;
2807 }
2808
2809 // Apply weights to tasks in each task set.
2810 for task_set in &mut self.task_sets {
2811 let (weighted_on_start_tasks, weighted_tasks, weighted_on_stop_tasks) =
2812 allocate_tasks(&task_set, &self.scheduler);
2813 task_set.weighted_on_start_tasks = weighted_on_start_tasks;
2814 task_set.weighted_tasks = weighted_tasks;
2815 task_set.weighted_on_stop_tasks = weighted_on_stop_tasks;
2816 debug!(
2817 "weighted {} on_start: {:?} tasks: {:?} on_stop: {:?}",
2818 task_set.name,
2819 task_set.weighted_on_start_tasks,
2820 task_set.weighted_tasks,
2821 task_set.weighted_on_stop_tasks
2822 );
2823 }
2824
2825 if self.attack_mode != AttackMode::Worker {
2826 // Stand-alone and Manager processes can display metrics.
2827 if !self.configuration.no_metrics {
2828 self.metrics.display_metrics = true;
2829 }
2830
2831 if self.attack_mode == AttackMode::StandAlone {
2832 // Allocate a state for each of the users we are about to start.
2833 self.weighted_users = self.weight_task_set_users()?;
2834 } else if self.attack_mode == AttackMode::Manager {
2835 // Build a list of users to be allocated on Workers.
2836 self.weighted_gaggle_users = self.prepare_worker_task_set_users()?;
2837 }
2838 }
2839
2840 Ok(())
2841 }
2842
2843 /// Helper to wrap configured host in `Option<>` if set.
2844 fn get_configuration_host(&self) -> Option<String> {
2845 if self.configuration.host.is_empty() {
2846 None
2847 } else {
2848 Some(self.configuration.host.to_string())
2849 }
2850 }
2851
2852 // Helper to spawn a throttle thread if configured. The throttle thread opens
2853 // a bounded channel to control how quickly [`SwanlingUser`](./swanling/struct.SwanlingUser.html)
2854 // threads can make requests.
2855 async fn setup_throttle(
2856 &self,
2857 ) -> (
2858 // A channel used by [`SwanlingUser`](./swanling/struct.SwanlingUser.html)s to throttle requests.
2859 Option<flume::Sender<bool>>,
2860 // A channel used by parent to tell throttle the load test is complete.
2861 Option<flume::Sender<bool>>,
2862 ) {
2863 // If the throttle isn't enabled, return immediately.
2864 if self.configuration.throttle_requests == 0 {
2865 return (None, None);
2866 }
2867
2868 // Create a bounded channel allowing single-sender multi-receiver to throttle
2869 // [`SwanlingUser`](./swanling/struct.SwanlingUser.html) threads.
2870 let (all_threads_throttle, throttle_receiver): (
2871 flume::Sender<bool>,
2872 flume::Receiver<bool>,
2873 ) = flume::bounded(self.configuration.throttle_requests);
2874
2875 // Create a channel allowing the parent to inform the throttle thread when the
2876 // load test is finished. Even though we only send one message, we can't use a
2877 // oneshot channel as we don't want to block waiting for a message.
2878 let (parent_to_throttle_tx, throttle_rx) = flume::bounded(1);
2879
2880 // Launch a new thread for throttling, no need to rejoin it.
2881 let _ = Some(tokio::spawn(throttle::throttle_main(
2882 self.configuration.throttle_requests,
2883 throttle_receiver,
2884 throttle_rx,
2885 )));
2886
2887 let sender = all_threads_throttle.clone();
2888 // We start from 1 instead of 0 to intentionally fill all but one slot in the
2889 // channel to avoid a burst of traffic during startup. The channel then provides
2890 // an implementation of the leaky bucket algorithm as a queue. Requests have to
2891 // add a token to the bucket before making a request, and are blocked until this
2892 // throttle thread "leaks out" a token thereby creating space. More information
2893 // can be found at: https://en.wikipedia.org/wiki/Leaky_bucket
2894 for _ in 1..self.configuration.throttle_requests {
2895 let _ = sender.send_async(true).await;
2896 }
2897
2898 (Some(all_threads_throttle), Some(parent_to_throttle_tx))
2899 }
2900
2901 // Helper to optionally spawn a telnet and/or WebSocket Controller thread. The Controller
2902 // threads share a control channel, allowing it to send requests to the parent process. When
2903 // a response is required, the Controller will also send a one-shot channel allowing a direct
2904 // reply.
2905 async fn setup_controllers(&mut self) -> Option<flume::Receiver<SwanlingControllerRequest>> {
2906 // If the telnet controller is disabled, return immediately.
2907 if self.configuration.no_telnet && self.configuration.no_websocket {
2908 return None;
2909 }
2910
2911 // Create an unbounded channel for controller threads to send requests to the parent
2912 // process.
2913 let (all_threads_controller_request_tx, controller_request_rx): (
2914 flume::Sender<SwanlingControllerRequest>,
2915 flume::Receiver<SwanlingControllerRequest>,
2916 ) = flume::unbounded();
2917
2918 // Configured telnet Controller if not disabled.
2919 if !self.configuration.no_telnet {
2920 // Configure telnet_host, using default if run-time option is not set.
2921 if self.configuration.telnet_host.is_empty() {
2922 self.configuration.telnet_host =
2923 if let Some(host) = self.defaults.telnet_host.clone() {
2924 host
2925 } else {
2926 "0.0.0.0".to_string()
2927 }
2928 }
2929
2930 // Then configure telnet_port, using default if run-time option is not set.
2931 if self.configuration.telnet_port == 0 {
2932 self.configuration.telnet_port = if let Some(port) = self.defaults.telnet_port {
2933 port
2934 } else {
2935 DEFAULT_TELNET_PORT.to_string().parse().unwrap()
2936 };
2937 }
2938
2939 // Spawn the initial controller thread to allow real-time control of the load test.
2940 // There is no need to rejoin this thread when the load test ends.
2941 let _ = Some(tokio::spawn(controller::controller_main(
2942 self.configuration.clone(),
2943 all_threads_controller_request_tx.clone(),
2944 SwanlingControllerProtocol::Telnet,
2945 )));
2946 }
2947
2948 // Configured WebSocket Controller if not disabled.
2949 if !self.configuration.no_websocket {
2950 // Configure websocket_host, using default if run-time option is not set.
2951 if self.configuration.websocket_host.is_empty() {
2952 self.configuration.websocket_host =
2953 if let Some(host) = self.defaults.websocket_host.clone() {
2954 host
2955 } else {
2956 "0.0.0.0".to_string()
2957 }
2958 }
2959
2960 // Then configure websocket_port, using default if run-time option is not set.
2961 if self.configuration.websocket_port == 0 {
2962 self.configuration.websocket_port = if let Some(port) = self.defaults.websocket_port
2963 {
2964 port
2965 } else {
2966 DEFAULT_WEBSOCKET_PORT.to_string().parse().unwrap()
2967 };
2968 }
2969
2970 // Spawn the initial controller thread to allow real-time control of the load test.
2971 // There is no need to rejoin this thread when the load test ends.
2972 let _ = Some(tokio::spawn(controller::controller_main(
2973 self.configuration.clone(),
2974 all_threads_controller_request_tx,
2975 SwanlingControllerProtocol::WebSocket,
2976 )));
2977 }
2978
2979 // Return the parent end of the Controller channel.
2980 Some(controller_request_rx)
2981 }
2982
2983 // Prepare an asynchronous file writer for `report_file` (if enabled).
2984 async fn prepare_report_file(&mut self) -> Result<Option<File>, SwanlingError> {
2985 if let Some(report_file_path) = self.get_report_file_path() {
2986 Ok(Some(File::create(&report_file_path).await?))
2987 } else {
2988 Ok(None)
2989 }
2990 }
2991
2992 // Invoke `test_start` tasks if existing.
2993 async fn run_test_start(&self) -> Result<(), SwanlingError> {
2994 // Initialize per-user states.
2995 if self.attack_mode != AttackMode::Worker {
2996 // First run global test_start_task, if defined.
2997 match &self.test_start_task {
2998 Some(t) => {
2999 info!("running test_start_task");
3000 // Create a one-time-use User to run the test_start_task.
3001 let base_url = swanling::get_base_url(
3002 self.get_configuration_host(),
3003 None,
3004 self.defaults.host.clone(),
3005 )?;
3006 let user = SwanlingUser::single(base_url, &self.configuration)?;
3007 let function = &t.function;
3008 let _ = function(&user).await;
3009 }
3010 // No test_start_task defined, nothing to do.
3011 None => (),
3012 }
3013 }
3014
3015 Ok(())
3016 }
3017
3018 // Invoke `test_stop` tasks if existing.
3019 async fn run_test_stop(&self) -> Result<(), SwanlingError> {
3020 // Initialize per-user states.
3021 if self.attack_mode != AttackMode::Worker {
3022 // First run global test_stop_task, if defined.
3023 match &self.test_stop_task {
3024 Some(t) => {
3025 info!("running test_stop_task");
3026 // Create a one-time-use User to run the test_stop_task.
3027 let base_url = swanling::get_base_url(
3028 self.get_configuration_host(),
3029 None,
3030 self.defaults.host.clone(),
3031 )?;
3032 let user = SwanlingUser::single(base_url, &self.configuration)?;
3033 let function = &t.function;
3034 let _ = function(&user).await;
3035 }
3036 // No test_stop_task defined, nothing to do.
3037 None => (),
3038 }
3039 }
3040
3041 Ok(())
3042 }
3043
3044 // Create a SwanlingAttackRunState object and do all initialization required
3045 // to start a [`SwanlingAttack`](./struct.SwanlingAttack.html).
3046 async fn initialize_attack(
3047 &mut self,
3048 socket: Option<Socket>,
3049 ) -> Result<SwanlingAttackRunState, SwanlingError> {
3050 trace!("initialize_attack");
3051
3052 // Create a single channel used to send metrics from SwanlingUser threads
3053 // to parent thread.
3054 let (all_threads_metrics_tx, metrics_rx): (
3055 flume::Sender<SwanlingMetric>,
3056 flume::Receiver<SwanlingMetric>,
3057 ) = flume::unbounded();
3058
3059 // Optionally spawn a telnet and/or Websocket Controller thread.
3060 let controller_channel_rx = self.setup_controllers().await;
3061
3062 // Grab now() once from the standard library, used by multiple timers in
3063 // the run state.
3064 let std_now = std::time::Instant::now();
3065
3066 let swanling_attack_run_state = SwanlingAttackRunState {
3067 spawn_user_timer: std_now,
3068 spawn_user_in_ms: 0,
3069 spawn_user_counter: 0,
3070 drift_timer: tokio::time::Instant::now(),
3071 all_threads_metrics_tx,
3072 metrics_rx,
3073 logger_handle: None,
3074 all_threads_logger_tx: None,
3075 throttle_threads_tx: None,
3076 parent_to_throttle_tx: None,
3077 controller_channel_rx,
3078 report_file: None,
3079 metrics_header_displayed: false,
3080 idle_status_displayed: false,
3081 users: Vec::new(),
3082 user_channels: Vec::new(),
3083 running_metrics_timer: std_now,
3084 display_running_metrics: false,
3085 all_users_spawned: false,
3086 shutdown_after_stop: !self.configuration.no_autostart,
3087 canceled: Arc::new(AtomicBool::new(false)),
3088 socket,
3089 };
3090
3091 // Access socket to avoid errors.
3092 trace!("socket: {:?}", &swanling_attack_run_state.socket);
3093
3094 // Catch ctrl-c to allow clean shutdown to display metrics.
3095 util::setup_ctrlc_handler(&swanling_attack_run_state.canceled);
3096
3097 Ok(swanling_attack_run_state)
3098 }
3099
3100 // Spawn [`SwanlingUser`](./swanling/struct.SwanlingUser.html) threads to generate a
3101 // [`SwanlingAttack`](./struct.SwanlingAttack.html).
3102 async fn spawn_attack(
3103 &mut self,
3104 swanling_attack_run_state: &mut SwanlingAttackRunState,
3105 ) -> Result<(), SwanlingError> {
3106 // If the run_timer has expired, stop spawning user threads and start stopping them
3107 // instead. Unwrap is safe here because load test had to start to get here.
3108 if util::timer_expired(self.started.unwrap(), self.run_time) {
3109 self.set_attack_phase(swanling_attack_run_state, AttackPhase::Stopping);
3110 return Ok(());
3111 }
3112
3113 // Hatch rate is used to schedule the next user, and to ensure we don't
3114 // sleep too long.
3115 let hatch_rate = util::get_hatch_rate(self.configuration.hatch_rate.clone());
3116
3117 // Determine if it's time to spawn a SwanlingUser.
3118 if swanling_attack_run_state.spawn_user_in_ms == 0
3119 || util::ms_timer_expired(
3120 swanling_attack_run_state.spawn_user_timer,
3121 swanling_attack_run_state.spawn_user_in_ms,
3122 )
3123 {
3124 // Reset the spawn timer.
3125 swanling_attack_run_state.spawn_user_timer = std::time::Instant::now();
3126
3127 // To determine how long before we spawn the next SwanlingUser, start with 1,000.0
3128 // milliseconds and divide by the hatch_rate.
3129 swanling_attack_run_state.spawn_user_in_ms = (1_000.0 / hatch_rate) as usize;
3130
3131 // If running on a Worker, multiple by the number of workers as each is spawning
3132 // SwanlingUsers at this rate.
3133 if self.attack_mode == AttackMode::Worker {
3134 swanling_attack_run_state.spawn_user_in_ms *=
3135 self.configuration.expect_workers.unwrap() as usize;
3136 }
3137
3138 // Spawn next scheduled SwanlingUser.
3139 let mut thread_user =
3140 self.weighted_users[swanling_attack_run_state.spawn_user_counter].clone();
3141 swanling_attack_run_state.spawn_user_counter += 1;
3142
3143 // Copy weighted tasks and weighted on start tasks into the user thread.
3144 thread_user.weighted_tasks = self.task_sets[thread_user.task_sets_index]
3145 .weighted_tasks
3146 .clone();
3147 thread_user.weighted_on_start_tasks = self.task_sets[thread_user.task_sets_index]
3148 .weighted_on_start_tasks
3149 .clone();
3150 thread_user.weighted_on_stop_tasks = self.task_sets[thread_user.task_sets_index]
3151 .weighted_on_stop_tasks
3152 .clone();
3153 // Remember which task group this user is using.
3154 thread_user.weighted_users_index = self.metrics.users;
3155
3156 // Create a per-thread channel allowing parent thread to control child threads.
3157 let (parent_sender, thread_receiver): (
3158 flume::Sender<SwanlingUserCommand>,
3159 flume::Receiver<SwanlingUserCommand>,
3160 ) = flume::unbounded();
3161 swanling_attack_run_state.user_channels.push(parent_sender);
3162
3163 // Clone the logger_tx if enabled, otherwise is None.
3164 thread_user.logger = swanling_attack_run_state.all_threads_logger_tx.clone();
3165
3166 // Copy the SwanlingUser-throttle receiver channel, used by all threads.
3167 thread_user.throttle = if self.configuration.throttle_requests > 0 {
3168 Some(
3169 swanling_attack_run_state
3170 .throttle_threads_tx
3171 .clone()
3172 .unwrap(),
3173 )
3174 } else {
3175 None
3176 };
3177
3178 // Copy the SwanlingUser-to-parent sender channel, used by all threads.
3179 thread_user.channel_to_parent =
3180 Some(swanling_attack_run_state.all_threads_metrics_tx.clone());
3181
3182 // Copy the appropriate task_set into the thread.
3183 let thread_task_set = self.task_sets[thread_user.task_sets_index].clone();
3184
3185 // We number threads from 1 as they're human-visible (in the logs),
3186 // whereas metrics.users starts at 0.
3187 let thread_number = self.metrics.users + 1;
3188
3189 let is_worker = self.attack_mode == AttackMode::Worker;
3190
3191 // If running on Worker, use Worker configuration in SwanlingUser.
3192 if is_worker {
3193 thread_user.config = self.configuration.clone();
3194 }
3195
3196 // Launch a new user.
3197 let user = tokio::spawn(user::user_main(
3198 thread_number,
3199 thread_task_set,
3200 thread_user,
3201 thread_receiver,
3202 is_worker,
3203 ));
3204
3205 swanling_attack_run_state.users.push(user);
3206 self.metrics.users += 1;
3207
3208 if let Some(running_metrics) = self.configuration.running_metrics {
3209 if self.attack_mode != AttackMode::Worker
3210 && util::timer_expired(
3211 swanling_attack_run_state.running_metrics_timer,
3212 running_metrics,
3213 )
3214 {
3215 swanling_attack_run_state.running_metrics_timer = time::Instant::now();
3216 self.metrics.print_running();
3217 }
3218 }
3219 } else {
3220 // If displaying running metrics, be sure we wake up often enough to
3221 // display them at the configured rate.
3222 let running_metrics = self.configuration.running_metrics.unwrap_or(0);
3223
3224 // Otherwise, sleep until the next time something needs to happen.
3225 let sleep_duration = if running_metrics > 0
3226 && running_metrics * 1_000 < swanling_attack_run_state.spawn_user_in_ms
3227 {
3228 let sleep_delay = self.configuration.running_metrics.unwrap() * 1_000;
3229 swanling_attack_run_state.spawn_user_in_ms -= sleep_delay;
3230 tokio::time::Duration::from_millis(sleep_delay as u64)
3231 } else {
3232 tokio::time::Duration::from_millis(
3233 swanling_attack_run_state.spawn_user_in_ms as u64,
3234 )
3235 };
3236 debug!("sleeping {:?}...", sleep_duration);
3237 swanling_attack_run_state.drift_timer =
3238 util::sleep_minus_drift(sleep_duration, swanling_attack_run_state.drift_timer)
3239 .await;
3240 }
3241
3242 // If enough users have been spawned, move onto the next attack phase.
3243 if self.metrics.users >= self.weighted_users.len() {
3244 // Pause a tenth of a second waiting for the final user to fully start up.
3245 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
3246
3247 if self.attack_mode == AttackMode::Worker {
3248 info!(
3249 "[{}] launched {} users...",
3250 get_worker_id(),
3251 self.metrics.users
3252 );
3253 } else {
3254 info!("launched {} users...", self.metrics.users);
3255 }
3256
3257 self.reset_metrics(swanling_attack_run_state).await?;
3258 self.set_attack_phase(swanling_attack_run_state, AttackPhase::Running);
3259 }
3260
3261 Ok(())
3262 }
3263
3264 // Let the [`SwanlingAttack`](./struct.SwanlingAttack.html) run until the timer expires
3265 // (or the test is canceled), and then trigger a shut down.
3266 async fn monitor_attack(
3267 &mut self,
3268 swanling_attack_run_state: &mut SwanlingAttackRunState,
3269 ) -> Result<(), SwanlingError> {
3270 // Exit if run_time timer expires.
3271 if util::timer_expired(self.started.unwrap(), self.run_time) {
3272 self.set_attack_phase(swanling_attack_run_state, AttackPhase::Stopping);
3273 } else {
3274 // Subtract the time spent doing other things, running the main parent loop twice
3275 // per second.
3276 swanling_attack_run_state.drift_timer = util::sleep_minus_drift(
3277 time::Duration::from_millis(500),
3278 swanling_attack_run_state.drift_timer,
3279 )
3280 .await;
3281 }
3282
3283 Ok(())
3284 }
3285
3286 async fn stop_running_users(
3287 &mut self,
3288 swanling_attack_run_state: &mut SwanlingAttackRunState,
3289 ) -> Result<(), SwanlingError> {
3290 if self.attack_mode == AttackMode::Worker {
3291 info!(
3292 "[{}] stopping after {} seconds...",
3293 get_worker_id(),
3294 self.metrics.duration
3295 );
3296
3297 // Load test is shutting down, update pipe handler so there is no panic
3298 // when the Manager goes away.
3299 #[cfg(feature = "gaggle")]
3300 {
3301 let manager = swanling_attack_run_state.socket.clone().unwrap();
3302 register_shutdown_pipe_handler(&manager);
3303 }
3304 } else {
3305 info!("stopping after {} seconds...", self.metrics.duration);
3306 }
3307 for (index, send_to_user) in swanling_attack_run_state.user_channels.iter().enumerate() {
3308 match send_to_user.send(SwanlingUserCommand::Exit) {
3309 Ok(_) => {
3310 debug!("telling user {} to exit", index);
3311 }
3312 Err(e) => {
3313 info!("failed to tell user {} to exit: {}", index, e);
3314 }
3315 }
3316 }
3317 if self.attack_mode == AttackMode::Worker {
3318 info!("[{}] waiting for users to exit", get_worker_id());
3319 } else {
3320 info!("waiting for users to exit");
3321 }
3322
3323 // If throttle is enabled, tell throttle thread the load test is over.
3324 if let Some(throttle_tx) = swanling_attack_run_state.parent_to_throttle_tx.clone() {
3325 let _ = throttle_tx.send(false);
3326 }
3327
3328 // Take the users vector out of the SwanlingAttackRunState object so it can be
3329 // consumed by futures::future::join_all().
3330 let users = std::mem::take(&mut swanling_attack_run_state.users);
3331 futures::future::join_all(users).await;
3332 debug!("all users exited");
3333
3334 // If the logger thread is enabled, tell it to flush and exit.
3335 if swanling_attack_run_state.logger_handle.is_some() {
3336 if let Err(e) = swanling_attack_run_state
3337 .all_threads_logger_tx
3338 .clone()
3339 .unwrap()
3340 .send(None)
3341 {
3342 warn!("unexpected error telling logger thread to exit: {}", e);
3343 };
3344 // Take logger out of the SwanlingAttackRunState object so it can be
3345 // consumed by tokio::join!().
3346 let logger = std::mem::take(&mut swanling_attack_run_state.logger_handle);
3347 let _ = tokio::join!(logger.unwrap());
3348 }
3349
3350 // If we're printing metrics, collect the final metrics received from users.
3351 if !self.configuration.no_metrics {
3352 // Set the second parameter to true, ensuring that Swanling waits until all metrics
3353 // are received.
3354 let _received_message = self
3355 .receive_metrics(swanling_attack_run_state, true)
3356 .await?;
3357 }
3358
3359 #[cfg(feature = "gaggle")]
3360 {
3361 // As worker, push metrics up to manager.
3362 if self.attack_mode == AttackMode::Worker {
3363 worker::push_metrics_to_manager(
3364 &swanling_attack_run_state.socket.clone().unwrap(),
3365 vec![
3366 GaggleMetrics::Requests(self.metrics.requests.clone()),
3367 GaggleMetrics::Errors(self.metrics.errors.clone()),
3368 GaggleMetrics::Tasks(self.metrics.tasks.clone()),
3369 ],
3370 true,
3371 );
3372 // No need to reset local metrics, the worker is exiting.
3373 }
3374 }
3375
3376 Ok(())
3377 }
3378
3379 // Cleanly shut down the [`SwanlingAttack`](./struct.SwanlingAttack.html).
3380 async fn stop_attack(&mut self) -> Result<(), SwanlingError> {
3381 // Run any configured test_stop() functions.
3382 self.run_test_stop().await?;
3383
3384 // Percentile and errors are only displayed when the load test is finished.
3385 self.metrics.final_metrics = true;
3386
3387 Ok(())
3388 }
3389
3390 // Reset the SwanlingAttackRunState before starting a load test. This is to allow a Controller
3391 // to stop and start the load test multiple times, for example from a UI.
3392 async fn reset_run_state(
3393 &mut self,
3394 swanling_attack_run_state: &mut SwanlingAttackRunState,
3395 ) -> Result<(), SwanlingError> {
3396 // Run any configured test_start() functions.
3397 self.run_test_start().await.unwrap();
3398
3399 // Prepare to collect metrics, if enabled.
3400 self.metrics = SwanlingMetrics::default();
3401 if !self.configuration.no_metrics {
3402 self.metrics
3403 .initialize_task_metrics(&self.task_sets, &self.configuration);
3404 self.metrics.display_metrics = true;
3405 // Only display status codes if enabled.
3406 self.metrics.display_status_codes = self.configuration.status_codes;
3407 }
3408
3409 // Reset the run state.
3410 let std_now = std::time::Instant::now();
3411 swanling_attack_run_state.spawn_user_timer = std_now;
3412 swanling_attack_run_state.spawn_user_in_ms = 0;
3413 swanling_attack_run_state.spawn_user_counter = 0;
3414 swanling_attack_run_state.drift_timer = tokio::time::Instant::now();
3415 swanling_attack_run_state.metrics_header_displayed = false;
3416 swanling_attack_run_state.idle_status_displayed = false;
3417 swanling_attack_run_state.users = Vec::new();
3418 swanling_attack_run_state.user_channels = Vec::new();
3419 swanling_attack_run_state.running_metrics_timer = std_now;
3420 swanling_attack_run_state.display_running_metrics = false;
3421 swanling_attack_run_state.shutdown_after_stop = !self.configuration.no_autostart;
3422 swanling_attack_run_state.all_users_spawned = false;
3423
3424 // If enabled, spawn a logger thread.
3425 let (logger_handle, all_threads_logger_tx) =
3426 self.configuration.setup_loggers(&self.defaults).await?;
3427 swanling_attack_run_state.logger_handle = logger_handle;
3428 swanling_attack_run_state.all_threads_logger_tx = all_threads_logger_tx;
3429
3430 // If enabled, spawn a throttle thread.
3431 let (throttle_threads_tx, parent_to_throttle_tx) = self.setup_throttle().await;
3432 swanling_attack_run_state.throttle_threads_tx = throttle_threads_tx;
3433 swanling_attack_run_state.parent_to_throttle_tx = parent_to_throttle_tx;
3434
3435 // If enabled, create an report file and confirm access.
3436 swanling_attack_run_state.report_file = match self.prepare_report_file().await {
3437 Ok(f) => f,
3438 Err(e) => {
3439 return Err(SwanlingError::InvalidOption {
3440 option: "--report-file".to_string(),
3441 value: self.get_report_file_path().unwrap(),
3442 detail: format!("Failed to create report file: {}", e),
3443 })
3444 }
3445 };
3446
3447 // Record when the SwanlingAttack officially started.
3448 self.started = Some(time::Instant::now());
3449
3450 // Also record a formattable timestamp, for human readable reports.
3451 self.metrics.started = Some(Local::now());
3452
3453 Ok(())
3454 }
3455
3456 // Called internally in local-mode and gaggle-mode.
3457 async fn start_attack(
3458 mut self,
3459 socket: Option<Socket>,
3460 ) -> Result<SwanlingAttack, SwanlingError> {
3461 trace!("start_attack: socket({:?})", socket);
3462
3463 // The SwanlingAttackRunState is used while spawning and running the
3464 // SwanlingUser threads that generate the load test.
3465 let mut swanling_attack_run_state = self
3466 .initialize_attack(socket)
3467 .await
3468 .expect("failed to initialize SwanlingAttackRunState");
3469
3470 // The Swanling parent process SwanlingAttack loop runs until Swanling shuts down. Swanling enters
3471 // the loop in AttackPhase::Idle, and exits in AttackPhase::Shutdown.
3472 loop {
3473 match self.attack_phase {
3474 // In the Idle phase the Swanling configuration can be changed by a Controller,
3475 // and otherwise nothing happens but sleeping an checking for messages.
3476 AttackPhase::Idle => {
3477 if self.configuration.no_autostart {
3478 // Sleep then check for further instructions.
3479 if swanling_attack_run_state.idle_status_displayed {
3480 let sleep_duration = tokio::time::Duration::from_millis(250);
3481 debug!("sleeping {:?}...", sleep_duration);
3482 swanling_attack_run_state.drift_timer = util::sleep_minus_drift(
3483 sleep_duration,
3484 swanling_attack_run_state.drift_timer,
3485 )
3486 .await;
3487 // Only display informational message about being idle one time.
3488 } else {
3489 info!("Swanling is currently idle.");
3490 swanling_attack_run_state.idle_status_displayed = true;
3491 }
3492 } else {
3493 // Prepare to start the load test, resetting timers and counters.
3494 self.reset_run_state(&mut swanling_attack_run_state).await?;
3495 self.set_attack_phase(
3496 &mut swanling_attack_run_state,
3497 AttackPhase::Starting,
3498 );
3499 }
3500 }
3501 // In the Start phase, Swanling launches SwanlingUser threads and starts a SwanlingAttack.
3502 AttackPhase::Starting => {
3503 self.update_duration();
3504 self.spawn_attack(&mut swanling_attack_run_state)
3505 .await
3506 .expect("failed to start SwanlingAttack");
3507 }
3508 // In the Running phase, Swanling maintains the configured SwanlingAttack.
3509 AttackPhase::Running => {
3510 self.update_duration();
3511 self.monitor_attack(&mut swanling_attack_run_state).await?;
3512 }
3513 // In the Stopping phase, Swanling stops all SwanlingUser threads and optionally reports
3514 // any collected metrics.
3515 AttackPhase::Stopping => {
3516 // If displaying metrics, update internal state reflecting how long load test
3517 // has been running.
3518 self.update_duration();
3519 // Tell all running SwanlingUsers to stop.
3520 self.stop_running_users(&mut swanling_attack_run_state)
3521 .await?;
3522 // Stop any running SwanlingUser threads.
3523 self.stop_attack().await?;
3524 // Collect all metrics sent by SwanlingUser threads.
3525 self.sync_metrics(&mut swanling_attack_run_state, true)
3526 .await?;
3527 // Write an html report, if enabled.
3528 self.write_html_report(&mut swanling_attack_run_state)
3529 .await?;
3530 // Shutdown Swanling or go into an idle waiting state.
3531 if swanling_attack_run_state.shutdown_after_stop {
3532 self.set_attack_phase(
3533 &mut swanling_attack_run_state,
3534 AttackPhase::Shutdown,
3535 );
3536 } else {
3537 // Print metrics, if enabled.
3538 if !self.configuration.no_metrics {
3539 println!("{}", self.metrics);
3540 }
3541 self.set_attack_phase(&mut swanling_attack_run_state, AttackPhase::Idle);
3542 }
3543 }
3544 // By reaching the Shutdown phase, break out of the SwanlingAttack loop.
3545 AttackPhase::Shutdown => break,
3546 }
3547 // Regularly synchronize metrics.
3548 self.sync_metrics(&mut swanling_attack_run_state, false)
3549 .await?;
3550
3551 // Check if a Controller has made a request.
3552 self.handle_controller_requests(&mut swanling_attack_run_state)
3553 .await?;
3554
3555 // Gracefully exit loop if ctrl-c is caught.
3556 if self.attack_phase != AttackPhase::Shutdown
3557 && swanling_attack_run_state.canceled.load(Ordering::SeqCst)
3558 {
3559 // Shutdown after stopping as the load test was canceled.
3560 swanling_attack_run_state.shutdown_after_stop = true;
3561
3562 // No metrics to display when sitting idle, so disable.
3563 if self.attack_phase == AttackPhase::Idle {
3564 self.metrics.display_metrics = false;
3565 }
3566
3567 // Cleanly stop the load test.
3568 self.set_attack_phase(&mut swanling_attack_run_state, AttackPhase::Stopping);
3569 }
3570 }
3571
3572 Ok(self)
3573 }
3574}
3575
3576/// All run-time options can optionally be configured with custom defaults.
3577///
3578/// For example, you can optionally configure a default host for the load test. This is
3579/// used if no per-[`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html) host is defined,
3580/// no `--host` CLI option is configured, and if the
3581/// [`SwanlingTask`](./swanling/struct.SwanlingTask.html) itself doesn't hard-code the host in
3582/// the base url of its request. In that case, this host is added to all requests.
3583///
3584/// For example, a load test could be configured to default to running against a local
3585/// development container, and the `--host` option could be used to override the host
3586/// value to run the load test against the production environment.
3587///
3588/// # Example
3589/// ```rust
3590/// use swanling::prelude::*;
3591///
3592/// fn main() -> Result<(), SwanlingError> {
3593/// SwanlingAttack::initialize()?
3594/// .set_default(SwanlingDefault::Host, "local.dev")?;
3595///
3596/// Ok(())
3597/// }
3598/// ```
3599///
3600/// The following run-time options can be configured with a custom default using a
3601/// borrowed string slice (`&str`):
3602/// - [SwanlingDefault::Host](../swanling/enum.SwanlingDefault.html#variant.Host)
3603/// - [SwanlingDefault::SwanlingLog](../swanling/enum.SwanlingDefault.html#variant.SwanlingLog)
3604/// - [SwanlingDefault::RequestFormat](../swanling/enum.SwanlingDefault.html#variant.RequestFormat)
3605/// - [SwanlingDefault::TaskLog](../swanling/enum.SwanlingDefault.html#variant.TaskLog)
3606/// - [SwanlingDefault::ErrorLog](../swanling/enum.SwanlingDefault.html#variant.ErrorLog)
3607/// - [SwanlingDefault::DebugLog](../swanling/enum.SwanlingDefault.html#variant.DebugLog)
3608/// - [SwanlingDefault::TelnetHost](../swanling/enum.SwanlingDefault.html#variant.TelnetHost)
3609/// - [SwanlingDefault::WebSocketHost](../swanling/enum.SwanlingDefault.html#variant.WebSocketHost)
3610/// - [SwanlingDefault::ManagerBindHost](../swanling/enum.SwanlingDefault.html#variant.ManagerBindHost)
3611/// - [SwanlingDefault::ManagerHost](../swanling/enum.SwanlingDefault.html#variant.ManagerHost)
3612///
3613/// The following run-time options can be configured with a custom default using a
3614/// `usize` integer:
3615/// - [SwanlingDefault::Users](../swanling/enum.SwanlingDefault.html#variant.Users)
3616/// - [SwanlingDefault::HatchRate](../swanling/enum.SwanlingDefault.html#variant.HatchRate)
3617/// - [SwanlingDefault::RunTime](../swanling/enum.SwanlingDefault.html#variant.RunTime)
3618/// - [SwanlingDefault::RunningMetrics](../swanling/enum.SwanlingDefault.html#variant.RunningMetrics)
3619/// - [SwanlingDefault::LogLevel](../swanling/enum.SwanlingDefault.html#variant.LogLevel)
3620/// - [SwanlingDefault::Verbose](../swanling/enum.SwanlingDefault.html#variant.Verbose)
3621/// - [SwanlingDefault::ThrottleRequests](../swanling/enum.SwanlingDefault.html#variant.ThrottleRequests)
3622/// - [SwanlingDefault::ExpectWorkers](../swanling/enum.SwanlingDefault.html#variant.ExpectWorkers)
3623/// - [SwanlingDefault::TelnetPort](../swanling/enum.SwanlingDefault.html#variant.TelnetPort)
3624/// - [SwanlingDefault::WebSocketPort](../swanling/enum.SwanlingDefault.html#variant.WebSocketPort)
3625/// - [SwanlingDefault::ManagerBindPort](../swanling/enum.SwanlingDefault.html#variant.ManagerBindPort)
3626/// - [SwanlingDefault::ManagerPort](../swanling/enum.SwanlingDefault.html#variant.ManagerPort)
3627///
3628/// The following run-time flags can be configured with a custom default using a
3629/// `bool` (and otherwise default to `false`).
3630/// - [SwanlingDefault::NoResetMetrics](../swanling/enum.SwanlingDefault.html#variant.NoResetMetrics)
3631/// - [SwanlingDefault::NoMetrics](../swanling/enum.SwanlingDefault.html#variant.NoMetrics)
3632/// - [SwanlingDefault::NoTaskMetrics](../swanling/enum.SwanlingDefault.html#variant.NoTaskMetrics)
3633/// - [SwanlingDefault::NoErrorSummary](../swanling/enum.SwanlingDefault.html#variant.NoErrorSummary)
3634/// - [SwanlingDefault::NoDebugBody](../swanling/enum.SwanlingDefault.html#variant.NoDebugBody)
3635/// - [SwanlingDefault::NoTelnet](../swanling/enum.SwanlingDefault.html#variant.NoTelnet)
3636/// - [SwanlingDefault::NoWebSocket](../swanling/enum.SwanlingDefault.html#variant.NoWebSocket)
3637/// - [SwanlingDefault::NoAutoStart](../swanling/enum.SwanlingDefault.html#variant.NoAutoStart)
3638/// - [SwanlingDefault::StatusCodes](../swanling/enum.SwanlingDefault.html#variant.StatusCodes)
3639/// - [SwanlingDefault::StickyFollow](../swanling/enum.SwanlingDefault.html#variant.StickyFollow)
3640/// - [SwanlingDefault::Manager](../swanling/enum.SwanlingDefault.html#variant.Manager)
3641/// - [SwanlingDefault::NoHashCheck](../swanling/enum.SwanlingDefault.html#variant.NoHashCheck)
3642/// - [SwanlingDefault::Worker](../swanling/enum.SwanlingDefault.html#variant.Worker)
3643///
3644/// The following run-time flags can be configured with a custom default using a
3645/// `SwanlingLogFormat`.
3646/// - [SwanlingDefault::RequestLog](../swanling/enum.SwanlingDefault.html#variant.RequestLog)
3647/// - [SwanlingDefault::TaskLog](../swanling/enum.SwanlingDefault.html#variant.TaskLog)
3648/// - [SwanlingDefault::DebugFormat](../swanling/enum.SwanlingDefault.html#variant.DebugFormat)
3649/// # Another Example
3650/// ```rust
3651/// use swanling::prelude::*;
3652///
3653/// fn main() -> Result<(), SwanlingError> {
3654/// SwanlingAttack::initialize()?
3655/// // Do not reset the metrics after the load test finishes starting.
3656/// .set_default(SwanlingDefault::NoResetMetrics, true)?
3657/// // Display info level logs while the test runs.
3658/// .set_default(SwanlingDefault::Verbose, 1)?
3659/// // Log all requests made during the test to `./swanling-request.log`.
3660/// .set_default(SwanlingDefault::RequestLog, "swanling-request.log")?;
3661///
3662/// Ok(())
3663/// }
3664/// ```
3665pub trait SwanlingDefaultType<T> {
3666 fn set_default(self, key: SwanlingDefault, value: T) -> Result<Box<Self>, SwanlingError>;
3667}
3668impl SwanlingDefaultType<&str> for SwanlingAttack {
3669 fn set_default(
3670 mut self,
3671 key: SwanlingDefault,
3672 value: &str,
3673 ) -> Result<Box<Self>, SwanlingError> {
3674 match key {
3675 // Set valid defaults.
3676 SwanlingDefault::HatchRate => self.defaults.hatch_rate = Some(value.to_string()),
3677 SwanlingDefault::Host => self.defaults.host = Some(value.to_string()),
3678 SwanlingDefault::SwanlingLog => self.defaults.swanling_log = Some(value.to_string()),
3679 SwanlingDefault::ReportFile => self.defaults.report_file = Some(value.to_string()),
3680 SwanlingDefault::RequestLog => self.defaults.request_log = Some(value.to_string()),
3681 SwanlingDefault::TaskLog => self.defaults.task_log = Some(value.to_string()),
3682 SwanlingDefault::ErrorLog => self.defaults.error_log = Some(value.to_string()),
3683 SwanlingDefault::DebugLog => self.defaults.debug_log = Some(value.to_string()),
3684 SwanlingDefault::TelnetHost => self.defaults.telnet_host = Some(value.to_string()),
3685 SwanlingDefault::WebSocketHost => {
3686 self.defaults.websocket_host = Some(value.to_string())
3687 }
3688 SwanlingDefault::ManagerBindHost => {
3689 self.defaults.manager_bind_host = Some(value.to_string())
3690 }
3691 SwanlingDefault::ManagerHost => self.defaults.manager_host = Some(value.to_string()),
3692 // Otherwise display a helpful and explicit error.
3693 SwanlingDefault::Users
3694 | SwanlingDefault::RunTime
3695 | SwanlingDefault::LogLevel
3696 | SwanlingDefault::Verbose
3697 | SwanlingDefault::ThrottleRequests
3698 | SwanlingDefault::ExpectWorkers
3699 | SwanlingDefault::TelnetPort
3700 | SwanlingDefault::WebSocketPort
3701 | SwanlingDefault::ManagerBindPort
3702 | SwanlingDefault::ManagerPort => {
3703 return Err(SwanlingError::InvalidOption {
3704 option: format!("SwanlingDefault::{:?}", key),
3705 value: value.to_string(),
3706 detail: format!(
3707 "set_default(SwanlingDefault::{:?}, {}) expected usize value, received &str",
3708 key, value
3709 ),
3710 });
3711 }
3712 SwanlingDefault::RunningMetrics
3713 | SwanlingDefault::NoResetMetrics
3714 | SwanlingDefault::NoMetrics
3715 | SwanlingDefault::NoTaskMetrics
3716 | SwanlingDefault::NoErrorSummary
3717 | SwanlingDefault::NoDebugBody
3718 | SwanlingDefault::NoTelnet
3719 | SwanlingDefault::NoWebSocket
3720 | SwanlingDefault::NoAutoStart
3721 | SwanlingDefault::StatusCodes
3722 | SwanlingDefault::StickyFollow
3723 | SwanlingDefault::Manager
3724 | SwanlingDefault::NoHashCheck
3725 | SwanlingDefault::Worker => {
3726 return Err(SwanlingError::InvalidOption {
3727 option: format!("SwanlingDefault::{:?}", key),
3728 value: value.to_string(),
3729 detail: format!(
3730 "set_default(SwanlingDefault::{:?}, {}) expected bool value, received &str",
3731 key, value
3732 ),
3733 });
3734 }
3735 SwanlingDefault::DebugFormat
3736 | SwanlingDefault::ErrorFormat
3737 | SwanlingDefault::TaskFormat
3738 | SwanlingDefault::RequestFormat => {
3739 return Err(SwanlingError::InvalidOption {
3740 option: format!("SwanlingDefault::{:?}", key),
3741 value: value.to_string(),
3742 detail: format!(
3743 "set_default(SwanlingDefault::{:?}, {}) expected SwanlingLogFormat value, received &str",
3744 key, value
3745 ),
3746 });
3747 }
3748 SwanlingDefault::CoordinatedOmissionMitigation => {
3749 return Err(SwanlingError::InvalidOption {
3750 option: format!("SwanlingDefault::{:?}", key),
3751 value: value.to_string(),
3752 detail: format!(
3753 "set_default(SwanlingDefault::{:?}, {}) expected SwanlingCoordinatedOmissionMitigation value, received &str",
3754 key, value
3755 ),
3756 });
3757 }
3758 }
3759 Ok(Box::new(self))
3760 }
3761}
3762impl SwanlingDefaultType<usize> for SwanlingAttack {
3763 fn set_default(
3764 mut self,
3765 key: SwanlingDefault,
3766 value: usize,
3767 ) -> Result<Box<Self>, SwanlingError> {
3768 match key {
3769 SwanlingDefault::Users => self.defaults.users = Some(value),
3770 SwanlingDefault::RunTime => self.defaults.run_time = Some(value),
3771 SwanlingDefault::RunningMetrics => self.defaults.running_metrics = Some(value),
3772 SwanlingDefault::LogLevel => self.defaults.log_level = Some(value as u8),
3773 SwanlingDefault::Verbose => self.defaults.verbose = Some(value as u8),
3774 SwanlingDefault::ThrottleRequests => self.defaults.throttle_requests = Some(value),
3775 SwanlingDefault::ExpectWorkers => self.defaults.expect_workers = Some(value as u16),
3776 SwanlingDefault::TelnetPort => self.defaults.telnet_port = Some(value as u16),
3777 SwanlingDefault::WebSocketPort => self.defaults.websocket_port = Some(value as u16),
3778 SwanlingDefault::ManagerBindPort => {
3779 self.defaults.manager_bind_port = Some(value as u16)
3780 }
3781 SwanlingDefault::ManagerPort => self.defaults.manager_port = Some(value as u16),
3782 // Otherwise display a helpful and explicit error.
3783 SwanlingDefault::Host
3784 | SwanlingDefault::HatchRate
3785 | SwanlingDefault::SwanlingLog
3786 | SwanlingDefault::ReportFile
3787 | SwanlingDefault::RequestLog
3788 | SwanlingDefault::TaskLog
3789 | SwanlingDefault::ErrorLog
3790 | SwanlingDefault::DebugLog
3791 | SwanlingDefault::TelnetHost
3792 | SwanlingDefault::WebSocketHost
3793 | SwanlingDefault::ManagerBindHost
3794 | SwanlingDefault::ManagerHost => {
3795 return Err(SwanlingError::InvalidOption {
3796 option: format!("SwanlingDefault::{:?}", key),
3797 value: format!("{}", value),
3798 detail: format!(
3799 "set_default(SwanlingDefault::{:?}, {}) expected &str value, received usize",
3800 key, value
3801 ),
3802 })
3803 }
3804 SwanlingDefault::NoResetMetrics
3805 | SwanlingDefault::NoMetrics
3806 | SwanlingDefault::NoTaskMetrics
3807 | SwanlingDefault::NoErrorSummary
3808 | SwanlingDefault::NoDebugBody
3809 | SwanlingDefault::NoTelnet
3810 | SwanlingDefault::NoWebSocket
3811 | SwanlingDefault::NoAutoStart
3812 | SwanlingDefault::StatusCodes
3813 | SwanlingDefault::StickyFollow
3814 | SwanlingDefault::Manager
3815 | SwanlingDefault::NoHashCheck
3816 | SwanlingDefault::Worker => {
3817 return Err(SwanlingError::InvalidOption {
3818 option: format!("SwanlingDefault::{:?}", key),
3819 value: format!("{}", value),
3820 detail: format!(
3821 "set_default(SwanlingDefault::{:?}, {}) expected bool value, received usize",
3822 key, value
3823 ),
3824 })
3825 }
3826 SwanlingDefault::RequestFormat
3827 | SwanlingDefault::DebugFormat
3828 | SwanlingDefault::ErrorFormat
3829 | SwanlingDefault::TaskFormat => {
3830 return Err(SwanlingError::InvalidOption {
3831 option: format!("SwanlingDefault::{:?}", key),
3832 value: value.to_string(),
3833 detail: format!(
3834 "set_default(SwanlingDefault::{:?}, {}) expected SwanlingLogFormat value, received usize",
3835 key, value
3836 ),
3837 });
3838 }
3839 SwanlingDefault::CoordinatedOmissionMitigation => {
3840 return Err(SwanlingError::InvalidOption {
3841 option: format!("SwanlingDefault::{:?}", key),
3842 value: value.to_string(),
3843 detail: format!(
3844 "set_default(SwanlingDefault::{:?}, {}) expected SwanlingCoordinatedOmissionMitigation value, received usize",
3845 key, value
3846 ),
3847 });
3848 }
3849 }
3850 Ok(Box::new(self))
3851 }
3852}
3853impl SwanlingDefaultType<bool> for SwanlingAttack {
3854 fn set_default(
3855 mut self,
3856 key: SwanlingDefault,
3857 value: bool,
3858 ) -> Result<Box<Self>, SwanlingError> {
3859 match key {
3860 SwanlingDefault::NoResetMetrics => self.defaults.no_reset_metrics = Some(value),
3861 SwanlingDefault::NoMetrics => self.defaults.no_metrics = Some(value),
3862 SwanlingDefault::NoTaskMetrics => self.defaults.no_task_metrics = Some(value),
3863 SwanlingDefault::NoErrorSummary => self.defaults.no_error_summary = Some(value),
3864 SwanlingDefault::NoDebugBody => self.defaults.no_debug_body = Some(value),
3865 SwanlingDefault::NoTelnet => self.defaults.no_telnet = Some(value),
3866 SwanlingDefault::NoWebSocket => self.defaults.no_websocket = Some(value),
3867 SwanlingDefault::NoAutoStart => self.defaults.no_autostart = Some(value),
3868 SwanlingDefault::StatusCodes => self.defaults.status_codes = Some(value),
3869 SwanlingDefault::StickyFollow => self.defaults.sticky_follow = Some(value),
3870 SwanlingDefault::Manager => self.defaults.manager = Some(value),
3871 SwanlingDefault::NoHashCheck => self.defaults.no_hash_check = Some(value),
3872 SwanlingDefault::Worker => self.defaults.worker = Some(value),
3873 // Otherwise display a helpful and explicit error.
3874 SwanlingDefault::Host
3875 | SwanlingDefault::SwanlingLog
3876 | SwanlingDefault::ReportFile
3877 | SwanlingDefault::RequestLog
3878 | SwanlingDefault::TaskLog
3879 | SwanlingDefault::RunningMetrics
3880 | SwanlingDefault::ErrorLog
3881 | SwanlingDefault::DebugLog
3882 | SwanlingDefault::TelnetHost
3883 | SwanlingDefault::WebSocketHost
3884 | SwanlingDefault::ManagerBindHost
3885 | SwanlingDefault::ManagerHost => {
3886 return Err(SwanlingError::InvalidOption {
3887 option: format!("SwanlingDefault::{:?}", key),
3888 value: format!("{}", value),
3889 detail: format!(
3890 "set_default(SwanlingDefault::{:?}, {}) expected &str value, received bool",
3891 key, value
3892 ),
3893 })
3894 }
3895 SwanlingDefault::Users
3896 | SwanlingDefault::HatchRate
3897 | SwanlingDefault::RunTime
3898 | SwanlingDefault::LogLevel
3899 | SwanlingDefault::Verbose
3900 | SwanlingDefault::ThrottleRequests
3901 | SwanlingDefault::ExpectWorkers
3902 | SwanlingDefault::TelnetPort
3903 | SwanlingDefault::WebSocketPort
3904 | SwanlingDefault::ManagerBindPort
3905 | SwanlingDefault::ManagerPort => {
3906 return Err(SwanlingError::InvalidOption {
3907 option: format!("SwanlingDefault::{:?}", key),
3908 value: format!("{}", value),
3909 detail: format!(
3910 "set_default(SwanlingDefault::{:?}, {}) expected usize value, received bool",
3911 key, value
3912 ),
3913 })
3914 }
3915 SwanlingDefault::RequestFormat
3916 | SwanlingDefault::DebugFormat
3917 | SwanlingDefault::ErrorFormat
3918 | SwanlingDefault::TaskFormat => {
3919 return Err(SwanlingError::InvalidOption {
3920 option: format!("SwanlingDefault::{:?}", key),
3921 value: value.to_string(),
3922 detail: format!(
3923 "set_default(SwanlingDefault::{:?}, {}) expected SwanlingLogFormat value, received bool",
3924 key, value
3925 ),
3926 });
3927 }
3928 SwanlingDefault::CoordinatedOmissionMitigation => {
3929 return Err(SwanlingError::InvalidOption {
3930 option: format!("SwanlingDefault::{:?}", key),
3931 value: value.to_string(),
3932 detail: format!(
3933 "set_default(SwanlingDefault::{:?}, {}) expected SwanlingCoordinatedOmissionMitigation value, received bool",
3934 key, value
3935 ),
3936 });
3937 }
3938 }
3939 Ok(Box::new(self))
3940 }
3941}
3942impl SwanlingDefaultType<SwanlingCoordinatedOmissionMitigation> for SwanlingAttack {
3943 fn set_default(
3944 mut self,
3945 key: SwanlingDefault,
3946 value: SwanlingCoordinatedOmissionMitigation,
3947 ) -> Result<Box<Self>, SwanlingError> {
3948 match key {
3949 SwanlingDefault::CoordinatedOmissionMitigation => self.defaults.co_mitigation = Some(value),
3950 // Otherwise display a helpful and explicit error.
3951 SwanlingDefault::NoResetMetrics
3952 | SwanlingDefault::NoMetrics
3953 | SwanlingDefault::NoTaskMetrics
3954 | SwanlingDefault::NoErrorSummary
3955 | SwanlingDefault::NoDebugBody
3956 | SwanlingDefault::NoTelnet
3957 | SwanlingDefault::NoWebSocket
3958 | SwanlingDefault::NoAutoStart
3959 | SwanlingDefault::StatusCodes
3960 | SwanlingDefault::StickyFollow
3961 | SwanlingDefault::Manager
3962 | SwanlingDefault::NoHashCheck
3963 | SwanlingDefault::Worker => {
3964 return Err(SwanlingError::InvalidOption {
3965 option: format!("SwanlingDefault::{:?}", key),
3966 value: format!("{:?}", value),
3967 detail: format!(
3968 "set_default(SwanlingDefault::{:?}, {:?}) expected bool value, received SwanlingCoordinatedOmissionMitigation",
3969 key, value
3970 ),
3971 })
3972 }
3973 SwanlingDefault::Host
3974 | SwanlingDefault::SwanlingLog
3975 | SwanlingDefault::ReportFile
3976 | SwanlingDefault::RequestLog
3977 | SwanlingDefault::TaskLog
3978 | SwanlingDefault::RunningMetrics
3979 | SwanlingDefault::ErrorLog
3980 | SwanlingDefault::DebugLog
3981 | SwanlingDefault::TelnetHost
3982 | SwanlingDefault::WebSocketHost
3983 | SwanlingDefault::ManagerBindHost
3984 | SwanlingDefault::ManagerHost => {
3985 return Err(SwanlingError::InvalidOption {
3986 option: format!("SwanlingDefault::{:?}", key),
3987 value: format!("{:?}", value),
3988 detail: format!(
3989 "set_default(SwanlingDefault::{:?}, {:?}) expected &str value, received SwanlingCoordinatedOmissionMitigation",
3990 key, value
3991 ),
3992 })
3993 }
3994 SwanlingDefault::Users
3995 | SwanlingDefault::HatchRate
3996 | SwanlingDefault::RunTime
3997 | SwanlingDefault::LogLevel
3998 | SwanlingDefault::Verbose
3999 | SwanlingDefault::ThrottleRequests
4000 | SwanlingDefault::ExpectWorkers
4001 | SwanlingDefault::TelnetPort
4002 | SwanlingDefault::WebSocketPort
4003 | SwanlingDefault::ManagerBindPort
4004 | SwanlingDefault::ManagerPort => {
4005 return Err(SwanlingError::InvalidOption {
4006 option: format!("SwanlingDefault::{:?}", key),
4007 value: format!("{:?}", value),
4008 detail: format!(
4009 "set_default(SwanlingDefault::{:?}, {:?}) expected usize value, received SwanlingCoordinatedOmissionMitigation",
4010 key, value
4011 ),
4012 })
4013 }
4014 SwanlingDefault::RequestFormat
4015 | SwanlingDefault::DebugFormat
4016 | SwanlingDefault::ErrorFormat
4017 | SwanlingDefault::TaskFormat => {
4018 return Err(SwanlingError::InvalidOption {
4019 option: format!("SwanlingDefault::{:?}", key),
4020 value: format!("{:?}", value),
4021 detail: format!(
4022 "set_default(SwanlingDefault::{:?}, {:?}) expected SwanlingLogFormat value, received SwanlingCoordinatedOmissionMitigation",
4023 key, value
4024 ),
4025 })
4026 }
4027 }
4028 Ok(Box::new(self))
4029 }
4030}
4031impl SwanlingDefaultType<SwanlingLogFormat> for SwanlingAttack {
4032 fn set_default(
4033 mut self,
4034 key: SwanlingDefault,
4035 value: SwanlingLogFormat,
4036 ) -> Result<Box<Self>, SwanlingError> {
4037 match key {
4038 SwanlingDefault::RequestFormat => self.defaults.request_format = Some(value),
4039 SwanlingDefault::DebugFormat => self.defaults.debug_format = Some(value),
4040 SwanlingDefault::ErrorFormat => self.defaults.error_format = Some(value),
4041 SwanlingDefault::TaskFormat => self.defaults.task_format = Some(value),
4042 // Otherwise display a helpful and explicit error.
4043 SwanlingDefault::NoResetMetrics
4044 | SwanlingDefault::NoMetrics
4045 | SwanlingDefault::NoTaskMetrics
4046 | SwanlingDefault::NoErrorSummary
4047 | SwanlingDefault::NoDebugBody
4048 | SwanlingDefault::NoTelnet
4049 | SwanlingDefault::NoWebSocket
4050 | SwanlingDefault::NoAutoStart
4051 | SwanlingDefault::StatusCodes
4052 | SwanlingDefault::StickyFollow
4053 | SwanlingDefault::Manager
4054 | SwanlingDefault::NoHashCheck
4055 | SwanlingDefault::Worker => {
4056 return Err(SwanlingError::InvalidOption {
4057 option: format!("SwanlingDefault::{:?}", key),
4058 value: format!("{:?}", value),
4059 detail: format!(
4060 "set_default(SwanlingDefault::{:?}, {:?}) expected bool value, received SwanlingCoordinatedOmissionMitigation",
4061 key, value
4062 ),
4063 })
4064 }
4065 SwanlingDefault::Host
4066 | SwanlingDefault::SwanlingLog
4067 | SwanlingDefault::ReportFile
4068 | SwanlingDefault::RequestLog
4069 | SwanlingDefault::TaskLog
4070 | SwanlingDefault::RunningMetrics
4071 | SwanlingDefault::ErrorLog
4072 | SwanlingDefault::DebugLog
4073 | SwanlingDefault::TelnetHost
4074 | SwanlingDefault::WebSocketHost
4075 | SwanlingDefault::ManagerBindHost
4076 | SwanlingDefault::ManagerHost => {
4077 return Err(SwanlingError::InvalidOption {
4078 option: format!("SwanlingDefault::{:?}", key),
4079 value: format!("{:?}", value),
4080 detail: format!(
4081 "set_default(SwanlingDefault::{:?}, {:?}) expected &str value, received SwanlingCoordinatedOmissionMitigation",
4082 key, value
4083 ),
4084 })
4085 }
4086 SwanlingDefault::Users
4087 | SwanlingDefault::HatchRate
4088 | SwanlingDefault::RunTime
4089 | SwanlingDefault::LogLevel
4090 | SwanlingDefault::Verbose
4091 | SwanlingDefault::ThrottleRequests
4092 | SwanlingDefault::ExpectWorkers
4093 | SwanlingDefault::TelnetPort
4094 | SwanlingDefault::WebSocketPort
4095 | SwanlingDefault::ManagerBindPort
4096 | SwanlingDefault::ManagerPort => {
4097 return Err(SwanlingError::InvalidOption {
4098 option: format!("SwanlingDefault::{:?}", key),
4099 value: format!("{:?}", value),
4100 detail: format!(
4101 "set_default(SwanlingDefault::{:?}, {:?}) expected usize value, received SwanlingCoordinatedOmissionMitigation",
4102 key, value
4103 ),
4104 })
4105 }
4106 SwanlingDefault::CoordinatedOmissionMitigation => {
4107 return Err(SwanlingError::InvalidOption {
4108 option: format!("SwanlingDefault::{:?}", key),
4109 value: format!("{:?}", value),
4110 detail: format!(
4111 "set_default(SwanlingDefault::{:?}, {:?}) expected SwanlingCoordinatedOmissionMitigation value, received SwanlingLogFormat",
4112 key, value
4113 ),
4114 })
4115
4116 }
4117 }
4118 Ok(Box::new(self))
4119 }
4120}
4121
4122/// Options available when launching a Swanling load test.
4123#[derive(Options, Debug, Clone, Serialize, Deserialize)]
4124pub struct SwanlingConfiguration {
4125 /// Displays this help
4126 #[options(short = "h")]
4127 pub help: bool,
4128 /// Prints version information
4129 #[options(short = "V")]
4130 pub version: bool,
4131 // Add a blank line after this option
4132 #[options(short = "l", help = "Lists all tasks and exits\n")]
4133 pub list: bool,
4134
4135 /// Defines host to load test (ie http://10.21.32.33)
4136 #[options(short = "H")]
4137 pub host: String,
4138 /// Sets concurrent users (default: number of CPUs)
4139 #[options(short = "u")]
4140 pub users: Option<usize>,
4141 /// Sets per-second user hatch rate (default: 1)
4142 #[options(short = "r", meta = "RATE")]
4143 pub hatch_rate: Option<String>,
4144 /// Stops after (30s, 20m, 3h, 1h30m, etc)
4145 #[options(short = "t", meta = "TIME")]
4146 pub run_time: String,
4147 /// Enables Swanling log file and sets name
4148 #[options(short = "G", meta = "NAME")]
4149 pub swanling_log: String,
4150 /// Sets Swanling log level (-g, -gg, etc)
4151 #[options(short = "g", count)]
4152 pub log_level: u8,
4153 #[options(
4154 count,
4155 short = "v",
4156 // Add a blank line and then a 'Metrics:' header after this option
4157 help = "Sets Swanling verbosity (-v, -vv, etc)\n\nMetrics:"
4158 )]
4159 pub verbose: u8,
4160
4161 /// How often to optionally print running metrics
4162 #[options(no_short, meta = "TIME")]
4163 pub running_metrics: Option<usize>,
4164 /// Doesn't reset metrics after all users have started
4165 #[options(no_short)]
4166 pub no_reset_metrics: bool,
4167 /// Doesn't track metrics
4168 #[options(no_short)]
4169 pub no_metrics: bool,
4170 /// Doesn't track task metrics
4171 #[options(no_short)]
4172 pub no_task_metrics: bool,
4173 /// Doesn't display an error summary
4174 #[options(no_short)]
4175 pub no_error_summary: bool,
4176 /// Create an html-formatted report
4177 #[options(no_short, meta = "NAME")]
4178 pub report_file: String,
4179 /// Sets request log file name
4180 #[options(short = "R", meta = "NAME")]
4181 pub request_log: String,
4182 /// Sets request log format (csv, json, raw)
4183 #[options(no_short, meta = "FORMAT")]
4184 pub request_format: Option<SwanlingLogFormat>,
4185 /// Sets task log file name
4186 #[options(short = "T", meta = "NAME")]
4187 pub task_log: String,
4188 /// Sets task log format (csv, json, raw)
4189 #[options(no_short, meta = "FORMAT")]
4190 pub task_format: Option<SwanlingLogFormat>,
4191 /// Sets error log file name
4192 #[options(short = "E", meta = "NAME")]
4193 pub error_log: String,
4194 /// Sets error log format (csv, json, raw)
4195 #[options(no_short, meta = "FORMAT")]
4196 pub error_format: Option<SwanlingLogFormat>,
4197 /// Sets debug log file name
4198 #[options(short = "D", meta = "NAME")]
4199 pub debug_log: String,
4200 /// Sets debug log format (csv, json, raw)
4201 #[options(no_short, meta = "FORMAT")]
4202 pub debug_format: Option<SwanlingLogFormat>,
4203 /// Do not include the response body in the debug log
4204 #[options(no_short)]
4205 pub no_debug_body: bool,
4206 // Add a blank line and then an Advanced: header after this option
4207 #[options(no_short, help = "Tracks additional status code metrics\n\nAdvanced:")]
4208 pub status_codes: bool,
4209
4210 /// Doesn't enable telnet Controller
4211 #[options(no_short)]
4212 pub no_telnet: bool,
4213 /// Sets telnet Controller host (default: 0.0.0.0)
4214 #[options(no_short, meta = "HOST")]
4215 pub telnet_host: String,
4216 /// Sets telnet Controller TCP port (default: 5116)
4217 #[options(no_short, meta = "PORT")]
4218 pub telnet_port: u16,
4219 /// Doesn't enable WebSocket Controller
4220 #[options(no_short)]
4221 pub no_websocket: bool,
4222 /// Sets WebSocket Controller host (default: 0.0.0.0)
4223 #[options(no_short, meta = "HOST")]
4224 pub websocket_host: String,
4225 /// Sets WebSocket Controller TCP port (default: 5117)
4226 #[options(no_short, meta = "PORT")]
4227 pub websocket_port: u16,
4228 /// Doesn't automatically start load test
4229 #[options(no_short)]
4230 pub no_autostart: bool,
4231 /// Sets coordinated omission mitigation strategy
4232 #[options(no_short, meta = "STRATEGY")]
4233 pub co_mitigation: Option<SwanlingCoordinatedOmissionMitigation>,
4234 /// Sets maximum requests per second
4235 #[options(no_short, meta = "VALUE")]
4236 pub throttle_requests: usize,
4237 #[options(
4238 no_short,
4239 help = "Follows base_url redirect with subsequent requests\n\nGaggle:"
4240 )]
4241 pub sticky_follow: bool,
4242
4243 /// Enables distributed load test Manager mode
4244 #[options(no_short)]
4245 pub manager: bool,
4246 /// Sets number of Workers to expect
4247 #[options(no_short, meta = "VALUE")]
4248 pub expect_workers: Option<u16>,
4249 /// Tells Manager to ignore load test checksum
4250 #[options(no_short)]
4251 pub no_hash_check: bool,
4252 /// Sets host Manager listens on (default: 0.0.0.0)
4253 #[options(no_short, meta = "HOST")]
4254 pub manager_bind_host: String,
4255 /// Sets port Manager listens on (default: 5115)
4256 #[options(no_short, meta = "PORT")]
4257 pub manager_bind_port: u16,
4258 /// Enables distributed load test Worker mode
4259 #[options(no_short)]
4260 pub worker: bool,
4261 /// Sets host Worker connects to (default: 127.0.0.1)
4262 #[options(no_short, meta = "HOST")]
4263 pub manager_host: String,
4264 /// Sets port Worker connects to (default: 5115)
4265 #[options(no_short, meta = "PORT")]
4266 pub manager_port: u16,
4267}
4268
4269/// Use the configured SwanlingScheduler to allocate all [`SwanlingTask`](./swanling/struct.SwanlingTask.html)s
4270/// within the [`SwanlingTaskSet`](./swanling/struct.SwanlingTaskSet.html) in the appropriate order. Returns
4271/// three set of ordered tasks: /// `on_start_tasks`, `tasks`, and `on_stop_tasks`. The
4272/// `on_start_tasks` are only run once when the [`SwanlingAttack`](./struct.SwanlingAttack.html) first
4273/// starts. Normal `tasks` are then run for the duration of the
4274/// [`SwanlingAttack`](./struct.SwanlingAttack.html). The `on_stop_tasks` finally are only run once when
4275/// the [`SwanlingAttack`](./struct.SwanlingAttack.html) stops.
4276fn allocate_tasks(
4277 task_set: &SwanlingTaskSet,
4278 scheduler: &SwanlingScheduler,
4279) -> (
4280 WeightedSwanlingTasks,
4281 WeightedSwanlingTasks,
4282 WeightedSwanlingTasks,
4283) {
4284 debug!(
4285 "allocating SwanlingTasks on SwanlingUsers with {:?} scheduler",
4286 scheduler
4287 );
4288
4289 // A BTreeMap of Vectors allows us to group and sort tasks per sequence value.
4290 let mut sequenced_tasks: SequencedSwanlingTasks = BTreeMap::new();
4291 let mut sequenced_on_start_tasks: SequencedSwanlingTasks = BTreeMap::new();
4292 let mut sequenced_on_stop_tasks: SequencedSwanlingTasks = BTreeMap::new();
4293 let mut unsequenced_tasks: UnsequencedSwanlingTasks = Vec::new();
4294 let mut unsequenced_on_start_tasks: UnsequencedSwanlingTasks = Vec::new();
4295 let mut unsequenced_on_stop_tasks: UnsequencedSwanlingTasks = Vec::new();
4296 let mut u: usize = 0;
4297 let mut v: usize;
4298
4299 // Find the greatest common divisor of all tasks in the task_set.
4300 for task in &task_set.tasks {
4301 if task.sequence > 0 {
4302 if task.on_start {
4303 if let Some(sequence) = sequenced_on_start_tasks.get_mut(&task.sequence) {
4304 // This is another task with this order value.
4305 sequence.push(task.clone());
4306 } else {
4307 // This is the first task with this order value.
4308 sequenced_on_start_tasks.insert(task.sequence, vec![task.clone()]);
4309 }
4310 }
4311 // Allow a task to be both on_start and on_stop.
4312 if task.on_stop {
4313 if let Some(sequence) = sequenced_on_stop_tasks.get_mut(&task.sequence) {
4314 // This is another task with this order value.
4315 sequence.push(task.clone());
4316 } else {
4317 // This is the first task with this order value.
4318 sequenced_on_stop_tasks.insert(task.sequence, vec![task.clone()]);
4319 }
4320 }
4321 if !task.on_start && !task.on_stop {
4322 if let Some(sequence) = sequenced_tasks.get_mut(&task.sequence) {
4323 // This is another task with this order value.
4324 sequence.push(task.clone());
4325 } else {
4326 // This is the first task with this order value.
4327 sequenced_tasks.insert(task.sequence, vec![task.clone()]);
4328 }
4329 }
4330 } else {
4331 if task.on_start {
4332 unsequenced_on_start_tasks.push(task.clone());
4333 }
4334 if task.on_stop {
4335 unsequenced_on_stop_tasks.push(task.clone());
4336 }
4337 if !task.on_start && !task.on_stop {
4338 unsequenced_tasks.push(task.clone());
4339 }
4340 }
4341 // Look for lowest common divisor amongst all tasks of any weight.
4342 if u == 0 {
4343 u = task.weight;
4344 } else {
4345 v = task.weight;
4346 trace!("calculating greatest common denominator of {} and {}", u, v);
4347 u = util::gcd(u, v);
4348 trace!("inner gcd: {}", u);
4349 }
4350 }
4351 // 'u' will always be the greatest common divisor
4352 debug!("gcd: {}", u);
4353
4354 // Apply weights to sequenced tasks.
4355 let weighted_sequenced_on_start_tasks = weight_sequenced_tasks(&sequenced_on_start_tasks, u);
4356 let weighted_sequenced_tasks = weight_sequenced_tasks(&sequenced_tasks, u);
4357 let weighted_sequenced_on_stop_tasks = weight_sequenced_tasks(&sequenced_on_stop_tasks, u);
4358
4359 // Apply weights to unsequenced tasks.
4360 let (weighted_unsequenced_on_start_tasks, total_unsequenced_on_start_tasks) =
4361 weight_unsequenced_tasks(&unsequenced_on_start_tasks, u);
4362 let (weighted_unsequenced_tasks, total_unsequenced_tasks) =
4363 weight_unsequenced_tasks(&unsequenced_tasks, u);
4364 let (weighted_unsequenced_on_stop_tasks, total_unsequenced_on_stop_tasks) =
4365 weight_unsequenced_tasks(&unsequenced_on_stop_tasks, u);
4366
4367 // Schedule sequenced tasks.
4368 let scheduled_sequenced_on_start_tasks =
4369 schedule_sequenced_tasks(&weighted_sequenced_on_start_tasks, scheduler);
4370 let scheduled_sequenced_tasks = schedule_sequenced_tasks(&weighted_sequenced_tasks, scheduler);
4371 let scheduled_sequenced_on_stop_tasks =
4372 schedule_sequenced_tasks(&weighted_sequenced_on_stop_tasks, scheduler);
4373
4374 // Schedule unsequenced tasks.
4375 let scheduled_unsequenced_on_start_tasks = schedule_unsequenced_tasks(
4376 &weighted_unsequenced_on_start_tasks,
4377 total_unsequenced_on_start_tasks,
4378 scheduler,
4379 );
4380 let scheduled_unsequenced_tasks = schedule_unsequenced_tasks(
4381 &weighted_unsequenced_tasks,
4382 total_unsequenced_tasks,
4383 scheduler,
4384 );
4385 let scheduled_unsequenced_on_stop_tasks = schedule_unsequenced_tasks(
4386 &weighted_unsequenced_on_stop_tasks,
4387 total_unsequenced_on_stop_tasks,
4388 scheduler,
4389 );
4390
4391 // Finally build a Vector of tuples: (task id, task name)
4392 let mut on_start_tasks = Vec::new();
4393 let mut tasks = Vec::new();
4394 let mut on_stop_tasks = Vec::new();
4395
4396 // Sequenced tasks come first.
4397 for task in scheduled_sequenced_on_start_tasks.iter() {
4398 on_start_tasks.extend(vec![(*task, task_set.tasks[*task].name.to_string())])
4399 }
4400 for task in scheduled_sequenced_tasks.iter() {
4401 tasks.extend(vec![(*task, task_set.tasks[*task].name.to_string())])
4402 }
4403 for task in scheduled_sequenced_on_stop_tasks.iter() {
4404 on_stop_tasks.extend(vec![(*task, task_set.tasks[*task].name.to_string())])
4405 }
4406
4407 // Unsequenced tasks come last.
4408 for task in scheduled_unsequenced_on_start_tasks.iter() {
4409 on_start_tasks.extend(vec![(*task, task_set.tasks[*task].name.to_string())])
4410 }
4411 for task in scheduled_unsequenced_tasks.iter() {
4412 tasks.extend(vec![(*task, task_set.tasks[*task].name.to_string())])
4413 }
4414 for task in scheduled_unsequenced_on_stop_tasks.iter() {
4415 on_stop_tasks.extend(vec![(*task, task_set.tasks[*task].name.to_string())])
4416 }
4417
4418 // Return sequenced buckets of weighted usize pointers to and names of Swanling Tasks
4419 (on_start_tasks, tasks, on_stop_tasks)
4420}
4421
4422/// Build a weighted vector of vectors of unsequenced SwanlingTasks.
4423fn weight_unsequenced_tasks(
4424 unsequenced_tasks: &[SwanlingTask],
4425 u: usize,
4426) -> (Vec<Vec<usize>>, usize) {
4427 // Build a vector of vectors to be used to schedule users.
4428 let mut available_unsequenced_tasks = Vec::with_capacity(unsequenced_tasks.len());
4429 let mut total_tasks = 0;
4430 for task in unsequenced_tasks.iter() {
4431 // divide by greatest common divisor so vector is as short as possible
4432 let weight = task.weight / u;
4433 trace!(
4434 "{}: {} has weight of {} (reduced with gcd to {})",
4435 task.tasks_index,
4436 task.name,
4437 task.weight,
4438 weight
4439 );
4440 let weighted_tasks = vec![task.tasks_index; weight];
4441 available_unsequenced_tasks.push(weighted_tasks);
4442 total_tasks += weight;
4443 }
4444 (available_unsequenced_tasks, total_tasks)
4445}
4446
4447/// Build a weighted vector of vectors of sequenced SwanlingTasks.
4448fn weight_sequenced_tasks(
4449 sequenced_tasks: &SequencedSwanlingTasks,
4450 u: usize,
4451) -> BTreeMap<usize, Vec<Vec<usize>>> {
4452 // Build a sequenced BTreeMap containing weighted vectors of SwanlingTasks.
4453 let mut available_sequenced_tasks = BTreeMap::new();
4454 // Step through sequences, each containing a bucket of all SwanlingTasks with the same
4455 // sequence value, allowing actual weighting to be done by weight_unsequenced_tasks().
4456 for (sequence, unsequenced_tasks) in sequenced_tasks.iter() {
4457 let (weighted_tasks, _total_weighted_tasks) =
4458 weight_unsequenced_tasks(&unsequenced_tasks, u);
4459 available_sequenced_tasks.insert(*sequence, weighted_tasks);
4460 }
4461
4462 available_sequenced_tasks
4463}
4464
4465fn schedule_sequenced_tasks(
4466 available_sequenced_tasks: &BTreeMap<usize, Vec<Vec<usize>>>,
4467 scheduler: &SwanlingScheduler,
4468) -> Vec<usize> {
4469 let mut weighted_tasks: Vec<usize> = Vec::new();
4470
4471 for (_sequence, tasks) in available_sequenced_tasks.iter() {
4472 let scheduled_tasks = schedule_unsequenced_tasks(tasks, tasks[0].len(), scheduler);
4473 weighted_tasks.extend(scheduled_tasks);
4474 }
4475
4476 weighted_tasks
4477}
4478
4479// Return a list of tasks in the order to be run.
4480fn schedule_unsequenced_tasks(
4481 available_unsequenced_tasks: &[Vec<usize>],
4482 total_tasks: usize,
4483 scheduler: &SwanlingScheduler,
4484) -> Vec<usize> {
4485 // Now build the weighted list with the appropriate scheduler.
4486 let mut weighted_tasks = Vec::new();
4487
4488 match scheduler {
4489 SwanlingScheduler::RoundRobin => {
4490 // Allocate task sets round robin.
4491 let tasks_len = available_unsequenced_tasks.len();
4492 let mut available_tasks = available_unsequenced_tasks.to_owned();
4493 loop {
4494 // Tasks are contained in a vector of vectors. The outer vectors each
4495 // contain a different SwanlingTask, and the inner vectors contain each
4496 // instance of that specific SwanlingTask.
4497 for (task_index, tasks) in available_tasks.iter_mut().enumerate().take(tasks_len) {
4498 if let Some(task) = tasks.pop() {
4499 debug!("allocating task from Task {}", task_index);
4500 weighted_tasks.push(task);
4501 }
4502 }
4503 if weighted_tasks.len() >= total_tasks {
4504 break;
4505 }
4506 }
4507 }
4508 SwanlingScheduler::Serial | SwanlingScheduler::Random => {
4509 // Allocate task sets serially in the weighted order defined. If the Random
4510 // scheduler is being used, tasks will get shuffled later.
4511 for (task_index, tasks) in available_unsequenced_tasks.iter().enumerate() {
4512 debug!(
4513 "allocating all {} tasks from Task {}",
4514 tasks.len(),
4515 task_index
4516 );
4517
4518 let mut tasks_clone = tasks.clone();
4519 if scheduler == &SwanlingScheduler::Random {
4520 tasks_clone.shuffle(&mut thread_rng());
4521 }
4522 weighted_tasks.append(&mut tasks_clone);
4523 }
4524 }
4525 }
4526
4527 weighted_tasks
4528}
4529
4530#[cfg(test)]
4531mod test {
4532 use super::*;
4533
4534 #[test]
4535 fn set_defaults() {
4536 let host = "http://example.com/".to_string();
4537 let users: usize = 10;
4538 let run_time: usize = 10;
4539 let hatch_rate = "2".to_string();
4540 let log_level: usize = 1;
4541 let swanling_log = "custom-swanling.log".to_string();
4542 let verbose: usize = 0;
4543 let report_file = "custom-swanling-report.html".to_string();
4544 let request_log = "custom-swanling-request.log".to_string();
4545 let task_log = "custom-swanling-task.log".to_string();
4546 let debug_log = "custom-swanling-debug.log".to_string();
4547 let error_log = "custom-swanling-error.log".to_string();
4548 let throttle_requests: usize = 25;
4549 let expect_workers: usize = 5;
4550 let manager_bind_host = "127.0.0.1".to_string();
4551 let manager_bind_port: usize = 1221;
4552 let manager_host = "127.0.0.1".to_string();
4553 let manager_port: usize = 1221;
4554
4555 let swanling_attack = SwanlingAttack::initialize()
4556 .unwrap()
4557 .set_default(SwanlingDefault::Host, host.as_str())
4558 .unwrap()
4559 .set_default(SwanlingDefault::Users, users)
4560 .unwrap()
4561 .set_default(SwanlingDefault::RunTime, run_time)
4562 .unwrap()
4563 .set_default(SwanlingDefault::HatchRate, hatch_rate.as_str())
4564 .unwrap()
4565 .set_default(SwanlingDefault::LogLevel, log_level)
4566 .unwrap()
4567 .set_default(SwanlingDefault::SwanlingLog, swanling_log.as_str())
4568 .unwrap()
4569 .set_default(SwanlingDefault::Verbose, verbose)
4570 .unwrap()
4571 .set_default(SwanlingDefault::RunningMetrics, 15)
4572 .unwrap()
4573 .set_default(SwanlingDefault::NoResetMetrics, true)
4574 .unwrap()
4575 .set_default(SwanlingDefault::NoMetrics, true)
4576 .unwrap()
4577 .set_default(SwanlingDefault::NoTaskMetrics, true)
4578 .unwrap()
4579 .set_default(SwanlingDefault::NoErrorSummary, true)
4580 .unwrap()
4581 .set_default(SwanlingDefault::NoTelnet, true)
4582 .unwrap()
4583 .set_default(SwanlingDefault::NoWebSocket, true)
4584 .unwrap()
4585 .set_default(SwanlingDefault::NoAutoStart, true)
4586 .unwrap()
4587 .set_default(SwanlingDefault::ReportFile, report_file.as_str())
4588 .unwrap()
4589 .set_default(SwanlingDefault::RequestLog, request_log.as_str())
4590 .unwrap()
4591 .set_default(SwanlingDefault::RequestFormat, SwanlingLogFormat::Raw)
4592 .unwrap()
4593 .set_default(SwanlingDefault::TaskLog, task_log.as_str())
4594 .unwrap()
4595 .set_default(SwanlingDefault::TaskFormat, SwanlingLogFormat::Raw)
4596 .unwrap()
4597 .set_default(SwanlingDefault::ErrorLog, error_log.as_str())
4598 .unwrap()
4599 .set_default(SwanlingDefault::ErrorFormat, SwanlingLogFormat::Csv)
4600 .unwrap()
4601 .set_default(SwanlingDefault::DebugLog, debug_log.as_str())
4602 .unwrap()
4603 .set_default(SwanlingDefault::DebugFormat, SwanlingLogFormat::Csv)
4604 .unwrap()
4605 .set_default(SwanlingDefault::NoDebugBody, true)
4606 .unwrap()
4607 .set_default(SwanlingDefault::StatusCodes, true)
4608 .unwrap()
4609 .set_default(
4610 SwanlingDefault::CoordinatedOmissionMitigation,
4611 SwanlingCoordinatedOmissionMitigation::Disabled,
4612 )
4613 .unwrap()
4614 .set_default(SwanlingDefault::ThrottleRequests, throttle_requests)
4615 .unwrap()
4616 .set_default(SwanlingDefault::StickyFollow, true)
4617 .unwrap()
4618 .set_default(SwanlingDefault::Manager, true)
4619 .unwrap()
4620 .set_default(SwanlingDefault::ExpectWorkers, expect_workers)
4621 .unwrap()
4622 .set_default(SwanlingDefault::NoHashCheck, true)
4623 .unwrap()
4624 .set_default(SwanlingDefault::ManagerBindHost, manager_bind_host.as_str())
4625 .unwrap()
4626 .set_default(SwanlingDefault::ManagerBindPort, manager_bind_port)
4627 .unwrap()
4628 .set_default(SwanlingDefault::Worker, true)
4629 .unwrap()
4630 .set_default(SwanlingDefault::ManagerHost, manager_host.as_str())
4631 .unwrap()
4632 .set_default(SwanlingDefault::ManagerPort, manager_port)
4633 .unwrap();
4634
4635 assert!(swanling_attack.defaults.host == Some(host));
4636 assert!(swanling_attack.defaults.users == Some(users));
4637 assert!(swanling_attack.defaults.run_time == Some(run_time));
4638 assert!(swanling_attack.defaults.hatch_rate == Some(hatch_rate));
4639 assert!(swanling_attack.defaults.log_level == Some(log_level as u8));
4640 assert!(swanling_attack.defaults.swanling_log == Some(swanling_log));
4641 assert!(swanling_attack.defaults.no_debug_body == Some(true));
4642 assert!(swanling_attack.defaults.verbose == Some(verbose as u8));
4643 assert!(swanling_attack.defaults.running_metrics == Some(15));
4644 assert!(swanling_attack.defaults.no_reset_metrics == Some(true));
4645 assert!(swanling_attack.defaults.no_metrics == Some(true));
4646 assert!(swanling_attack.defaults.no_task_metrics == Some(true));
4647 assert!(swanling_attack.defaults.no_error_summary == Some(true));
4648 assert!(swanling_attack.defaults.no_telnet == Some(true));
4649 assert!(swanling_attack.defaults.no_websocket == Some(true));
4650 assert!(swanling_attack.defaults.no_autostart == Some(true));
4651 assert!(swanling_attack.defaults.report_file == Some(report_file));
4652 assert!(swanling_attack.defaults.request_log == Some(request_log));
4653 assert!(swanling_attack.defaults.request_format == Some(SwanlingLogFormat::Raw));
4654 assert!(swanling_attack.defaults.error_log == Some(error_log));
4655 assert!(swanling_attack.defaults.error_format == Some(SwanlingLogFormat::Csv));
4656 assert!(swanling_attack.defaults.debug_log == Some(debug_log));
4657 assert!(swanling_attack.defaults.debug_format == Some(SwanlingLogFormat::Csv));
4658 assert!(swanling_attack.defaults.status_codes == Some(true));
4659 assert!(
4660 swanling_attack.defaults.co_mitigation
4661 == Some(SwanlingCoordinatedOmissionMitigation::Disabled)
4662 );
4663 assert!(swanling_attack.defaults.throttle_requests == Some(throttle_requests));
4664 assert!(swanling_attack.defaults.sticky_follow == Some(true));
4665 assert!(swanling_attack.defaults.manager == Some(true));
4666 assert!(swanling_attack.defaults.expect_workers == Some(expect_workers as u16));
4667 assert!(swanling_attack.defaults.no_hash_check == Some(true));
4668 assert!(swanling_attack.defaults.manager_bind_host == Some(manager_bind_host));
4669 assert!(swanling_attack.defaults.manager_bind_port == Some(manager_bind_port as u16));
4670 assert!(swanling_attack.defaults.worker == Some(true));
4671 assert!(swanling_attack.defaults.manager_host == Some(manager_host));
4672 assert!(swanling_attack.defaults.manager_port == Some(manager_port as u16));
4673 }
4674}