mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
scx_bpf_cid_override() predates the cid-form arena transition and takes its arrays as verifier-checked mem+size buffers, forcing scx_qmap to keep the cpu_to_cid and shard_start arrays in writable bss while the rest of its state lives in the arena. Unify on arena arguments before cid-form schedulers start seeing real use. BPF now translates between BPF and kernel arena addresses for __arena arguments. Take the arrays as __arena arguments, with the counts passed in entries. The counts now size the snapshot copies and are bounds-checked before them. scx_qmap moves the arrays into struct qmap_arena. As the arena is mmapped at load, the loader populates them between load and attach instead of before load. The arena argument address translation is currently implemented only on x86-64. Schedulers calling this kfunc load only there for now. Signed-off-by: Tejun Heo <tj@kernel.org>
502 lines
16 KiB
C
502 lines
16 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
|
|
* Copyright (c) 2022 Tejun Heo <tj@kernel.org>
|
|
* Copyright (c) 2022 David Vernet <dvernet@meta.com>
|
|
*/
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
#include <signal.h>
|
|
#include <libgen.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <bpf/bpf.h>
|
|
#include <scx/common.h>
|
|
#include "scx_qmap.h"
|
|
#include "scx_qmap.bpf.skel.h"
|
|
|
|
const char help_fmt[] =
|
|
"A simple five-level FIFO queue sched_ext scheduler.\n"
|
|
"\n"
|
|
"It also demonstrates hierarchical sub-scheduling: a scheduler can hand some\n"
|
|
"of its cpus to a child cgroup that runs its own scheduler. Run one qmap as\n"
|
|
"the parent, then run another qmap on a child cgroup with -c to attach it\n"
|
|
"beneath the parent.\n"
|
|
"\n"
|
|
"The policy below is deliberately simplistic and the resulting behavior can\n"
|
|
"look odd. qmap is a demo: it exists to exercise every sub-scheduling\n"
|
|
"primitive the kernel offers with as little code as possible, not to schedule\n"
|
|
"well.\n"
|
|
"\n"
|
|
"A parent divides the full cpus it holds among itself and its children in\n"
|
|
"proportion to cpu.weight. The cpus left over by rounding are time-shared,\n"
|
|
"handed to each participant in turn every -R ms. A cpu a scheduler only\n"
|
|
"holds a time-share of is never handed further down, and a parent left with\n"
|
|
"no full cpu of its own shuts its children down.\n"
|
|
"\n"
|
|
"See the top-of-file comment in .bpf.c for the design.\n"
|
|
"\n"
|
|
"Usage: %s [-s SLICE_US] [-e COUNT] [-t COUNT] [-T COUNT] [-l COUNT] [-b COUNT]\n"
|
|
" [-N COUNT] [-P] [-M] [-H] [-c CG_PATH] [-d PID] [-D LEN] [-S] [-p] [-I]\n"
|
|
" [-F COUNT] [-i SEC] [-R MS] [-J MODE] [-v]\n"
|
|
"\n"
|
|
" -s SLICE_US Override slice duration\n"
|
|
" -e COUNT Trigger scx_bpf_error() after COUNT enqueues\n"
|
|
" -t COUNT Stall every COUNT'th user thread\n"
|
|
" -T COUNT Stall every COUNT'th kernel thread\n"
|
|
" -N COUNT Size of the task_ctx arena slab (default 16384)\n"
|
|
" -l COUNT Trigger dispatch infinite looping after COUNT dispatches\n"
|
|
" -b COUNT Dispatch upto COUNT tasks together\n"
|
|
" -P Print out DSQ content and event counters to trace_pipe every second\n"
|
|
" -M Print out debug messages to trace_pipe\n"
|
|
" -H Boost nice -20 tasks in SHARED_DSQ, use with -b\n"
|
|
" -c CG_PATH Cgroup path to attach as sub-scheduler, must run parent scheduler first\n"
|
|
" -d PID Disallow a process from switching into SCHED_EXT (-1 for self)\n"
|
|
" -D LEN Set scx_exit_info.dump buffer length\n"
|
|
" -S Suppress qmap-specific debug dump\n"
|
|
" -p Switch only tasks on SCHED_EXT policy instead of all\n"
|
|
" -I Turn on SCX_OPS_ALWAYS_ENQ_IMMED\n"
|
|
" -F COUNT IMMED stress: force every COUNT'th enqueue to a busy local DSQ (use with -I)\n"
|
|
" -C MODE cid-override test (shuffle|bad-dup|bad-range|bad-mono)\n"
|
|
" -i SEC Stats interval, seconds (default 5)\n"
|
|
" -R MS Round-robin period for time-shared cpus, ms (default 200)\n"
|
|
" -J MODE Fault injection (wrong-cid: dispatch to a cid not held,\n"
|
|
" init-fail/cgrp-init-fail: fail init_task/cpuctl_init for\n"
|
|
" \"qmfail*\" comms/cgroups)\n"
|
|
" -B PPT Rescue bandwidth in parts per thousand, 0 disables (root only, default 20)\n"
|
|
" -q US Rescue batch quantum in microseconds (root only, default 5000)\n"
|
|
" -v Print libbpf debug messages\n"
|
|
" -h Display this help and exit\n";
|
|
|
|
static bool verbose;
|
|
static volatile int exit_req;
|
|
|
|
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
|
|
{
|
|
if (level == LIBBPF_DEBUG && !verbose)
|
|
return 0;
|
|
return vfprintf(stderr, format, args);
|
|
}
|
|
|
|
static void sigint_handler(int dummy)
|
|
{
|
|
exit_req = 1;
|
|
}
|
|
|
|
static void invoke_flush_alloc(struct scx_qmap *skel)
|
|
{
|
|
LIBBPF_OPTS(bpf_test_run_opts, opts);
|
|
|
|
bpf_prog_test_run_opts(bpf_program__fd(skel->progs.flush_alloc), &opts);
|
|
}
|
|
|
|
/* previous counter snapshots for the per-interval hier stats */
|
|
struct hier_prev {
|
|
u64 alloc_ns[MAX_SUB_SCHEDS];
|
|
u64 self_alloc_ns;
|
|
u64 alloc_window_ns;
|
|
u64 nr_dsps[MAX_SUB_SCHEDS];
|
|
u64 nr_reenq_cap;
|
|
u64 nr_reenq_immed;
|
|
u64 nr_inject_attempts;
|
|
u64 nr_rescue_dsp;
|
|
};
|
|
|
|
/* current wall-clock time as "HH:MM:SS" for the startup and interval headers */
|
|
static const char *tstamp(char *buf, size_t sz)
|
|
{
|
|
time_t now = time(NULL);
|
|
|
|
strftime(buf, sz, "%H:%M:%S", localtime(&now));
|
|
return buf;
|
|
}
|
|
|
|
/* format the cids whose cid_owner[] matches @owner as "0-3,8", "-" if none */
|
|
static void format_cid_ranges(struct qmap_arena *qa, s32 owner, char *buf, size_t sz)
|
|
{
|
|
u32 nr = qa->nr_cids, cid;
|
|
size_t off = 0;
|
|
s32 start = -1;
|
|
|
|
buf[0] = '\0';
|
|
for (cid = 0; cid <= nr; cid++) {
|
|
bool match = cid < nr && qa->part.cid_owner[cid] == owner;
|
|
int n;
|
|
|
|
if (match) {
|
|
if (start < 0)
|
|
start = cid;
|
|
continue;
|
|
}
|
|
if (start < 0)
|
|
continue;
|
|
|
|
if (start == (s32)cid - 1)
|
|
n = snprintf(buf + off, sz - off, "%s%d",
|
|
off ? "," : "", start);
|
|
else
|
|
n = snprintf(buf + off, sz - off, "%s%d-%d",
|
|
off ? "," : "", start, cid - 1);
|
|
if (n < 0 || (size_t)n >= sz - off) {
|
|
strcpy(&buf[sz - 4], "...");
|
|
return;
|
|
}
|
|
off += n;
|
|
start = -1;
|
|
}
|
|
if (!off)
|
|
strcpy(buf, "-");
|
|
}
|
|
|
|
/* partition summary + one row per sched: weight, cpus, dispatch rate, cids */
|
|
static void print_hier(struct qmap_arena *qa, struct hier_prev *prev, u64 own_cgid)
|
|
{
|
|
char ranges[128], who[16];
|
|
const char *rr = "-";
|
|
double secs;
|
|
u32 i;
|
|
|
|
/*
|
|
* account_alloc() bumps alloc_window_ns together with the per-owner
|
|
* counters, so dividing by the same window yields exact cid counts.
|
|
*/
|
|
secs = (qa->alloc_window_ns - prev->alloc_window_ns) / 1e9;
|
|
prev->alloc_window_ns = qa->alloc_window_ns;
|
|
|
|
/* resolve the live shared-pool holder */
|
|
if (qa->part.nr_shared && qa->part.nr_rr) {
|
|
u64 cgid = qa->part.rr_slots[qa->part.rr_pos];
|
|
|
|
rr = "self";
|
|
if (cgid) {
|
|
rr = "?";
|
|
for (i = 0; i < MAX_SUB_SCHEDS; i++) {
|
|
if (qa->sub_sched_ctxs[i].cgroup_id == cgid) {
|
|
snprintf(who, sizeof(who), "sub%u", i);
|
|
rr = who;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
format_cid_ranges(qa, CID_SHARED, ranges, sizeof(ranges));
|
|
printf("hier : nsub=%llu excl=%u shared=%s rr=%s reenq cap/immed +%llu/+%llu inj=+%llu rescue=+%llu\n",
|
|
(unsigned long long)qa->nr_sub_scheds, qa->part.nr_excl, ranges, rr,
|
|
(unsigned long long)(qa->nr_reenq_cap - prev->nr_reenq_cap),
|
|
(unsigned long long)(qa->nr_reenq_immed - prev->nr_reenq_immed),
|
|
(unsigned long long)(qa->nr_inject_attempts - prev->nr_inject_attempts),
|
|
(unsigned long long)(qa->nr_rescue_dsp - prev->nr_rescue_dsp));
|
|
prev->nr_reenq_cap = qa->nr_reenq_cap;
|
|
prev->nr_reenq_immed = qa->nr_reenq_immed;
|
|
prev->nr_inject_attempts = qa->nr_inject_attempts;
|
|
prev->nr_rescue_dsp = qa->nr_rescue_dsp;
|
|
|
|
printf("hier : %-4s %10s %4s %6s %8s %s\n",
|
|
"", "cgroup", "w", "alloc", "disp/s", "cids");
|
|
|
|
format_cid_ranges(qa, CID_SELF, ranges, sizeof(ranges));
|
|
printf("hier : %-4s %10llu %4u %6.2f %8s %s\n", "self",
|
|
(unsigned long long)own_cgid, 100,
|
|
secs > 0 ? (qa->self_alloc_ns - prev->self_alloc_ns) / (secs * 1e9) : 0.0,
|
|
"-", ranges);
|
|
prev->self_alloc_ns = qa->self_alloc_ns;
|
|
|
|
for (i = 0; i < MAX_SUB_SCHEDS; i++) {
|
|
struct sub_sched_ctx *sc = &qa->sub_sched_ctxs[i];
|
|
|
|
if (!sc->cgroup_id)
|
|
continue;
|
|
|
|
snprintf(who, sizeof(who), "sub%u", i);
|
|
format_cid_ranges(qa, i, ranges, sizeof(ranges));
|
|
printf("hier : %-4s %10llu %4u %6.2f %8.1f %s\n", who,
|
|
(unsigned long long)sc->cgroup_id, sc->weight,
|
|
secs > 0 ? (qa->alloc_ns[i] - prev->alloc_ns[i]) / (secs * 1e9) : 0.0,
|
|
secs > 0 ? (sc->nr_dsps - prev->nr_dsps[i]) / secs : 0.0,
|
|
ranges);
|
|
prev->alloc_ns[i] = qa->alloc_ns[i];
|
|
prev->nr_dsps[i] = sc->nr_dsps;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct scx_qmap *skel;
|
|
struct bpf_link *link;
|
|
struct qmap_arena *qa;
|
|
u32 test_error_cnt = 0;
|
|
u64 ecode;
|
|
int opt, stats_intv = 5, i, round_robin_ms = 200;
|
|
struct hier_prev hprev = {};
|
|
const char *sub_cg_path = NULL;
|
|
char tbuf[32];
|
|
u32 inject_mode = 0;
|
|
u64 own_cgid = 0;
|
|
s32 cid_override_shard_sz = 4;
|
|
|
|
libbpf_set_print(libbpf_print_fn);
|
|
signal(SIGINT, sigint_handler);
|
|
signal(SIGTERM, sigint_handler);
|
|
|
|
if (libbpf_num_possible_cpus() > SCX_QMAP_MAX_CPUS) {
|
|
fprintf(stderr,
|
|
"scx_qmap: %d possible CPUs exceeds compile-time cap %d; "
|
|
"rebuild with larger SCX_QMAP_MAX_CPUS\n",
|
|
libbpf_num_possible_cpus(), SCX_QMAP_MAX_CPUS);
|
|
return 1;
|
|
}
|
|
restart:
|
|
optind = 1;
|
|
skel = SCX_OPS_CID_OPEN(qmap_ops, scx_qmap);
|
|
|
|
skel->rodata->slice_ns = __COMPAT_ENUM_OR_ZERO("scx_public_consts", "SCX_SLICE_DFL");
|
|
skel->rodata->max_tasks = 16384;
|
|
|
|
while ((opt = getopt(argc, argv,
|
|
"s:e:t:T:l:b:N:PMHc:d:D:SpIF:C:i:R:J:B:q:vh")) != -1) {
|
|
switch (opt) {
|
|
case 's':
|
|
skel->rodata->slice_ns = strtoull(optarg, NULL, 0) * 1000;
|
|
break;
|
|
case 'e':
|
|
test_error_cnt = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 't':
|
|
skel->rodata->stall_user_nth = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'T':
|
|
skel->rodata->stall_kernel_nth = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'l':
|
|
skel->rodata->dsp_inf_loop_after = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'b':
|
|
skel->rodata->dsp_batch = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'N':
|
|
skel->rodata->max_tasks = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'P':
|
|
skel->rodata->print_dsqs_and_events = true;
|
|
break;
|
|
case 'M':
|
|
skel->rodata->print_msgs = true;
|
|
break;
|
|
case 'H':
|
|
skel->rodata->highpri_boosting = true;
|
|
break;
|
|
case 'c': {
|
|
struct stat st;
|
|
if (stat(optarg, &st) < 0) {
|
|
perror("stat");
|
|
return 1;
|
|
}
|
|
skel->struct_ops.qmap_ops->sub_cgroup_id = st.st_ino;
|
|
skel->rodata->sub_cgroup_id = st.st_ino;
|
|
own_cgid = st.st_ino;
|
|
sub_cg_path = optarg;
|
|
break;
|
|
}
|
|
case 'd':
|
|
skel->rodata->disallow_tgid = strtol(optarg, NULL, 0);
|
|
if (skel->rodata->disallow_tgid < 0)
|
|
skel->rodata->disallow_tgid = getpid();
|
|
break;
|
|
case 'D':
|
|
skel->struct_ops.qmap_ops->exit_dump_len = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'S':
|
|
skel->rodata->suppress_dump = true;
|
|
break;
|
|
case 'p':
|
|
skel->struct_ops.qmap_ops->flags |= SCX_OPS_SWITCH_PARTIAL;
|
|
break;
|
|
case 'I':
|
|
skel->struct_ops.qmap_ops->flags |= SCX_OPS_ALWAYS_ENQ_IMMED;
|
|
break;
|
|
case 'F':
|
|
skel->rodata->immed_stress_nth = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'C': {
|
|
u32 nr_cpus = libbpf_num_possible_cpus();
|
|
u32 mode;
|
|
|
|
if (!strcmp(optarg, "shuffle"))
|
|
mode = QMAP_CID_OVR_SHUFFLE;
|
|
else if (!strcmp(optarg, "bad-dup"))
|
|
mode = QMAP_CID_OVR_BAD_DUP;
|
|
else if (!strcmp(optarg, "bad-range"))
|
|
mode = QMAP_CID_OVR_BAD_RANGE;
|
|
else if (!strcmp(optarg, "bad-mono"))
|
|
mode = QMAP_CID_OVR_BAD_MONO;
|
|
else {
|
|
fprintf(stderr, "unknown cid-override mode '%s'\n", optarg);
|
|
return 1;
|
|
}
|
|
skel->rodata->cid_override_mode = mode;
|
|
cid_override_shard_sz = 4;
|
|
|
|
/*
|
|
* bad-mono needs >= 3 shards to build a 0-based but
|
|
* non-monotonic shard_start. Shrink the shard size so
|
|
* the test runs on any machine with >= 3 cpus.
|
|
*/
|
|
if (mode == QMAP_CID_OVR_BAD_MONO) {
|
|
if (nr_cpus < 3) {
|
|
fprintf(stderr, "bad-mono needs >= 3 cpus (have %u)\n",
|
|
nr_cpus);
|
|
return 1;
|
|
}
|
|
cid_override_shard_sz = nr_cpus / 3;
|
|
}
|
|
|
|
/* shards of shard_sz each */
|
|
skel->rodata->cid_override_nr_shards =
|
|
(nr_cpus + cid_override_shard_sz - 1) / cid_override_shard_sz;
|
|
break;
|
|
}
|
|
case 'i':
|
|
stats_intv = atoi(optarg);
|
|
if (stats_intv < 1)
|
|
stats_intv = 1;
|
|
break;
|
|
case 'R':
|
|
round_robin_ms = atoi(optarg);
|
|
if (round_robin_ms < 10)
|
|
round_robin_ms = 10;
|
|
break;
|
|
case 'J':
|
|
if (!strcmp(optarg, "wrong-cid"))
|
|
inject_mode = QMAP_INJ_WRONG_CID;
|
|
else if (!strcmp(optarg, "init-fail"))
|
|
inject_mode = QMAP_INJ_INIT_FAIL;
|
|
else if (!strcmp(optarg, "cgrp-init-fail"))
|
|
inject_mode = QMAP_INJ_CGRP_INIT_FAIL;
|
|
else
|
|
inject_mode = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'B': {
|
|
u32 ppt = strtoul(optarg, NULL, 0);
|
|
|
|
if (!ppt)
|
|
ppt = __COMPAT_ENUM_OR_ZERO("scx_consts", "SCX_RESCUE_DISABLE");
|
|
skel->struct_ops.qmap_ops->rescue_bandwidth_ppt = ppt;
|
|
break;
|
|
}
|
|
case 'q':
|
|
skel->struct_ops.qmap_ops->rescue_quantum_us = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'v':
|
|
verbose = true;
|
|
break;
|
|
default:
|
|
fprintf(stderr, help_fmt, basename(argv[0]));
|
|
return opt != 'h';
|
|
}
|
|
}
|
|
|
|
skel->rodata->round_robin_ns = (u64)round_robin_ms * 1000000;
|
|
|
|
SCX_OPS_LOAD(skel, qmap_ops, scx_qmap, uei);
|
|
|
|
qa = &skel->arena->qa;
|
|
|
|
/*
|
|
* The cid-override arrays live in the arena, which is mmapped at load.
|
|
* Populate them before qmap_init_cids() consumes them at attach.
|
|
*/
|
|
if (skel->rodata->cid_override_mode) {
|
|
u32 mode = skel->rodata->cid_override_mode;
|
|
u32 nr_cpus = libbpf_num_possible_cpus();
|
|
u32 i;
|
|
|
|
/* shuffle: reversed cpu_to_cid; others: identity */
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (mode == QMAP_CID_OVR_SHUFFLE)
|
|
qa->cid_override_cpu_to_cid[i] = nr_cpus - 1 - i;
|
|
else
|
|
qa->cid_override_cpu_to_cid[i] = i;
|
|
}
|
|
if (mode == QMAP_CID_OVR_BAD_DUP && nr_cpus >= 2)
|
|
qa->cid_override_cpu_to_cid[1] = 0;
|
|
if (mode == QMAP_CID_OVR_BAD_RANGE)
|
|
qa->cid_override_cpu_to_cid[0] = (s32)nr_cpus;
|
|
|
|
for (i = 0; i < skel->rodata->cid_override_nr_shards; i++)
|
|
qa->cid_override_shard_start[i] = i * cid_override_shard_sz;
|
|
|
|
if (mode == QMAP_CID_OVR_BAD_MONO) {
|
|
/* swap [1] and [2] to break monotonicity */
|
|
s32 tmp = qa->cid_override_shard_start[1];
|
|
qa->cid_override_shard_start[1] = qa->cid_override_shard_start[2];
|
|
qa->cid_override_shard_start[2] = tmp;
|
|
}
|
|
}
|
|
|
|
link = SCX_OPS_ATTACH(skel, qmap_ops, scx_qmap);
|
|
|
|
qa->test_error_cnt = test_error_cnt;
|
|
qa->inject_mode = inject_mode;
|
|
|
|
if (sub_cg_path)
|
|
printf("%s scx_qmap started: sub-scheduler on %s, stats every %ds\n",
|
|
tstamp(tbuf, sizeof(tbuf)), sub_cg_path, stats_intv);
|
|
else
|
|
printf("%s scx_qmap started: root scheduler, stats every %ds\n",
|
|
tstamp(tbuf, sizeof(tbuf)), stats_intv);
|
|
fflush(stdout);
|
|
|
|
while (!exit_req && !UEI_EXITED(skel, uei)) {
|
|
long nr_enqueued = qa->nr_enqueued;
|
|
long nr_dispatched = qa->nr_dispatched;
|
|
|
|
printf("---- %s ----\n",
|
|
tstamp(tbuf, sizeof(tbuf)));
|
|
printf("stats : enq=%lu dsp=%lu delta=%ld reenq/cid0=%llu/%llu deq=%llu core=%llu enq_ddsp=%llu\n",
|
|
nr_enqueued, nr_dispatched, nr_enqueued - nr_dispatched,
|
|
(unsigned long long)qa->nr_reenqueued,
|
|
(unsigned long long)qa->nr_reenqueued_cid0,
|
|
(unsigned long long)qa->nr_dequeued,
|
|
(unsigned long long)qa->nr_core_sched_execed,
|
|
(unsigned long long)qa->nr_ddsp_from_enq);
|
|
printf(" exp_local=%llu exp_remote=%llu exp_timer=%llu exp_lost=%llu\n",
|
|
(unsigned long long)qa->nr_expedited_local,
|
|
(unsigned long long)qa->nr_expedited_remote,
|
|
(unsigned long long)qa->nr_expedited_from_timer,
|
|
(unsigned long long)qa->nr_expedited_lost);
|
|
if (__COMPAT_has_ksym("scx_bpf_cidperf_cur"))
|
|
printf("cpuperf: cur min/avg/max=%u/%u/%u target min/avg/max=%u/%u/%u\n",
|
|
qa->cpuperf_min,
|
|
qa->cpuperf_avg,
|
|
qa->cpuperf_max,
|
|
qa->cpuperf_target_min,
|
|
qa->cpuperf_target_avg,
|
|
qa->cpuperf_target_max);
|
|
|
|
invoke_flush_alloc(skel);
|
|
print_hier(qa, &hprev, own_cgid);
|
|
fflush(stdout);
|
|
|
|
for (i = 0; i < stats_intv && !exit_req && !UEI_EXITED(skel, uei); i++)
|
|
sleep(1);
|
|
}
|
|
|
|
bpf_link__destroy(link);
|
|
ecode = UEI_REPORT(skel, uei);
|
|
scx_qmap__destroy(skel);
|
|
|
|
if (!exit_req && UEI_ECODE_RESTART(ecode))
|
|
goto restart;
|
|
return 0;
|
|
}
|