#include <scx/common.bpf.h>
#include <bpf_arena_common.bpf.h>
#include <lib/topology.h>
#include <lib/cgroup.h>
#include <lib/atq.h>
#ifndef U64_MAX
#define U64_MAX ((u64)~0ULL)
#endif
extern int scx_cgroup_bw_enqueue_cb(u64 taskc);
enum scx_cgroup_consts {
SCX_CACHELINE_SIZE = 64,
CBW_CLOCK_BOOTTIME = 7,
CBW_REPLENISH_PERIOD = (100ULL * 1000ULL * 1000ULL),
CBW_REPLENISH_PERIOD_MIN = (1ULL * 1000ULL * 1000ULL),
CBW_ACCOUNTING_PERIOD_MIN = (1ULL * 1000ULL * 1000ULL),
CBW_ACCOUNTING_PERIOD_MAX = (20ULL * 1000ULL * 1000ULL),
CBW_ACCOUNTING_PERIOD_DIVISOR = 4,
CBW_SHIFT = 10,
CBW_SCALE = (1 << CBW_SHIFT),
CBW_CONSUMPTION_RATE_DECAY = 3,
CBW_NR_CGRP_MAX = 2048,
CBW_CGRP_TREE_HEIGHT_MAX = 32,
CBW_RUNTUME_INF_RAW = ((u64)~0ULL),
CBW_RUNTUME_INF = ((s64)~((u64)1 << 63)),
CBW_REENQ_MAX_BATCH = 2,
CBW_DEFERRED_BTQ_SIZE = 256,
};
#define ROOT_CGID 1ULL
static u32 cbw_loader_tgid;
struct scx_cgroup_ctx {
struct {
u64 free_next;
u64 id;
u64 parent_id;
u32 level;
u64 quota;
u64 period;
u64 burst;
u64 nquota;
u64 nquota_ub;
bool has_llcx;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
struct {
bool is_throttled;
u32 nr_throttled_periods;
bool was_throttled;
u32 nr_consec_throttled_periods;
u32 max_consec_throttled_periods;
u64 period_start_clk;
s64 burst_remaining;
s64 period_budget;
s64 runtime_total_sloppy;
s64 runtime_total_last;
u64 avg_consumption_rate;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
typedef struct scx_cgroup_ctx __arena scx_cgroup_ctx_t;
struct scx_cgroup_llc_ctx {
u64 free_next;
u64 id;
s64 runtime_total;
scx_atq_t *btq;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
typedef struct scx_cgroup_llc_ctx __arena scx_cgroup_llc_ctx_t;
static struct scx_cgroup_bw_config cbw_config;
struct cbw_cgrp_entry {
u64 cgx;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, u64);
__type(value, struct cbw_cgrp_entry);
__uint(max_entries, CBW_NR_CGRP_MAX);
} cbw_cgrp_map SEC(".maps");
struct cgroup_llc_id {
u64 cgrp_id;
int llc_id;
} __attribute__((packed));
struct cbw_llc_entry {
u64 llcx;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct cgroup_llc_id);
__type(value, struct cbw_llc_entry);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, CBW_NR_CGRP_MAX);
} cbw_cgrp_llc_map SEC(".maps");
static inline void __arena *cbw_freelist_pop(u64 *head)
{
u64 old_head, new_head;
u64 __arena *node;
old_head = *head;
while (can_loop && old_head) {
node = (u64 __arena *)old_head;
new_head = *node;
if (__sync_bool_compare_and_swap(head, old_head, new_head))
return (void __arena *)node;
old_head = *head;
}
return NULL;
}
static inline void cbw_freelist_push(u64 *head, void __arena *ptr)
{
u64 __arena *node = (u64 __arena *)ptr;
u64 old_head;
old_head = *head;
do {
*node = old_head;
if (__sync_bool_compare_and_swap(head, old_head, (u64)node))
return;
old_head = *head;
} while (can_loop);
}
static u64 cbw_llcx_free_head __attribute__((aligned(SCX_CACHELINE_SIZE)));
static inline scx_cgroup_llc_ctx_t *cbw_alloc_llcx(void)
{
scx_cgroup_llc_ctx_t *llcx;
llcx = cbw_freelist_pop(&cbw_llcx_free_head);
if (!llcx)
llcx = scx_static_alloc(sizeof(*llcx), SCX_CACHELINE_SIZE);
return llcx;
}
static inline void cbw_free_llcx(scx_cgroup_llc_ctx_t *llcx)
{
int i;
for (i = 0; can_loop && i < sizeof(*llcx); i++)
((char __arena *)llcx)[i] = 0;
cbw_freelist_push(&cbw_llcx_free_head, llcx);
}
static u64 cbw_cgx_free_head __attribute__((aligned(SCX_CACHELINE_SIZE)));
static inline scx_cgroup_ctx_t *cbw_alloc_cgx(void)
{
scx_cgroup_ctx_t *cgx;
cgx = cbw_freelist_pop(&cbw_cgx_free_head);
if (!cgx)
cgx = scx_static_alloc(sizeof(*cgx), SCX_CACHELINE_SIZE);
return cgx;
}
static inline void cbw_free_cgx(scx_cgroup_ctx_t *cgx)
{
int i;
for (i = 0; can_loop && i < sizeof(*cgx); i++)
((char __arena *)cgx)[i] = 0;
cbw_freelist_push(&cbw_cgx_free_head, cgx);
}
struct tree_levels {
s64 levels[CBW_CGRP_TREE_HEIGHT_MAX];
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, struct tree_levels);
__uint(max_entries, 1);
} tree_levels_map SEC(".maps");
static u64 cbw_nr_cgroups;
static u64 cbw_cgroup_ids[CBW_NR_CGRP_MAX];
static u64 cbw_nr_cgx;
static u64 cbw_throttled_cgroup_ids[CBW_NR_CGRP_MAX];
struct replenish_timer {
struct bpf_timer timer;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct replenish_timer);
} replenish_timer SEC(".maps") __weak;
static u64 cbw_last_replenish_at;
static
int replenish_timerfn(void *map, int *key, struct bpf_timer *timer);
struct accounting_timer {
struct bpf_timer timer;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct accounting_timer);
} accounting_timer SEC(".maps") __weak;
static
int accounting_timerfn(void *map, int *key, struct bpf_timer *timer);
union backlog_stat {
struct {
u32 rp_seq;
u16 nr_throttled_cgroups;
u16 has_throttled_tasks;
};
u64 val;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
static union backlog_stat cbw_backlog_stat;
static inline
bool cbw_update_backlog_stat_cas(union backlog_stat *old,
u32 rp_seq,
u16 nr_throttled_cgroups,
u16 has_throttled_tasks)
{
union backlog_stat new = {
.rp_seq = rp_seq,
.nr_throttled_cgroups = nr_throttled_cgroups,
.has_throttled_tasks = has_throttled_tasks,
};
return __sync_bool_compare_and_swap(&cbw_backlog_stat.val, old->val,
new.val);
}
static inline
bool cbw_top_half_running(void)
{
union backlog_stat stat;
stat.val = smp_load_acquire(&cbw_backlog_stat.val);
return stat.rp_seq & 0x1;
}
static inline
void cbw_top_half_begin(void)
{
union backlog_stat old, new, ret;
ret.val = smp_load_acquire(&cbw_backlog_stat.val);
do {
new.val = old.val = ret.val;
new.rp_seq++;
ret.val = __sync_val_compare_and_swap(&cbw_backlog_stat.val,
old.val, new.val);
} while (can_loop && (ret.val != old.val));
}
static inline
void cbw_top_half_abort(void)
{
cbw_top_half_begin();
}
static inline
void cbw_top_half_end(u16 nr_throttled_cgroups, u16 has_throttled_tasks)
{
union backlog_stat old, new, ret;
ret.val = smp_load_acquire(&cbw_backlog_stat.val);
do {
new.val = old.val = ret.val;
new.rp_seq++;
new.nr_throttled_cgroups = nr_throttled_cgroups;
new.has_throttled_tasks = has_throttled_tasks;
ret.val = __sync_val_compare_and_swap(&cbw_backlog_stat.val,
old.val, new.val);
} while (can_loop && (ret.val != old.val));
}
#define cbw_err(fmt, ...) do { \
bpf_printk("[%s:%d] ERROR: " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_warn(fmt, ...) do { \
bpf_printk("[%s:%d] WARNING: " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_info(fmt, ...) do { \
bpf_printk("[%s:%d] INFO: " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_dbg(fmt, ...) do { \
if (cbw_config.verbose > 0) \
bpf_printk("[%s:%d] " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_dbg_cgrp(fmt, ...) do { \
if (cbw_config.verbose > 0) \
bpf_printk("[%s:%d/cgid%llu] " fmt, __func__, __LINE__, \
cgrp->kn->id, ##__VA_ARGS__); \
} while(0)
#define dbg_cgx(cgx, str, ...) do { \
cbw_dbg(str "cgid%llu -- cgx:period_budget: %lld -- " \
"cgx:runtime_total_last: %lld -- " \
"cgx:runtime_total_sloppy: %lld -- " \
"cgx:nquota: %lld -- " \
"cgx:nquota_ub: %lld -- " \
"cgx:is_throttled: %d -- " \
"cgx:avg_consumption_rate: %llu " \
##__VA_ARGS__, \
cgx->id, cgx->period_budget, \
cgx->runtime_total_last, cgx->runtime_total_sloppy, \
cgx->nquota, cgx->nquota_ub, cgx->is_throttled, \
cgx->avg_consumption_rate); \
} while (0);
#define dbg_llcx(llcx, str, ...) do { \
cbw_dbg(str "cgid%llu -- llcx:runtime_total: %lld", \
##__VA_ARGS__, \
llcx->id, llcx->runtime_total); \
} while (0);
#define info_llcx(llcx, str, ...) do { \
cbw_dbg(str "cgid%llu -- llcx:runtime_total: %lld", \
##__VA_ARGS__, \
llcx->id, llcx->runtime_total); \
} while (0);
#define info_cgx(cgx, str, ...) do { \
cbw_info(str "cgid%llu -- cgx:period_budget: %lld -- " \
"cgx:runtime_total_last: %lld -- " \
"cgx:runtime_total_sloppy: %lld -- " \
"cgx:nquota: %lld -- " \
"cgx:nquota_ub: %lld -- " \
"cgx:is_throttled: %d -- " \
"cgx:avg_consumption_rate: %llu" \
##__VA_ARGS__, \
cgx->id, cgx->period_budget, \
cgx->runtime_total_last, cgx->runtime_total_sloppy, \
cgx->nquota, cgx->nquota_ub, cgx->is_throttled, \
cgx->avg_consumption_rate); \
} while (0);
#ifndef min
#define min(X, Y) (((X) < (Y)) ? (X) : (Y))
#endif
#ifndef max
#define max(X, Y) (((X) < (Y)) ? (Y) : (X))
#endif
#ifndef clamp
#define clamp(val, lo, hi) min(max(val, lo), hi)
#endif
static
bool is_kernel_compatible(void)
{
return bpf_core_field_exists(struct scx_cgroup_init_args, bw_period_us);
}
__hidden
int scx_cgroup_bw_lib_init(struct scx_cgroup_bw_config *config)
{
struct bpf_timer *rp_timer, *ac_timer;
u32 key = 0;
int ret;
if (!is_kernel_compatible()) {
cbw_err("The kernel does not support the cpu.max for scx.");
return -EOPNOTSUPP;
}
if (!config)
return -EINVAL;
cbw_config = *config;
cbw_loader_tgid = (u32)(bpf_get_current_pid_tgid() >> 32);
rp_timer = bpf_map_lookup_elem(&replenish_timer, &key);
if (!rp_timer) {
cbw_err("Failed to lookup replenish timer");
return -ESRCH;
}
cbw_last_replenish_at = scx_bpf_now();
bpf_timer_init(rp_timer, &replenish_timer, CBW_CLOCK_BOOTTIME);
bpf_timer_set_callback(rp_timer, replenish_timerfn);
if ((ret = bpf_timer_start(rp_timer, CBW_REPLENISH_PERIOD, 0))) {
cbw_err("Failed to start replenish timer");
return ret;
}
ac_timer = bpf_map_lookup_elem(&accounting_timer, &key);
if (!ac_timer) {
cbw_err("Failed to lookup accounting timer");
return -ESRCH;
}
bpf_timer_init(ac_timer, &accounting_timer, CBW_CLOCK_BOOTTIME);
bpf_timer_set_callback(ac_timer, accounting_timerfn);
if ((ret = bpf_timer_start(ac_timer, CBW_ACCOUNTING_PERIOD_MAX, 0))) {
cbw_err("Failed to start accounting timer");
return ret;
}
return 0;
}
static
bool cgroup_is_threaded(struct cgroup *cgrp)
{
return cgrp->dom_cgrp != cgrp;
}
static
u64 cgroup_get_id(struct cgroup *cgrp)
{
return cgrp->kn->id;
}
static __always_inline
u64 cbw_get_cgroup_ctx_raw(u64 cgrp_id)
{
struct cbw_cgrp_entry *entry;
entry = bpf_map_lookup_elem(&cbw_cgrp_map, &cgrp_id);
return entry ? entry->cgx : 0;
}
static __always_inline
scx_cgroup_ctx_t *cbw_get_cgroup_ctx_with_id(u64 cgrp_id)
{
return (scx_cgroup_ctx_t *)cbw_get_cgroup_ctx_raw(cgrp_id);
}
static __always_inline
scx_cgroup_ctx_t *cbw_get_cgroup_ctx(struct cgroup *cgrp)
{
return (scx_cgroup_ctx_t *)cbw_get_cgroup_ctx_raw(cgroup_get_id(cgrp));
}
long cbw_del_cgroup_ctx(u64 cgrp_id)
{
scx_cgroup_ctx_t *cgx = cbw_get_cgroup_ctx_with_id(cgrp_id);
if (cgx)
cbw_free_cgx(cgx);
return bpf_map_delete_elem(&cbw_cgrp_map, &cgrp_id);
}
static
scx_cgroup_llc_ctx_t *cbw_alloc_llc_ctx(struct cgroup *cgrp,
scx_cgroup_ctx_t *cgx,
int llc_id)
{
scx_cgroup_llc_ctx_t *llcx;
struct cbw_llc_entry entry = {};
struct cgroup_llc_id key = {
.cgrp_id = cgroup_get_id(cgrp),
.llc_id = llc_id,
};
llcx = cbw_alloc_llcx();
if (!llcx)
return NULL;
llcx->id = cgroup_get_id(cgrp);
llcx->btq = (scx_atq_t *)scx_atq_create(false);
if (!llcx->btq) {
cbw_err("Fail to allocate a BTQ");
cbw_free_llcx(llcx);
return NULL;
}
entry.llcx = (u64)llcx;
if (bpf_map_update_elem(&cbw_cgrp_llc_map, &key, &entry, BPF_NOEXIST)) {
scx_atq_destroy(llcx->btq);
llcx->btq = NULL;
cbw_free_llcx(llcx);
return NULL;
}
return llcx;
}
static __always_inline
u64 cbw_get_llc_ctx_raw_with_id(u64 cgrp_id, int llc_id)
{
struct cbw_llc_entry *entry;
struct cgroup_llc_id key = {
.cgrp_id = cgrp_id,
.llc_id = llc_id,
};
entry = bpf_map_lookup_elem(&cbw_cgrp_llc_map, &key);
return entry ? entry->llcx : 0;
}
static __always_inline
scx_cgroup_llc_ctx_t *cbw_get_llc_ctx_with_id(u64 cgrp_id, int llc_id)
{
return (scx_cgroup_llc_ctx_t *)cbw_get_llc_ctx_raw_with_id(cgrp_id, llc_id);
}
static __always_inline
scx_cgroup_llc_ctx_t *cbw_get_llc_ctx(struct cgroup *cgrp, int llc_id)
{
return cbw_get_llc_ctx_with_id(cgroup_get_id(cgrp), llc_id);
}
static
long cbw_del_llc_ctx_with_id(u64 cgrp_id, int llc_id)
{
struct cgroup_llc_id key = {
.cgrp_id = cgrp_id,
.llc_id = llc_id,
};
return bpf_map_delete_elem(&cbw_cgrp_llc_map, &key);
}
static
int cbw_init_llc_ctx(struct cgroup *cgrp, scx_cgroup_ctx_t *cgx)
{
int i;
if (!cgx || !cgrp)
return -EINVAL;
bpf_for(i, 0, TOPO_NR(LLC)) {
scx_cgroup_llc_ctx_t *llcx;
llcx = cbw_alloc_llc_ctx(cgrp, cgx, i);
if (!llcx)
return -ENOMEM;
}
cgx->has_llcx = true;
return 0;
}
__hidden
int cbw_put_aside(u64 ctx, u64 vtime, u64 cgrp_id);
static void schedule_atq_destroy(scx_atq_t *btq)
{
static u64 slots[CBW_DEFERRED_BTQ_SIZE] __attribute__((aligned(SCX_CACHELINE_SIZE)));
static u64 tail __attribute__((aligned(SCX_CACHELINE_SIZE)));
u64 slot, old, prev;
do {
slot = __sync_fetch_and_add(&tail, 1) % CBW_DEFERRED_BTQ_SIZE;
old = __sync_val_compare_and_swap(&slots[slot], 0, (u64)btq);
if (!old)
return;
prev = __sync_val_compare_and_swap(&slots[slot], old, (u64)btq);
if (likely(old == prev)) {
scx_atq_destroy((scx_atq_t *)old);
return;
}
} while (can_loop);
}
static __always_inline
int cbw_free_llc_ctx(scx_cgroup_ctx_t *cgx, u64 cgrp_id)
{
scx_cgroup_llc_ctx_t *llcx;
volatile int nr_moved = 0;
int i, ret;
scx_atq_t *btq;
u64 taskc;
if (unlikely(cgrp_id == ROOT_CGID))
return 0;
if (cgx) {
if (!cgx->has_llcx)
return 0;
cgx->has_llcx = false;
}
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx_with_id(cgrp_id, i);
if (!llcx || !(btq = READ_ONCE(llcx->btq)))
continue;
if (!__sync_bool_compare_and_swap(&llcx->btq, btq, NULL)) {
continue;
}
if (cgrp_id != ROOT_CGID) {
while (can_loop && (taskc = scx_atq_pop(btq, true))) {
scx_task_cgroup_bw_t *t = (scx_task_cgroup_bw_t *)taskc;
WRITE_ONCE(t->cgx_raw, 0);
WRITE_ONCE(t->llcx_raw, 0);
ret = cbw_put_aside(taskc, 0, ROOT_CGID);
if (likely(!ret)) {
nr_moved++;
} else {
cbw_err("Failed to put aside a task "
"while exiting cgid%llu: %d",
cgrp_id, ret);
}
scx_atq_task_drop((scx_task_common *)taskc);
}
}
if (cbw_del_llc_ctx_with_id(cgrp_id, i)) {
cbw_err("Failed to delete an LLC context: [%llu/%d]",
cgrp_id, i);
} else {
cbw_free_llcx(llcx);
}
schedule_atq_destroy(btq);
}
return nr_moved;
}
__noinline
int cbw_set_bandwidth(u64 cgx_raw, u64 period_us, u64 quota_us, u64 burst_us)
{
scx_cgroup_ctx_t *cgx = (scx_cgroup_ctx_t *)cgx_raw;
scx_arena_subprog_init();
cgx->period = period_us * 1000;
cgx->period_start_clk = scx_bpf_now();
if (quota_us == CBW_RUNTUME_INF_RAW) {
cgx->quota = CBW_RUNTUME_INF_RAW;
cgx->nquota = CBW_RUNTUME_INF;
cgx->burst = 0;
} else {
cgx->quota = quota_us * 1000;
cgx->nquota = div_round_up(quota_us * CBW_REPLENISH_PERIOD,
period_us);
cgx->burst = burst_us * 1000;
}
cgx->burst_remaining = cgx->burst;
return 0;
}
__noinline
int cbw_update_nquota_ub(u64 cgx_raw)
{
scx_cgroup_ctx_t *cgx = (scx_cgroup_ctx_t *)cgx_raw;
scx_cgroup_ctx_t *parentx;
if (!cgx)
return -EINVAL;
cgx->nquota_ub = cgx->nquota;
if (cgx->level > 1) {
parentx = cbw_get_cgroup_ctx_with_id(cgx->parent_id);
if (!parentx) {
cbw_err("Fail to lookup parent ctx: %llu",
cgx->parent_id);
return -ESRCH;
}
cgx->nquota_ub = min(cgx->nquota_ub, parentx->nquota_ub);
}
return 0;
}
int scx_cgroup_bw_init(struct cgroup *cgrp __arg_trusted, struct scx_cgroup_init_args *args __arg_trusted)
{
struct cbw_cgrp_entry entry;
scx_cgroup_ctx_t *cgx, *parentx;
struct cgroup *parent;
u64 cgrp_id;
int ret;
cbw_dbg_cgrp(" level: %d -- period_us: %llu -- quota_us: %llu -- burst_us: %llu ",
cgrp->level, args->bw_period_us, args->bw_quota_us, args->bw_burst_us);
cgrp_id = cgroup_get_id(cgrp);
if (cgrp->level >= CBW_CGRP_TREE_HEIGHT_MAX) {
cbw_err("cgroup %llu level %d exceeds max tree height %d; aborting",
cgrp_id, cgrp->level, CBW_CGRP_TREE_HEIGHT_MAX);
return -E2BIG;
}
if (READ_ONCE(cbw_nr_cgx) >= CBW_NR_CGRP_MAX) {
cbw_err("cgroup %llu exceeds max cgroups %d; aborting",
cgrp_id, CBW_NR_CGRP_MAX);
return -ENOSPC;
}
if (__sync_fetch_and_add(&cbw_nr_cgx, 1) >= CBW_NR_CGRP_MAX) {
__sync_fetch_and_sub(&cbw_nr_cgx, 1);
cbw_err("cgroup %llu exceeds max cgroups %d; aborting",
cgrp_id, CBW_NR_CGRP_MAX);
return -ENOSPC;
}
cgx = cbw_alloc_cgx();
if (!cgx) {
cbw_err("Failed to allocate cgroup ctx: %llu", cgrp_id);
ret = -ENOMEM;
goto err_unreserve;
}
cgx->id = cgrp_id;
cgx->level = cgrp->level;
if (cgrp->level > 0 &&
(parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1))) {
cgx->parent_id = cgroup_get_id(parent);
bpf_cgroup_release(parent);
} else {
cgx->parent_id = 0;
}
cbw_set_bandwidth((u64)cgx, args->bw_period_us, args->bw_quota_us,
args->bw_burst_us);
cbw_update_nquota_ub((u64)cgx);
cgx->runtime_total_sloppy = 0;
cgx->period_budget = cgx->nquota_ub;
cgx->is_throttled = false;
if ((cgrp->level > 0) &&
(parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1))) {
if (cgroup_get_id(parent) != ROOT_CGID) {
parentx = cbw_get_cgroup_ctx(parent);
if (parentx && !cgroup_is_threaded(parent)) {
cbw_free_llc_ctx(parentx, parentx->id);
}
}
bpf_cgroup_release(parent);
}
if ((ret = cbw_init_llc_ctx(cgrp, cgx))) {
cbw_err("Failed to init LLC contexts: %llu (%d)", cgrp_id, ret);
goto err_free;
}
entry.cgx = (u64)cgx;
if (bpf_map_update_elem(&cbw_cgrp_map, &cgrp_id, &entry, BPF_ANY)) {
cbw_err("Failed to insert cgroup entry: %llu", cgrp_id);
ret = -ENOMEM;
goto err_free;
}
return 0;
err_free:
cgx->has_llcx = true;
cbw_free_llc_ctx(cgx, cgrp_id);
cbw_free_cgx(cgx);
err_unreserve:
__sync_fetch_and_sub(&cbw_nr_cgx, 1);
return ret;
}
__noinline
int cbw_unthrottle_cgroup_for_exit(u64 cgrp_id)
{
scx_cgroup_ctx_t *cgx;
if (!(cgx = cbw_get_cgroup_ctx_with_id(cgrp_id))) {
cbw_err("Failed to lookup a cgroup ctx: %llu", cgrp_id);
return -ESRCH;
}
if (cgx->nquota_ub == CBW_RUNTUME_INF)
return 0;
WRITE_ONCE(cgx->nquota_ub, CBW_RUNTUME_INF);
WRITE_ONCE(cgx->period_budget, CBW_RUNTUME_INF);
smp_mb();
WRITE_ONCE(cgx->is_throttled, false);
smp_mb();
return 0;
}
__hidden
int scx_cgroup_bw_exit(struct cgroup *cgrp __arg_trusted)
{
u64 cgrp_id;
cbw_dbg_cgrp();
cgrp_id = cgroup_get_id(cgrp);
if (!cbw_get_cgroup_ctx_with_id(cgrp_id))
return 0;
cbw_unthrottle_cgroup_for_exit(cgrp_id);
if (!cbw_del_cgroup_ctx(cgrp_id))
__sync_fetch_and_sub(&cbw_nr_cgx, 1);
cbw_free_llc_ctx(NULL, cgrp_id);
return 0;
}
__hidden
int scx_cgroup_bw_set(struct cgroup *cgrp __arg_trusted, u64 period_us, u64 quota_us, u64 burst_us)
{
struct cgroup *cur_cgrp;
u64 cgx_raw, cur_cgx_raw;
struct cgroup_subsys_state *start_css, *pos;
int ret = 0;
cbw_dbg_cgrp();
cgx_raw = cbw_get_cgroup_ctx_raw(cgroup_get_id(cgrp));
if (!cgx_raw) {
return 0;
}
cbw_set_bandwidth(cgx_raw, period_us, quota_us, burst_us);
bpf_rcu_read_lock();
start_css = &cgrp->self;
bpf_for_each(css, pos, start_css, BPF_CGROUP_ITER_DESCENDANTS_PRE) {
cur_cgrp = pos->cgroup;
cur_cgx_raw = cbw_get_cgroup_ctx_raw(cgroup_get_id(cur_cgrp));
if (!cur_cgx_raw) {
continue;
}
ret = cbw_update_nquota_ub(cur_cgx_raw);
if (ret)
goto unlock_out;
}
unlock_out:
bpf_rcu_read_unlock();
return ret;
}
static
s64 cbw_sum_rumtime_total_llcx(struct cgroup *cgrp, scx_cgroup_ctx_t *cgx)
{
scx_cgroup_llc_ctx_t *llcx;
s64 sum;
int i;
if (!cgx->has_llcx)
return 0;
sum = 0;
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx(cgrp, i);
if (!llcx)
break;
sum += READ_ONCE(llcx->runtime_total);
}
return sum;
}
static
struct tree_levels *get_clean_tree_levels(void)
{
const u32 idx = 0;
struct tree_levels *tree;
tree = bpf_map_lookup_elem(&tree_levels_map, &idx);
if (tree)
__builtin_memset(tree, 0, sizeof(*tree));
return tree;
}
static
int cbw_update_runtime_total_sloppy(struct cgroup *cgrp)
{
u32 cur_level, prev_level = CBW_CGRP_TREE_HEIGHT_MAX;
struct cgroup_subsys_state *start_css, *pos;
scx_cgroup_ctx_t *cur_cgx = NULL;
struct tree_levels *tree;
struct cgroup *cur_cgrp;
s64 rt_llcx;
int ret = 0;
tree = get_clean_tree_levels();
if (!tree)
return -ENOMEM;
bpf_rcu_read_lock();
start_css = &cgrp->self;
bpf_for_each(css, pos, start_css, BPF_CGROUP_ITER_DESCENDANTS_POST) {
cur_cgrp = pos->cgroup;
cur_level = cur_cgrp->level;
if (can_loop && cur_level == 0)
break;
if (cur_level >= CBW_CGRP_TREE_HEIGHT_MAX) {
ret = -E2BIG;
break;
}
if (prev_level == CBW_CGRP_TREE_HEIGHT_MAX)
prev_level = cur_level;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
continue;
}
rt_llcx = cbw_sum_rumtime_total_llcx(cur_cgrp, cur_cgx);
if (prev_level == cur_level) {
WRITE_ONCE(cur_cgx->runtime_total_sloppy, rt_llcx);
}
else if (prev_level < cur_level) {
WRITE_ONCE(cur_cgx->runtime_total_sloppy, rt_llcx);
}
else if (prev_level > cur_level) {
WRITE_ONCE(cur_cgx->runtime_total_sloppy,
tree->levels[prev_level] + rt_llcx);
tree->levels[prev_level] = 0;
}
if (READ_ONCE(cur_cgx->runtime_total_sloppy) >= cur_cgx->period_budget)
WRITE_ONCE(cur_cgx->is_throttled, true);
tree->levels[cur_level] += READ_ONCE(cur_cgx->runtime_total_sloppy);
prev_level = cur_level;
cbw_dbg("cgid%llu -- rt_llcx: %lld -- runtime_total_sloppy: %lld",
cur_cgx->id, rt_llcx, cur_cgx->runtime_total_sloppy);
}
bpf_rcu_read_unlock();
return ret;
}
static
u64 cbw_throttle_cgroups(struct cgroup *cgrp)
{
struct cgroup_subsys_state *start_css, *pos, *anc_css;
scx_cgroup_ctx_t *cur_cgx, *cur_anc_cgx;
struct cgroup *cur_anc_cgrp;
u64 min_time_to_throttle = U64_MAX;
u64 time_to_throttle;
s64 remaining;
int i;
bpf_rcu_read_lock();
start_css = &cgrp->self;
bpf_for_each(css, pos, start_css, BPF_CGROUP_ITER_DESCENDANTS_POST) {
cur_cgx = cbw_get_cgroup_ctx(pos->cgroup);
if (!cur_cgx) {
continue;
}
if (cur_cgx->nquota_ub == CBW_RUNTUME_INF)
continue;
if (READ_ONCE(cur_cgx->is_throttled))
continue;
if (unlikely(cbw_top_half_running())) {
min_time_to_throttle = U64_MAX;
break;
}
anc_css = pos->parent;
bpf_for(i, 0, CBW_CGRP_TREE_HEIGHT_MAX) {
if (!anc_css)
break;
cur_anc_cgrp = anc_css->cgroup;
if (!cur_anc_cgrp || cur_anc_cgrp->level == 0)
break;
cur_anc_cgx = cbw_get_cgroup_ctx(cur_anc_cgrp);
if (cur_anc_cgx && READ_ONCE(cur_anc_cgx->is_throttled)) {
WRITE_ONCE(cur_cgx->is_throttled, true);
break;
}
anc_css = anc_css->parent;
}
if (!READ_ONCE(cur_cgx->is_throttled) &&
cur_cgx->avg_consumption_rate > 0) {
remaining = READ_ONCE(cur_cgx->period_budget) -
READ_ONCE(cur_cgx->runtime_total_sloppy);
if (remaining > 0) {
time_to_throttle = (u64)remaining * CBW_SCALE /
cur_cgx->avg_consumption_rate;
if (time_to_throttle < min_time_to_throttle)
min_time_to_throttle = time_to_throttle;
}
}
}
bpf_rcu_read_unlock();
return clamp(min_time_to_throttle / CBW_ACCOUNTING_PERIOD_DIVISOR,
(u64)CBW_ACCOUNTING_PERIOD_MIN,
(u64)CBW_ACCOUNTING_PERIOD_MAX);
}
static
int cbw_get_current_llc_id(void)
{
u32 cpu = bpf_get_smp_processor_id();
return topo_cpu_to_llc_id(cpu);
}
static
int cbw_cgroup_bw_throttled(u64 cgrp_id, u64 taskc_raw)
{
scx_task_cgroup_bw_t *taskc = (scx_task_cgroup_bw_t *)taskc_raw;
scx_cgroup_ctx_t *cgx;
u64 cgx_raw;
if (cgrp_id == ROOT_CGID)
return 0;
if (unlikely(cgrp_id == 0))
return 0;
if (taskc && taskc->cgx_raw) {
cgx_raw = taskc->cgx_raw;
} else {
cgx_raw = cbw_get_cgroup_ctx_raw(cgrp_id);
if (!cgx_raw) {
cbw_dbg("Failed to lookup a cgroup ctx: %llu", cgrp_id);
return -ESRCH;
}
if (taskc)
taskc->cgx_raw = cgx_raw;
}
cgx = (scx_cgroup_ctx_t *)cgx_raw;
if (READ_ONCE(cgx->is_throttled)) {
dbg_cgx(cgx, "throttled: ");
return -EAGAIN;
}
return 0;
}
__hidden
int scx_cgroup_bw_throttled(u64 cgrp_id,
struct task_struct *p __arg_trusted, u64 taskc)
{
if (p->flags & PF_EXITING)
return 0;
return cbw_cgroup_bw_throttled(cgrp_id, taskc);
}
__hidden
int scx_cgroup_bw_consume(u64 cgrp_id, u64 consumed_ns, u64 taskc_raw)
{
scx_task_cgroup_bw_t *taskc = (scx_task_cgroup_bw_t *)taskc_raw;
scx_cgroup_llc_ctx_t *llcx;
scx_cgroup_ctx_t *cgx;
u64 cgx_raw, llcx_raw;
int llc_id;
if (cgrp_id == ROOT_CGID)
return 0;
if (unlikely(!taskc)) {
if ((llc_id = cbw_get_current_llc_id()) < 0) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
llcx = cbw_get_llc_ctx_with_id(cgrp_id, llc_id);
if (!llcx)
return 0;
goto accounting_out;
}
if (taskc->cgx_raw) {
cgx_raw = taskc->cgx_raw;
} else {
cgx_raw = cbw_get_cgroup_ctx_raw(cgrp_id);
if (!cgx_raw)
return 0;
taskc->cgx_raw = cgx_raw;
}
cgx = (scx_cgroup_ctx_t *)cgx_raw;
if (READ_ONCE(cgx->nquota_ub) == CBW_RUNTUME_INF)
return 0;
if ((llc_id = cbw_get_current_llc_id()) < 0) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
if (taskc->llcx_raw && taskc->last_llc_id == llc_id) {
llcx = (scx_cgroup_llc_ctx_t *)taskc->llcx_raw;
} else {
llcx_raw = cbw_get_llc_ctx_raw_with_id(cgx->id, llc_id);
if (!llcx_raw)
return 0;
taskc->llcx_raw = llcx_raw;
taskc->last_llc_id = llc_id;
llcx = (scx_cgroup_llc_ctx_t *)llcx_raw;
}
accounting_out:
__sync_fetch_and_add(&llcx->runtime_total, consumed_ns);
cbw_dbg(" cgrp_id: %llu -- llc_id: %d -- consumed_ns: %llu -- llcx:runtime_total: %lld",
cgrp_id, llc_id, consumed_ns, READ_ONCE(llcx->runtime_total));
return 0;
}
__hidden
int cbw_put_aside(u64 ctx, u64 vtime, u64 cgrp_id)
{
scx_task_common *taskc = (scx_task_common *)ctx;
scx_cgroup_llc_ctx_t *llcx;
scx_atq_t *btq;
scx_atq_t *task_atq;
int llc_id, ret;
if ((llc_id = cbw_get_current_llc_id()) < 0) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
llcx = cbw_get_llc_ctx_with_id(cgrp_id, llc_id);
if (!llcx) {
cbw_err("Failed to lookup an LLC ctx: [%llu/%d]",
cgrp_id, llc_id);
return -ESRCH;
}
btq = READ_ONCE(llcx->btq);
if (!btq)
return -ESRCH;
ret = scx_atq_lock(btq);
if (ret) {
cbw_err("Failed to lock ATQ.");
return -EBUSY;
}
scx_atq_t *btq_now = READ_ONCE(llcx->btq);
if (btq_now != btq) {
scx_atq_unlock(btq);
cbw_warn("put_aside skipped: BTQ has changed in the middle: "
"cgid=%llu, btq1=%llx, btq2=%llx",
cgrp_id, (u64)btq, (u64)btq_now);
return -ESRCH;
}
task_atq = (scx_atq_t *)READ_ONCE(taskc->atq);
if (task_atq == (scx_atq_t *)SCX_ATQ_DEAD) {
scx_atq_unlock(btq);
return 0;
}
if (task_atq) {
cbw_dbg("Possible double enqueue detected.");
scx_atq_unlock(btq);
cbw_warn("put_aside skipped: already in BTQ; cgid=%llu", cgrp_id);
return 0;
}
ret = scx_atq_insert_vtime_unlocked(btq, taskc, vtime);
scx_atq_unlock(btq);
if (unlikely(ret == -ECANCELED)) {
return 0;
} else if (unlikely(ret == -EALREADY)) {
cbw_warn("put_aside skipped: already in BTQ; cgid=%llu", cgrp_id);
return 0;
} else if (unlikely(ret)) {
cbw_err("Failed to insert a task to BTQ: %d", ret);
}
return ret;
}
__hidden
int scx_cgroup_bw_put_aside(struct task_struct *p __arg_trusted, u64 ctx, u64 vtime, u64 cgrp_id)
{
cbw_dbg(" [%s/%d]", p->comm, p->pid);
return cbw_put_aside(ctx, vtime, cgrp_id);
}
static
bool cbw_has_backlogged_tasks(scx_cgroup_ctx_t *cgx)
{
scx_cgroup_llc_ctx_t *llcx;
int i;
if (!cgx || !cgx->has_llcx)
return false;
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx_with_id(cgx->id, i);
if (!llcx)
continue;
if (scx_atq_nr_queued(llcx->btq))
return true;
}
return false;
}
static
bool cbw_replenish_cgroup(scx_cgroup_ctx_t *cgx, u64 now)
{
s64 burst_credit = 0, debt = 0, budget;
bool period_end, was_throttled, keep_throttled = false;
if (cgx->nquota_ub == CBW_RUNTUME_INF)
goto out_no_replenish;
period_end = time_delta(now, cgx->period_start_clk) >= cgx->period;
if (period_end)
WRITE_ONCE(cgx->period_start_clk, now);
debt = max(cgx->runtime_total_last - cgx->period_budget, 0LL);
burst_credit = clamp((s64)cgx->nquota - cgx->runtime_total_last,
0LL, cgx->burst_remaining);
if (period_end)
WRITE_ONCE(cgx->burst_remaining, cgx->burst);
else
WRITE_ONCE(cgx->burst_remaining,
cgx->burst_remaining - burst_credit);
budget = (s64)cgx->nquota_ub + burst_credit - debt;
WRITE_ONCE(cgx->period_budget, budget);
keep_throttled = (budget <= 0);
if (cgx->runtime_total_last > 0) {
u64 rate = (u64)cgx->runtime_total_last * CBW_SCALE /
CBW_REPLENISH_PERIOD;
u64 avg = cgx->avg_consumption_rate;
cgx->avg_consumption_rate =
__calc_avg(avg, rate, CBW_CONSUMPTION_RATE_DECAY);
}
out_no_replenish:
smp_mb();
was_throttled = READ_ONCE(cgx->is_throttled);
WRITE_ONCE(cgx->is_throttled, keep_throttled);
return was_throttled || keep_throttled || cbw_has_backlogged_tasks(cgx);
}
__hidden
int scx_cgroup_bw_cancel(u64 ctx, u64 flags)
{
scx_task_common *taskc = (scx_task_common *)ctx;
int ret;
if (flags & SCX_CGROUP_BW_CANCEL_DROP)
return scx_atq_task_detach(taskc);
ret = scx_atq_task_fini(taskc);
return ret < 0 ? ret : 0;
}
static __always_inline
int cbw_cancel_with_hold(scx_task_common __arg_arena *taskc, bool *cancelled)
{
scx_atq_t *atq;
int ret;
*cancelled = false;
while (can_loop) {
atq = (scx_atq_t *)READ_ONCE(taskc->atq);
if (!atq || atq == (scx_atq_t *)SCX_ATQ_DEAD)
return 0;
if ((ret = scx_atq_lock(atq))) {
cbw_err("Failed to lock BTQ while moving task: %d", ret);
return ret;
}
if (READ_ONCE(taskc->atq) != atq) {
scx_atq_unlock(atq);
continue;
}
scx_atq_task_hold(taskc);
ret = scx_atq_remove_unlocked(atq, taskc);
scx_atq_unlock(atq);
if (ret) {
scx_atq_task_drop(taskc);
return ret;
}
*cancelled = true;
return 0;
}
return 0;
}
static struct cgroup *cbw_get_root_cgrp(void)
{
struct task_struct *task;
struct cgroup *cgrp, *root = NULL;
if (unlikely(!cbw_loader_tgid))
goto out;
task = bpf_task_from_pid((s32)cbw_loader_tgid);
if (!task)
goto out;
bpf_rcu_read_lock();
cgrp = task->cgroups->dfl_cgrp;
if (cgrp)
root = bpf_cgroup_ancestor(cgrp, 0);
bpf_rcu_read_unlock();
bpf_task_release(task);
out:
if (unlikely(!root)) {
cbw_err("Failed to resolve root cgroup via loader task "
"(tgid=%u)", cbw_loader_tgid);
}
return root;
}
static
int accounting_timerfn(void *map, int *key, struct bpf_timer *timer)
{
struct cgroup *root_cgrp;
u64 now, next_interval = CBW_ACCOUNTING_PERIOD_MAX;
int ret;
root_cgrp = cbw_get_root_cgrp();
if (unlikely(!root_cgrp))
goto rearm_out;
if (unlikely(cbw_top_half_running()))
goto release_out;
now = scx_bpf_now();
cbw_dbg("at %llu", now);
cbw_update_runtime_total_sloppy(root_cgrp);
next_interval = cbw_throttle_cgroups(root_cgrp);
smp_mb();
release_out:
bpf_cgroup_release(root_cgrp);
rearm_out:
if ((ret = bpf_timer_start(timer, next_interval, 0)))
cbw_err("Failed to re-arm accounting timer: %d", ret);
return 0;
}
static
int replenish_timerfn(void *map, int *key, struct bpf_timer *timer)
{
static int nr_throttled;
struct cgroup *root_cgrp, *cur_cgrp;
u64 *ids, now;
struct cgroup_subsys_state *root_css, *pos;
scx_cgroup_ctx_t *cur_cgx;
scx_cgroup_llc_ctx_t *cur_llcx;
const struct cpumask *online_mask;
s64 interval, jitter, period;
int i, ret;
s32 idle_cpu;
bool is_throttled;
scx_arena_subprog_init();
now = scx_bpf_now();
cbw_top_half_begin();
cbw_dbg("at %llu", now);
root_cgrp = cbw_get_root_cgrp();
if (!root_cgrp) {
cbw_top_half_abort();
goto rearm_out;
}
cbw_update_runtime_total_sloppy(root_cgrp);
bpf_rcu_read_lock();
root_css = &root_cgrp->self;
bpf_for_each(css, pos, root_css, BPF_CGROUP_ITER_DESCENDANTS_POST) {
cur_cgrp = pos->cgroup;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
continue;
}
if (cur_cgx->has_llcx) {
bpf_for(i, 0, TOPO_NR(LLC)) {
cur_llcx = cbw_get_llc_ctx(cur_cgrp, i);
if (cur_llcx)
WRITE_ONCE(cur_llcx->runtime_total, 0);
}
}
WRITE_ONCE(cur_cgx->runtime_total_last,
READ_ONCE(cur_cgx->runtime_total_sloppy));
WRITE_ONCE(cur_cgx->runtime_total_sloppy, 0);
}
bpf_rcu_read_unlock();
bpf_rcu_read_lock();
cbw_nr_cgroups = 0;
root_css = &root_cgrp->self;
bpf_for_each(css, pos, root_css, BPF_CGROUP_ITER_DESCENDANTS_PRE) {
cur_cgrp = pos->cgroup;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
continue;
}
ids = MEMBER_VPTR(cbw_cgroup_ids,
[cbw_nr_cgroups]);
if (!ids) {
cbw_err("Failed to fetch a cgroup table.");
continue;
}
*ids = cgroup_get_id(cur_cgrp);
cbw_nr_cgroups++;
}
bpf_rcu_read_unlock();
bpf_cgroup_release(root_cgrp);
cbw_dbg("Start replenish %llu cgroups.", cbw_nr_cgroups);
nr_throttled = 0;
bpf_for(i, 0, cbw_nr_cgroups) {
ids = MEMBER_VPTR(cbw_cgroup_ids, [i]);
if (!ids) {
cbw_err("Failed to fetch a cgroup table.");
continue;
}
cur_cgx = cbw_get_cgroup_ctx_with_id(ids[0]);
if (!cur_cgx) {
cbw_dbg("Failed to lookup a cgroup ctx: cgid%llu", ids[0]);
continue;
}
is_throttled = READ_ONCE(cur_cgx->is_throttled);
if (is_throttled) {
cur_cgx->nr_throttled_periods++;
if (cur_cgx->was_throttled &&
++cur_cgx->nr_consec_throttled_periods >
cur_cgx->max_consec_throttled_periods)
cur_cgx->max_consec_throttled_periods =
cur_cgx->nr_consec_throttled_periods;
} else {
cur_cgx->nr_consec_throttled_periods = 0;
}
cur_cgx->was_throttled = is_throttled;
if (cbw_replenish_cgroup(cur_cgx, now)) {
ids = MEMBER_VPTR(cbw_throttled_cgroup_ids,
[nr_throttled]);
if (!ids) {
cbw_err("Failed to fetch a throttled cgroup table.");
continue;
}
WRITE_ONCE(ids[0], cur_cgx->id);
nr_throttled++;
}
}
if (nr_throttled > 0) {
cbw_top_half_end(nr_throttled, true);
root_cgrp = cbw_get_root_cgrp();
if (root_cgrp) {
cbw_throttle_cgroups(root_cgrp);
bpf_cgroup_release(root_cgrp);
}
online_mask = scx_bpf_get_online_cpumask();
idle_cpu = scx_bpf_pick_idle_cpu(online_mask, SCX_PICK_IDLE_CORE);
if (idle_cpu == -EBUSY)
idle_cpu = scx_bpf_pick_idle_cpu(online_mask, 0);
if (idle_cpu >= 0)
scx_bpf_kick_cpu(idle_cpu, SCX_KICK_IDLE);
scx_bpf_put_cpumask(online_mask);
}
else {
cbw_top_half_end(0, false);
}
rearm_out:
interval = time_delta(now, cbw_last_replenish_at);
jitter = time_delta(interval, CBW_REPLENISH_PERIOD);
period = max(time_delta(CBW_REPLENISH_PERIOD, jitter), CBW_REPLENISH_PERIOD_MIN);
if ((ret = bpf_timer_start(timer, period, 0)))
cbw_err("Failed to re-arm replenish timer: %d", ret);
cbw_last_replenish_at = now;
return 0;
}
static
int cbw_drain_btq_batch(scx_cgroup_ctx_t *cgx,
scx_cgroup_llc_ctx_t *llcx)
{
scx_task_common *taskc;
scx_atq_t *btq;
int i;
for (i = 0; can_loop && i < CBW_REENQ_MAX_BATCH &&
(btq = READ_ONCE(llcx->btq)) &&
(taskc = (scx_task_common *)scx_atq_pop(btq, true)); i++) {
scx_cgroup_bw_enqueue_cb((u64)taskc);
scx_atq_task_drop(taskc);
cbw_dbg("cgid%llu", cgx->id);
}
return i;
}
static
int cbw_reenqueue_cgroup(scx_cgroup_ctx_t *cgx, u64 cgrp_id, u64 nuance)
{
scx_cgroup_llc_ctx_t *llcx;
int i, idx, nr_enq = 0;
if (!cgx->has_llcx)
return false;
cbw_dbg("cgid%llu", cgrp_id);
bpf_for(i, 0, TOPO_NR(LLC)) {
idx = (nuance + i) % TOPO_NR(LLC);
llcx = cbw_get_llc_ctx_with_id(cgrp_id, idx);
if (!llcx) {
cbw_err("Failed to lookup an LLC context: cgid%llu", cgrp_id);
continue;
}
if (cbw_cgroup_bw_throttled(cgrp_id, 0) == -EAGAIN)
break;
nr_enq += cbw_drain_btq_batch(cgx, llcx);
if (nr_enq >= CBW_REENQ_MAX_BATCH)
break;
}
return nr_enq;
}
static
bool cbw_has_throttled_tasks(union backlog_stat *stat)
{
stat->val = READ_ONCE(cbw_backlog_stat.val);
if (unlikely(stat->has_throttled_tasks)) {
stat->val = smp_load_acquire(&cbw_backlog_stat.val);
return stat->has_throttled_tasks;
}
return false;
}
__hidden
int scx_cgroup_bw_reenqueue(void)
{
union backlog_stat backlog_stat;
scx_cgroup_ctx_t *cur_cgx;
int i, idx, n, nr_enq = 0;
u64 nuance, nuance2, nr_tcgs;
u64 *ids, cur_cgrp_id;
if (likely(!cbw_has_throttled_tasks(&backlog_stat)))
return 0;
cbw_dbg();
nuance = bpf_get_prandom_u32();
nr_tcgs = backlog_stat.nr_throttled_cgroups;
bpf_for(i, 0, nr_tcgs) {
nuance2 = nuance + i;
idx = nuance2 % nr_tcgs;
ids = MEMBER_VPTR(cbw_throttled_cgroup_ids, [idx]);
if (!ids) {
cbw_err("Failed to fetch a throttled cgroup table.");
continue;
}
cur_cgrp_id = READ_ONCE(ids[0]);
if (cur_cgrp_id == 0)
continue;
cur_cgx = cbw_get_cgroup_ctx_with_id(cur_cgrp_id);
if (!cur_cgx) {
if (cur_cgrp_id == ROOT_CGID)
continue;
cbw_dbg("Failed to lookup a cgroup ctx: cgid%llu",
cur_cgrp_id);
__sync_bool_compare_and_swap(ids, cur_cgrp_id, 0);
continue;
}
n = cbw_reenqueue_cgroup(cur_cgx, cur_cgrp_id, nuance2);
if ((n == 0) && !cbw_top_half_running()) {
__sync_bool_compare_and_swap(ids, cur_cgrp_id, 0);
}
nr_enq += n;
if (nr_enq >= CBW_REENQ_MAX_BATCH)
break;
}
if ((nr_enq == 0) && !cbw_top_half_running()) {
cbw_update_backlog_stat_cas(&backlog_stat,
backlog_stat.rp_seq,
backlog_stat.nr_throttled_cgroups,
false);
}
return 0;
}
__hidden
int scx_cgroup_bw_is_cgroup_throttled(u64 cgrp_id)
{
scx_cgroup_ctx_t *cgx;
cgx = cbw_get_cgroup_ctx_with_id(cgrp_id);
if (!cgx)
return 0;
return READ_ONCE(cgx->is_throttled);
}
__hidden
int scx_cgroup_bw_is_task_throttled(u64 taskc)
{
scx_task_common *ctx = (scx_task_common *)taskc;
scx_atq_t *atq;
if (!ctx)
return false;
atq = READ_ONCE(ctx->atq);
return atq != NULL && atq != (scx_atq_t *)SCX_ATQ_DEAD;
}
__hidden __noinline
int scx_cgroup_bw_move(struct task_struct *p __arg_trusted, u64 task_ptr,
struct cgroup *from __arg_trusted,
struct cgroup *to __arg_trusted)
{
volatile scx_task_cgroup_bw_t *tc;
scx_task_common *taskc = (scx_task_common *)task_ptr;
bool cancelled;
int ret;
scx_arena_subprog_init();
tc = (scx_task_cgroup_bw_t *)taskc;
if (tc) {
__sync_lock_test_and_set(&tc->cgx_raw, 0);
__sync_lock_test_and_set(&tc->llcx_raw, 0);
}
if (!scx_cgroup_bw_is_task_throttled(task_ptr))
return 0;
ret = cbw_cancel_with_hold(taskc, &cancelled);
if (ret) {
cbw_err("Fail to cancel a throttled task (%s:%d) from a cgroup (cgid%llu): %d",
p->comm, p->pid, cgroup_get_id(from), ret);
return ret;
}
if (!cancelled)
return 0;
if ((ret = cbw_put_aside(task_ptr, p->scx.dsq_vtime, cgroup_get_id(to)))) {
if (ret == -ESRCH) {
cbw_warn("Destination cgroup unavailable while moving throttled task (%s:%d) to cgid%llu",
p->comm, p->pid, cgroup_get_id(to));
}
if (!(ret = cbw_put_aside(task_ptr, 0, ROOT_CGID)))
goto out_drop;
cbw_err("Fail to put aside a throttled task (%s:%d) to a cgroup (cgid%llu): %d",
p->comm, p->pid, cgroup_get_id(to), ret);
}
out_drop:
scx_atq_task_drop(taskc);
return ret;
}
static __noinline
int cbw_dump_cgroup(struct cgroup *cgrp __arg_trusted, bool indent)
{
static const char indent_strs[][64] = {
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
};
static const u32 indent_max = sizeof(indent_strs) / sizeof(indent_strs[0]);
scx_cgroup_llc_ctx_t *llcx;
int i, nr_throttled_tasks = 0;
scx_cgroup_ctx_t *cgx;
const char *indent_str;
scx_atq_t *btq;
char name[64];
scx_arena_subprog_init();
cgx = cbw_get_cgroup_ctx(cgrp);
if (!cgx) {
cbw_dbg("Failed to lookup a cgroup context: %llu", cgroup_get_id(cgrp));
return -ESRCH;
}
indent_str = indent_strs[ clamp((u32)cgrp->level, 0, indent_max - 1) ];
bpf_probe_read_kernel_str(name, sizeof(name), BPF_CORE_READ(cgrp->kn, name));
bpf_printk("%s +-- %s (id: %llu, level: %d)", indent_str,
name, cgroup_get_id(cgrp), (u32)cgrp->level);
if (cgx->nquota_ub == CBW_RUNTUME_INF)
return 0;
if (cgx->has_llcx) {
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx(cgrp, i);
if (!llcx || !(btq = READ_ONCE(llcx->btq)))
continue;
nr_throttled_tasks += scx_atq_nr_queued(btq);
}
}
bpf_printk("%s \\_ quota: %llu/%llu/%llu, period: %llu, burst: %llu", indent_str,
cgx->quota, cgx->period, cgx->burst);
bpf_printk("%s \\_ nquota: %llu, nquota_ub: %llu, has_llcx: %d", indent_str,
cgx->nquota, cgx->nquota_ub, cgx->has_llcx);
bpf_printk("%s \\_ is_throttled: %d, nr_throttled_periods: %d/%d (%u/%u), nr_throttled_tasks: %d", indent_str,
cgx->is_throttled,
cgx->nr_throttled_periods, READ_ONCE(cbw_backlog_stat.rp_seq) / 2,
cgx->nr_consec_throttled_periods, cgx->max_consec_throttled_periods,
nr_throttled_tasks);
bpf_printk("%s \\_ period_budget: %lld, burst_remaining: %lld", indent_str,
cgx->period_budget, cgx->burst_remaining);
bpf_printk("%s \\_ runtime_total_sloppy: %lld, runtime_total_last: %lld", indent_str,
cgx->runtime_total_sloppy, cgx->runtime_total_last);
return 0;
}
__hidden
int scx_cgroup_bw_dump(u64 cgrp_id, bool descendent, bool accurate, bool indent)
{
struct cgroup_subsys_state *start_css, *pos;
struct cgroup *start_cgrp, *cur_cgrp;
if (cgrp_id == ROOT_CGID)
start_cgrp = cbw_get_root_cgrp();
else
start_cgrp = bpf_cgroup_from_id(cgrp_id);
if (!start_cgrp) {
cbw_dbg("Failed to fetch a cgroup pointer: cgid%llu", cgrp_id);
return -ESRCH;
}
if (accurate)
cbw_update_runtime_total_sloppy(start_cgrp);
if (!descendent) {
cbw_dump_cgroup(start_cgrp, indent);
goto release_out;
}
bpf_rcu_read_lock();
start_css = &start_cgrp->self;
bpf_for_each(css, pos, start_css, BPF_CGROUP_ITER_DESCENDANTS_PRE) {
cur_cgrp = pos->cgroup;
cbw_dump_cgroup(cur_cgrp, indent);
}
bpf_rcu_read_unlock();
release_out:
bpf_cgroup_release(start_cgrp);
return 0;
}