summaryrefslogtreecommitdiff
path: root/tools/sched_ext/scx_qmap.c
blob: 5bb5f687e579cec08861ec6f4ba7ed46b3de24b4 (plain)
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
 * Copyright (c) 2022 Tejun Heo <tj@kernel.org>
 * Copyright (c) 2022 David Vernet <dvernet@meta.com>
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <signal.h>
#include <libgen.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <bpf/bpf.h>
#include <scx/common.h>
#include "scx_qmap.h"
#include "scx_qmap.bpf.skel.h"

const char help_fmt[] =
"A simple five-level FIFO queue sched_ext scheduler.\n"
"\n"
"It also demonstrates hierarchical sub-scheduling: a scheduler can hand some\n"
"of its cpus to a child cgroup that runs its own scheduler. Run one qmap as\n"
"the parent, then run another qmap on a child cgroup with -c to attach it\n"
"beneath the parent.\n"
"\n"
"The policy below is deliberately simplistic and the resulting behavior can\n"
"look odd. qmap is a demo: it exists to exercise every sub-scheduling\n"
"primitive the kernel offers with as little code as possible, not to schedule\n"
"well.\n"
"\n"
"A parent divides the full cpus it holds among itself and its children in\n"
"proportion to cpu.weight. The cpus left over by rounding are time-shared,\n"
"handed to each participant in turn every -R ms. A cpu a scheduler only\n"
"holds a time-share of is never handed further down, and a parent left with\n"
"no full cpu of its own shuts its children down.\n"
"\n"
"See the top-of-file comment in .bpf.c for the design.\n"
"\n"
"Usage: %s [-s SLICE_US] [-e COUNT] [-t COUNT] [-T COUNT] [-l COUNT] [-b COUNT]\n"
"       [-N COUNT] [-P] [-M] [-H] [-c CG_PATH] [-d PID] [-D LEN] [-S] [-p] [-I]\n"
"       [-F COUNT] [-i SEC] [-R MS] [-J MODE] [-v]\n"
"\n"
"  -s SLICE_US   Override slice duration\n"
"  -e COUNT      Trigger scx_bpf_error() after COUNT enqueues\n"
"  -t COUNT      Stall every COUNT'th user thread\n"
"  -T COUNT      Stall every COUNT'th kernel thread\n"
"  -N COUNT      Size of the task_ctx arena slab (default 16384)\n"
"  -l COUNT      Trigger dispatch infinite looping after COUNT dispatches\n"
"  -b COUNT      Dispatch upto COUNT tasks together\n"
"  -P            Print out DSQ content and event counters to trace_pipe every second\n"
"  -M            Print out debug messages to trace_pipe\n"
"  -H            Boost nice -20 tasks in SHARED_DSQ, use with -b\n"
"  -c CG_PATH    Cgroup path to attach as sub-scheduler, must run parent scheduler first\n"
"  -d PID        Disallow a process from switching into SCHED_EXT (-1 for self)\n"
"  -D LEN        Set scx_exit_info.dump buffer length\n"
"  -S            Suppress qmap-specific debug dump\n"
"  -p            Switch only tasks on SCHED_EXT policy instead of all\n"
"  -I            Turn on SCX_OPS_ALWAYS_ENQ_IMMED\n"
"  -F COUNT      IMMED stress: force every COUNT'th enqueue to a busy local DSQ (use with -I)\n"
"  -C MODE       cid-override test (shuffle|bad-dup|bad-range|bad-mono)\n"
"  -i SEC        Stats interval, seconds (default 5)\n"
"  -R MS         Round-robin period for time-shared cpus, ms (default 200)\n"
"  -J MODE       Fault injection (wrong-cid: dispatch to a cid not held,\n"
"                init-fail/cgrp-init-fail: fail init_task/cpuctl_init for\n"
"                \"qmfail*\" comms/cgroups)\n"
"  -B PPT        Rescue bandwidth in parts per thousand, 0 disables (root only, default 20)\n"
"  -q US         Rescue batch quantum in microseconds (root only, default 5000)\n"
"  -v            Print libbpf debug messages\n"
"  -h            Display this help and exit\n";

static bool verbose;
static volatile int exit_req;

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
	if (level == LIBBPF_DEBUG && !verbose)
		return 0;
	return vfprintf(stderr, format, args);
}

