scx_qmap.bpf.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. /* SPDX-License-Identifier: GPL-2.0 */
  2. /*
  3. * A simple five-level FIFO queue scheduler.
  4. *
  5. * There are five FIFOs implemented using BPF_MAP_TYPE_QUEUE. A task gets
  6. * assigned to one depending on its compound weight. Each CPU round robins
  7. * through the FIFOs and dispatches more from FIFOs with higher indices - 1 from
  8. * queue0, 2 from queue1, 4 from queue2 and so on.
  9. *
  10. * This scheduler demonstrates:
  11. *
  12. * - BPF-side queueing using PIDs.
  13. * - Sleepable per-task storage allocation using ops.prep_enable().
  14. * - Using ops.cpu_release() to handle a higher priority scheduling class taking
  15. * the CPU away.
  16. * - Core-sched support.
  17. *
  18. * This scheduler is primarily for demonstration and testing of sched_ext
  19. * features and unlikely to be useful for actual workloads.
  20. *
  21. * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
  22. * Copyright (c) 2022 Tejun Heo <tj@kernel.org>
  23. * Copyright (c) 2022 David Vernet <dvernet@meta.com>
  24. */
  25. #include <scx/common.bpf.h>
  26. enum consts {
  27. ONE_SEC_IN_NS = 1000000000,
  28. SHARED_DSQ = 0,
  29. HIGHPRI_DSQ = 1,
  30. HIGHPRI_WEIGHT = 8668, /* this is what -20 maps to */
  31. };
  32. char _license[] SEC("license") = "GPL";
  33. const volatile u64 slice_ns = SCX_SLICE_DFL;
  34. const volatile u32 stall_user_nth;
  35. const volatile u32 stall_kernel_nth;
  36. const volatile u32 dsp_inf_loop_after;
  37. const volatile u32 dsp_batch;
  38. const volatile bool highpri_boosting;
  39. const volatile bool print_shared_dsq;
  40. const volatile s32 disallow_tgid;
  41. const volatile bool suppress_dump;
  42. u64 nr_highpri_queued;
  43. u32 test_error_cnt;
  44. UEI_DEFINE(uei);
  45. struct qmap {
  46. __uint(type, BPF_MAP_TYPE_QUEUE);
  47. __uint(max_entries, 4096);
  48. __type(value, u32);
  49. } queue0 SEC(".maps"),
  50. queue1 SEC(".maps"),
  51. queue2 SEC(".maps"),
  52. queue3 SEC(".maps"),
  53. queue4 SEC(".maps");
  54. struct {
  55. __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
  56. __uint(max_entries, 5);
  57. __type(key, int);
  58. __array(values, struct qmap);
  59. } queue_arr SEC(".maps") = {
  60. .values = {
  61. [0] = &queue0,
  62. [1] = &queue1,
  63. [2] = &queue2,
  64. [3] = &queue3,
  65. [4] = &queue4,
  66. },
  67. };
  68. /*
  69. * If enabled, CPU performance target is set according to the queue index
  70. * according to the following table.
  71. */
  72. static const u32 qidx_to_cpuperf_target[] = {
  73. [0] = SCX_CPUPERF_ONE * 0 / 4,
  74. [1] = SCX_CPUPERF_ONE * 1 / 4,
  75. [2] = SCX_CPUPERF_ONE * 2 / 4,
  76. [3] = SCX_CPUPERF_ONE * 3 / 4,
  77. [4] = SCX_CPUPERF_ONE * 4 / 4,
  78. };
  79. /*
  80. * Per-queue sequence numbers to implement core-sched ordering.
  81. *
  82. * Tail seq is assigned to each queued task and incremented. Head seq tracks the
  83. * sequence number of the latest dispatched task. The distance between the a
  84. * task's seq and the associated queue's head seq is called the queue distance
  85. * and used when comparing two tasks for ordering. See qmap_core_sched_before().
  86. */
  87. static u64 core_sched_head_seqs[5];
  88. static u64 core_sched_tail_seqs[5];
  89. /* Per-task scheduling context */
  90. struct task_ctx {
  91. bool force_local; /* Dispatch directly to local_dsq */
  92. bool highpri;
  93. u64 core_sched_seq;
  94. };
  95. struct {
  96. __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
  97. __uint(map_flags, BPF_F_NO_PREALLOC);
  98. __type(key, int);
  99. __type(value, struct task_ctx);
  100. } task_ctx_stor SEC(".maps");
  101. struct cpu_ctx {
  102. u64 dsp_idx; /* dispatch index */
  103. u64 dsp_cnt; /* remaining count */
  104. u32 avg_weight;
  105. u32 cpuperf_target;
  106. };
  107. struct {
  108. __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
  109. __uint(max_entries, 1);
  110. __type(key, u32);
  111. __type(value, struct cpu_ctx);
  112. } cpu_ctx_stor SEC(".maps");
  113. /* Statistics */
  114. u64 nr_enqueued, nr_dispatched, nr_reenqueued, nr_dequeued, nr_ddsp_from_enq;
  115. u64 nr_core_sched_execed;
  116. u64 nr_expedited_local, nr_expedited_remote, nr_expedited_lost, nr_expedited_from_timer;
  117. u32 cpuperf_min, cpuperf_avg, cpuperf_max;
  118. u32 cpuperf_target_min, cpuperf_target_avg, cpuperf_target_max;
  119. static s32 pick_direct_dispatch_cpu(struct task_struct *p, s32 prev_cpu)
  120. {
  121. s32 cpu;
  122. if (p->nr_cpus_allowed == 1 ||
  123. scx_bpf_test_and_clear_cpu_idle(prev_cpu))
  124. return prev_cpu;
  125. cpu = scx_bpf_pick_idle_cpu(p->cpus_ptr, 0);
  126. if (cpu >= 0)
  127. return cpu;
  128. return -1;
  129. }
  130. static struct task_ctx *lookup_task_ctx(struct task_struct *p)
  131. {
  132. struct task_ctx *tctx;
  133. if (!(tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0))) {
  134. scx_bpf_error("task_ctx lookup failed");
  135. return NULL;
  136. }
  137. return tctx;
  138. }
  139. s32 BPF_STRUCT_OPS(qmap_select_cpu, struct task_struct *p,
  140. s32 prev_cpu, u64 wake_flags)
  141. {
  142. struct task_ctx *tctx;
  143. s32 cpu;
  144. if (!(tctx = lookup_task_ctx(p)))
  145. return -ESRCH;
  146. cpu = pick_direct_dispatch_cpu(p, prev_cpu);
  147. if (cpu >= 0) {
  148. tctx->force_local = true;
  149. return cpu;
  150. } else {
  151. return prev_cpu;
  152. }
  153. }
  154. static int weight_to_idx(u32 weight)
  155. {
  156. /* Coarsely map the compound weight to a FIFO. */
  157. if (weight <= 25)
  158. return 0;
  159. else if (weight <= 50)
  160. return 1;
  161. else if (weight < 200)
  162. return 2;
  163. else if (weight < 400)
  164. return 3;
  165. else
  166. return 4;
  167. }
  168. void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags)
  169. {
  170. static u32 user_cnt, kernel_cnt;
  171. struct task_ctx *tctx;
  172. u32 pid = p->pid;
  173. int idx = weight_to_idx(p->scx.weight);
  174. void *ring;
  175. s32 cpu;
  176. if (p->flags & PF_KTHREAD) {
  177. if (stall_kernel_nth && !(++kernel_cnt % stall_kernel_nth))
  178. return;
  179. } else {
  180. if (stall_user_nth && !(++user_cnt % stall_user_nth))
  181. return;
  182. }
  183. if (test_error_cnt && !--test_error_cnt)
  184. scx_bpf_error("test triggering error");
  185. if (!(tctx = lookup_task_ctx(p)))
  186. return;
  187. /*
  188. * All enqueued tasks must have their core_sched_seq updated for correct
  189. * core-sched ordering. Also, take a look at the end of qmap_dispatch().
  190. */
  191. tctx->core_sched_seq = core_sched_tail_seqs[idx]++;
  192. /*
  193. * If qmap_select_cpu() is telling us to or this is the last runnable
  194. * task on the CPU, enqueue locally.
  195. */
  196. if (tctx->force_local) {
  197. tctx->force_local = false;
  198. scx_bpf_dispatch(p, SCX_DSQ_LOCAL, slice_ns, enq_flags);
  199. return;
  200. }
  201. /* if select_cpu() wasn't called, try direct dispatch */
  202. if (!(enq_flags & SCX_ENQ_CPU_SELECTED) &&
  203. (cpu = pick_direct_dispatch_cpu(p, scx_bpf_task_cpu(p))) >= 0) {
  204. __sync_fetch_and_add(&nr_ddsp_from_enq, 1);
  205. scx_bpf_dispatch(p, SCX_DSQ_LOCAL_ON | cpu, slice_ns, enq_flags);
  206. return;
  207. }
  208. /*
  209. * If the task was re-enqueued due to the CPU being preempted by a
  210. * higher priority scheduling class, just re-enqueue the task directly
  211. * on the global DSQ. As we want another CPU to pick it up, find and
  212. * kick an idle CPU.
  213. */
  214. if (enq_flags & SCX_ENQ_REENQ) {
  215. s32 cpu;
  216. scx_bpf_dispatch(p, SHARED_DSQ, 0, enq_flags);
  217. cpu = scx_bpf_pick_idle_cpu(p->cpus_ptr, 0);
  218. if (cpu >= 0)
  219. scx_bpf_kick_cpu(cpu, SCX_KICK_IDLE);
  220. return;
  221. }
  222. ring = bpf_map_lookup_elem(&queue_arr, &idx);
  223. if (!ring) {
  224. scx_bpf_error("failed to find ring %d", idx);
  225. return;
  226. }
  227. /* Queue on the selected FIFO. If the FIFO overflows, punt to global. */
  228. if (bpf_map_push_elem(ring, &pid, 0)) {
  229. scx_bpf_dispatch(p, SHARED_DSQ, slice_ns, enq_flags);
  230. return;
  231. }
  232. if (highpri_boosting && p->scx.weight >= HIGHPRI_WEIGHT) {
  233. tctx->highpri = true;
  234. __sync_fetch_and_add(&nr_highpri_queued, 1);
  235. }
  236. __sync_fetch_and_add(&nr_enqueued, 1);
  237. }
  238. /*
  239. * The BPF queue map doesn't support removal and sched_ext can handle spurious
  240. * dispatches. qmap_dequeue() is only used to collect statistics.
  241. */
  242. void BPF_STRUCT_OPS(qmap_dequeue, struct task_struct *p, u64 deq_flags)
  243. {
  244. __sync_fetch_and_add(&nr_dequeued, 1);
  245. if (deq_flags & SCX_DEQ_CORE_SCHED_EXEC)
  246. __sync_fetch_and_add(&nr_core_sched_execed, 1);
  247. }
  248. static void update_core_sched_head_seq(struct task_struct *p)
  249. {
  250. int idx = weight_to_idx(p->scx.weight);
  251. struct task_ctx *tctx;
  252. if ((tctx = lookup_task_ctx(p)))
  253. core_sched_head_seqs[idx] = tctx->core_sched_seq;
  254. }
  255. /*
  256. * To demonstrate the use of scx_bpf_dispatch_from_dsq(), implement silly
  257. * selective priority boosting mechanism by scanning SHARED_DSQ looking for
  258. * highpri tasks, moving them to HIGHPRI_DSQ and then consuming them first. This
  259. * makes minor difference only when dsp_batch is larger than 1.
  260. *
  261. * scx_bpf_dispatch[_vtime]_from_dsq() are allowed both from ops.dispatch() and
  262. * non-rq-lock holding BPF programs. As demonstration, this function is called
  263. * from qmap_dispatch() and monitor_timerfn().
  264. */
  265. static bool dispatch_highpri(bool from_timer)
  266. {
  267. struct task_struct *p;
  268. s32 this_cpu = bpf_get_smp_processor_id();
  269. /* scan SHARED_DSQ and move highpri tasks to HIGHPRI_DSQ */
  270. bpf_for_each(scx_dsq, p, SHARED_DSQ, 0) {
  271. static u64 highpri_seq;
  272. struct task_ctx *tctx;
  273. if (!(tctx = lookup_task_ctx(p)))
  274. return false;
  275. if (tctx->highpri) {
  276. /* exercise the set_*() and vtime interface too */
  277. __COMPAT_scx_bpf_dispatch_from_dsq_set_slice(
  278. BPF_FOR_EACH_ITER, slice_ns * 2);
  279. __COMPAT_scx_bpf_dispatch_from_dsq_set_vtime(
  280. BPF_FOR_EACH_ITER, highpri_seq++);
  281. __COMPAT_scx_bpf_dispatch_vtime_from_dsq(
  282. BPF_FOR_EACH_ITER, p, HIGHPRI_DSQ, 0);
  283. }
  284. }
  285. /*
  286. * Scan HIGHPRI_DSQ and dispatch until a task that can run on this CPU
  287. * is found.
  288. */
  289. bpf_for_each(scx_dsq, p, HIGHPRI_DSQ, 0) {
  290. bool dispatched = false;
  291. s32 cpu;
  292. if (bpf_cpumask_test_cpu(this_cpu, p->cpus_ptr))
  293. cpu = this_cpu;
  294. else
  295. cpu = scx_bpf_pick_any_cpu(p->cpus_ptr, 0);
  296. if (__COMPAT_scx_bpf_dispatch_from_dsq(BPF_FOR_EACH_ITER, p,
  297. SCX_DSQ_LOCAL_ON | cpu,
  298. SCX_ENQ_PREEMPT)) {
  299. if (cpu == this_cpu) {
  300. dispatched = true;
  301. __sync_fetch_and_add(&nr_expedited_local, 1);
  302. } else {
  303. __sync_fetch_and_add(&nr_expedited_remote, 1);
  304. }
  305. if (from_timer)
  306. __sync_fetch_and_add(&nr_expedited_from_timer, 1);
  307. } else {
  308. __sync_fetch_and_add(&nr_expedited_lost, 1);
  309. }
  310. if (dispatched)
  311. return true;
  312. }
  313. return false;
  314. }
  315. void BPF_STRUCT_OPS(qmap_dispatch, s32 cpu, struct task_struct *prev)
  316. {
  317. struct task_struct *p;
  318. struct cpu_ctx *cpuc;
  319. struct task_ctx *tctx;
  320. u32 zero = 0, batch = dsp_batch ?: 1;
  321. void *fifo;
  322. s32 i, pid;
  323. if (dispatch_highpri(false))
  324. return;
  325. if (!nr_highpri_queued && scx_bpf_consume(SHARED_DSQ))
  326. return;
  327. if (dsp_inf_loop_after && nr_dispatched > dsp_inf_loop_after) {
  328. /*
  329. * PID 2 should be kthreadd which should mostly be idle and off
  330. * the scheduler. Let's keep dispatching it to force the kernel
  331. * to call this function over and over again.
  332. */
  333. p = bpf_task_from_pid(2);
  334. if (p) {
  335. scx_bpf_dispatch(p, SCX_DSQ_LOCAL, slice_ns, 0);
  336. bpf_task_release(p);
  337. return;
  338. }
  339. }
  340. if (!(cpuc = bpf_map_lookup_elem(&cpu_ctx_stor, &zero))) {
  341. scx_bpf_error("failed to look up cpu_ctx");
  342. return;
  343. }
  344. for (i = 0; i < 5; i++) {
  345. /* Advance the dispatch cursor and pick the fifo. */
  346. if (!cpuc->dsp_cnt) {
  347. cpuc->dsp_idx = (cpuc->dsp_idx + 1) % 5;
  348. cpuc->dsp_cnt = 1 << cpuc->dsp_idx;
  349. }
  350. fifo = bpf_map_lookup_elem(&queue_arr, &cpuc->dsp_idx);
  351. if (!fifo) {
  352. scx_bpf_error("failed to find ring %llu", cpuc->dsp_idx);
  353. return;
  354. }
  355. /* Dispatch or advance. */
  356. bpf_repeat(BPF_MAX_LOOPS) {
  357. struct task_ctx *tctx;
  358. if (bpf_map_pop_elem(fifo, &pid))
  359. break;
  360. p = bpf_task_from_pid(pid);
  361. if (!p)
  362. continue;
  363. if (!(tctx = lookup_task_ctx(p))) {
  364. bpf_task_release(p);
  365. return;
  366. }
  367. if (tctx->highpri)
  368. __sync_fetch_and_sub(&nr_highpri_queued, 1);
  369. update_core_sched_head_seq(p);
  370. __sync_fetch_and_add(&nr_dispatched, 1);
  371. scx_bpf_dispatch(p, SHARED_DSQ, slice_ns, 0);
  372. bpf_task_release(p);
  373. batch--;
  374. cpuc->dsp_cnt--;
  375. if (!batch || !scx_bpf_dispatch_nr_slots()) {
  376. if (dispatch_highpri(false))
  377. return;
  378. scx_bpf_consume(SHARED_DSQ);
  379. return;
  380. }
  381. if (!cpuc->dsp_cnt)
  382. break;
  383. }
  384. cpuc->dsp_cnt = 0;
  385. }
  386. /*
  387. * No other tasks. @prev will keep running. Update its core_sched_seq as
  388. * if the task were enqueued and dispatched immediately.
  389. */
  390. if (prev) {
  391. tctx = bpf_task_storage_get(&task_ctx_stor, prev, 0, 0);
  392. if (!tctx) {
  393. scx_bpf_error("task_ctx lookup failed");
  394. return;
  395. }
  396. tctx->core_sched_seq =
  397. core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++;
  398. }
  399. }
  400. void BPF_STRUCT_OPS(qmap_tick, struct task_struct *p)
  401. {
  402. struct cpu_ctx *cpuc;
  403. u32 zero = 0;
  404. int idx;
  405. if (!(cpuc = bpf_map_lookup_elem(&cpu_ctx_stor, &zero))) {
  406. scx_bpf_error("failed to look up cpu_ctx");
  407. return;
  408. }
  409. /*
  410. * Use the running avg of weights to select the target cpuperf level.
  411. * This is a demonstration of the cpuperf feature rather than a
  412. * practical strategy to regulate CPU frequency.
  413. */
  414. cpuc->avg_weight = cpuc->avg_weight * 3 / 4 + p->scx.weight / 4;
  415. idx = weight_to_idx(cpuc->avg_weight);
  416. cpuc->cpuperf_target = qidx_to_cpuperf_target[idx];
  417. scx_bpf_cpuperf_set(scx_bpf_task_cpu(p), cpuc->cpuperf_target);
  418. }
  419. /*
  420. * The distance from the head of the queue scaled by the weight of the queue.
  421. * The lower the number, the older the task and the higher the priority.
  422. */
  423. static s64 task_qdist(struct task_struct *p)
  424. {
  425. int idx = weight_to_idx(p->scx.weight);
  426. struct task_ctx *tctx;
  427. s64 qdist;
  428. tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
  429. if (!tctx) {
  430. scx_bpf_error("task_ctx lookup failed");
  431. return 0;
  432. }
  433. qdist = tctx->core_sched_seq - core_sched_head_seqs[idx];
  434. /*
  435. * As queue index increments, the priority doubles. The queue w/ index 3
  436. * is dispatched twice more frequently than 2. Reflect the difference by
  437. * scaling qdists accordingly. Note that the shift amount needs to be
  438. * flipped depending on the sign to avoid flipping priority direction.
  439. */
  440. if (qdist >= 0)
  441. return qdist << (4 - idx);
  442. else
  443. return qdist << idx;
  444. }
  445. /*
  446. * This is called to determine the task ordering when core-sched is picking
  447. * tasks to execute on SMT siblings and should encode about the same ordering as
  448. * the regular scheduling path. Use the priority-scaled distances from the head
  449. * of the queues to compare the two tasks which should be consistent with the
  450. * dispatch path behavior.
  451. */
  452. bool BPF_STRUCT_OPS(qmap_core_sched_before,
  453. struct task_struct *a, struct task_struct *b)
  454. {
  455. return task_qdist(a) > task_qdist(b);
  456. }
  457. void BPF_STRUCT_OPS(qmap_cpu_release, s32 cpu, struct scx_cpu_release_args *args)
  458. {
  459. u32 cnt;
  460. /*
  461. * Called when @cpu is taken by a higher priority scheduling class. This
  462. * makes @cpu no longer available for executing sched_ext tasks. As we
  463. * don't want the tasks in @cpu's local dsq to sit there until @cpu
  464. * becomes available again, re-enqueue them into the global dsq. See
  465. * %SCX_ENQ_REENQ handling in qmap_enqueue().
  466. */
  467. cnt = scx_bpf_reenqueue_local();
  468. if (cnt)
  469. __sync_fetch_and_add(&nr_reenqueued, cnt);
  470. }
  471. s32 BPF_STRUCT_OPS(qmap_init_task, struct task_struct *p,
  472. struct scx_init_task_args *args)
  473. {
  474. if (p->tgid == disallow_tgid)
  475. p->scx.disallow = true;
  476. /*
  477. * @p is new. Let's ensure that its task_ctx is available. We can sleep
  478. * in this function and the following will automatically use GFP_KERNEL.
  479. */
  480. if (bpf_task_storage_get(&task_ctx_stor, p, 0,
  481. BPF_LOCAL_STORAGE_GET_F_CREATE))
  482. return 0;
  483. else
  484. return -ENOMEM;
  485. }
  486. void BPF_STRUCT_OPS(qmap_dump, struct scx_dump_ctx *dctx)
  487. {
  488. s32 i, pid;
  489. if (suppress_dump)
  490. return;
  491. bpf_for(i, 0, 5) {
  492. void *fifo;
  493. if (!(fifo = bpf_map_lookup_elem(&queue_arr, &i)))
  494. return;
  495. scx_bpf_dump("QMAP FIFO[%d]:", i);
  496. bpf_repeat(4096) {
  497. if (bpf_map_pop_elem(fifo, &pid))
  498. break;
  499. scx_bpf_dump(" %d", pid);
  500. }
  501. scx_bpf_dump("\n");
  502. }
  503. }
  504. void BPF_STRUCT_OPS(qmap_dump_cpu, struct scx_dump_ctx *dctx, s32 cpu, bool idle)
  505. {
  506. u32 zero = 0;
  507. struct cpu_ctx *cpuc;
  508. if (suppress_dump || idle)
  509. return;
  510. if (!(cpuc = bpf_map_lookup_percpu_elem(&cpu_ctx_stor, &zero, cpu)))
  511. return;
  512. scx_bpf_dump("QMAP: dsp_idx=%llu dsp_cnt=%llu avg_weight=%u cpuperf_target=%u",
  513. cpuc->dsp_idx, cpuc->dsp_cnt, cpuc->avg_weight,
  514. cpuc->cpuperf_target);
  515. }
  516. void BPF_STRUCT_OPS(qmap_dump_task, struct scx_dump_ctx *dctx, struct task_struct *p)
  517. {
  518. struct task_ctx *taskc;
  519. if (suppress_dump)
  520. return;
  521. if (!(taskc = bpf_task_storage_get(&task_ctx_stor, p, 0, 0)))
  522. return;
  523. scx_bpf_dump("QMAP: force_local=%d core_sched_seq=%llu",
  524. taskc->force_local, taskc->core_sched_seq);
  525. }
  526. /*
  527. * Print out the online and possible CPU map using bpf_printk() as a
  528. * demonstration of using the cpumask kfuncs and ops.cpu_on/offline().
  529. */
  530. static void print_cpus(void)
  531. {
  532. const struct cpumask *possible, *online;
  533. s32 cpu;
  534. char buf[128] = "", *p;
  535. int idx;
  536. possible = scx_bpf_get_possible_cpumask();
  537. online = scx_bpf_get_online_cpumask();
  538. idx = 0;
  539. bpf_for(cpu, 0, scx_bpf_nr_cpu_ids()) {
  540. if (!(p = MEMBER_VPTR(buf, [idx++])))
  541. break;
  542. if (bpf_cpumask_test_cpu(cpu, online))
  543. *p++ = 'O';
  544. else if (bpf_cpumask_test_cpu(cpu, possible))
  545. *p++ = 'X';
  546. else
  547. *p++ = ' ';
  548. if ((cpu & 7) == 7) {
  549. if (!(p = MEMBER_VPTR(buf, [idx++])))
  550. break;
  551. *p++ = '|';
  552. }
  553. }
  554. buf[sizeof(buf) - 1] = '\0';
  555. scx_bpf_put_cpumask(online);
  556. scx_bpf_put_cpumask(possible);
  557. bpf_printk("CPUS: |%s", buf);
  558. }
  559. void BPF_STRUCT_OPS(qmap_cpu_online, s32 cpu)
  560. {
  561. bpf_printk("CPU %d coming online", cpu);
  562. /* @cpu is already online at this point */
  563. print_cpus();
  564. }
  565. void BPF_STRUCT_OPS(qmap_cpu_offline, s32 cpu)
  566. {
  567. bpf_printk("CPU %d going offline", cpu);
  568. /* @cpu is still online at this point */
  569. print_cpus();
  570. }
  571. struct monitor_timer {
  572. struct bpf_timer timer;
  573. };
  574. struct {
  575. __uint(type, BPF_MAP_TYPE_ARRAY);
  576. __uint(max_entries, 1);
  577. __type(key, u32);
  578. __type(value, struct monitor_timer);
  579. } monitor_timer SEC(".maps");
  580. /*
  581. * Print out the min, avg and max performance levels of CPUs every second to
  582. * demonstrate the cpuperf interface.
  583. */
  584. static void monitor_cpuperf(void)
  585. {
  586. u32 zero = 0, nr_cpu_ids;
  587. u64 cap_sum = 0, cur_sum = 0, cur_min = SCX_CPUPERF_ONE, cur_max = 0;
  588. u64 target_sum = 0, target_min = SCX_CPUPERF_ONE, target_max = 0;
  589. const struct cpumask *online;
  590. int i, nr_online_cpus = 0;
  591. nr_cpu_ids = scx_bpf_nr_cpu_ids();
  592. online = scx_bpf_get_online_cpumask();
  593. bpf_for(i, 0, nr_cpu_ids) {
  594. struct cpu_ctx *cpuc;
  595. u32 cap, cur;
  596. if (!bpf_cpumask_test_cpu(i, online))
  597. continue;
  598. nr_online_cpus++;
  599. /* collect the capacity and current cpuperf */
  600. cap = scx_bpf_cpuperf_cap(i);
  601. cur = scx_bpf_cpuperf_cur(i);
  602. cur_min = cur < cur_min ? cur : cur_min;
  603. cur_max = cur > cur_max ? cur : cur_max;
  604. /*
  605. * $cur is relative to $cap. Scale it down accordingly so that
  606. * it's in the same scale as other CPUs and $cur_sum/$cap_sum
  607. * makes sense.
  608. */
  609. cur_sum += cur * cap / SCX_CPUPERF_ONE;
  610. cap_sum += cap;
  611. if (!(cpuc = bpf_map_lookup_percpu_elem(&cpu_ctx_stor, &zero, i))) {
  612. scx_bpf_error("failed to look up cpu_ctx");
  613. goto out;
  614. }
  615. /* collect target */
  616. cur = cpuc->cpuperf_target;
  617. target_sum += cur;
  618. target_min = cur < target_min ? cur : target_min;
  619. target_max = cur > target_max ? cur : target_max;
  620. }
  621. cpuperf_min = cur_min;
  622. cpuperf_avg = cur_sum * SCX_CPUPERF_ONE / cap_sum;
  623. cpuperf_max = cur_max;
  624. cpuperf_target_min = target_min;
  625. cpuperf_target_avg = target_sum / nr_online_cpus;
  626. cpuperf_target_max = target_max;
  627. out:
  628. scx_bpf_put_cpumask(online);
  629. }
  630. /*
  631. * Dump the currently queued tasks in the shared DSQ to demonstrate the usage of
  632. * scx_bpf_dsq_nr_queued() and DSQ iterator. Raise the dispatch batch count to
  633. * see meaningful dumps in the trace pipe.
  634. */
  635. static void dump_shared_dsq(void)
  636. {
  637. struct task_struct *p;
  638. s32 nr;
  639. if (!(nr = scx_bpf_dsq_nr_queued(SHARED_DSQ)))
  640. return;
  641. bpf_printk("Dumping %d tasks in SHARED_DSQ in reverse order", nr);
  642. bpf_rcu_read_lock();
  643. bpf_for_each(scx_dsq, p, SHARED_DSQ, SCX_DSQ_ITER_REV)
  644. bpf_printk("%s[%d]", p->comm, p->pid);
  645. bpf_rcu_read_unlock();
  646. }
  647. static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer)
  648. {
  649. bpf_rcu_read_lock();
  650. dispatch_highpri(true);
  651. bpf_rcu_read_unlock();
  652. monitor_cpuperf();
  653. if (print_shared_dsq)
  654. dump_shared_dsq();
  655. bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
  656. return 0;
  657. }
  658. s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
  659. {
  660. u32 key = 0;
  661. struct bpf_timer *timer;
  662. s32 ret;
  663. print_cpus();
  664. ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
  665. if (ret)
  666. return ret;
  667. ret = scx_bpf_create_dsq(HIGHPRI_DSQ, -1);
  668. if (ret)
  669. return ret;
  670. timer = bpf_map_lookup_elem(&monitor_timer, &key);
  671. if (!timer)
  672. return -ESRCH;
  673. bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC);
  674. bpf_timer_set_callback(timer, monitor_timerfn);
  675. return bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
  676. }
  677. void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
  678. {
  679. UEI_RECORD(uei, ei);
  680. }
  681. SCX_OPS_DEFINE(qmap_ops,
  682. .select_cpu = (void *)qmap_select_cpu,
  683. .enqueue = (void *)qmap_enqueue,
  684. .dequeue = (void *)qmap_dequeue,
  685. .dispatch = (void *)qmap_dispatch,
  686. .tick = (void *)qmap_tick,
  687. .core_sched_before = (void *)qmap_core_sched_before,
  688. .cpu_release = (void *)qmap_cpu_release,
  689. .init_task = (void *)qmap_init_task,
  690. .dump = (void *)qmap_dump,
  691. .dump_cpu = (void *)qmap_dump_cpu,
  692. .dump_task = (void *)qmap_dump_task,
  693. .cpu_online = (void *)qmap_cpu_online,
  694. .cpu_offline = (void *)qmap_cpu_offline,
  695. .init = (void *)qmap_init,
  696. .exit = (void *)qmap_exit,
  697. .timeout_ms = 5000U,
  698. .name = "qmap");