mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-18 01:14:38 +00:00
MINOR: debug: add "debug dev sched" to stress the scheduler.
This command supports starting a bunch of tasks or tasklets, either on the current thread (mask=0), all (default), or any set, either single-threaded or multi-threaded, and possibly auto-scheduled. These tasks/tasklets will randomly pick another one to wake it up. The tasks only do it 50% of the time while tasklets always wake two tasks up, in order to achieve roughly 50% load (since the target might already be woken up).
This commit is contained in:
parent
1e237d037b
commit
a5a4479849
205
src/debug.c
205
src/debug.c
@ -675,6 +675,210 @@ static int debug_parse_cli_stream(char **args, char *payload, struct appctx *app
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct task *debug_task_handler(struct task *t, void *ctx, unsigned short state)
|
||||||
|
{
|
||||||
|
unsigned long *tctx = ctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
|
||||||
|
unsigned long inter = tctx[1];
|
||||||
|
unsigned long rnd;
|
||||||
|
|
||||||
|
t->expire = tick_add(now_ms, inter);
|
||||||
|
|
||||||
|
/* half of the calls will wake up another entry */
|
||||||
|
rnd = ha_random64();
|
||||||
|
if (rnd & 1) {
|
||||||
|
rnd >>= 1;
|
||||||
|
rnd %= tctx[0];
|
||||||
|
rnd = tctx[rnd + 2];
|
||||||
|
|
||||||
|
if (rnd & 1)
|
||||||
|
task_wakeup((struct task *)(rnd - 1), TASK_WOKEN_MSG);
|
||||||
|
else
|
||||||
|
tasklet_wakeup((struct tasklet *)rnd);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct task *debug_tasklet_handler(struct task *t, void *ctx, unsigned short state)
|
||||||
|
{
|
||||||
|
unsigned long *tctx = ctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
|
||||||
|
unsigned long rnd;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* wake up two random entries */
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
rnd = ha_random64() % tctx[0];
|
||||||
|
rnd = tctx[rnd + 2];
|
||||||
|
|
||||||
|
if (rnd & 1)
|
||||||
|
task_wakeup((struct task *)(rnd - 1), TASK_WOKEN_MSG);
|
||||||
|
else
|
||||||
|
tasklet_wakeup((struct tasklet *)rnd);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse a "debug dev sched" command
|
||||||
|
* debug dev sched {task|tasklet} [count=<count>] [mask=<mask>] [single=<single>] [inter=<inter>]
|
||||||
|
*/
|
||||||
|
static int debug_parse_cli_sched(char **args, char *payload, struct appctx *appctx, void *private)
|
||||||
|
{
|
||||||
|
int arg;
|
||||||
|
void *ptr;
|
||||||
|
int size;
|
||||||
|
const char *word, *end;
|
||||||
|
struct ist name;
|
||||||
|
char *msg = NULL;
|
||||||
|
char *endarg;
|
||||||
|
unsigned long long new;
|
||||||
|
unsigned long count = 0;
|
||||||
|
unsigned long thrid = 0;
|
||||||
|
unsigned int inter = 0;
|
||||||
|
unsigned long mask, tmask;
|
||||||
|
unsigned long i;
|
||||||
|
int mode = 0; // 0 = tasklet; 1 = task
|
||||||
|
int single = 0;
|
||||||
|
unsigned long *tctx; // [0] = #tasks, [1] = inter, [2+] = { tl | (tsk+1) }
|
||||||
|
|
||||||
|
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
ptr = NULL; size = 0;
|
||||||
|
mask = all_threads_mask;
|
||||||
|
|
||||||
|
if (strcmp(args[3], "task") != 0 && strcmp(args[3], "tasklet") != 0) {
|
||||||
|
return cli_err(appctx,
|
||||||
|
"Usage: debug dev sched {task|tasklet} { <obj> = <value> }*\n"
|
||||||
|
" <obj> = {count | mask | inter | single }\n"
|
||||||
|
" <value> = 64-bit dec/hex integer (0x prefix supported)\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = strcmp(args[3], "task") == 0;
|
||||||
|
|
||||||
|
_HA_ATOMIC_ADD(&debug_commands_issued, 1);
|
||||||
|
for (arg = 4; *args[arg]; arg++) {
|
||||||
|
end = word = args[arg];
|
||||||
|
while (*end && *end != '=' && *end != '^' && *end != '+' && *end != '-')
|
||||||
|
end++;
|
||||||
|
name = ist2(word, end - word);
|
||||||
|
if (isteq(name, ist("count"))) {
|
||||||
|
ptr = &count; size = sizeof(count);
|
||||||
|
} else if (isteq(name, ist("mask"))) {
|
||||||
|
ptr = &mask; size = sizeof(mask);
|
||||||
|
} else if (isteq(name, ist("tid"))) {
|
||||||
|
ptr = &thrid; size = sizeof(thrid);
|
||||||
|
} else if (isteq(name, ist("inter"))) {
|
||||||
|
ptr = &inter; size = sizeof(inter);
|
||||||
|
} else if (isteq(name, ist("single"))) {
|
||||||
|
ptr = &single; size = sizeof(single);
|
||||||
|
} else
|
||||||
|
return cli_dynerr(appctx, memprintf(&msg, "Unsupported setting: '%s'.\n", word));
|
||||||
|
|
||||||
|
/* parse the new value . */
|
||||||
|
new = strtoll(end + 1, &endarg, 0);
|
||||||
|
if (end[1] && *endarg) {
|
||||||
|
memprintf(&msg,
|
||||||
|
"%sIgnoring unparsable value '%s' for field '%.*s'.\n",
|
||||||
|
msg ? msg : "", end + 1, (int)(end - word), word);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write the new value */
|
||||||
|
if (size == 8)
|
||||||
|
write_u64(ptr, new);
|
||||||
|
else if (size == 4)
|
||||||
|
write_u32(ptr, new);
|
||||||
|
else if (size == 2)
|
||||||
|
write_u16(ptr, new);
|
||||||
|
else
|
||||||
|
*(uint8_t *)ptr = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
tctx = calloc(sizeof(*tctx), count + 2);
|
||||||
|
if (!tctx)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
tctx[0] = (unsigned long)count;
|
||||||
|
tctx[1] = (unsigned long)inter;
|
||||||
|
|
||||||
|
mask &= all_threads_mask;
|
||||||
|
if (!mask)
|
||||||
|
mask = tid_bit;
|
||||||
|
|
||||||
|
tmask = 0;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
if (single || mode == 0) {
|
||||||
|
/* look for next bit matching a bit in mask or loop back to zero */
|
||||||
|
for (tmask <<= 1; !(mask & tmask); ) {
|
||||||
|
if (!(mask & -tmask))
|
||||||
|
tmask = 1;
|
||||||
|
else
|
||||||
|
tmask <<= 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* multi-threaded task */
|
||||||
|
tmask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* now, if poly or mask was set, tmask corresponds to the
|
||||||
|
* valid thread mask to use, otherwise it remains zero.
|
||||||
|
*/
|
||||||
|
//printf("%lu: mode=%d mask=%#lx\n", i, mode, tmask);
|
||||||
|
if (mode == 0) {
|
||||||
|
struct tasklet *tl = tasklet_new();
|
||||||
|
|
||||||
|
if (!tl)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (tmask)
|
||||||
|
tl->tid = my_ffsl(tmask) - 1;
|
||||||
|
tl->process = debug_tasklet_handler;
|
||||||
|
tl->context = tctx;
|
||||||
|
tctx[i + 2] = (unsigned long)tl;
|
||||||
|
} else {
|
||||||
|
struct task *task = task_new(tmask ? tmask : tid_bit);
|
||||||
|
|
||||||
|
if (!task)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
task->process = debug_task_handler;
|
||||||
|
task->context = tctx;
|
||||||
|
tctx[i + 2] = (unsigned long)task + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* start the tasks and tasklets */
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
unsigned long ctx = tctx[i + 2];
|
||||||
|
|
||||||
|
if (ctx & 1)
|
||||||
|
task_wakeup((struct task *)(ctx - 1), TASK_WOKEN_INIT);
|
||||||
|
else
|
||||||
|
tasklet_wakeup((struct tasklet *)ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg && *msg)
|
||||||
|
return cli_dynmsg(appctx, LOG_INFO, msg);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
/* free partially allocated entries */
|
||||||
|
for (i = 0; tctx && i < count; i++) {
|
||||||
|
unsigned long ctx = tctx[i + 2];
|
||||||
|
|
||||||
|
if (!ctx)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (ctx & 1)
|
||||||
|
task_destroy((struct task *)(ctx - 1));
|
||||||
|
else
|
||||||
|
tasklet_free((struct tasklet *)ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(tctx);
|
||||||
|
return cli_err(appctx, "Not enough memory");
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(DEBUG_MEM_STATS)
|
#if defined(DEBUG_MEM_STATS)
|
||||||
/* CLI parser for the "debug dev memstats" command */
|
/* CLI parser for the "debug dev memstats" command */
|
||||||
static int debug_parse_cli_memstats(char **args, char *payload, struct appctx *appctx, void *private)
|
static int debug_parse_cli_memstats(char **args, char *payload, struct appctx *appctx, void *private)
|
||||||
@ -917,6 +1121,7 @@ static struct cli_kw_list cli_kws = {{ },{
|
|||||||
{{ "debug", "dev", "memstats", NULL }, "debug dev memstats [reset|all] : dump/reset memory statistics", debug_parse_cli_memstats, debug_iohandler_memstats, NULL, NULL, ACCESS_EXPERT },
|
{{ "debug", "dev", "memstats", NULL }, "debug dev memstats [reset|all] : dump/reset memory statistics", debug_parse_cli_memstats, debug_iohandler_memstats, NULL, NULL, ACCESS_EXPERT },
|
||||||
#endif
|
#endif
|
||||||
{{ "debug", "dev", "panic", NULL }, "debug dev panic : immediately trigger a panic", debug_parse_cli_panic, NULL, NULL, NULL, ACCESS_EXPERT },
|
{{ "debug", "dev", "panic", NULL }, "debug dev panic : immediately trigger a panic", debug_parse_cli_panic, NULL, NULL, NULL, ACCESS_EXPERT },
|
||||||
|
{{ "debug", "dev", "sched", NULL }, "debug dev sched ... : stress the scheduler", debug_parse_cli_sched, NULL, NULL, NULL, ACCESS_EXPERT },
|
||||||
{{ "debug", "dev", "stream",NULL }, "debug dev stream ... : show/manipulate stream flags", debug_parse_cli_stream,NULL, NULL, NULL, ACCESS_EXPERT },
|
{{ "debug", "dev", "stream",NULL }, "debug dev stream ... : show/manipulate stream flags", debug_parse_cli_stream,NULL, NULL, NULL, ACCESS_EXPERT },
|
||||||
{{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread", debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT },
|
{{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread", debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT },
|
||||||
{{ "debug", "dev", "write", NULL }, "debug dev write [size] : write that many bytes", debug_parse_cli_write, NULL, NULL, NULL, ACCESS_EXPERT },
|
{{ "debug", "dev", "write", NULL }, "debug dev write [size] : write that many bytes", debug_parse_cli_write, NULL, NULL, NULL, ACCESS_EXPERT },
|
||||||
|
Loading…
Reference in New Issue
Block a user