static void sigint_handler(int dummy)
{
	exit_req = 1;
}

static void invoke_flush_alloc(struct scx_qmap *skel)
{
	LIBBPF_OPTS(bpf_test_run_opts, opts);

	bpf_prog_test_run_opts(bpf_program__fd(skel->progs.flush_alloc), &opts);
}

/* previous counter snapshots for the per-interval hier stats */
struct hier_prev {
	u64 alloc_ns[MAX_SUB_SCHEDS];
	u64 self_alloc_ns;
	u64 alloc_window_ns;
	u64 nr_dsps[MAX_SUB_SCHEDS];
	u64 nr_reenq_cap;
	u64 nr_reenq_immed;
	u64 nr_inject_attempts;
	u64 nr_rescue_dsp;
};

/* current wall-clock time as "HH:MM:SS" for the startup and interval headers */
static const char *tstamp(char *buf, size_t sz)
{
	time_t now = time(NULL);

	strftime(buf, sz, "%H:%M:%S", localtime(&now));
	return buf;
}

/* format the cids whose cid_owner[] matches @owner as "0-3,8", "-" if none */
static void format_cid_ranges(struct qmap_arena *qa, s32 owner, char *buf, size_t sz)
{
	u32 nr = qa->nr_cids, cid;
	size_t off = 0;
	s32 start = -1;

	buf[0] = '\0';
	for (cid = 0; cid <= nr; cid++) {
		bool match = cid < nr && qa->part.cid_owner[cid] == owner;
		int n;

		if (match) {
			if (start < 0)
				start = cid;
			continue;
		}
		if (start < 0)
			continue;

		if (start == (s32)cid - 1)
			n = snprintf(buf + off, sz - off, "%s%d",
				     off ? "," : "", start);
		else
			n = snprintf(buf + off, sz - off, "%s%d-%d",
				     off ? "," : "", start, cid - 1);
		if (n < 0 || (size_t)n >= sz - off) {
			strcpy(&buf[sz - 4], "...");
			return;
		}
		off += n;
		start = -1;
	}
	if (!off)
		strcpy(buf, "-");
}

/* partition summary + one row per sched: weight, cpus, dispatch rate, cids */
static void print_hier(struct qmap_arena *qa, struct hier_prev *prev, u64 own_cgid)
{
	char ranges[128], who[16];
	const char *rr = "-";
	double secs;
	u32 i;

	/*
	 * account_alloc() bumps alloc_window_ns together with the per-owner
	 * counters, so dividing by the same window yields exact cid counts.
	 */
	secs = (qa->alloc_window_ns - prev->alloc_window_ns) / 1e9;
	prev->alloc_window_ns = qa->alloc_window_ns;

	/* resolve the live shared-pool holder */
	if (qa->part.nr_shared && qa->part.nr_rr) {
		u64 cgid = qa->part.rr_slots[qa->part.rr_pos];

		rr = "self";
		if (cgid) {
			rr = "?";
			for (i = 0; i < MAX_SUB_SCHEDS; i++) {
				if (qa->sub_sched_ctxs[i].cgroup_id == cgid) {
					snprintf(who, sizeof(who), "sub%u", i);
					rr = who;
					break;
				}
			}
		}
	}

	format_cid_ranges(qa, CID_SHARED, ranges, sizeof(ranges));
	printf("hier   : nsub=%llu excl=%u shared=%s rr=%s reenq cap/immed +%llu/+%llu inj=+%llu rescue=+%llu\n",
	       (unsigned long long)qa->nr_sub_scheds, qa->part.nr_excl, ranges, rr,
	       (unsigned long long)(qa->nr_reenq_cap - prev->nr_reenq_cap),
	       (unsigned long long)(qa->nr_reenq_immed - prev->nr_reenq_immed),
	       (unsigned long long)(qa->nr_inject_attempts - prev->nr_inject_attempts),
	       (unsigned long long)(qa->nr_rescue_dsp - prev->nr_rescue_dsp));
	prev->nr_reenq_cap = qa->nr_reenq_cap;
	prev->nr_reenq_immed = qa->nr_reenq_immed;
	prev->nr_inject_attempts = qa->nr_inject_attempts;
	prev->nr_rescue_dsp = qa->nr_rescue_dsp;

	printf("hier   : %-4s %10s %4s %6s %8s  %s\n",
	       "", "cgroup", "w", "alloc", "disp/s", "cids");

	format_cid_ranges(qa, CID_SELF, ranges, sizeof(ranges));
	printf("hier   : %-4s %10llu %4u %6.2f %8s  %s\n", "self",
	       (unsigned long long)own_cgid, 100,
	       secs > 0 ? (qa->self_alloc_ns - prev->self_alloc_ns) / (secs * 1e9) : 0.0,
	       "-", ranges);
	prev->self_alloc_ns = qa->self_alloc_ns;

	for (i = 0; i < MAX_SUB_SCHEDS; i++) {
		struct sub_sched_ctx *sc = &qa->sub_sched_ctxs[i];

		if (!sc->cgroup_id)
			continue;

		snprintf(who, sizeof(who), "sub%u", i);
		format_cid_ranges(qa, i, ranges, sizeof(ranges));
		printf("hier   : %-4s %10llu %4u %6.2f %8.1f  %s\n", who,
		       (unsigned long long)sc->cgroup_id, sc->weight,
		       secs > 0 ? (qa->alloc_ns[i] - prev->alloc_ns[i]) / (secs * 1e9) : 0.0,
		       secs > 0 ? (sc->nr_dsps - prev->nr_dsps[i]) / secs : 0.0,
		       ranges);
		prev->alloc_ns[i] = qa->alloc_ns[i];
		prev->nr_dsps[i] = sc->nr_dsps;
	}
}

