mirror of
https://github.com/elwrcl/scx_soryu.git
synced 2026-10-31 10:58:03 +03:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
183b2fecaf | ||
|
|
f67d41925e | ||
|
|
c614b88efb |
No files matched your search
@@ -0,0 +1,70 @@
|
|||||||
|
# Linux kernel style, matching src/bpf/main.bpf.c as written.
|
||||||
|
#
|
||||||
|
# Without this file clangd falls back to LLVM style (2-space indent, braces on
|
||||||
|
# the same line) and editor.formatOnSave rewrites the whole file on first save.
|
||||||
|
# Derived from the kernel tree's own .clang-format, trimmed to what this
|
||||||
|
# single-file BPF source actually needs.
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
# Indentation: hard tabs, 8 wide, as in the kernel.
|
||||||
|
UseTab: Always
|
||||||
|
TabWidth: 8
|
||||||
|
IndentWidth: 8
|
||||||
|
ContinuationIndentWidth: 8
|
||||||
|
ConstructorInitializerIndentWidth: 8
|
||||||
|
|
||||||
|
ColumnLimit: 0
|
||||||
|
|
||||||
|
# Kernel brace placement: functions get a brace on the next line, everything
|
||||||
|
# else keeps it on the same line.
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterFunction: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterEnum: true
|
||||||
|
AfterControlStatement: Never
|
||||||
|
BeforeElse: false
|
||||||
|
BeforeWhile: false
|
||||||
|
|
||||||
|
AlignEscapedNewlines: Left
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: true
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
Cpp11BracedListStyle: false
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentGotoLabels: false
|
||||||
|
IndentPPDirectives: None
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
PointerAlignment: Right
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
SortIncludes: Never
|
||||||
|
ReflowComments: false
|
||||||
|
|
||||||
|
# BPF/kernel macros that take a block or act as control flow; without these
|
||||||
|
# clang-format indents the bodies as if they were expressions.
|
||||||
|
ForEachMacros:
|
||||||
|
- bpf_for
|
||||||
|
- bpf_for_each
|
||||||
|
- bpf_repeat
|
||||||
|
- list_for_each_entry
|
||||||
|
- list_for_each_entry_safe
|
||||||
|
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AlignConsecutiveMacros: false
|
||||||
@@ -4,3 +4,6 @@
|
|||||||
src/bpf/vmlinux.h
|
src/bpf/vmlinux.h
|
||||||
/nixos.qcow2
|
/nixos.qcow2
|
||||||
/Cargo.lock.orig
|
/Cargo.lock.orig
|
||||||
|
/compile_commands.json
|
||||||
|
/.direnv/
|
||||||
|
/.cache/
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
// main.bpf.c is hand-formatted kernel style with tab-aligned trailing
|
||||||
|
// comments, which clang-format cannot reproduce. The global
|
||||||
|
// editor.formatOnSave would rewrite ~200 lines on every save, so it is off
|
||||||
|
// for C here. .clang-format still exists for deliberate formatting of new
|
||||||
|
// code (right-click -> Format Selection).
|
||||||
|
"[c]": {
|
||||||
|
"editor.formatOnSave": false
|
||||||
|
},
|
||||||
|
"[cpp]": {
|
||||||
|
"editor.formatOnSave": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# `--gpu-boost-cap`: don't deboost the render thread
|
||||||
|
|
||||||
|
A GPU feeder is a task that submitted GPU work within the last 100 ms, as seen
|
||||||
|
by a `tp_btf/i915_request_add` hook: a game's render thread, the compositor,
|
||||||
|
Xwayland. BMQ deboosts a task for burning whole slices, which is exactly what a
|
||||||
|
render thread does on a heavy frame, and strict priority then queues it behind
|
||||||
|
every better level. `--gpu-boost-cap` stops a recent feeder from being
|
||||||
|
deboosted past the given boost.
|
||||||
|
|
||||||
|
**Result: `-1` removes most of the stutter, and is the default.**
|
||||||
|
|
||||||
|
The idea came from reading the Infinity scheduler (galpt/infinity-scheduler),
|
||||||
|
whose v4.8 couples CPU and GPU accounting. Its GPU half patches `drm_sched`,
|
||||||
|
which i915 does not use, so none of that applies to Ivy Bridge. What carried
|
||||||
|
over was the question of whether the CPU side lets the GPU's feeders wait.
|
||||||
|
|
||||||
|
## What was run
|
||||||
|
|
||||||
|
Half-Life 2 (native GL, Mesa crocus), the same 60 s camera pan each round, on
|
||||||
|
the Ivy Bridge target (i5-3210M, 2c/4t, HD 4000, kernel 7.2.8). Frame times
|
||||||
|
from MangoHud's CSV log; scheduler numbers from `SORYU_LOG` at one report per
|
||||||
|
second. Both settings ran from the same binary, alternating off, on, off, on,
|
||||||
|
so the knob is the only thing that differs.
|
||||||
|
|
||||||
|
## Finding the cause
|
||||||
|
|
||||||
|
The first instrument only saw wake->run, and feeders looked fine there: p99
|
||||||
|
around 0.5-0.8 ms with or without load. The render thread's problem is not
|
||||||
|
waking up. It rarely sleeps mid-frame; it burns its slice, stops still
|
||||||
|
runnable, and waits to get the CPU back -- a path that never passes
|
||||||
|
`ops.runnable` and so was invisible.
|
||||||
|
|
||||||
|
Measuring that requeue wait, split by the feeder's boost at the moment it
|
||||||
|
stopped (one 60 s pan, knob off):
|
||||||
|
|
||||||
|
| | p99, median second | p99.9, worst second | seconds with p99.9 > 4 ms |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| boosted (boost < 0) | 0.92 ms | 3.1 ms | 0 of 72 |
|
||||||
|
| deboosted (boost >= 0) | 1.0 ms | **25.2 ms** | **6 of 61** |
|
||||||
|
|
||||||
|
Boosted, the wait never went past a few slices. Deboosted, it reached two
|
||||||
|
frames at 85 fps. Feeder boost spent that run at a median of -7.6, so the
|
||||||
|
render thread was crossing into the deboosted class constantly.
|
||||||
|
|
||||||
|
## Frames, idle desktop
|
||||||
|
|
||||||
|
| | median frame | p99.9 frame | frames > 2x median | frames > 25 ms | 0.1% low |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| off, round 1 | 13.2 ms | 44.0 ms | 15 | 27 | 22.6 fps |
|
||||||
|
| **-1, round 1** | **12.5 ms** | **26.1 ms** | **6** | **6** | **37.8 fps** |
|
||||||
|
| off, round 2 | 12.7 ms | 43.2 ms | 23 | 26 | 21.7 fps |
|
||||||
|
| **-1, round 2** | **12.5 ms** | **25.7 ms** | **8** | **8** | **37.9 fps** |
|
||||||
|
|
||||||
|
The two rounds of each setting agree closely. Frames over 25 ms fell by about
|
||||||
|
70%.
|
||||||
|
|
||||||
|
On the scheduler side, deboosted feeder requeues went from ~785/s to ~1/s,
|
||||||
|
while the cap itself fired only ~35 times a second -- a small intervention.
|
||||||
|
|
||||||
|
## Under load
|
||||||
|
|
||||||
|
The same pan with `stress-ng --cpu 4 --timeout 60s` running alongside:
|
||||||
|
|
||||||
|
| | avg fps | 1% low | 0.1% low | stress-ng ops/s (real) | ops/s (usr+sys) |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| off | 60.8 | 30.0 | 21.2 | 1026 | 778 |
|
||||||
|
| **-1** | **73.3** | **46.6** | **36.8** | 750 | 753 |
|
||||||
|
|
||||||
|
## The cost
|
||||||
|
|
||||||
|
stress-ng lost 27% of its throughput. Its ops per CPU-second barely moved (778
|
||||||
|
against 753), so it did not run slower -- it got less CPU, 60 CPU-seconds
|
||||||
|
instead of 79 over the minute, and the game took the difference. That is the
|
||||||
|
trade the knob exists to make.
|
||||||
|
|
||||||
|
Nothing starved. The worst wait was ~130 jiffies in both rounds, which is the
|
||||||
|
`starve_ns` bound itself, and `starve_serve` moved from 1.2/s to 1.5/s.
|
||||||
|
|
||||||
|
On an idle desktop the other side of the trade shows up in the `deboosted`
|
||||||
|
class: its wake->run p99 went from ~0.5 ms to 1.0-1.3 ms.
|
||||||
|
|
||||||
|
The cap applies to anything that submits GPU work, including an OpenCL render
|
||||||
|
(DaVinci, beignet/pocl). A build running alongside one will lose a similar
|
||||||
|
share. That was judged acceptable for a desktop; `--gpu-boost-cap=12` turns it
|
||||||
|
off.
|
||||||
|
|
||||||
|
## Dropped: waking out of a GPU wait
|
||||||
|
|
||||||
|
The first version also hooked `i915_request_wait_begin/end` and
|
||||||
|
`dma_fence_wait_start/end`, to time feeders waking from a GPU wait. Over 20
|
||||||
|
minutes of play that saw 1251 such wakeups against 434k submissions: crocus
|
||||||
|
does not block in the kernel for its fences, so there was nothing there to
|
||||||
|
schedule. The hooks were removed.
|
||||||
|
|
||||||
|
## For reference: EEVDF
|
||||||
|
|
||||||
|
With soryu unloaded, the same game under `stress-ng --cpu 4` ran at 8-11 fps
|
||||||
|
(read off the overlay, one run), against 40-50 for soryu without the cap. On an
|
||||||
|
idle desktop EEVDF's 1% low was 47.7 against 43.2 for soryu without the cap,
|
||||||
|
which is the stutter this change removes; with it, soryu's 1% low is 46.6-47.3.
|
||||||
|
|
||||||
|
## Open
|
||||||
|
|
||||||
|
- One game. A Vulkan title through hasvk, and a game whose render thread
|
||||||
|
rarely burns a full slice, would say whether -1 is right in general or only
|
||||||
|
for this workload.
|
||||||
|
- No AMD numbers. Without `/sys/kernel/btf/i915` the hook does not load and the
|
||||||
|
cap has no feeders to act on; an amdgpu equivalent would need its own
|
||||||
|
tracepoint.
|
||||||
+174
-4
@@ -359,6 +359,75 @@ u64 bounce_hist[LAT_BUCKETS];
|
|||||||
#define RELEASE_REASONS 4
|
#define RELEASE_REASONS 4
|
||||||
u64 release_reason[RELEASE_REASONS];
|
u64 release_reason[RELEASE_REASONS];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GPU feeders: tasks that submitted GPU work within the last GPU_FEEDER_NS,
|
||||||
|
* as seen by the i915_request_add hook at the bottom of this file.
|
||||||
|
*
|
||||||
|
* The thread that hands work to the GPU (a game's render thread, the
|
||||||
|
* compositor, Xwayland) is the one whose lateness costs a frame: if it runs
|
||||||
|
* late, the batch reaches the ring late, and on Gen7 there is no GPU-side
|
||||||
|
* scheduling to make that back up -- legacy ringbuffer submission, no
|
||||||
|
* preemption. Infinity v4.8 coupled CPU and GPU accounting for this reason.
|
||||||
|
*
|
||||||
|
* Three populations, all against lat_hist's buckets:
|
||||||
|
*
|
||||||
|
* 0 feeder: wake->run of a feeder
|
||||||
|
* 1 feeder requeue, boosted: stopped still runnable (slice end, kick,
|
||||||
|
* bounce) with boost < 0, until it runs again
|
||||||
|
* 2 feeder requeue, not boosted: same, boost >= 0
|
||||||
|
*
|
||||||
|
* 1 and 2 are the population wake->run cannot see. A render thread that burns
|
||||||
|
* its slice mid-frame does not sleep, so it never passes ops.runnable and
|
||||||
|
* never gets a wake_at; the wait it pays to get the CPU back was invisible.
|
||||||
|
* The split is by boost at the moment it stopped, because the question was
|
||||||
|
* whether being deboosted is what makes that wait long. It was -- see
|
||||||
|
* gpu_boost_cap below and docs/gpu-feeder.md.
|
||||||
|
*
|
||||||
|
* Feeders waking out of a GPU wait were measured too, through the i915 and
|
||||||
|
* dma_fence wait tracepoints, and dropped: 1251 such wakeups in 20 minutes of
|
||||||
|
* play. Mesa's crocus does not block in the kernel for its fences, so there
|
||||||
|
* was no population there to schedule.
|
||||||
|
*
|
||||||
|
* 100 ms is three frames at 30 fps: generous on purpose, so the population is
|
||||||
|
* every thread that is plausibly feeding a frame and not only the ones caught
|
||||||
|
* mid-burst.
|
||||||
|
*/
|
||||||
|
#define GPU_FEEDER_NS (100ULL * 1000 * 1000)
|
||||||
|
#define GPU_POPS 3
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A recent feeder is not deboosted past gpu_boost_cap. BOOST_RANGE (12) turns
|
||||||
|
* it off, since boost never exceeds it anyway.
|
||||||
|
*
|
||||||
|
* BMQ deboosts a render thread for doing exactly what a heavy frame needs,
|
||||||
|
* burning whole slices, and strict priority then queues it behind every
|
||||||
|
* better level. One 60 s camera pan in Half-Life 2, no background load: a
|
||||||
|
* feeder that stopped still runnable while boosted got the CPU back within
|
||||||
|
* 3.1 ms at p99.9 in every second of the run. Deboosted, it passed 4 ms in 6
|
||||||
|
* of 61 seconds and reached 25 ms -- two frames at 85 fps. -1 keeps feeders
|
||||||
|
* in the boosted class, where that wait never went long.
|
||||||
|
*
|
||||||
|
* Measured both ways, same binary, alternating (docs/gpu-feeder.md):
|
||||||
|
*
|
||||||
|
* idle desktop, two rounds each frames > 25 ms 26-27 -> 6-8
|
||||||
|
* p99.9 frametime 44 ms -> 26 ms
|
||||||
|
* under stress-ng --cpu 4 average fps 60.8 -> 73.3
|
||||||
|
* 1% low 30.0 -> 46.6
|
||||||
|
*
|
||||||
|
* The cost is that anything submitting to the GPU keeps its standing however
|
||||||
|
* much CPU it burns -- an OpenCL render included -- and outranks nice-0 hogs
|
||||||
|
* while it does. Measured: stress-ng lost 27% of its throughput to the game,
|
||||||
|
* at unchanged ops per CPU-second, so it ran less rather than slower, and the
|
||||||
|
* worst wait stayed at the starve_ns bound either way. That is the trade a
|
||||||
|
* desktop scheduler should make, so it is the default.
|
||||||
|
*/
|
||||||
|
const volatile s32 gpu_boost_cap = -1;
|
||||||
|
|
||||||
|
u64 gpu_lat_hist[GPU_POPS * LAT_BUCKETS];
|
||||||
|
|
||||||
|
/* boost_hist, restricted to feeders. */
|
||||||
|
u64 gpu_boost_hist[2 * BOOST_RANGE + 1];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bucket index for @ns: octave * LAT_SUBS + the two bits below the leading
|
* Bucket index for @ns: octave * LAT_SUBS + the two bits below the leading
|
||||||
* one. Values under 4 ns have no mantissa bits to take and all land in
|
* one. Values under 4 ns have no mantissa bits to take and all land in
|
||||||
@@ -393,6 +462,9 @@ struct task_ctx
|
|||||||
u64 bounce_at; /* scx_bpf_now() when ops.cpu_release threw it back */
|
u64 bounce_at; /* scx_bpf_now() when ops.cpu_release threw it back */
|
||||||
s32 cgrp_adj; /* cached level adjustment from cpu.weight */
|
s32 cgrp_adj; /* cached level adjustment from cpu.weight */
|
||||||
u64 cgrp_seen; /* cgrp_gen this adjustment was computed against */
|
u64 cgrp_seen; /* cgrp_gen this adjustment was computed against */
|
||||||
|
u64 gpu_submit_at; /* scx_bpf_now() at its last i915_request_add */
|
||||||
|
u64 requeue_at; /* scx_bpf_now() when a feeder stopped still runnable */
|
||||||
|
bool requeue_boosted; /* its boost was < 0 at that moment */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct cgrp_ctx
|
struct cgrp_ctx
|
||||||
@@ -445,6 +517,8 @@ enum soryu_stat_idx
|
|||||||
SORYU_STAT_STARVE_BLOCKED,
|
SORYU_STAT_STARVE_BLOCKED,
|
||||||
SORYU_STAT_BITMAP_REPAIR,
|
SORYU_STAT_BITMAP_REPAIR,
|
||||||
SORYU_STAT_WAKE_DONATE,
|
SORYU_STAT_WAKE_DONATE,
|
||||||
|
SORYU_STAT_GPU_SUBMIT,
|
||||||
|
SORYU_STAT_GPU_CAP,
|
||||||
SORYU_NR_STATS,
|
SORYU_NR_STATS,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -470,6 +544,16 @@ static struct task_ctx *lookup_task_ctx(struct task_struct *p)
|
|||||||
return bpf_task_storage_get(&task_ctxs, p, 0, 0);
|
return bpf_task_storage_get(&task_ctxs, p, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Signed, so a submit stamped on another CPU a hair ahead of this one's clock
|
||||||
|
* still reads as recent rather than as 2^64 ns ago.
|
||||||
|
*/
|
||||||
|
static bool gpu_feeder(struct task_ctx *tctx, u64 now)
|
||||||
|
{
|
||||||
|
return tctx->gpu_submit_at &&
|
||||||
|
(s64)(now - tctx->gpu_submit_at) < (s64)GPU_FEEDER_NS;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BMQ boost_task(): the floor depends on policy. SCHED_NORMAL may go all the
|
* BMQ boost_task(): the floor depends on policy. SCHED_NORMAL may go all the
|
||||||
* way to -12; SCHED_BATCH may only climb back to 0, so a batch task can recover
|
* way to -12; SCHED_BATCH may only climb back to 0, so a batch task can recover
|
||||||
@@ -1221,9 +1305,15 @@ void BPF_STRUCT_OPS(soryu_running, struct task_struct *p)
|
|||||||
if (tctx)
|
if (tctx)
|
||||||
{
|
{
|
||||||
u32 idx = (u32)(tctx->boost + BOOST_RANGE);
|
u32 idx = (u32)(tctx->boost + BOOST_RANGE);
|
||||||
|
u64 now = scx_bpf_now();
|
||||||
|
bool feeder = gpu_feeder(tctx, now);
|
||||||
|
|
||||||
if (idx < 2 * BOOST_RANGE + 1)
|
if (idx < 2 * BOOST_RANGE + 1)
|
||||||
|
{
|
||||||
boost_hist[idx]++;
|
boost_hist[idx]++;
|
||||||
|
if (feeder)
|
||||||
|
gpu_boost_hist[idx]++;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clamp, do not drop. ule_score() returns a closed [0, 100],
|
* Clamp, do not drop. ule_score() returns a closed [0, 100],
|
||||||
@@ -1251,7 +1341,7 @@ void BPF_STRUCT_OPS(soryu_running, struct task_struct *p)
|
|||||||
if (vtime_within_level && time_before(READ_ONCE(vtime_now), p->scx.dsq_vtime))
|
if (vtime_within_level && time_before(READ_ONCE(vtime_now), p->scx.dsq_vtime))
|
||||||
WRITE_ONCE(vtime_now, p->scx.dsq_vtime);
|
WRITE_ONCE(vtime_now, p->scx.dsq_vtime);
|
||||||
|
|
||||||
tctx->run_at = scx_bpf_now();
|
tctx->run_at = now;
|
||||||
tctx->slice_given = p->scx.slice;
|
tctx->slice_given = p->scx.slice;
|
||||||
|
|
||||||
if (tctx->wake_at)
|
if (tctx->wake_at)
|
||||||
@@ -1260,13 +1350,30 @@ void BPF_STRUCT_OPS(soryu_running, struct task_struct *p)
|
|||||||
? tctx->run_at - tctx->wake_at
|
? tctx->run_at - tctx->wake_at
|
||||||
: 0;
|
: 0;
|
||||||
u32 cls = tctx->boost < 0 ? 0 : (tctx->boost ? 2 : 1);
|
u32 cls = tctx->boost < 0 ? 0 : (tctx->boost ? 2 : 1);
|
||||||
u32 slot = cls * LAT_BUCKETS + lat_bucket(lat);
|
u32 b = lat_bucket(lat);
|
||||||
|
u32 slot = cls * LAT_BUCKETS + b;
|
||||||
if (slot < LAT_CLASSES * LAT_BUCKETS)
|
if (slot < LAT_CLASSES * LAT_BUCKETS)
|
||||||
lat_hist[slot]++;
|
lat_hist[slot]++;
|
||||||
|
|
||||||
|
/* Every sample here is also in lat_hist above; this is a
|
||||||
|
* subset of it, not a separate measurement. */
|
||||||
|
if (feeder && b < LAT_BUCKETS)
|
||||||
|
gpu_lat_hist[b]++;
|
||||||
|
|
||||||
tctx->wake_at = 0;
|
tctx->wake_at = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tctx->requeue_at)
|
||||||
|
{
|
||||||
|
u64 rl = now > tctx->requeue_at ? now - tctx->requeue_at : 0;
|
||||||
|
u32 pop = tctx->requeue_boosted ? 1 : 2;
|
||||||
|
u32 slot = pop * LAT_BUCKETS + lat_bucket(rl);
|
||||||
|
|
||||||
|
if (slot < GPU_POPS * LAT_BUCKETS)
|
||||||
|
gpu_lat_hist[slot]++;
|
||||||
|
tctx->requeue_at = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recorded independently of wake_at, not instead of it. A task
|
* Recorded independently of wake_at, not instead of it. A task
|
||||||
* that woke and was then bounced belongs in both: wake->run is
|
* that woke and was then bounced belongs in both: wake->run is
|
||||||
@@ -1332,10 +1439,23 @@ void BPF_STRUCT_OPS(soryu_stopping, struct task_struct *p, bool runnable)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Boost as it was while running, before the deboost below can move it. */
|
||||||
|
if (gpu_feeder(tctx, now))
|
||||||
|
{
|
||||||
|
tctx->requeue_at = now;
|
||||||
|
tctx->requeue_boosted = tctx->boost < 0;
|
||||||
|
}
|
||||||
|
|
||||||
left = tctx->slice_given > ran ? tctx->slice_given - ran : 0;
|
left = tctx->slice_given > ran ? tctx->slice_given - ran : 0;
|
||||||
|
|
||||||
if (left < RESCHED_NS)
|
if (left < RESCHED_NS)
|
||||||
{
|
{
|
||||||
|
/* The first test keeps gpu_cap at zero when the knob is off,
|
||||||
|
* rather than counting deboosts that were no-ops at +12. */
|
||||||
|
if (gpu_boost_cap < BOOST_RANGE && tctx->boost >= gpu_boost_cap &&
|
||||||
|
gpu_feeder(tctx, now))
|
||||||
|
stat_inc(SORYU_STAT_GPU_CAP);
|
||||||
|
else
|
||||||
deboost_task(p, tctx);
|
deboost_task(p, tctx);
|
||||||
tctx->slice_left = 0;
|
tctx->slice_left = 0;
|
||||||
stat_inc(SORYU_STAT_DEBOOST);
|
stat_inc(SORYU_STAT_DEBOOST);
|
||||||
@@ -1384,6 +1504,9 @@ s32 BPF_STRUCT_OPS(soryu_init_task, struct task_struct *p,
|
|||||||
tctx->bounce_at = 0;
|
tctx->bounce_at = 0;
|
||||||
tctx->cgrp_adj = 0;
|
tctx->cgrp_adj = 0;
|
||||||
tctx->cgrp_seen = 0; /* forces a refresh on the first enqueue */
|
tctx->cgrp_seen = 0; /* forces a refresh on the first enqueue */
|
||||||
|
tctx->gpu_submit_at = 0;
|
||||||
|
tctx->requeue_at = 0;
|
||||||
|
tctx->requeue_boosted = false;
|
||||||
|
|
||||||
/* Starting at 0 would read as an enormous credit against vtime_now and
|
/* Starting at 0 would read as an enormous credit against vtime_now and
|
||||||
* hand every new task the head of its level. */
|
* hand every new task the head of its level. */
|
||||||
@@ -1427,7 +1550,21 @@ s32 BPF_STRUCT_OPS(soryu_init_task, struct task_struct *p,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 BPF_STRUCT_OPS(soryu_cgroup_init, struct cgroup *cgrp,
|
/*
|
||||||
|
* Sleepable, and never fatal.
|
||||||
|
*
|
||||||
|
* This used to return -ENOMEM when the storage could not be created, and an
|
||||||
|
* error from ops.cgroup_init ejects the scheduler. It did, at boot, once per
|
||||||
|
* boot on 7-11 Oct: "ops.cgroup_init() failed (-12)" while systemd was
|
||||||
|
* creating cgroups, then systemd restarted us 5 s later -- 5 s of EEVDF every
|
||||||
|
* boot. Non-sleepable, the create is a GFP_ATOMIC allocation, and that is the
|
||||||
|
* likeliest way for it to fail there; sleepable makes it GFP_KERNEL.
|
||||||
|
*
|
||||||
|
* And a missing cgrp_ctx is not worth an ejection anyway: cgrp_refresh() and
|
||||||
|
* ops.cgroup_set_weight both already treat it as "no weight known", so the
|
||||||
|
* cost is that one cgroup's tasks run at the neutral adjustment.
|
||||||
|
*/
|
||||||
|
s32 BPF_STRUCT_OPS_SLEEPABLE(soryu_cgroup_init, struct cgroup *cgrp,
|
||||||
struct scx_cgroup_init_args *args)
|
struct scx_cgroup_init_args *args)
|
||||||
{
|
{
|
||||||
struct cgrp_ctx *cgc;
|
struct cgrp_ctx *cgc;
|
||||||
@@ -1435,7 +1572,7 @@ s32 BPF_STRUCT_OPS(soryu_cgroup_init, struct cgroup *cgrp,
|
|||||||
cgc = bpf_cgrp_storage_get(&cgrp_ctxs, cgrp, 0,
|
cgc = bpf_cgrp_storage_get(&cgrp_ctxs, cgrp, 0,
|
||||||
BPF_LOCAL_STORAGE_GET_F_CREATE);
|
BPF_LOCAL_STORAGE_GET_F_CREATE);
|
||||||
if (!cgc)
|
if (!cgc)
|
||||||
return -ENOMEM;
|
return 0;
|
||||||
|
|
||||||
cgc->weight = args->weight;
|
cgc->weight = args->weight;
|
||||||
__sync_fetch_and_add(&cgrp_gen, 1);
|
__sync_fetch_and_add(&cgrp_gen, 1);
|
||||||
@@ -1517,6 +1654,39 @@ void BPF_STRUCT_OPS(soryu_exit, struct scx_exit_info *ei)
|
|||||||
UEI_RECORD(uei, ei);
|
UEI_RECORD(uei, ei);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The feeder hook. i915_request_add fires in the context of the task doing
|
||||||
|
* the submitting, so current is the task we want.
|
||||||
|
*
|
||||||
|
* Kernel threads are skipped: i915 submits its own requests from workers
|
||||||
|
* (heartbeats, retires, display), and a kworker that once touched the ring is
|
||||||
|
* not a frame feeder. A task scx has not initialised yet has no tctx and is
|
||||||
|
* skipped too.
|
||||||
|
*
|
||||||
|
* The tracepoint lives in i915's module BTF. The loader turns this program
|
||||||
|
* off when /sys/kernel/btf/i915 is absent -- the test VM, the AMD machines --
|
||||||
|
* so the scheduler still loads there, with no feeders and gpu_boost_cap
|
||||||
|
* inert. The request pointer is never dereferenced, which is also why it is
|
||||||
|
* typed void: nothing here depends on i915's layout.
|
||||||
|
*/
|
||||||
|
SEC("tp_btf/i915_request_add")
|
||||||
|
int BPF_PROG(soryu_i915_request_add, void *rq)
|
||||||
|
{
|
||||||
|
struct task_struct *p = bpf_get_current_task_btf();
|
||||||
|
struct task_ctx *tctx;
|
||||||
|
|
||||||
|
if (!p || (p->flags & PF_KTHREAD))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
tctx = lookup_task_ctx(p);
|
||||||
|
if (tctx)
|
||||||
|
{
|
||||||
|
tctx->gpu_submit_at = scx_bpf_now();
|
||||||
|
stat_inc(SORYU_STAT_GPU_SUBMIT);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
SCX_OPS_DEFINE(soryu_ops,
|
SCX_OPS_DEFINE(soryu_ops,
|
||||||
.select_cpu = (void *)soryu_select_cpu,
|
.select_cpu = (void *)soryu_select_cpu,
|
||||||
.enqueue = (void *)soryu_enqueue,
|
.enqueue = (void *)soryu_enqueue,
|
||||||
|
|||||||
+95
-20
@@ -81,6 +81,8 @@ soryu_stats!(
|
|||||||
starve_blocked,
|
starve_blocked,
|
||||||
bitmap_repair,
|
bitmap_repair,
|
||||||
wake_donate,
|
wake_donate,
|
||||||
|
gpu_submit,
|
||||||
|
gpu_cap,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// mirrors BOOST_RANGE in main.bpf.c.
|
/// mirrors BOOST_RANGE in main.bpf.c.
|
||||||
@@ -97,6 +99,19 @@ const LAT_SUBS: usize = 4;
|
|||||||
const LAT_BUCKETS: usize = LAT_OCTAVES * LAT_SUBS;
|
const LAT_BUCKETS: usize = LAT_OCTAVES * LAT_SUBS;
|
||||||
const LAT_CLASS_NAMES: [&str; LAT_CLASSES] = ["boosted", "neutral", "deboosted"];
|
const LAT_CLASS_NAMES: [&str; LAT_CLASSES] = ["boosted", "neutral", "deboosted"];
|
||||||
|
|
||||||
|
/// mirrors GPU_POPS in main.bpf.c.
|
||||||
|
const GPU_POPS: usize = 3;
|
||||||
|
const GPU_POP_NAMES: [&str; GPU_POPS] = [
|
||||||
|
"gpu-feeder",
|
||||||
|
"gpu-requeue boosted",
|
||||||
|
"gpu-requeue deboosted",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Module BTF the i915 tracepoints resolve against. Absent means no i915 --
|
||||||
|
/// the test VM, or an AMD machine -- and those programs must not be loaded,
|
||||||
|
/// or the whole object fails with them.
|
||||||
|
const I915_BTF: &str = "/sys/kernel/btf/i915";
|
||||||
|
|
||||||
/// Lower edge of bucket @i in ns: mantissa (4 + sub) / 4, scaled by 2^octave.
|
/// Lower edge of bucket @i in ns: mantissa (4 + sub) / 4, scaled by 2^octave.
|
||||||
/// Inverse of lat_bucket() in main.bpf.c.
|
/// Inverse of lat_bucket() in main.bpf.c.
|
||||||
fn bucket_ns(i: usize) -> u64 {
|
fn bucket_ns(i: usize) -> u64 {
|
||||||
@@ -178,6 +193,14 @@ struct Opts {
|
|||||||
#[arg(long, default_value_t = false, env = "SORYU_VTIME_WITHIN_LEVEL",
|
#[arg(long, default_value_t = false, env = "SORYU_VTIME_WITHIN_LEVEL",
|
||||||
action = clap::ArgAction::Set)]
|
action = clap::ArgAction::Set)]
|
||||||
vtime_within_level: bool,
|
vtime_within_level: bool,
|
||||||
|
|
||||||
|
/// Boost a recent GPU feeder may not be deboosted past, in [-12, 12].
|
||||||
|
/// The default -1 keeps feeders in the boosted class; 12 turns it off.
|
||||||
|
/// Measured in docs/gpu-feeder.md.
|
||||||
|
#[arg(long, default_value_t = -1, env = "SORYU_GPU_BOOST_CAP",
|
||||||
|
value_parser = clap::value_parser!(i32).range(-12..=12))]
|
||||||
|
gpu_boost_cap: i32,
|
||||||
|
|
||||||
#[arg(long, default_value_t = 1, env = "SORYU_REPORT_SEC")]
|
#[arg(long, default_value_t = 1, env = "SORYU_REPORT_SEC")]
|
||||||
report_sec: u64,
|
report_sec: u64,
|
||||||
#[arg(long, env = "SORYU_LOG")]
|
#[arg(long, env = "SORYU_LOG")]
|
||||||
@@ -221,6 +244,32 @@ fn percentiles(buckets: &[u64]) -> Option<(u64, u64, u64)> {
|
|||||||
Some((at(0.50), at(0.99), at(0.999)))
|
Some((at(0.50), at(0.99), at(0.999)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mean and occupied-bucket distribution of a boost histogram, None if empty.
|
||||||
|
fn boost_summary(hist: &[u64]) -> Option<(f64, String)> {
|
||||||
|
let total: u64 = hist.iter().sum();
|
||||||
|
if total == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let occupied: Vec<String> = hist
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, &c)| c > 0)
|
||||||
|
.map(|(i, &c)| {
|
||||||
|
let b = i as i32 - BOOST_RANGE;
|
||||||
|
format!("{b:+}:{:.1}%", 100.0 * c as f64 / total as f64)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let weighted: i64 = hist
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &c)| (i as i64 - BOOST_RANGE as i64) * c as i64)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
Some((weighted as f64 / total as f64, occupied.join(" ")))
|
||||||
|
}
|
||||||
|
|
||||||
struct Scheduler<'a> {
|
struct Scheduler<'a> {
|
||||||
opts: Opts,
|
opts: Opts,
|
||||||
skel: BpfSkel<'a>,
|
skel: BpfSkel<'a>,
|
||||||
@@ -243,6 +292,8 @@ struct Scheduler<'a> {
|
|||||||
prev_ule: Vec<u64>,
|
prev_ule: Vec<u64>,
|
||||||
prev_bounce: Vec<u64>,
|
prev_bounce: Vec<u64>,
|
||||||
prev_release: Vec<u64>,
|
prev_release: Vec<u64>,
|
||||||
|
prev_gpu_lat: Vec<u64>,
|
||||||
|
prev_gpu_boost: Vec<u64>,
|
||||||
|
|
||||||
/// Separate snapshots for the stats server: it samples on its own schedule
|
/// Separate snapshots for the stats server: it samples on its own schedule
|
||||||
/// and must not consume the report's intervals, or vice versa.
|
/// and must not consume the report's intervals, or vice versa.
|
||||||
@@ -281,9 +332,16 @@ impl<'a> Scheduler<'a> {
|
|||||||
rodata.cgrp_weight_levels = opts.cgrp_weight_levels;
|
rodata.cgrp_weight_levels = opts.cgrp_weight_levels;
|
||||||
rodata.wake_donate = opts.wake_donate;
|
rodata.wake_donate = opts.wake_donate;
|
||||||
rodata.vtime_within_level = opts.vtime_within_level;
|
rodata.vtime_within_level = opts.vtime_within_level;
|
||||||
|
rodata.gpu_boost_cap = opts.gpu_boost_cap;
|
||||||
|
|
||||||
rodata.slice_ns = opts.slice_us * 1_000;
|
rodata.slice_ns = opts.slice_us * 1_000;
|
||||||
|
|
||||||
|
let have_i915 = std::path::Path::new(I915_BTF).exists();
|
||||||
|
if !have_i915 {
|
||||||
|
skel.progs.soryu_i915_request_add.set_autoload(false);
|
||||||
|
info!("no {I915_BTF}: no GPU feeders, gpu_boost_cap has nothing to act on");
|
||||||
|
}
|
||||||
|
|
||||||
let mut skel = scx_ops_load!(skel, soryu_ops, uei)?;
|
let mut skel = scx_ops_load!(skel, soryu_ops, uei)?;
|
||||||
let struct_ops = Some(scx_ops_attach!(skel, soryu_ops)?);
|
let struct_ops = Some(scx_ops_attach!(skel, soryu_ops)?);
|
||||||
|
|
||||||
@@ -291,7 +349,7 @@ impl<'a> Scheduler<'a> {
|
|||||||
info!(
|
info!(
|
||||||
"{} attached: {} CPUs, slice {} us, boost_on_wake={}, \
|
"{} attached: {} CPUs, slice {} us, boost_on_wake={}, \
|
||||||
starve {} ms, clear_bitmap={}, ule_weight={} (window {} ms), \
|
starve {} ms, clear_bitmap={}, ule_weight={} (window {} ms), \
|
||||||
vtime_within_level={}, timeout {} ms",
|
vtime_within_level={}, gpu_boost_cap={}, timeout {} ms",
|
||||||
SCHEDULER_NAME,
|
SCHEDULER_NAME,
|
||||||
*scx_utils::NR_CPU_IDS,
|
*scx_utils::NR_CPU_IDS,
|
||||||
rd.slice_ns / 1000,
|
rd.slice_ns / 1000,
|
||||||
@@ -301,6 +359,7 @@ impl<'a> Scheduler<'a> {
|
|||||||
rd.ule_weight,
|
rd.ule_weight,
|
||||||
rd.ule_window_ns / 1_000_000,
|
rd.ule_window_ns / 1_000_000,
|
||||||
rd.vtime_within_level,
|
rd.vtime_within_level,
|
||||||
|
rd.gpu_boost_cap,
|
||||||
opts.timeout_ms
|
opts.timeout_ms
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -317,6 +376,8 @@ impl<'a> Scheduler<'a> {
|
|||||||
prev_ule: Vec::new(),
|
prev_ule: Vec::new(),
|
||||||
prev_bounce: Vec::new(),
|
prev_bounce: Vec::new(),
|
||||||
prev_release: Vec::new(),
|
prev_release: Vec::new(),
|
||||||
|
prev_gpu_lat: Vec::new(),
|
||||||
|
prev_gpu_boost: Vec::new(),
|
||||||
stats_prev_lat: Vec::new(),
|
stats_prev_lat: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -379,6 +440,8 @@ impl<'a> Scheduler<'a> {
|
|||||||
starve_blocked: g(stat::starve_blocked),
|
starve_blocked: g(stat::starve_blocked),
|
||||||
bitmap_repair: g(stat::bitmap_repair),
|
bitmap_repair: g(stat::bitmap_repair),
|
||||||
wake_donate: g(stat::wake_donate),
|
wake_donate: g(stat::wake_donate),
|
||||||
|
gpu_submit: g(stat::gpu_submit),
|
||||||
|
gpu_cap: g(stat::gpu_cap),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -485,39 +548,33 @@ impl<'a> Scheduler<'a> {
|
|||||||
let ule_cur = bss.ule_hist.to_vec();
|
let ule_cur = bss.ule_hist.to_vec();
|
||||||
let bounce_cur = bss.bounce_hist.to_vec();
|
let bounce_cur = bss.bounce_hist.to_vec();
|
||||||
let release_cur = bss.release_reason.to_vec();
|
let release_cur = bss.release_reason.to_vec();
|
||||||
|
let gpu_lat_cur = bss.gpu_lat_hist.to_vec();
|
||||||
|
let gpu_boost_cur = bss.gpu_boost_hist.to_vec();
|
||||||
|
|
||||||
let boost = hist_delta(&boost_cur, &self.prev_boost);
|
let boost = hist_delta(&boost_cur, &self.prev_boost);
|
||||||
let lat = hist_delta(&lat_cur, &self.prev_lat);
|
let lat = hist_delta(&lat_cur, &self.prev_lat);
|
||||||
let ule = hist_delta(&ule_cur, &self.prev_ule);
|
let ule = hist_delta(&ule_cur, &self.prev_ule);
|
||||||
let bounce = hist_delta(&bounce_cur, &self.prev_bounce);
|
let bounce = hist_delta(&bounce_cur, &self.prev_bounce);
|
||||||
let release = hist_delta(&release_cur, &self.prev_release);
|
let release = hist_delta(&release_cur, &self.prev_release);
|
||||||
|
let gpu_lat = hist_delta(&gpu_lat_cur, &self.prev_gpu_lat);
|
||||||
|
let gpu_boost = hist_delta(&gpu_boost_cur, &self.prev_gpu_boost);
|
||||||
|
|
||||||
self.prev_boost = boost_cur;
|
self.prev_boost = boost_cur;
|
||||||
self.prev_lat = lat_cur;
|
self.prev_lat = lat_cur;
|
||||||
self.prev_ule = ule_cur;
|
self.prev_ule = ule_cur;
|
||||||
self.prev_bounce = bounce_cur;
|
self.prev_bounce = bounce_cur;
|
||||||
self.prev_release = release_cur;
|
self.prev_release = release_cur;
|
||||||
|
self.prev_gpu_lat = gpu_lat_cur;
|
||||||
|
self.prev_gpu_boost = gpu_boost_cur;
|
||||||
|
|
||||||
let total: u64 = boost.iter().sum();
|
if let Some((mean, dist)) = boost_summary(&boost) {
|
||||||
if total > 0 {
|
info!(" boost mean={mean:+.2} dist: {dist}");
|
||||||
let occupied: Vec<String> = boost
|
}
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, &c)| c > 0)
|
|
||||||
.map(|(i, &c)| {
|
|
||||||
let b = i as i32 - BOOST_RANGE;
|
|
||||||
format!("{b:+}:{:.1}%", 100.0 * c as f64 / total as f64)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let weighted: i64 = boost
|
// Same shape as the line above, so the two read side by side: if
|
||||||
.iter()
|
// feeders sit on the floor like everyone else, boost already has them.
|
||||||
.enumerate()
|
if let Some((mean, dist)) = boost_summary(&gpu_boost) {
|
||||||
.map(|(i, &c)| (i as i64 - BOOST_RANGE as i64) * c as i64)
|
info!(" gpu-feeder boost mean={mean:+.2} dist: {dist}");
|
||||||
.sum();
|
|
||||||
let mean = weighted as f64 / total as f64;
|
|
||||||
|
|
||||||
info!(" boost mean={mean:+.2} dist: {}", occupied.join(" "));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let step = ULE_MAX / ULE_BUCKETS;
|
let step = ULE_MAX / ULE_BUCKETS;
|
||||||
@@ -576,6 +633,24 @@ impl<'a> Scheduler<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subsets of the lines above, cut by what the task was doing with the
|
||||||
|
// GPU rather than by its boost.
|
||||||
|
for (p, name) in GPU_POP_NAMES.iter().enumerate() {
|
||||||
|
let slice = &gpu_lat[p * LAT_BUCKETS..(p + 1) * LAT_BUCKETS];
|
||||||
|
let Some((p50, p99, p999)) = percentiles(slice) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
" wake->run {}: p50={} p99={} p99.9={} n={}",
|
||||||
|
name,
|
||||||
|
fmt_ns(p50),
|
||||||
|
fmt_ns(p99),
|
||||||
|
fmt_ns(p999),
|
||||||
|
slice.iter().sum::<u64>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// The population every other latency line here is blind to: bounced
|
// The population every other latency line here is blind to: bounced
|
||||||
// by ops.cpu_release, re-enqueued without ever passing through
|
// by ops.cpu_release, re-enqueued without ever passing through
|
||||||
// ops.runnable, so no wake_at and no wake->run sample.
|
// ops.runnable, so no wake_at and no wake->run sample.
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ pub struct Metrics {
|
|||||||
#[stat(desc = "Wakeups where the waker handed the wakee some of its boost")]
|
#[stat(desc = "Wakeups where the waker handed the wakee some of its boost")]
|
||||||
pub wake_donate: u64,
|
pub wake_donate: u64,
|
||||||
|
|
||||||
|
#[stat(desc = "GPU submissions (i915_request_add) by scx tasks")]
|
||||||
|
pub gpu_submit: u64,
|
||||||
|
#[stat(desc = "Deboosts skipped because the task is a recent GPU feeder")]
|
||||||
|
pub gpu_cap: u64,
|
||||||
|
|
||||||
#[stat(desc = "Mean boost, x100 (negative is more interactive)")]
|
#[stat(desc = "Mean boost, x100 (negative is more interactive)")]
|
||||||
pub boost_mean_x100: i64,
|
pub boost_mean_x100: i64,
|
||||||
#[stat(desc = "Percent of scheduling events at the -12 boost floor")]
|
#[stat(desc = "Percent of scheduling events at the -12 boost floor")]
|
||||||
@@ -172,6 +177,11 @@ impl Metrics {
|
|||||||
self.starve_blocked,
|
self.starve_blocked,
|
||||||
)?;
|
)?;
|
||||||
writeln!(w, "donate wake_donate {}", self.wake_donate)?;
|
writeln!(w, "donate wake_donate {}", self.wake_donate)?;
|
||||||
|
writeln!(
|
||||||
|
w,
|
||||||
|
"gpu submit {} cap {}",
|
||||||
|
self.gpu_submit, self.gpu_cap,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +213,8 @@ impl Metrics {
|
|||||||
starve_blocked: self.starve_blocked.saturating_sub(rhs.starve_blocked),
|
starve_blocked: self.starve_blocked.saturating_sub(rhs.starve_blocked),
|
||||||
bitmap_repair: self.bitmap_repair.saturating_sub(rhs.bitmap_repair),
|
bitmap_repair: self.bitmap_repair.saturating_sub(rhs.bitmap_repair),
|
||||||
wake_donate: self.wake_donate.saturating_sub(rhs.wake_donate),
|
wake_donate: self.wake_donate.saturating_sub(rhs.wake_donate),
|
||||||
|
gpu_submit: self.gpu_submit.saturating_sub(rhs.gpu_submit),
|
||||||
|
gpu_cap: self.gpu_cap.saturating_sub(rhs.gpu_cap),
|
||||||
// lat_boosted_* are already per-interval: the loader computes them
|
// lat_boosted_* are already per-interval: the loader computes them
|
||||||
// from a differenced histogram, since percentiles cannot be
|
// from a differenced histogram, since percentiles cannot be
|
||||||
// subtracted. Differencing the count here too would zero it.
|
// subtracted. Differencing the count here too would zero it.
|
||||||
|
|||||||
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerate compile_commands.json for src/bpf/main.bpf.c so clangd sees the
|
||||||
|
# same flags the real BPF build uses.
|
||||||
|
#
|
||||||
|
# scx_cargo prints its computed cflags to the build-script output as a line
|
||||||
|
# starting with `scx_utils:clang=`. The include paths in it point into
|
||||||
|
# target/<profile>/build/scx_soryu-<hash>/out, and that hash changes whenever
|
||||||
|
# the build-script inputs change, so this is generated rather than committed.
|
||||||
|
#
|
||||||
|
# Run inside the devShell after a build:
|
||||||
|
# cargo build --release && ./tools/gen-compile-commands.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
root="$PWD"
|
||||||
|
|
||||||
|
profile="${1:-release}"
|
||||||
|
out=$(ls -td "target/$profile"/build/scx_soryu-*/output 2>/dev/null | head -1 || true)
|
||||||
|
|
||||||
|
if [ -z "$out" ]; then
|
||||||
|
echo "error: no build-script output under target/$profile/build/." >&2
|
||||||
|
echo " run 'cargo build --${profile/dev/}' first (inside the devShell)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
line=$(grep -h '^scx_utils:clang=' "$out" | tail -1 || true)
|
||||||
|
if [ -z "$line" ]; then
|
||||||
|
echo "error: no 'scx_utils:clang=' line in $out" >&2
|
||||||
|
echo " the build script was cached; touch src/bpf/main.bpf.c and rebuild." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The cflags are the bracketed Rust Vec<String> debug print at the end of the
|
||||||
|
# line: ["-g", "-O2", ...]. Pull the quoted strings out of that part only.
|
||||||
|
flags=$(printf '%s' "$line" \
|
||||||
|
| sed 's/.*\(\[[^][]*\]\)$/\1/' \
|
||||||
|
| grep -o '"[^"]*"' \
|
||||||
|
| sed 's/^"//; s/"$//')
|
||||||
|
|
||||||
|
if [ -z "$flags" ]; then
|
||||||
|
echo "error: could not parse cflags out of: $line" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '[\n {\n'
|
||||||
|
printf ' "directory": "%s",\n' "$root"
|
||||||
|
printf ' "file": "%s/src/bpf/main.bpf.c",\n' "$root"
|
||||||
|
printf ' "arguments": [\n'
|
||||||
|
printf ' "clang",\n'
|
||||||
|
printf ' "-xc",\n'
|
||||||
|
printf ' "-target", "bpf",\n'
|
||||||
|
while IFS= read -r f; do
|
||||||
|
printf ' %s,\n' "$(printf '%s' "$f" | sed 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/$/"/')"
|
||||||
|
done <<<"$flags"
|
||||||
|
printf ' "-c",\n'
|
||||||
|
printf ' "%s/src/bpf/main.bpf.c"\n' "$root"
|
||||||
|
printf ' ]\n }\n]\n'
|
||||||
|
} >compile_commands.json
|
||||||
|
|
||||||
|
echo "wrote compile_commands.json ($(wc -l <<<"$flags") flags, profile=$profile)"
|
||||||
Reference in new issue
Block a user