21bool parseNumber(
const std::string& text, T& value) {
22 if (text.empty() || (std::is_unsigned<T>::value && text[0] ==
'-')) {
25 std::istringstream stream(text);
28 if (!stream || !stream.eof()) {
37void ask(std::istream& in, std::ostream& out,
const std::string& question, T& value) {
39 out << question <<
" [" << value <<
"]: " << std::flush;
41 if (!std::getline(in, line) || line.empty()) {
44 if (parseNumber(line, value)) {
47 out <<
" Please enter a number.\n";
51void askYesNo(std::istream& in, std::ostream& out,
const std::string& question,
bool& value) {
52 out << question <<
" [" << (value ?
"y" :
"n") <<
"]: " << std::flush;
54 if (std::getline(in, line) && !line.empty()) {
55 value = line[0] ==
'y' || line[0] ==
'Y';
61void printUsage(std::ostream& out,
const std::string& program) {
62 out <<
"Usage: " << program <<
" [options]\n"
63 <<
"Run with no options to be asked for the main settings.\n\n"
65 <<
" --servers N initial web servers (default 20)\n"
66 <<
" --cycles N clock cycles to simulate (default 100000)\n"
67 <<
" --seed N|random random seed (default 412)\n"
68 <<
" --verbose print every event in color\n"
69 <<
" --quiet print only status lines and the summary (default)\n"
70 <<
" --status-every N cycles between status lines (default: cycles/20, 100 if verbose)\n"
71 <<
" --log FILE write the summary (and events in verbose mode) to FILE\n"
72 <<
" --switch bonus: route P and S jobs to two load balancers\n"
73 <<
" --interactive ask for the main settings, then apply the options given\n"
74 <<
" --no-color disable ANSI colors\n\n"
76 <<
" --min-task N shortest task time in cycles (default 10)\n"
77 <<
" --max-task N longest task time in cycles (default 100)\n"
78 <<
" --arrival-rate X average new requests per cycle (default 0.45)\n"
79 <<
" --wave-amplitude X traffic wave size 0-1; rate swings by +/- X (default 0.6)\n"
80 <<
" --wave-period N cycles per busy/quiet traffic wave, 0 = steady (default cycles/4)\n"
81 <<
" --streaming-share X fraction of streaming jobs, 0-1 (default 0.35)\n"
82 <<
" --queue-per-server N initial queue = servers x N (default 100)\n\n"
83 <<
"Scaling and protection\n"
84 <<
" --upper X add a server when queue > X x servers (default 80)\n"
85 <<
" --lower X remove a server when queue < X x servers (default 50)\n"
86 <<
" --cooldown N cycles between scaling checks (default 10)\n"
87 <<
" --min-servers N fewest active servers (default 1)\n"
88 <<
" --max-servers N most live servers (default 100)\n"
89 <<
" --max-queue N queue capacity, extra requests are discarded (default 0 = unlimited)\n"
90 <<
" --block CIDR block a source range (repeatable), e.g. 203.0.113.0/24\n"
91 <<
" --no-default-blocks clear the default blocked ranges 45.0.0.0/8 and 103.0.0.0/8\n"
92 <<
" --help show this message\n";
96 bool keepDefaultBlocks =
true;
97 bool sawSetting =
false;
98 std::vector<std::string> extraBlocks;
99 for (
int i = 1; i < argc; ++i) {
100 const std::string option = argv[i];
101 const auto needValue = [&](std::string& value) {
103 error = option +
" needs a value";
109 const auto number = [&](
auto& target) {
111 if (!needValue(value)) {
114 if (!parseNumber(value, target)) {
115 error =
"invalid value for " + option +
": " + value;
121 if (option !=
"--no-color") {
124 if (option ==
"--help" || option ==
"-h") {
126 }
else if (option ==
"--servers") {
127 if (!number(config.
servers))
return false;
128 }
else if (option ==
"--cycles") {
129 if (!number(config.
cycles))
return false;
130 }
else if (option ==
"--seed") {
132 if (!needValue(value))
return false;
133 if (value ==
"random") {
134 config.
seed = std::random_device{}();
135 }
else if (!parseNumber(value, config.
seed)) {
136 error =
"invalid value for --seed: " + value;
139 }
else if (option ==
"--verbose" || option ==
"-v") {
141 }
else if (option ==
"--quiet" || option ==
"-q") {
143 }
else if (option ==
"--status-every") {
145 }
else if (option ==
"--log") {
146 if (!needValue(config.
logPath))
return false;
147 }
else if (option ==
"--switch") {
149 }
else if (option ==
"--interactive" || option ==
"-i") {
151 }
else if (option ==
"--no-color") {
152 config.
color =
false;
153 }
else if (option ==
"--min-task") {
154 if (!number(config.
minTask))
return false;
155 }
else if (option ==
"--max-task") {
156 if (!number(config.
maxTask))
return false;
157 }
else if (option ==
"--arrival-rate") {
159 }
else if (option ==
"--wave-amplitude") {
161 }
else if (option ==
"--wave-period") {
164 }
else if (option ==
"--streaming-share") {
166 }
else if (option ==
"--queue-per-server") {
168 }
else if (option ==
"--upper") {
170 }
else if (option ==
"--lower") {
172 }
else if (option ==
"--cooldown") {
173 if (!number(config.
cooldown))
return false;
174 }
else if (option ==
"--min-servers") {
176 }
else if (option ==
"--max-servers") {
178 }
else if (option ==
"--max-queue") {
179 if (!number(config.
maxQueue))
return false;
180 }
else if (option ==
"--block") {
182 if (!needValue(range))
return false;
183 extraBlocks.push_back(range);
184 }
else if (option ==
"--no-default-blocks") {
185 keepDefaultBlocks =
false;
187 error =
"unknown option: " + option +
" (use --help)";
194 if (!keepDefaultBlocks) {
202 out <<
"Load balancer setup (press Enter to keep the value in brackets)\n";
203 ask(in, out,
" Number of initial web servers", config.
servers);
204 ask(in, out,
" Clock cycles to run", config.
cycles);
205 ask(in, out,
" Shortest task time (cycles)", config.
minTask);
206 ask(in, out,
" Longest task time (cycles)", config.
maxTask);
207 ask(in, out,
" Average new requests per cycle", config.
arrivalRate);
208 ask(in, out,
" Random seed", config.
seed);
209 askYesNo(in, out,
" Verbose output (y/n)", config.
verbose);
210 askYesNo(in, out,
" Use the bonus switch for P/S jobs (y/n)", config.
useSwitch);
216 error =
"the number of servers must be at least 1";
218 error =
"switch mode needs at least 2 servers (one per load balancer)";
219 }
else if (config.
cycles == 0) {
220 error =
"the number of clock cycles must be at least 1";
222 error =
"task times need 1 <= min-task <= max-task";
224 error =
"arrival rate must be between 0 and 1000 requests per cycle";
226 error =
"wave amplitude must be between 0 and 1";
228 error =
"streaming share must be between 0 and 1";
230 error =
"scaling thresholds need 0 <= lower < upper";
232 error =
"cooldown must be at least 1 cycle";
234 error =
"server limits need 1 <= min-servers <= max-servers";
236 error =
"initial servers cannot exceed max-servers";
241 error =
"invalid blocked range: " + range;
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.
void printUsage(std::ostream &out, const std::string &program)
Prints the list of command-line options.
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.
Rejects requests whose source address falls in a blocked IPv4 range.
bool addRange(const std::string &cidr)
Adds a blocked range.
Every user-adjustable setting for one simulation run.
unsigned int queuePerServer
Initial queue size = servers x queuePerServer.
double arrivalRate
Average new requests per cycle.
std::string logPath
File for the run summary (and events in verbose mode).
double lowerFactor
Scale down when queue < lowerFactor x servers.
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.
double waveAmplitude
Traffic wave size, 0 (steady) to 1.
double streamingShare
Fraction of requests that are streaming ('S') jobs.
std::uint32_t seed
Random seed (logged so a run can be repeated).
unsigned int cooldown
Cycles between scaling checks.
unsigned long statusEvery
Cycles between status lines; 0 chooses automatically.
std::size_t maxQueue
Queue capacity; 0 means unlimited.
unsigned int minServers
Lower bound on active servers.
bool color
Use ANSI colors on a terminal.
unsigned long wavePeriod
Cycles per traffic wave; 0 disables the wave.
unsigned int minTask
Shortest task time, in cycles.
bool showHelp
Print usage and exit.
double upperFactor
Scale up when queue > upperFactor x servers.
std::vector< std::string > blockedRanges
Firewall CIDR ranges.
bool interactive
Ask for the main settings on standard input.
bool autoWavePeriod
Use cycles / 4 (four busy/quiet waves) unless –wave-period is given.
unsigned int maxServers
Upper bound on live servers.