int main(int argc, char **argv)
{
	struct scx_qmap *skel;
	struct bpf_link *link;
	struct qmap_arena *qa;
	u32 test_error_cnt = 0;
	u64 ecode;
	int opt, stats_intv = 5, i, round_robin_ms = 200;
	struct hier_prev hprev = {};
	const char *sub_cg_path = NULL;
	char tbuf[32];
	u32 inject_mode = 0;
	u64 own_cgid = 0;
	s32 cid_override_shard_sz = 4;

	libbpf_set_print(libbpf_print_fn);
	signal(SIGINT, sigint_handler);
	signal(SIGTERM, sigint_handler);

	if (libbpf_num_possible_cpus() > SCX_QMAP_MAX_CPUS) {
		fprintf(stderr,
			"scx_qmap: %d possible CPUs exceeds compile-time cap %d; "
			"rebuild with larger SCX_QMAP_MAX_CPUS\n",
			libbpf_num_possible_cpus(), SCX_QMAP_MAX_CPUS);
		return 1;
	}
restart:
	optind = 1;
	skel = SCX_OPS_CID_OPEN(qmap_ops, scx_qmap);

	skel->rodata->slice_ns = __COMPAT_ENUM_OR_ZERO("scx_public_consts", "SCX_SLICE_DFL");
	skel->rodata->max_tasks = 16384;

	while ((opt = getopt(argc, argv,
			     "s:e:t:T:l:b:N:PMHc:d:D:SpIF:C:i:R:J:B:q:vh")) != -1) {
		switch (opt) {
		case 's':
			skel->rodata->slice_ns = strtoull(optarg, NULL, 0) * 1000;
			break;
		case 'e':
			test_error_cnt = strtoul(optarg, NULL, 0);
			break;
		case 't':
			skel->rodata->stall_user_nth = strtoul(optarg, NULL, 0);
			break;
		case 'T':
			skel->rodata->stall_kernel_nth = strtoul(optarg, NULL, 0);
			break;
		case 'l':
			skel->rodata->dsp_inf_loop_after = strtoul(optarg, NULL, 0);
			break;
		case 'b':
			skel->rodata->dsp_batch = strtoul(optarg, NULL, 0);
			break;
		case 'N':
			skel->rodata->max_tasks = strtoul(optarg, NULL, 0);
			break;
		case 'P':
			skel->rodata->print_dsqs_and_events = true;
			break;
		case 'M':
			skel->rodata->print_msgs = true;
			break;
		case 'H':
			skel->rodata->highpri_boosting = true;
			break;
		case 'c': {
			struct stat st;
			if (stat(optarg, &st) < 0) {
				perror("stat");
				return 1;
			}
			skel->struct_ops.qmap_ops->sub_cgroup_id = st.st_ino;
			skel->rodata->sub_cgroup_id = st.st_ino;
			own_cgid = st.st_ino;
			sub_cg_path = optarg;
			break;
		}
		case 'd':
			skel->rodata->disallow_tgid = strtol(optarg, NULL, 0);
			if (skel->rodata->disallow_tgid < 0)
				skel->rodata->disallow_tgid = getpid();
			break;
		case 'D':
			skel->struct_ops.qmap_ops->exit_dump_len = strtoul(optarg, NULL, 0);
			break;
		case 'S':
			skel->rodata->suppress_dump = true;
			break;
		case 'p':
			skel->struct_ops.qmap_ops->flags |= SCX_OPS_SWITCH_PARTIAL;
			break;
		case 'I':
			skel->struct_ops.qmap_ops->flags |= SCX_OPS_ALWAYS_ENQ_IMMED;
			break;
		case 'F':
			skel->rodata->immed_stress_nth = strtoul(optarg, NULL, 0);
			break;
		case 'C': {
			u32 nr_cpus = libbpf_num_possible_cpus();
			u32 mode;

			if (!strcmp(optarg, "shuffle"))
				mode = QMAP_CID_OVR_SHUFFLE;
			else if (!strcmp(optarg, "bad-dup"))
				mode = QMAP_CID_OVR_BAD_DUP;
			else if (!strcmp(optarg, "bad-range"))
				mode = QMAP_CID_OVR_BAD_RANGE;
			else if (!strcmp(optarg, "bad-mono"))
				mode = QMAP_CID_OVR_BAD_MONO;
			else {
				fprintf(stderr, "unknown cid-override mode '%s'\n", optarg);
				return 1;
			}
			skel->rodata->cid_override_mode = mode;
			cid_override_shard_sz = 4;

			/*
			 * bad-mono needs >= 3 shards to build a 0-based but
			 * non-monotonic shard_start. Shrink the shard size so
			 * the test runs on any machine with >= 3 cpus.
			 */
			if (mode == QMAP_CID_OVR_BAD_MONO) {
				if (nr_cpus < 3) {
					fprintf(stderr, "bad-mono needs >= 3 cpus (have %u)\n",
						nr_cpus);
					return 1;
				}
				cid_override_shard_sz = nr_cpus / 3;
			}

			/* shards of shard_sz each */
			skel->rodata->cid_override_nr_shards =
				(nr_cpus + cid_override_shard_sz - 1) / cid_override_shard_sz;
			break;
		}
		case 'i':
			stats_intv = atoi(optarg);
			if (stats_intv < 1)
				stats_intv = 1;
			break;
		case 'R':
			round_robin_ms = atoi(optarg);
			if (round_robin_ms < 10)
				round_robin_ms = 10;
			break;
		case 'J':
			if (!strcmp(optarg, "wrong-cid"))
				inject_mode = QMAP_INJ_WRONG_CID;
			else if (!strcmp(optarg, "init-fail"))
				inject_mode = QMAP_INJ_INIT_FAIL;
			else if (!strcmp(optarg, "cgrp-init-fail"))
				inject_mode = QMAP_INJ_CGRP_INIT_FAIL;
			else
				inject_mode = strtoul(optarg, NULL, 0);
			break;
		case 'B': {
			u32 ppt = strtoul(optarg, NULL, 0);

			if (!ppt)
				ppt = __COMPAT_ENUM_OR_ZERO("scx_consts", "SCX_RESCUE_DISABLE");
			skel->struct_ops.qmap_ops->rescue_bandwidth_ppt = ppt;
			break;
		}
		case 'q':
			skel->struct_ops.qmap_ops->rescue_quantum_us = strtoul(optarg, NULL, 0);
			break;
		case 'v':
			verbose = true;
			break;
		default:
			fprintf(stderr, help_fmt, basename(argv[0]));
			return opt != 'h';
		}
	}

	skel->rodata->round_robin_ns = (u64)round_robin_ms * 1000000;

	SCX_OPS_LOAD(skel, qmap_ops, scx_qmap, uei);

	qa = &skel->arena->qa;

	/*
	 * The cid-override arrays live in the arena, which is mmapped at load.
	 * Populate them before qmap_init_cids() consumes them at attach.
	 */
	if (skel->rodata->cid_override_mode) {
		u32 mode = skel->rodata->cid_override_mode;
		u32 nr_cpus = libbpf_num_possible_cpus();
		u32 i;

		/* shuffle: reversed cpu_to_cid; others: identity */
		for (i = 0; i < nr_cpus; i++) {
			if (mode == QMAP_CID_OVR_SHUFFLE)
				qa->cid_override_cpu_to_cid[i] = nr_cpus - 1 - i;
			else
				qa->cid_override_cpu_to_cid[i] = i;
		}
		if (mode == QMAP_CID_OVR_BAD_DUP && nr_cpus >= 2)
			qa->cid_override_cpu_to_cid[1] = 0;
		if (mode == QMAP_CID_OVR_BAD_RANGE)
			qa->cid_override_cpu_to_cid[0] = (s32)nr_cpus;

		for (i = 0; i < skel->rodata->cid_override_nr_shards; i++)
			qa->cid_override_shard_start[i] = i * cid_override_shard_sz;

		if (mode == QMAP_CID_OVR_BAD_MONO) {
			/* swap [1] and [2] to break monotonicity */
			s32 tmp = qa->cid_override_shard_start[1];
			qa->cid_override_shard_start[1] = qa->cid_override_shard_start[2];
			qa->cid_override_shard_start[2] = tmp;
		}
	}

	link = SCX_OPS_ATTACH(skel, qmap_ops, scx_qmap);

	qa->test_error_cnt = test_error_cnt;
	qa->inject_mode = inject_mode;

	if (sub_cg_path)
		printf("%s scx_qmap started: sub-scheduler on %s, stats every %ds\n",
		       tstamp(tbuf, sizeof(tbuf)), sub_cg_path, stats_intv);
	else
		printf("%s scx_qmap started: root scheduler, stats every %ds\n",
		       tstamp(tbuf, sizeof(tbuf)), stats_intv);
	fflush(stdout);

	while (!exit_req && !UEI_EXITED(skel, uei)) {
		long nr_enqueued = qa->nr_enqueued;
		long nr_dispatched = qa->nr_dispatched;

		printf("---- %s ----\n",
		       tstamp(tbuf, sizeof(tbuf)));
		printf("stats  : enq=%lu dsp=%lu delta=%ld reenq/cid0=%llu/%llu deq=%llu core=%llu enq_ddsp=%llu\n",
		       nr_enqueued, nr_dispatched, nr_enqueued - nr_dispatched,
		       (unsigned long long)qa->nr_reenqueued,
		       (unsigned long long)qa->nr_reenqueued_cid0,
		       (unsigned long long)qa->nr_dequeued,
		       (unsigned long long)qa->nr_core_sched_execed,
		       (unsigned long long)qa->nr_ddsp_from_enq);
		printf("         exp_local=%llu exp_remote=%llu exp_timer=%llu exp_lost=%llu\n",
		       (unsigned long long)qa->nr_expedited_local,
		       (unsigned long long)qa->nr_expedited_remote,
		       (unsigned long long)qa->nr_expedited_from_timer,
		       (unsigned long long)qa->nr_expedited_lost);
		if (__COMPAT_has_ksym("scx_bpf_cidperf_cur"))
			printf("cpuperf: cur min/avg/max=%u/%u/%u target min/avg/max=%u/%u/%u\n",
			       qa->cpuperf_min,
			       qa->cpuperf_avg,
			       qa->cpuperf_max,
			       qa->cpuperf_target_min,
			       qa->cpuperf_target_avg,
			       qa->cpuperf_target_max);

		invoke_flush_alloc(skel);
		print_hier(qa, &hprev, own_cgid);
		fflush(stdout);

		for (i = 0; i < stats_intv && !exit_req && !UEI_EXITED(skel, uei); i++)
			sleep(1);
	}

	bpf_link__destroy(link);
	ecode = UEI_REPORT(skel, uei);
	scx_qmap__destroy(skel);

	if (!exit_req && UEI_ECODE_RESTART(ecode))
		goto restart;
	return 0;
}