1 #include <uwsgi.h>
2 
3 /*
4 
5 	Busyness cheaper algorithm (by Łukasz Mierzwa)
6 
7 */
8 
9 extern struct uwsgi_server uwsgi;
10 
11 // this global struct containes all of the relevant values
12 struct uwsgi_cheaper_busyness_global {
13 	uint64_t busyness_max;
14 	uint64_t busyness_min;
15 	uint64_t *last_values;
16 	uint64_t *current_busyness;
17 	uint64_t total_avg_busyness;
18 	int *was_busy;
19 	uint64_t tcheck;
20 	uint64_t last_cheaped;      // last time worker was cheaped due to low busyness
21 	uint64_t next_cheap;        // timestamp, we can cheap worker after it
22 	uint64_t penalty;       // penalty for respawning to fast, it will be added to multiplier
23 	uint64_t min_multi;     //initial multiplier will be stored here
24 	uint64_t cheap_multi;   // current multiplier value
25 	int last_action;            // 1 - spawn workers ; 2 - cheap worker
26 	int verbose;                // 1 - show debug logs, 0 - only important
27 	uint64_t tolerance_counter; // used to keep track of what to do if min <= busyness <= max for few cycles in row
28 	int emergency_workers; // counts the number of running emergency workers
29 #ifdef __linux__
30 	int backlog_alert;
31 	int backlog_step;
32 	uint64_t backlog_multi; // multiplier used to cheap emergency workers
33 	uint64_t backlog_nonzero_alert;
34 	int backlog_is_nonzero;
35 	uint64_t backlog_nonzero_since; // since when backlog is > 0
36 #endif
37 } uwsgi_cheaper_busyness_global;
38 
39 struct uwsgi_option uwsgi_cheaper_busyness_options[] = {
40 
41 	{"cheaper-busyness-max", required_argument, 0,
42 		"set the cheaper busyness high percent limit, above that value worker is considered loaded (default 50)",
43 		uwsgi_opt_set_64bit, &uwsgi_cheaper_busyness_global.busyness_max, 0},
44 
45 	{"cheaper-busyness-min", required_argument, 0,
46 		"set the cheaper busyness low percent limit, below that value worker is considered idle (default 25)",
47 		uwsgi_opt_set_64bit, &uwsgi_cheaper_busyness_global.busyness_min, 0},
48 
49 	{"cheaper-busyness-multiplier", required_argument, 0,
50 		"set initial cheaper multiplier, worker needs to be idle for cheaper-overload*multiplier seconds to be cheaped (default 10)",
51 		uwsgi_opt_set_64bit, &uwsgi_cheaper_busyness_global.cheap_multi, 0},
52 
53 	{"cheaper-busyness-penalty", required_argument, 0,
54 		"penalty for respawning workers to fast, it will be added to the current multiplier value if worker is cheaped and than respawned back too fast (default 2)",
55 		uwsgi_opt_set_64bit, &uwsgi_cheaper_busyness_global.penalty, 0},
56 
57 	{"cheaper-busyness-verbose", no_argument, 0, "enable verbose log messages from busyness algorithm",
58 		uwsgi_opt_true, &uwsgi_cheaper_busyness_global.verbose, 0},
59 
60 #ifdef __linux__
61 	{"cheaper-busyness-backlog-alert", required_argument, 0,
62 		"spawn emergency worker(s) if any time listen queue is higher than this value (default 33)",
63 		uwsgi_opt_set_int, &uwsgi_cheaper_busyness_global.backlog_alert, 0},
64 	{"cheaper-busyness-backlog-multiplier", required_argument, 0,
65 		"set cheaper multiplier used for emergency workers (default 3)",
66 		uwsgi_opt_set_64bit, &uwsgi_cheaper_busyness_global.backlog_multi, 0},
67 	{"cheaper-busyness-backlog-step", required_argument, 0,
68 		"number of emergency workers to spawn at a time (default 1)",
69 		uwsgi_opt_set_int, &uwsgi_cheaper_busyness_global.backlog_step, 0},
70 	{"cheaper-busyness-backlog-nonzero", required_argument, 0,
71 		"spawn emergency worker(s) if backlog is > 0 for more then N seconds (default 60)",
72 		uwsgi_opt_set_64bit, &uwsgi_cheaper_busyness_global.backlog_nonzero_alert, 0},
73 #endif
74 
75 	{0, 0, 0, 0, 0, 0 ,0},
76 
77 };
78 
79 
80 // used to set time after when we allow to cheap workers
set_next_cheap_time(void)81 void set_next_cheap_time(void) {
82 	uint64_t now = uwsgi_micros();
83 
84 #ifdef __linux__
85 	if (uwsgi_cheaper_busyness_global.emergency_workers > 0) {
86 		// we have some emergency workers running, we will use minimum delay (2 cycles) to cheap workers
87 		// to have quicker recovery from big but short load spikes
88 		// otherwise we might wait a lot before cheaping all emergency workers
89 		if (uwsgi_cheaper_busyness_global.verbose)
90 			uwsgi_log("[busyness] %d emergency worker(s) running, using %llu seconds cheaper timer\n",
91 				uwsgi_cheaper_busyness_global.emergency_workers, uwsgi.cheaper_overload*uwsgi_cheaper_busyness_global.backlog_multi);
92 		uwsgi_cheaper_busyness_global.next_cheap = now + uwsgi.cheaper_overload*uwsgi_cheaper_busyness_global.backlog_multi*1000000;
93 	} else {
94 #endif
95 		// no emergency workers running, we use normal math for setting timer
96 		uwsgi_cheaper_busyness_global.next_cheap = now + uwsgi.cheaper_overload*uwsgi_cheaper_busyness_global.cheap_multi*1000000;
97 #ifdef __linux__
98 	}
99 #endif
100 }
101 
102 
decrease_multi(void)103 void decrease_multi(void) {
104 	// will decrease multiplier but only down to initial value
105 	if (uwsgi_cheaper_busyness_global.cheap_multi > uwsgi_cheaper_busyness_global.min_multi) {
106 		uwsgi_cheaper_busyness_global.cheap_multi--;
107 		uwsgi_log("[busyness] decreasing cheaper multiplier to %llu\n", uwsgi_cheaper_busyness_global.cheap_multi);
108 	}
109 }
110 
111 
112 #ifdef __linux__
spawn_emergency_worker(int backlog)113 int spawn_emergency_worker(int backlog) {
114 	// reset cheaper multiplier to minimum value so we can start cheaping workers sooner
115 	// if this was just random spike
116 	uwsgi_cheaper_busyness_global.cheap_multi = uwsgi_cheaper_busyness_global.min_multi;
117 
118 	// set last action to spawn
119 	uwsgi_cheaper_busyness_global.last_action = 1;
120 
121 	int decheaped = 0;
122 	int i;
123 	for (i = 1; i <= uwsgi.numproc; i++) {
124 		if (uwsgi.workers[i].cheaped == 1 && uwsgi.workers[i].pid == 0) {
125 			decheaped++;
126 			if (decheaped >= uwsgi_cheaper_busyness_global.backlog_step) break;
127 		}
128 	}
129 
130 	// reset cheap timer, so that we need to start counting idle cycles from zero
131 	set_next_cheap_time();
132 
133 	if (decheaped > 0) {
134 		uwsgi_cheaper_busyness_global.emergency_workers += decheaped;
135 		uwsgi_log("[busyness] %d requests in listen queue, spawning %d emergency worker(s) (%d)!\n",
136 			backlog, decheaped, uwsgi_cheaper_busyness_global.emergency_workers);
137 	} else {
138 		uwsgi_log("[busyness] %d requests in listen queue but we are already started maximum number of workers (%d)\n",
139 			backlog, uwsgi.numproc);
140 	}
141 
142 	return decheaped;
143 }
144 #endif
145 
146 
cheaper_busyness_algo(int can_spawn)147 int cheaper_busyness_algo(int can_spawn) {
148 
149 	int i;
150 	// we use microseconds
151 	uint64_t t = uwsgi.cheaper_overload*1000000;
152 
153 	int active_workers = 0;
154 	uint64_t total_busyness = 0;
155 	uint64_t avg_busyness = 0;
156 
157 	for (i = 0; i < uwsgi.numproc; i++) {
158 		if (uwsgi.workers[i+1].cheaped == 0 && uwsgi.workers[i+1].pid > 0) {
159 			active_workers++;
160 			uwsgi_cheaper_busyness_global.was_busy[i] += uwsgi_worker_is_busy(i+1);
161 		} else {
162 			uwsgi_cheaper_busyness_global.was_busy[i] = 0;
163 		}
164 	}
165 
166 #ifdef __linux__
167 	int backlog = uwsgi.shared->backlog;
168 #endif
169 
170 	uint64_t now = uwsgi_micros();
171 	if (now - uwsgi_cheaper_busyness_global.tcheck >= t) {
172 		uwsgi_cheaper_busyness_global.tcheck = now;
173 		for (i = 0; i < uwsgi.numproc; i++) {
174 			if (uwsgi.workers[i+1].cheaped == 0 && uwsgi.workers[i+1].pid > 0) {
175 				uint64_t percent = (( (uwsgi.workers[i+1].running_time-uwsgi_cheaper_busyness_global.last_values[i])*100)/t);
176 				if (percent > 100) {
177 					percent = 100;
178 				}
179 				else if (uwsgi.workers[i+1].running_time-uwsgi_cheaper_busyness_global.last_values[i] == 0 && percent == 0 && uwsgi_cheaper_busyness_global.was_busy[i] > 0) {
180 					// running_time did not change but workers were busy
181 					// this means that workers had response times > busyness check interval
182 					if (uwsgi_cheaper_busyness_global.verbose) {
183 						uwsgi_log("[busyness] worker %d was busy %d time(s) in last cycle but no request was completed during this time, marking as 100%% busy\n",
184 							i+1, uwsgi_cheaper_busyness_global.was_busy[i]);
185 					}
186 					percent = 100;
187 				}
188 				uwsgi_cheaper_busyness_global.was_busy[i] = 0;
189 				total_busyness += percent;
190 				if (uwsgi_cheaper_busyness_global.verbose && active_workers > 1)
191 					uwsgi_log("[busyness] worker nr %d %llus average busyness is at %llu%%\n",
192 						i+1, uwsgi.cheaper_overload, percent);
193 				if (uwsgi.has_metrics) {
194 					// update metrics
195 					uwsgi_wlock(uwsgi.metrics_lock);
196 					uwsgi_cheaper_busyness_global.current_busyness[i] = percent;
197 					uwsgi_rwunlock(uwsgi.metrics_lock);
198 				}
199 			}
200 			uwsgi_cheaper_busyness_global.last_values[i] = uwsgi.workers[i+1].running_time;
201 		}
202 
203 		avg_busyness = (active_workers ? total_busyness / active_workers : 0);
204 		if (uwsgi.has_metrics) {
205 			uwsgi_wlock(uwsgi.metrics_lock);
206 			uwsgi_cheaper_busyness_global.total_avg_busyness = avg_busyness;
207 			uwsgi_rwunlock(uwsgi.metrics_lock);
208 		}
209 
210 		if (uwsgi_cheaper_busyness_global.verbose)
211 			uwsgi_log("[busyness] %ds average busyness of %d worker(s) is at %d%%\n",
212 				(int) uwsgi.cheaper_overload, (int) active_workers, (int) avg_busyness);
213 
214 		if (avg_busyness > uwsgi_cheaper_busyness_global.busyness_max) {
215 
216 			// we need to reset this to 0 since this is not idle cycle
217 			uwsgi_cheaper_busyness_global.tolerance_counter = 0;
218 
219 			int decheaped = 0;
220 			if (can_spawn) {
221 				for (i = 1; i <= uwsgi.numproc; i++) {
222 					if (uwsgi.workers[i].cheaped == 1 && uwsgi.workers[i].pid == 0) {
223 						decheaped++;
224 						if (decheaped >= uwsgi.cheaper_step) break;
225 					}
226 				}
227 			}
228 
229 			if (decheaped > 0) {
230 				// store information that we just spawned new workers
231 				uwsgi_cheaper_busyness_global.last_action = 1;
232 
233 				// calculate number of seconds since last worker was cheaped
234 				if ((now - uwsgi_cheaper_busyness_global.last_cheaped)/uwsgi.cheaper_overload/1000000 <= uwsgi_cheaper_busyness_global.cheap_multi) {
235 					// worker was cheaped and then spawned back in less than current multiplier*cheaper_overload seconds
236 					// we will increase the multiplier so that next time worker will need to wait longer before being cheaped
237 					uwsgi_cheaper_busyness_global.cheap_multi += uwsgi_cheaper_busyness_global.penalty;
238 					uwsgi_log("[busyness] worker(s) respawned to fast, increasing cheaper multiplier to %llu (+%llu)\n",
239 						uwsgi_cheaper_busyness_global.cheap_multi, uwsgi_cheaper_busyness_global.penalty);
240 				} else {
241 					decrease_multi();
242 				}
243 
244 				set_next_cheap_time();
245 
246 				uwsgi_log("[busyness] %llus average busyness is at %llu%%, will spawn %d new worker(s)\n",
247 					uwsgi.cheaper_overload, avg_busyness, decheaped);
248 			} else {
249 				uwsgi_log("[busyness] %llus average busyness is at %llu%% but we already started maximum number of workers available with current limits (%d)\n",
250 					uwsgi.cheaper_overload, avg_busyness, active_workers);
251 			}
252 
253 			// return the maximum number of workers to spawn
254 			return decheaped;
255 
256 #ifdef __linux__
257 		} else if (can_spawn && backlog > uwsgi_cheaper_busyness_global.backlog_alert && active_workers < uwsgi.numproc) {
258 			return spawn_emergency_worker(backlog);
259 #endif
260 
261 		} else if (avg_busyness < uwsgi_cheaper_busyness_global.busyness_min) {
262 
263 			// with only 1 worker running there is no point in doing all that magic
264 			if (active_workers == 1) return 0;
265 
266 			// we need to reset this to 0 since this is not idle cycle
267 			uwsgi_cheaper_busyness_global.tolerance_counter = 0;
268 
269 			if (active_workers > uwsgi.cheaper_count) {
270 				// cheap a worker if too much are running
271 				if (now >= uwsgi_cheaper_busyness_global.next_cheap) {
272 					// lower cheaper multiplier if this is subsequent cheap
273 					if (uwsgi_cheaper_busyness_global.last_action == 2) decrease_multi();
274 					set_next_cheap_time();
275 
276 					uwsgi_log("[busyness] %llus average busyness is at %llu%%, cheap one of %d running workers\n",
277 						uwsgi.cheaper_overload, avg_busyness, (int) active_workers);
278 					// store timestamp
279 					uwsgi_cheaper_busyness_global.last_cheaped = uwsgi_micros();
280 
281 					// store information that last action performed was cheaping worker
282 					uwsgi_cheaper_busyness_global.last_action = 2;
283 
284 					if (uwsgi_cheaper_busyness_global.emergency_workers > 0)
285 						uwsgi_cheaper_busyness_global.emergency_workers--;
286 
287 					return -1;
288 				} else if (uwsgi_cheaper_busyness_global.verbose)
289 					uwsgi_log("[busyness] need to wait %llu more second(s) to cheap worker\n", (uwsgi_cheaper_busyness_global.next_cheap - now)/1000000);
290 			}
291 
292 		} else {
293 			// with only 1 worker running there is no point in doing all that magic
294 			if (active_workers == 1) return 0;
295 
296 			if (uwsgi_cheaper_busyness_global.emergency_workers > 0)
297 				// we had emergency workers running and we went down to the busyness
298 				// level that is high enough to slow down cheaping workers at extra speed
299 				uwsgi_cheaper_busyness_global.emergency_workers--;
300 
301 			// we have min <= busyness <= max we need to check what happened before
302 
303 			uwsgi_cheaper_busyness_global.tolerance_counter++;
304 			if (uwsgi_cheaper_busyness_global.tolerance_counter >= 3) {
305 				// we had three or more cycles when min <= busyness <= max, lets reset the cheaper timer
306 				// this is to prevent workers from being cheaped if we had idle cycles for almost all
307 				// time needed to cheap them, than a lot min<busy<max when we do not reset timer
308 				// and then another idle cycle than would trigger cheaping
309 				if (uwsgi_cheaper_busyness_global.verbose)
310 					uwsgi_log("[busyness] %llus average busyness is at %llu%%, %llu non-idle cycle(s), resetting cheaper timer\n",
311 						uwsgi.cheaper_overload, avg_busyness, uwsgi_cheaper_busyness_global.tolerance_counter);
312 				set_next_cheap_time();
313 			} else {
314 				// we had < 3 idle cycles in a row so we won't reset idle timer yet since this might be just short load spike
315 				// but we need to add cheaper-overload seconds to the cheaper timer so this cycle isn't counted as idle
316 				if (uwsgi_cheaper_busyness_global.verbose)
317 					uwsgi_log("[busyness] %llus average busyness is at %llu%%, %llu non-idle cycle(s), adjusting cheaper timer\n",
318 						uwsgi.cheaper_overload, avg_busyness, uwsgi_cheaper_busyness_global.tolerance_counter);
319 				uwsgi_cheaper_busyness_global.next_cheap += uwsgi.cheaper_overload*1000000;
320 			}
321 		}
322 	}
323 
324 #ifdef __linux__
325 	else if (can_spawn && backlog > uwsgi_cheaper_busyness_global.backlog_alert && active_workers < uwsgi.numproc) {
326 		// we check for backlog overload every cycle
327 		return spawn_emergency_worker(backlog);
328 	}
329 	else if (backlog > 0) {
330 		if (uwsgi_cheaper_busyness_global.backlog_is_nonzero) {
331 			// backlog was > 0 last time, check timestamp and spawn workers if needed
332 			if (can_spawn && (now - uwsgi_cheaper_busyness_global.backlog_nonzero_since)/1000000 >= uwsgi_cheaper_busyness_global.backlog_nonzero_alert) {
333 				uwsgi_log("[busyness] backlog was non-zero for %llu second(s), spawning new worker(s)\n", (now - uwsgi_cheaper_busyness_global.backlog_nonzero_since)/1000000);
334 				uwsgi_cheaper_busyness_global.backlog_nonzero_since = now;
335 				return spawn_emergency_worker(backlog);
336 			}
337 		}
338 		else {
339 			// this is first > 0 pass, setup timer
340 			if (uwsgi_cheaper_busyness_global.verbose)
341 				uwsgi_log("[busyness] backlog is starting to fill (%d)\n", backlog);
342 			uwsgi_cheaper_busyness_global.backlog_is_nonzero = 1;
343 			uwsgi_cheaper_busyness_global.backlog_nonzero_since = now;
344 		}
345 	}
346 	else if (uwsgi_cheaper_busyness_global.backlog_is_nonzero) {
347 		if (uwsgi_cheaper_busyness_global.verbose)
348 			uwsgi_log("[busyness] backlog is now empty\n");
349 		uwsgi_cheaper_busyness_global.backlog_is_nonzero = 0;
350 	}
351 #endif
352 
353 	return 0;
354 }
355 
356 
357 // registration hook
uwsgi_cheaper_register_busyness(void)358 void uwsgi_cheaper_register_busyness(void) {
359 	uwsgi_register_cheaper_algo("busyness", cheaper_busyness_algo);
360 }
361 
uwsgi_cheaper_busyness_init(void)362 static int uwsgi_cheaper_busyness_init(void) {
363 	if (!uwsgi.requested_cheaper_algo || strcmp(uwsgi.requested_cheaper_algo, "busyness")) return 0;
364 	// this happens on the first run, the required memory is allocated
365 	uwsgi_cheaper_busyness_global.last_values = uwsgi_calloc(sizeof(uint64_t) * uwsgi.numproc);
366 	uwsgi_cheaper_busyness_global.was_busy = uwsgi_calloc(sizeof(int) * uwsgi.numproc);
367 
368 	if (uwsgi.has_metrics) {
369 		// allocate metrics memory
370 		uwsgi_cheaper_busyness_global.current_busyness = uwsgi_calloc(sizeof(uint64_t) * uwsgi.numproc);
371 	}
372 
373 	// set defaults
374 	if (!uwsgi_cheaper_busyness_global.busyness_max) uwsgi_cheaper_busyness_global.busyness_max = 50;
375 	if (!uwsgi_cheaper_busyness_global.busyness_min) uwsgi_cheaper_busyness_global.busyness_min = 25;
376 	if (!uwsgi_cheaper_busyness_global.cheap_multi) uwsgi_cheaper_busyness_global.cheap_multi = 10;
377 	if (!uwsgi_cheaper_busyness_global.penalty) uwsgi_cheaper_busyness_global.penalty = 2;
378 
379 #ifdef __linux__
380 	if (!uwsgi_cheaper_busyness_global.backlog_alert) uwsgi_cheaper_busyness_global.backlog_alert = 33;
381 	if (!uwsgi_cheaper_busyness_global.backlog_multi) uwsgi_cheaper_busyness_global.backlog_multi = 3;
382 	if (!uwsgi_cheaper_busyness_global.backlog_step) uwsgi_cheaper_busyness_global.backlog_step = 1;
383 	if (!uwsgi_cheaper_busyness_global.backlog_nonzero_alert) uwsgi_cheaper_busyness_global.backlog_nonzero_alert = 60;
384 #endif
385 
386 	// store initial multiplier so we don't loose that value
387 	uwsgi_cheaper_busyness_global.min_multi = uwsgi_cheaper_busyness_global.cheap_multi;
388 	// since this is first run we will print current values
389 	uwsgi_log("[busyness] settings: min=%llu%%, max=%llu%%, overload=%llu, multiplier=%llu, respawn penalty=%llu\n",
390 		uwsgi_cheaper_busyness_global.busyness_min, uwsgi_cheaper_busyness_global.busyness_max,
391 		uwsgi.cheaper_overload, uwsgi_cheaper_busyness_global.cheap_multi, uwsgi_cheaper_busyness_global.penalty);
392 #ifdef __linux__
393 	uwsgi_log("[busyness] backlog alert is set to %d request(s), step is %d\n",
394 		uwsgi_cheaper_busyness_global.backlog_alert, uwsgi_cheaper_busyness_global.backlog_step);
395 	uwsgi_log("[busyness] backlog non-zero alert is set to %llu second(s)\n", uwsgi_cheaper_busyness_global.backlog_nonzero_alert);
396 #endif
397 
398 	// register metrics if enabled
399 	if (uwsgi.has_metrics) {
400 		int i;
401 		char buf[4096];
402 		char buf2[4096];
403 		for (i = 0; i < uwsgi.numproc; i++) {
404 			if (snprintf(buf, 4096, "worker.%d.plugin.cheaper_busyness.busyness", i+1) <= 0) {
405 				uwsgi_log("[busyness] unable to register busyness metric for worker %d\n", i+1);
406 				exit(1);
407 			}
408 			if (snprintf(buf2, 4096, "3.%d.100.1", i+1) <= 0) {
409 				uwsgi_log("[busyness] unable to register busyness metric oid for worker %d\n", i+1);
410 				exit(1);
411 			}
412 			uwsgi_register_metric(buf, buf2, UWSGI_METRIC_GAUGE, "ptr", &uwsgi_cheaper_busyness_global.current_busyness[i], 0, NULL);
413 		}
414 		uwsgi_register_metric("plugin.cheaper_busyness.total_avg_busyness", "4.100.1", UWSGI_METRIC_GAUGE, "ptr", &uwsgi_cheaper_busyness_global.total_avg_busyness, 0, NULL);
415 		uwsgi_log("[busyness] metrics registered\n");
416 	}
417 
418 	// initialize timers
419 	uwsgi_cheaper_busyness_global.tcheck = uwsgi_micros();
420 	set_next_cheap_time();
421 
422 	return 0;
423 }
424 
425 struct uwsgi_plugin cheaper_busyness_plugin = {
426 
427 	.name = "cheaper_busyness",
428 	.on_load = uwsgi_cheaper_register_busyness,
429 	.options = uwsgi_cheaper_busyness_options,
430 	.init = uwsgi_cheaper_busyness_init
431 };
432