1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Settings that control the test
//!
//! This is a struct and impl that contains the configuration options. `Config` should be easy
//! to copy, as it is passed to each `connection()` call.
//!

extern crate proc_macro;

use std::net::SocketAddr;
use std::time::Duration;

/// Settings for the load test
///
/// todo: Make write/read optional. (Enum?)
///
#[derive(Debug, Copy, Clone)]
pub struct Config {
    /// Socket address (ip and port) of the host we'll be calling
    pub target: SocketAddr,
    /// Connections is the a key knob to turn when tuning a performance test. Honestly
    /// 'connections' isn't the best name; it implies a certain persistance that
    pub connections: u32,
    /// Optional rate-limiting. Precisely timing high rates is unreliable; if you're
    /// seeing slower than expected performance try running with no rate limit at all.
    pub rate: Option<u32>,
    /// Optional duration. If duration is None, clobber will run indefinitely.
    pub duration: Option<Duration>,
    /// Number of OS threads to distribute work between. 0 becomes num_cpus.
    pub threads: Option<u32>,
    /// Optionally time out requests at a number of milliseconds. Note: checking timeouts
    /// costs CPU cycles; max performance will suffer. However, if you have an ill-behaving
    /// server that isn't connecting consistently and is hanging onto connections, this can
    /// improve the situation.
    pub connect_timeout: Option<u32>,
    /// Optionally time out read requests at a number of milliseconds. Note: checking timeouts
    /// costs CPU cycles; max performance will suffer. However, if you have an ill-behaving server
    /// that isn't sending EOF bytes or otherwise isn't dropping connections, this can be
    /// essential to maintaing a high(ish) throughput, at the cost of more CPU load.
    pub read_timeout: Option<u32>,
    /// Absolute number of requests to be made. Should split evenly across threads.
    pub limit: Option<u32>,
    /// Repeats the outgoing message
    pub repeat: u32,
}

impl Config {
    pub fn new(target: SocketAddr) -> Config {
        let connections = 100; // todo: detect?
        Config {
            target,
            connections,
            rate: None,
            limit: None,
            repeat: 1,
            duration: None,
            threads: None,
            read_timeout: None,
            connect_timeout: None,
        }
    }

    /// Number of user-defined threads, or all the threads on the host.
    pub fn num_threads(&self) -> u32 {
        match self.threads {
            None => num_cpus::get() as u32,
            Some(n) => n,
        }
    }

    /// Number of connection loops each thread should maintain. Will never be less than the number
    /// of threads.
    pub fn connections_per_thread(&self) -> u32 {
        match self.connections / self.num_threads() as u32 {
            0 => 1,
            n => n,
        }
    }

    /// The amount a single connection should wait between loops in order to maintain the defined
    /// rate. Returns a default duration if there is no rate.
    pub fn connection_delay(&self) -> Duration {
        match self.rate {
            Some(rate) => {
                Duration::from_secs(1) / rate * self.connections_per_thread() * self.num_threads()
            }
            None => Duration::default(),
        }
    }

    /// The number of iterations each connection loop should perform before stopping. Doesn't play
    /// nice with limits that are not divisible by the number of threads and connections.
    pub fn limit_per_connection(&self) -> Option<u32> {
        match self.limit {
            None => None,
            Some(n) => Some(n / self.connections),
        }
    }
}

pub struct ConfigBuilder {
    config: Config,
}

// todo: Oof, this should be a macro
impl ConfigBuilder {
    pub fn new(target: SocketAddr) -> ConfigBuilder {
        ConfigBuilder {
            config: Config::new(target),
        }
    }

    pub fn build(self) -> Config {
        self.config
    }

    pub fn connections(mut self, connections: u32) -> ConfigBuilder {
        self.config.connections = connections;
        self
    }

    pub fn rate(mut self, rate: Option<u32>) -> ConfigBuilder {
        self.config.rate = rate;
        self
    }

    pub fn duration(mut self, duration: Option<Duration>) -> ConfigBuilder {
        self.config.duration = duration;
        self
    }

    pub fn threads(mut self, threads: Option<u32>) -> ConfigBuilder {
        self.config.threads = threads;
        self
    }

    pub fn connect_timeout(mut self, connect_timeout: Option<u32>) -> ConfigBuilder {
        self.config.connect_timeout = connect_timeout;
        self
    }

    pub fn read_timeout(mut self, read_timeout: Option<u32>) -> ConfigBuilder {
        self.config.read_timeout = read_timeout;
        self
    }

    pub fn limit(mut self, limit: Option<u32>) -> ConfigBuilder {
        self.config.limit = limit;
        self
    }

    pub fn repeat(mut self, repeat: u32) -> ConfigBuilder {
        self.config.repeat = repeat;
        self
    }
}