scx_flatcg.bpf.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. /* SPDX-License-Identifier: GPL-2.0 */
  2. /*
  3. * A demo sched_ext flattened cgroup hierarchy scheduler. It implements
  4. * hierarchical weight-based cgroup CPU control by flattening the cgroup
  5. * hierarchy into a single layer by compounding the active weight share at each
  6. * level. Consider the following hierarchy with weights in parentheses:
  7. *
  8. * R + A (100) + B (100)
  9. * | \ C (100)
  10. * \ D (200)
  11. *
  12. * Ignoring the root and threaded cgroups, only B, C and D can contain tasks.
  13. * Let's say all three have runnable tasks. The total share that each of these
  14. * three cgroups is entitled to can be calculated by compounding its share at
  15. * each level.
  16. *
  17. * For example, B is competing against C and in that competition its share is
  18. * 100/(100+100) == 1/2. At its parent level, A is competing against D and A's
  19. * share in that competition is 100/(200+100) == 1/3. B's eventual share in the
  20. * system can be calculated by multiplying the two shares, 1/2 * 1/3 == 1/6. C's
  21. * eventual shaer is the same at 1/6. D is only competing at the top level and
  22. * its share is 200/(100+200) == 2/3.
  23. *
  24. * So, instead of hierarchically scheduling level-by-level, we can consider it
  25. * as B, C and D competing each other with respective share of 1/6, 1/6 and 2/3
  26. * and keep updating the eventual shares as the cgroups' runnable states change.
  27. *
  28. * This flattening of hierarchy can bring a substantial performance gain when
  29. * the cgroup hierarchy is nested multiple levels. in a simple benchmark using
  30. * wrk[8] on apache serving a CGI script calculating sha1sum of a small file, it
  31. * outperforms CFS by ~3% with CPU controller disabled and by ~10% with two
  32. * apache instances competing with 2:1 weight ratio nested four level deep.
  33. *
  34. * However, the gain comes at the cost of not being able to properly handle
  35. * thundering herd of cgroups. For example, if many cgroups which are nested
  36. * behind a low priority parent cgroup wake up around the same time, they may be
  37. * able to consume more CPU cycles than they are entitled to. In many use cases,
  38. * this isn't a real concern especially given the performance gain. Also, there
  39. * are ways to mitigate the problem further by e.g. introducing an extra
  40. * scheduling layer on cgroup delegation boundaries.
  41. *
  42. * The scheduler first picks the cgroup to run and then schedule the tasks
  43. * within by using nested weighted vtime scheduling by default. The
  44. * cgroup-internal scheduling can be switched to FIFO with the -f option.
  45. */
  46. #include <scx/common.bpf.h>
  47. #include "scx_flatcg.h"
  48. /*
  49. * Maximum amount of retries to find a valid cgroup.
  50. */
  51. enum {
  52. FALLBACK_DSQ = 0,
  53. CGROUP_MAX_RETRIES = 1024,
  54. };
  55. char _license[] SEC("license") = "GPL";
  56. const volatile u32 nr_cpus = 32; /* !0 for veristat, set during init */
  57. const volatile u64 cgrp_slice_ns;
  58. const volatile bool fifo_sched;
  59. u64 cvtime_now;
  60. UEI_DEFINE(uei);
  61. struct {
  62. __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
  63. __type(key, u32);
  64. __type(value, u64);
  65. __uint(max_entries, FCG_NR_STATS);
  66. } stats SEC(".maps");
  67. static void stat_inc(enum fcg_stat_idx idx)
  68. {
  69. u32 idx_v = idx;
  70. u64 *cnt_p = bpf_map_lookup_elem(&stats, &idx_v);
  71. if (cnt_p)
  72. (*cnt_p)++;
  73. }
  74. struct fcg_cpu_ctx {
  75. u64 cur_cgid;
  76. u64 cur_at;
  77. };
  78. struct {
  79. __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
  80. __type(key, u32);
  81. __type(value, struct fcg_cpu_ctx);
  82. __uint(max_entries, 1);
  83. } cpu_ctx SEC(".maps");
  84. struct {
  85. __uint(type, BPF_MAP_TYPE_CGRP_STORAGE);
  86. __uint(map_flags, BPF_F_NO_PREALLOC);
  87. __type(key, int);
  88. __type(value, struct fcg_cgrp_ctx);
  89. } cgrp_ctx SEC(".maps");
  90. struct cgv_node {
  91. struct bpf_rb_node rb_node;
  92. __u64 cvtime;
  93. __u64 cgid;
  94. };
  95. private(CGV_TREE) struct bpf_spin_lock cgv_tree_lock;
  96. private(CGV_TREE) struct bpf_rb_root cgv_tree __contains(cgv_node, rb_node);
  97. struct cgv_node_stash {
  98. struct cgv_node __kptr *node;
  99. };
  100. struct {
  101. __uint(type, BPF_MAP_TYPE_HASH);
  102. __uint(max_entries, 16384);
  103. __type(key, __u64);
  104. __type(value, struct cgv_node_stash);
  105. } cgv_node_stash SEC(".maps");
  106. struct fcg_task_ctx {
  107. u64 bypassed_at;
  108. };
  109. struct {
  110. __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
  111. __uint(map_flags, BPF_F_NO_PREALLOC);
  112. __type(key, int);
  113. __type(value, struct fcg_task_ctx);
  114. } task_ctx SEC(".maps");
  115. /* gets inc'd on weight tree changes to expire the cached hweights */
  116. u64 hweight_gen = 1;
  117. static u64 div_round_up(u64 dividend, u64 divisor)
  118. {
  119. return (dividend + divisor - 1) / divisor;
  120. }
  121. static bool cgv_node_less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
  122. {
  123. struct cgv_node *cgc_a, *cgc_b;
  124. cgc_a = container_of(a, struct cgv_node, rb_node);
  125. cgc_b = container_of(b, struct cgv_node, rb_node);
  126. return cgc_a->cvtime < cgc_b->cvtime;
  127. }
  128. static struct fcg_cpu_ctx *find_cpu_ctx(void)
  129. {
  130. struct fcg_cpu_ctx *cpuc;
  131. u32 idx = 0;
  132. cpuc = bpf_map_lookup_elem(&cpu_ctx, &idx);
  133. if (!cpuc) {
  134. scx_bpf_error("cpu_ctx lookup failed");
  135. return NULL;
  136. }
  137. return cpuc;
  138. }
  139. static struct fcg_cgrp_ctx *find_cgrp_ctx(struct cgroup *cgrp)
  140. {
  141. struct fcg_cgrp_ctx *cgc;
  142. cgc = bpf_cgrp_storage_get(&cgrp_ctx, cgrp, 0, 0);
  143. if (!cgc) {
  144. scx_bpf_error("cgrp_ctx lookup failed for cgid %llu", cgrp->kn->id);
  145. return NULL;
  146. }
  147. return cgc;
  148. }
  149. static struct fcg_cgrp_ctx *find_ancestor_cgrp_ctx(struct cgroup *cgrp, int level)
  150. {
  151. struct fcg_cgrp_ctx *cgc;
  152. cgrp = bpf_cgroup_ancestor(cgrp, level);
  153. if (!cgrp) {
  154. scx_bpf_error("ancestor cgroup lookup failed");
  155. return NULL;
  156. }
  157. cgc = find_cgrp_ctx(cgrp);
  158. if (!cgc)
  159. scx_bpf_error("ancestor cgrp_ctx lookup failed");
  160. bpf_cgroup_release(cgrp);
  161. return cgc;
  162. }
  163. static void cgrp_refresh_hweight(struct cgroup *cgrp, struct fcg_cgrp_ctx *cgc)
  164. {
  165. int level;
  166. if (!cgc->nr_active) {
  167. stat_inc(FCG_STAT_HWT_SKIP);
  168. return;
  169. }
  170. if (cgc->hweight_gen == hweight_gen) {
  171. stat_inc(FCG_STAT_HWT_CACHE);
  172. return;
  173. }
  174. stat_inc(FCG_STAT_HWT_UPDATES);
  175. bpf_for(level, 0, cgrp->level + 1) {
  176. struct fcg_cgrp_ctx *cgc;
  177. bool is_active;
  178. cgc = find_ancestor_cgrp_ctx(cgrp, level);
  179. if (!cgc)
  180. break;
  181. if (!level) {
  182. cgc->hweight = FCG_HWEIGHT_ONE;
  183. cgc->hweight_gen = hweight_gen;
  184. } else {
  185. struct fcg_cgrp_ctx *pcgc;
  186. pcgc = find_ancestor_cgrp_ctx(cgrp, level - 1);
  187. if (!pcgc)
  188. break;
  189. /*
  190. * We can be opportunistic here and not grab the
  191. * cgv_tree_lock and deal with the occasional races.
  192. * However, hweight updates are already cached and
  193. * relatively low-frequency. Let's just do the
  194. * straightforward thing.
  195. */
  196. bpf_spin_lock(&cgv_tree_lock);
  197. is_active = cgc->nr_active;
  198. if (is_active) {
  199. cgc->hweight_gen = pcgc->hweight_gen;
  200. cgc->hweight =
  201. div_round_up(pcgc->hweight * cgc->weight,
  202. pcgc->child_weight_sum);
  203. }
  204. bpf_spin_unlock(&cgv_tree_lock);
  205. if (!is_active) {
  206. stat_inc(FCG_STAT_HWT_RACE);
  207. break;
  208. }
  209. }
  210. }
  211. }
  212. static void cgrp_cap_budget(struct cgv_node *cgv_node, struct fcg_cgrp_ctx *cgc)
  213. {
  214. u64 delta, cvtime, max_budget;
  215. /*
  216. * A node which is on the rbtree can't be pointed to from elsewhere yet
  217. * and thus can't be updated and repositioned. Instead, we collect the
  218. * vtime deltas separately and apply it asynchronously here.
  219. */
  220. delta = __sync_fetch_and_sub(&cgc->cvtime_delta, cgc->cvtime_delta);
  221. cvtime = cgv_node->cvtime + delta;
  222. /*
  223. * Allow a cgroup to carry the maximum budget proportional to its
  224. * hweight such that a full-hweight cgroup can immediately take up half
  225. * of the CPUs at the most while staying at the front of the rbtree.
  226. */
  227. max_budget = (cgrp_slice_ns * nr_cpus * cgc->hweight) /
  228. (2 * FCG_HWEIGHT_ONE);
  229. if (time_before(cvtime, cvtime_now - max_budget))
  230. cvtime = cvtime_now - max_budget;
  231. cgv_node->cvtime = cvtime;
  232. }
  233. static void cgrp_enqueued(struct cgroup *cgrp, struct fcg_cgrp_ctx *cgc)
  234. {
  235. struct cgv_node_stash *stash;
  236. struct cgv_node *cgv_node;
  237. u64 cgid = cgrp->kn->id;
  238. /* paired with cmpxchg in try_pick_next_cgroup() */
  239. if (__sync_val_compare_and_swap(&cgc->queued, 0, 1)) {
  240. stat_inc(FCG_STAT_ENQ_SKIP);
  241. return;
  242. }
  243. stash = bpf_map_lookup_elem(&cgv_node_stash, &cgid);
  244. if (!stash) {
  245. scx_bpf_error("cgv_node lookup failed for cgid %llu", cgid);
  246. return;
  247. }
  248. /* NULL if the node is already on the rbtree */
  249. cgv_node = bpf_kptr_xchg(&stash->node, NULL);
  250. if (!cgv_node) {
  251. stat_inc(FCG_STAT_ENQ_RACE);
  252. return;
  253. }
  254. bpf_spin_lock(&cgv_tree_lock);
  255. cgrp_cap_budget(cgv_node, cgc);
  256. bpf_rbtree_add(&cgv_tree, &cgv_node->rb_node, cgv_node_less);
  257. bpf_spin_unlock(&cgv_tree_lock);
  258. }
  259. static void set_bypassed_at(struct task_struct *p, struct fcg_task_ctx *taskc)
  260. {
  261. /*
  262. * Tell fcg_stopping() that this bypassed the regular scheduling path
  263. * and should be force charged to the cgroup. 0 is used to indicate that
  264. * the task isn't bypassing, so if the current runtime is 0, go back by
  265. * one nanosecond.
  266. */
  267. taskc->bypassed_at = p->se.sum_exec_runtime ?: (u64)-1;
  268. }
  269. s32 BPF_STRUCT_OPS(fcg_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags)
  270. {
  271. struct fcg_task_ctx *taskc;
  272. bool is_idle = false;
  273. s32 cpu;
  274. cpu = scx_bpf_select_cpu_dfl(p, prev_cpu, wake_flags, &is_idle);
  275. taskc = bpf_task_storage_get(&task_ctx, p, 0, 0);
  276. if (!taskc) {
  277. scx_bpf_error("task_ctx lookup failed");
  278. return cpu;
  279. }
  280. /*
  281. * If select_cpu_dfl() is recommending local enqueue, the target CPU is
  282. * idle. Follow it and charge the cgroup later in fcg_stopping() after
  283. * the fact.
  284. */
  285. if (is_idle) {
  286. set_bypassed_at(p, taskc);
  287. stat_inc(FCG_STAT_LOCAL);
  288. scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, 0);
  289. }
  290. return cpu;
  291. }
  292. void BPF_STRUCT_OPS(fcg_enqueue, struct task_struct *p, u64 enq_flags)
  293. {
  294. struct fcg_task_ctx *taskc;
  295. struct cgroup *cgrp;
  296. struct fcg_cgrp_ctx *cgc;
  297. taskc = bpf_task_storage_get(&task_ctx, p, 0, 0);
  298. if (!taskc) {
  299. scx_bpf_error("task_ctx lookup failed");
  300. return;
  301. }
  302. /*
  303. * Use the direct dispatching and force charging to deal with tasks with
  304. * custom affinities so that we don't have to worry about per-cgroup
  305. * dq's containing tasks that can't be executed from some CPUs.
  306. */
  307. if (p->nr_cpus_allowed != nr_cpus) {
  308. set_bypassed_at(p, taskc);
  309. /*
  310. * The global dq is deprioritized as we don't want to let tasks
  311. * to boost themselves by constraining its cpumask. The
  312. * deprioritization is rather severe, so let's not apply that to
  313. * per-cpu kernel threads. This is ham-fisted. We probably wanna
  314. * implement per-cgroup fallback dq's instead so that we have
  315. * more control over when tasks with custom cpumask get issued.
  316. */
  317. if (p->nr_cpus_allowed == 1 && (p->flags & PF_KTHREAD)) {
  318. stat_inc(FCG_STAT_LOCAL);
  319. scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL,
  320. enq_flags);
  321. } else {
  322. stat_inc(FCG_STAT_GLOBAL);
  323. scx_bpf_dsq_insert(p, FALLBACK_DSQ, SCX_SLICE_DFL,
  324. enq_flags);
  325. }
  326. return;
  327. }
  328. cgrp = scx_bpf_task_cgroup(p);
  329. cgc = find_cgrp_ctx(cgrp);
  330. if (!cgc)
  331. goto out_release;
  332. if (fifo_sched) {
  333. scx_bpf_dsq_insert(p, cgrp->kn->id, SCX_SLICE_DFL, enq_flags);
  334. } else {
  335. u64 tvtime = p->scx.dsq_vtime;
  336. /*
  337. * Limit the amount of budget that an idling task can accumulate
  338. * to one slice.
  339. */
  340. if (time_before(tvtime, cgc->tvtime_now - SCX_SLICE_DFL))
  341. tvtime = cgc->tvtime_now - SCX_SLICE_DFL;
  342. scx_bpf_dsq_insert_vtime(p, cgrp->kn->id, SCX_SLICE_DFL,
  343. tvtime, enq_flags);
  344. }
  345. cgrp_enqueued(cgrp, cgc);
  346. out_release:
  347. bpf_cgroup_release(cgrp);
  348. }
  349. /*
  350. * Walk the cgroup tree to update the active weight sums as tasks wake up and
  351. * sleep. The weight sums are used as the base when calculating the proportion a
  352. * given cgroup or task is entitled to at each level.
  353. */
  354. static void update_active_weight_sums(struct cgroup *cgrp, bool runnable)
  355. {
  356. struct fcg_cgrp_ctx *cgc;
  357. bool updated = false;
  358. int idx;
  359. cgc = find_cgrp_ctx(cgrp);
  360. if (!cgc)
  361. return;
  362. /*
  363. * In most cases, a hot cgroup would have multiple threads going to
  364. * sleep and waking up while the whole cgroup stays active. In leaf
  365. * cgroups, ->nr_runnable which is updated with __sync operations gates
  366. * ->nr_active updates, so that we don't have to grab the cgv_tree_lock
  367. * repeatedly for a busy cgroup which is staying active.
  368. */
  369. if (runnable) {
  370. if (__sync_fetch_and_add(&cgc->nr_runnable, 1))
  371. return;
  372. stat_inc(FCG_STAT_ACT);
  373. } else {
  374. if (__sync_sub_and_fetch(&cgc->nr_runnable, 1))
  375. return;
  376. stat_inc(FCG_STAT_DEACT);
  377. }
  378. /*
  379. * If @cgrp is becoming runnable, its hweight should be refreshed after
  380. * it's added to the weight tree so that enqueue has the up-to-date
  381. * value. If @cgrp is becoming quiescent, the hweight should be
  382. * refreshed before it's removed from the weight tree so that the usage
  383. * charging which happens afterwards has access to the latest value.
  384. */
  385. if (!runnable)
  386. cgrp_refresh_hweight(cgrp, cgc);
  387. /* propagate upwards */
  388. bpf_for(idx, 0, cgrp->level) {
  389. int level = cgrp->level - idx;
  390. struct fcg_cgrp_ctx *cgc, *pcgc = NULL;
  391. bool propagate = false;
  392. cgc = find_ancestor_cgrp_ctx(cgrp, level);
  393. if (!cgc)
  394. break;
  395. if (level) {
  396. pcgc = find_ancestor_cgrp_ctx(cgrp, level - 1);
  397. if (!pcgc)
  398. break;
  399. }
  400. /*
  401. * We need the propagation protected by a lock to synchronize
  402. * against weight changes. There's no reason to drop the lock at
  403. * each level but bpf_spin_lock() doesn't want any function
  404. * calls while locked.
  405. */
  406. bpf_spin_lock(&cgv_tree_lock);
  407. if (runnable) {
  408. if (!cgc->nr_active++) {
  409. updated = true;
  410. if (pcgc) {
  411. propagate = true;
  412. pcgc->child_weight_sum += cgc->weight;
  413. }
  414. }
  415. } else {
  416. if (!--cgc->nr_active) {
  417. updated = true;
  418. if (pcgc) {
  419. propagate = true;
  420. pcgc->child_weight_sum -= cgc->weight;
  421. }
  422. }
  423. }
  424. bpf_spin_unlock(&cgv_tree_lock);
  425. if (!propagate)
  426. break;
  427. }
  428. if (updated)
  429. __sync_fetch_and_add(&hweight_gen, 1);
  430. if (runnable)
  431. cgrp_refresh_hweight(cgrp, cgc);
  432. }
  433. void BPF_STRUCT_OPS(fcg_runnable, struct task_struct *p, u64 enq_flags)
  434. {
  435. struct cgroup *cgrp;
  436. cgrp = scx_bpf_task_cgroup(p);
  437. update_active_weight_sums(cgrp, true);
  438. bpf_cgroup_release(cgrp);
  439. }
  440. void BPF_STRUCT_OPS(fcg_running, struct task_struct *p)
  441. {
  442. struct cgroup *cgrp;
  443. struct fcg_cgrp_ctx *cgc;
  444. if (fifo_sched)
  445. return;
  446. cgrp = scx_bpf_task_cgroup(p);
  447. cgc = find_cgrp_ctx(cgrp);
  448. if (cgc) {
  449. /*
  450. * @cgc->tvtime_now always progresses forward as tasks start
  451. * executing. The test and update can be performed concurrently
  452. * from multiple CPUs and thus racy. Any error should be
  453. * contained and temporary. Let's just live with it.
  454. */
  455. if (time_before(cgc->tvtime_now, p->scx.dsq_vtime))
  456. cgc->tvtime_now = p->scx.dsq_vtime;
  457. }
  458. bpf_cgroup_release(cgrp);
  459. }
  460. void BPF_STRUCT_OPS(fcg_stopping, struct task_struct *p, bool runnable)
  461. {
  462. struct fcg_task_ctx *taskc;
  463. struct cgroup *cgrp;
  464. struct fcg_cgrp_ctx *cgc;
  465. /*
  466. * Scale the execution time by the inverse of the weight and charge.
  467. *
  468. * Note that the default yield implementation yields by setting
  469. * @p->scx.slice to zero and the following would treat the yielding task
  470. * as if it has consumed all its slice. If this penalizes yielding tasks
  471. * too much, determine the execution time by taking explicit timestamps
  472. * instead of depending on @p->scx.slice.
  473. */
  474. if (!fifo_sched)
  475. p->scx.dsq_vtime +=
  476. (SCX_SLICE_DFL - p->scx.slice) * 100 / p->scx.weight;
  477. taskc = bpf_task_storage_get(&task_ctx, p, 0, 0);
  478. if (!taskc) {
  479. scx_bpf_error("task_ctx lookup failed");
  480. return;
  481. }
  482. if (!taskc->bypassed_at)
  483. return;
  484. cgrp = scx_bpf_task_cgroup(p);
  485. cgc = find_cgrp_ctx(cgrp);
  486. if (cgc) {
  487. __sync_fetch_and_add(&cgc->cvtime_delta,
  488. p->se.sum_exec_runtime - taskc->bypassed_at);
  489. taskc->bypassed_at = 0;
  490. }
  491. bpf_cgroup_release(cgrp);
  492. }
  493. void BPF_STRUCT_OPS(fcg_quiescent, struct task_struct *p, u64 deq_flags)
  494. {
  495. struct cgroup *cgrp;
  496. cgrp = scx_bpf_task_cgroup(p);
  497. update_active_weight_sums(cgrp, false);
  498. bpf_cgroup_release(cgrp);
  499. }
  500. void BPF_STRUCT_OPS(fcg_cgroup_set_weight, struct cgroup *cgrp, u32 weight)
  501. {
  502. struct fcg_cgrp_ctx *cgc, *pcgc = NULL;
  503. cgc = find_cgrp_ctx(cgrp);
  504. if (!cgc)
  505. return;
  506. if (cgrp->level) {
  507. pcgc = find_ancestor_cgrp_ctx(cgrp, cgrp->level - 1);
  508. if (!pcgc)
  509. return;
  510. }
  511. bpf_spin_lock(&cgv_tree_lock);
  512. if (pcgc && cgc->nr_active)
  513. pcgc->child_weight_sum += (s64)weight - cgc->weight;
  514. cgc->weight = weight;
  515. bpf_spin_unlock(&cgv_tree_lock);
  516. }
  517. static bool try_pick_next_cgroup(u64 *cgidp)
  518. {
  519. struct bpf_rb_node *rb_node;
  520. struct cgv_node_stash *stash;
  521. struct cgv_node *cgv_node;
  522. struct fcg_cgrp_ctx *cgc;
  523. struct cgroup *cgrp;
  524. u64 cgid;
  525. /* pop the front cgroup and wind cvtime_now accordingly */
  526. bpf_spin_lock(&cgv_tree_lock);
  527. rb_node = bpf_rbtree_first(&cgv_tree);
  528. if (!rb_node) {
  529. bpf_spin_unlock(&cgv_tree_lock);
  530. stat_inc(FCG_STAT_PNC_NO_CGRP);
  531. *cgidp = 0;
  532. return true;
  533. }
  534. rb_node = bpf_rbtree_remove(&cgv_tree, rb_node);
  535. bpf_spin_unlock(&cgv_tree_lock);
  536. if (!rb_node) {
  537. /*
  538. * This should never happen. bpf_rbtree_first() was called
  539. * above while the tree lock was held, so the node should
  540. * always be present.
  541. */
  542. scx_bpf_error("node could not be removed");
  543. return true;
  544. }
  545. cgv_node = container_of(rb_node, struct cgv_node, rb_node);
  546. cgid = cgv_node->cgid;
  547. if (time_before(cvtime_now, cgv_node->cvtime))
  548. cvtime_now = cgv_node->cvtime;
  549. /*
  550. * If lookup fails, the cgroup's gone. Free and move on. See
  551. * fcg_cgroup_exit().
  552. */
  553. cgrp = bpf_cgroup_from_id(cgid);
  554. if (!cgrp) {
  555. stat_inc(FCG_STAT_PNC_GONE);
  556. goto out_free;
  557. }
  558. cgc = bpf_cgrp_storage_get(&cgrp_ctx, cgrp, 0, 0);
  559. if (!cgc) {
  560. bpf_cgroup_release(cgrp);
  561. stat_inc(FCG_STAT_PNC_GONE);
  562. goto out_free;
  563. }
  564. if (!scx_bpf_dsq_move_to_local(cgid)) {
  565. bpf_cgroup_release(cgrp);
  566. stat_inc(FCG_STAT_PNC_EMPTY);
  567. goto out_stash;
  568. }
  569. /*
  570. * Successfully consumed from the cgroup. This will be our current
  571. * cgroup for the new slice. Refresh its hweight.
  572. */
  573. cgrp_refresh_hweight(cgrp, cgc);
  574. bpf_cgroup_release(cgrp);
  575. /*
  576. * As the cgroup may have more tasks, add it back to the rbtree. Note
  577. * that here we charge the full slice upfront and then exact later
  578. * according to the actual consumption. This prevents lowpri thundering
  579. * herd from saturating the machine.
  580. */
  581. bpf_spin_lock(&cgv_tree_lock);
  582. cgv_node->cvtime += cgrp_slice_ns * FCG_HWEIGHT_ONE / (cgc->hweight ?: 1);
  583. cgrp_cap_budget(cgv_node, cgc);
  584. bpf_rbtree_add(&cgv_tree, &cgv_node->rb_node, cgv_node_less);
  585. bpf_spin_unlock(&cgv_tree_lock);
  586. *cgidp = cgid;
  587. stat_inc(FCG_STAT_PNC_NEXT);
  588. return true;
  589. out_stash:
  590. stash = bpf_map_lookup_elem(&cgv_node_stash, &cgid);
  591. if (!stash) {
  592. stat_inc(FCG_STAT_PNC_GONE);
  593. goto out_free;
  594. }
  595. /*
  596. * Paired with cmpxchg in cgrp_enqueued(). If they see the following
  597. * transition, they'll enqueue the cgroup. If they are earlier, we'll
  598. * see their task in the dq below and requeue the cgroup.
  599. */
  600. __sync_val_compare_and_swap(&cgc->queued, 1, 0);
  601. if (scx_bpf_dsq_nr_queued(cgid)) {
  602. bpf_spin_lock(&cgv_tree_lock);
  603. bpf_rbtree_add(&cgv_tree, &cgv_node->rb_node, cgv_node_less);
  604. bpf_spin_unlock(&cgv_tree_lock);
  605. stat_inc(FCG_STAT_PNC_RACE);
  606. } else {
  607. cgv_node = bpf_kptr_xchg(&stash->node, cgv_node);
  608. if (cgv_node) {
  609. scx_bpf_error("unexpected !NULL cgv_node stash");
  610. goto out_free;
  611. }
  612. }
  613. return false;
  614. out_free:
  615. bpf_obj_drop(cgv_node);
  616. return false;
  617. }
  618. void BPF_STRUCT_OPS(fcg_dispatch, s32 cpu, struct task_struct *prev)
  619. {
  620. struct fcg_cpu_ctx *cpuc;
  621. struct fcg_cgrp_ctx *cgc;
  622. struct cgroup *cgrp;
  623. u64 now = scx_bpf_now();
  624. bool picked_next = false;
  625. cpuc = find_cpu_ctx();
  626. if (!cpuc)
  627. return;
  628. if (!cpuc->cur_cgid)
  629. goto pick_next_cgroup;
  630. if (time_before(now, cpuc->cur_at + cgrp_slice_ns)) {
  631. if (scx_bpf_dsq_move_to_local(cpuc->cur_cgid)) {
  632. stat_inc(FCG_STAT_CNS_KEEP);
  633. return;
  634. }
  635. stat_inc(FCG_STAT_CNS_EMPTY);
  636. } else {
  637. stat_inc(FCG_STAT_CNS_EXPIRE);
  638. }
  639. /*
  640. * The current cgroup is expiring. It was already charged a full slice.
  641. * Calculate the actual usage and accumulate the delta.
  642. */
  643. cgrp = bpf_cgroup_from_id(cpuc->cur_cgid);
  644. if (!cgrp) {
  645. stat_inc(FCG_STAT_CNS_GONE);
  646. goto pick_next_cgroup;
  647. }
  648. cgc = bpf_cgrp_storage_get(&cgrp_ctx, cgrp, 0, 0);
  649. if (cgc) {
  650. /*
  651. * We want to update the vtime delta and then look for the next
  652. * cgroup to execute but the latter needs to be done in a loop
  653. * and we can't keep the lock held. Oh well...
  654. */
  655. bpf_spin_lock(&cgv_tree_lock);
  656. __sync_fetch_and_add(&cgc->cvtime_delta,
  657. (cpuc->cur_at + cgrp_slice_ns - now) *
  658. FCG_HWEIGHT_ONE / (cgc->hweight ?: 1));
  659. bpf_spin_unlock(&cgv_tree_lock);
  660. } else {
  661. stat_inc(FCG_STAT_CNS_GONE);
  662. }
  663. bpf_cgroup_release(cgrp);
  664. pick_next_cgroup:
  665. cpuc->cur_at = now;
  666. if (scx_bpf_dsq_move_to_local(FALLBACK_DSQ)) {
  667. cpuc->cur_cgid = 0;
  668. return;
  669. }
  670. bpf_repeat(CGROUP_MAX_RETRIES) {
  671. if (try_pick_next_cgroup(&cpuc->cur_cgid)) {
  672. picked_next = true;
  673. break;
  674. }
  675. }
  676. /*
  677. * This only happens if try_pick_next_cgroup() races against enqueue
  678. * path for more than CGROUP_MAX_RETRIES times, which is extremely
  679. * unlikely and likely indicates an underlying bug. There shouldn't be
  680. * any stall risk as the race is against enqueue.
  681. */
  682. if (!picked_next)
  683. stat_inc(FCG_STAT_PNC_FAIL);
  684. }
  685. s32 BPF_STRUCT_OPS(fcg_init_task, struct task_struct *p,
  686. struct scx_init_task_args *args)
  687. {
  688. struct fcg_task_ctx *taskc;
  689. struct fcg_cgrp_ctx *cgc;
  690. /*
  691. * @p is new. Let's ensure that its task_ctx is available. We can sleep
  692. * in this function and the following will automatically use GFP_KERNEL.
  693. */
  694. taskc = bpf_task_storage_get(&task_ctx, p, 0,
  695. BPF_LOCAL_STORAGE_GET_F_CREATE);
  696. if (!taskc)
  697. return -ENOMEM;
  698. taskc->bypassed_at = 0;
  699. if (!(cgc = find_cgrp_ctx(args->cgroup)))
  700. return -ENOENT;
  701. p->scx.dsq_vtime = cgc->tvtime_now;
  702. return 0;
  703. }
  704. int BPF_STRUCT_OPS_SLEEPABLE(fcg_cgroup_init, struct cgroup *cgrp,
  705. struct scx_cgroup_init_args *args)
  706. {
  707. struct fcg_cgrp_ctx *cgc;
  708. struct cgv_node *cgv_node;
  709. struct cgv_node_stash empty_stash = {}, *stash;
  710. u64 cgid = cgrp->kn->id;
  711. int ret;
  712. /*
  713. * Technically incorrect as cgroup ID is full 64bit while dsq ID is
  714. * 63bit. Should not be a problem in practice and easy to spot in the
  715. * unlikely case that it breaks.
  716. */
  717. ret = scx_bpf_create_dsq(cgid, -1);
  718. if (ret) {
  719. scx_bpf_error("scx_bpf_create_dsq failed (%d)", ret);
  720. return ret;
  721. }
  722. cgc = bpf_cgrp_storage_get(&cgrp_ctx, cgrp, 0,
  723. BPF_LOCAL_STORAGE_GET_F_CREATE);
  724. if (!cgc) {
  725. ret = -ENOMEM;
  726. goto err_destroy_dsq;
  727. }
  728. cgc->weight = args->weight;
  729. cgc->hweight = FCG_HWEIGHT_ONE;
  730. ret = bpf_map_update_elem(&cgv_node_stash, &cgid, &empty_stash,
  731. BPF_NOEXIST);
  732. if (ret) {
  733. if (ret != -ENOMEM)
  734. scx_bpf_error("unexpected stash creation error (%d)",
  735. ret);
  736. goto err_destroy_dsq;
  737. }
  738. stash = bpf_map_lookup_elem(&cgv_node_stash, &cgid);
  739. if (!stash) {
  740. scx_bpf_error("unexpected cgv_node stash lookup failure");
  741. ret = -ENOENT;
  742. goto err_destroy_dsq;
  743. }
  744. cgv_node = bpf_obj_new(struct cgv_node);
  745. if (!cgv_node) {
  746. ret = -ENOMEM;
  747. goto err_del_cgv_node;
  748. }
  749. cgv_node->cgid = cgid;
  750. cgv_node->cvtime = cvtime_now;
  751. cgv_node = bpf_kptr_xchg(&stash->node, cgv_node);
  752. if (cgv_node) {
  753. scx_bpf_error("unexpected !NULL cgv_node stash");
  754. ret = -EBUSY;
  755. goto err_drop;
  756. }
  757. return 0;
  758. err_drop:
  759. bpf_obj_drop(cgv_node);
  760. err_del_cgv_node:
  761. bpf_map_delete_elem(&cgv_node_stash, &cgid);
  762. err_destroy_dsq:
  763. scx_bpf_destroy_dsq(cgid);
  764. return ret;
  765. }
  766. void BPF_STRUCT_OPS(fcg_cgroup_exit, struct cgroup *cgrp)
  767. {
  768. u64 cgid = cgrp->kn->id;
  769. /*
  770. * For now, there's no way find and remove the cgv_node if it's on the
  771. * cgv_tree. Let's drain them in the dispatch path as they get popped
  772. * off the front of the tree.
  773. */
  774. bpf_map_delete_elem(&cgv_node_stash, &cgid);
  775. scx_bpf_destroy_dsq(cgid);
  776. }
  777. void BPF_STRUCT_OPS(fcg_cgroup_move, struct task_struct *p,
  778. struct cgroup *from, struct cgroup *to)
  779. {
  780. struct fcg_cgrp_ctx *from_cgc, *to_cgc;
  781. s64 delta;
  782. /* find_cgrp_ctx() triggers scx_ops_error() on lookup failures */
  783. if (!(from_cgc = find_cgrp_ctx(from)) || !(to_cgc = find_cgrp_ctx(to)))
  784. return;
  785. delta = time_delta(p->scx.dsq_vtime, from_cgc->tvtime_now);
  786. p->scx.dsq_vtime = to_cgc->tvtime_now + delta;
  787. }
  788. s32 BPF_STRUCT_OPS_SLEEPABLE(fcg_init)
  789. {
  790. int ret;
  791. ret = scx_bpf_create_dsq(FALLBACK_DSQ, -1);
  792. if (ret) {
  793. scx_bpf_error("failed to create DSQ %d (%d)", FALLBACK_DSQ, ret);
  794. return ret;
  795. }
  796. return 0;
  797. }
  798. void BPF_STRUCT_OPS(fcg_exit, struct scx_exit_info *ei)
  799. {
  800. UEI_RECORD(uei, ei);
  801. }
  802. SCX_OPS_DEFINE(flatcg_ops,
  803. .select_cpu = (void *)fcg_select_cpu,
  804. .enqueue = (void *)fcg_enqueue,
  805. .dispatch = (void *)fcg_dispatch,
  806. .runnable = (void *)fcg_runnable,
  807. .running = (void *)fcg_running,
  808. .stopping = (void *)fcg_stopping,
  809. .quiescent = (void *)fcg_quiescent,
  810. .init_task = (void *)fcg_init_task,
  811. .cgroup_set_weight = (void *)fcg_cgroup_set_weight,
  812. .cgroup_init = (void *)fcg_cgroup_init,
  813. .cgroup_exit = (void *)fcg_cgroup_exit,
  814. .cgroup_move = (void *)fcg_cgroup_move,
  815. .init = (void *)fcg_init,
  816. .exit = (void *)fcg_exit,
  817. .flags = SCX_OPS_HAS_CGROUP_WEIGHT | SCX_OPS_ENQ_EXITING,
  818. .name = "flatcg");