25void check(
bool condition,
const std::string& description) {
29 std::cout <<
" FAIL: " << description <<
'\n';
33Request makeRequest(
unsigned long id,
unsigned int task,
const std::string& source =
"10.0.0.1",
47 check(server.isIdle(),
"new server is idle");
48 server.assign(makeRequest(1, 2), 0);
49 check(!server.isIdle(),
"server is busy after assign");
50 check(!server.tick(),
"two-cycle task is not done after one tick");
51 check(server.tick(),
"two-cycle task is done after two ticks");
52 check(server.isIdle() && server.completedCount() == 1 && server.busyCycles() == 2,
53 "server returns to idle and counts the completion");
55 server.assign(makeRequest(2, 5), 0);
57 server.assign(makeRequest(3, 5), 0);
58 }
catch (
const std::logic_error&) {
61 check(threw,
"assigning to a busy server throws");
66 check(firewall.
addRange(
"192.168.0.0/16"),
"valid CIDR is accepted");
67 check(firewall.
addRange(
"8.8.8.8"),
"single host is accepted");
68 check(!firewall.
addRange(
"300.1.1.1/8"),
"octet above 255 is rejected");
69 check(!firewall.
addRange(
"10.0.0.0/33"),
"prefix above 32 is rejected");
70 check(firewall.
isBlocked(
"192.168.44.9"),
"address inside a range is blocked");
71 check(firewall.
isBlocked(
"8.8.8.8"),
"blocked single host");
72 check(!firewall.
isBlocked(
"8.8.8.9"),
"neighbor of a blocked host is allowed");
73 check(!firewall.
isBlocked(
"192.169.0.1"),
"address outside the range is allowed");
74 check(firewall.
isBlocked(
"not-an-ip"),
"malformed address is blocked");
87 for (
unsigned long cycle = 1; cycle <= 500; ++cycle) {
88 const std::vector<Request> a = first.arrivalsFor(cycle);
89 const std::vector<Request> b = second.arrivalsFor(cycle);
90 same = same && a.size() == b.size();
91 for (std::size_t i = 0; i < a.size() && i < b.size(); ++i) {
92 inRange = inRange && a[i].taskCycles >= 3 && a[i].taskCycles <= 7;
93 same = same && a[i].sourceIp == b[i].sourceIp && a[i].taskCycles == b[i].taskCycles;
96 check(inRange,
"task times stay inside the configured range");
97 check(same,
"the same seed reproduces the same requests");
98 const double mean =
static_cast<double>(first.stats().total) / 500.0;
99 check(mean > 1.7 && mean < 2.3,
"observed arrival rate is close to the configured mean");
104 check(wave.rateAt(25) > 2.9 && wave.rateAt(75) < 1.1,
"traffic wave raises and lowers the rate");
108 Logger logger(
false,
false);
115 for (
unsigned long id = 1;
id <= 40; ++id) {
116 balancer.admit(makeRequest(
id, 50), 0);
119 check(balancer.busyServers() == 2 && balancer.queueSize() == 38,
"start dispatches to every server");
120 balancer.processCycle(1, {});
121 check(balancer.liveServers() == 3,
"queue above upper x servers adds a server");
122 balancer.processCycle(2, {});
123 balancer.processCycle(3, {});
124 check(balancer.liveServers() == 4,
"pool never exceeds maxServers");
127 for (
unsigned long cycle = 4; cycle <= 3000; ++cycle) {
128 balancer.processCycle(cycle, {});
130 check(balancer.liveServers() == 1,
"an empty queue shrinks the pool to minServers");
131 check(balancer.stats().completed == 40,
"every request is completed");
132 check(balancer.stats().admitted ==
133 balancer.stats().completed + balancer.queueSize() + balancer.inService(),
134 "admitted requests are all accounted for");
137void testRetiringServerFinishesWork() {
138 Logger logger(
false,
false);
144 balancer.admit(makeRequest(1, 10), 0);
145 balancer.admit(makeRequest(2, 3), 0);
147 balancer.processCycle(1, {});
148 check(balancer.retiringServers() == 1,
"busy server is marked retiring instead of being dropped");
149 for (
unsigned long cycle = 2; cycle <= 3; ++cycle) {
150 balancer.processCycle(cycle, {});
152 check(balancer.liveServers() == 1 && balancer.stats().completed == 1,
153 "retiring server is removed only after finishing its request");
156void testRejectAndDiscard() {
157 Logger logger(
false,
false);
161 check(!balancer.admit(makeRequest(1, 5,
"66.1.2.3"), 0),
"blocked source is rejected");
162 check(balancer.admit(makeRequest(2, 5), 0) && balancer.admit(makeRequest(3, 5), 0),
"allowed requests queue");
163 check(!balancer.admit(makeRequest(4, 5), 0),
"request beyond queue capacity is discarded");
164 check(balancer.stats().rejected == 1 && balancer.stats().discarded == 1,
"reject and discard are counted");
168 Logger logger(
false,
false);
174 check(router.processing().queueSize() == 1 && router.streaming().queueSize() == 2,
175 "switch routes requests by job type");
181 const char* args[] = {
"lb",
"--servers",
"7",
"--cycles",
"50",
"--block",
"1.2.3.0/24",
"--no-default-blocks"};
182 check(
parseArguments(8,
const_cast<char**
>(args), config, error),
"options parse");
183 check(config.
servers == 7 && config.
cycles == 50,
"numeric options are read");
185 "--no-default-blocks keeps only user ranges");
186 const char* bad[] = {
"lb",
"--servers",
"-3"};
188 check(!
parseArguments(3,
const_cast<char**
>(bad), other, error),
"negative server count is rejected");
192 check(!
validateConfig(invalid, error),
"min task above max task is invalid");
195 std::istringstream answers(
"12\n500\n\n\n\n\ny\nn\n");
196 std::ostringstream questions;
199 "interactive prompts read answers and keep defaults on blank lines");
213 testRetiringServerFinishesWork();
214 testRejectAndDiscard();
217 std::cout << (checks - failures) <<
"/" << checks <<
" checks passed\n";
218 return failures == 0 ? 0 : 1;
void promptForSettings(Config &config, std::istream &in, std::ostream &out)
Asks the user for the main settings, keeping the current value when the answer is blank.
bool validateConfig(const Config &config, std::string &error)
Checks that the settings are consistent.
bool parseArguments(int argc, char *argv[], Config &config, std::string &error)
Reads command-line options into a Config.
Declares the Config structure and the command-line and interactive input functions.
Declares the Firewall class, an IPv4 range blocker used before requests are queued.
Declares the LoadBalancer class, which queues requests, dispatches them and scales its server pool.
Declares the Logger class that prints colored, mode-aware simulation events.
JobType
Category of work carried by a Request.
@ Processing
Short computational job, such as rendering a page or running a query.
@ Streaming
Long-lived transfer, such as streaming media or a large download.
Declares the RequestGenerator class, the seeded source of random traffic.
Declares the Switch class (bonus), which routes requests to separate load balancers by job type.
Declares the WebServer class, a worker that processes one Request at a time.
Rejects requests whose source address falls in a blocked IPv4 range.
bool addRange(const std::string &cidr)
Adds a blocked range.
bool isBlocked(const std::string &ip) const
Checks whether an address is blocked.
Distributes queued requests across a dynamically sized pool of WebServer objects.
Writes simulation events to the console and, optionally, to a file.
Produces random requests from a single seeded Mersenne Twister engine.
Routes each request to a processing or a streaming LoadBalancer.
A simulated web server that processes at most one request at a time.
int main(int argc, char *argv[])
Parses input, runs the simulation and writes the summary to the console and log file.
Every user-adjustable setting for one simulation run.
unsigned int servers
Initial number of web servers.
bool useSwitch
Bonus: route P and S jobs to two load balancers.
unsigned long cycles
Clock cycles to simulate.
unsigned int maxTask
Longest task time, in cycles.
bool verbose
Print every event instead of only status lines.
unsigned int minTask
Shortest task time, in cycles.
std::vector< std::string > blockedRanges
Firewall CIDR ranges.
Tunable parameters for random request generation.
unsigned int minTaskCycles
Shortest possible task, in clock cycles.
double waveAmplitude
Traffic wave size: the rate swings between (1 - a) and (1 + a) x arrivalRate.
unsigned long wavePeriod
Cycles in one busy-to-quiet traffic wave; 0 disables the wave.
double arrivalRate
Average new requests per clock cycle (Poisson mean).
unsigned int maxTaskCycles
Longest possible task, in clock cycles.
A single web request waiting for, or receiving, service.
unsigned int taskCycles
Clock cycles of processing required (always at least 1).
unsigned long arrivalCycle
Clock cycle at which the request was generated.
JobType jobType
Processing ('P') or streaming ('S') work.
std::string destinationIp
IPv4 address the response is returned to.
unsigned long id
Sequential identifier assigned by the generator.
std::string sourceIp
IPv4 address of the client sending the request.
Rules for growing and shrinking the server pool.
unsigned int maxServers
Never more live servers (active + retiring) than this.
double lowerFactor
Scale down when queue < lowerFactor x active servers.
unsigned int cooldownCycles
Cycles between scaling evaluations.
double upperFactor
Scale up when queue > upperFactor x active servers.