repo: merge the 60 fps present batch (perf-60)
Busy desktop windows ran at ~50 copies/s because WindowServer, holding at most two swaps, got one refresh per frame while the kext completed each swap a tick after its copy, and the kext's per-IB cache maintenance (3.4 ms over ~98 MiB) sat on WindowServer's path. The GPU is not the limit: 94% of the time at 1700 MHz, ~7% busy. ADRENO_PRESENT_EARLY_DONE (--present-early-done, adaptive mode 2) completes a layer-0 swap at its scanout copy after a late frame; the desktop-early-done profile adds it to the coherent backlight build so --gpu-coherent can drop the maintenance. Replay: busy 49.8 -> 56.3 copies/s, 57.9 with coherent.
This commit is contained in:
commit
822226cfed
12 files changed
+849
-4
No files matched your search
+22
-3
@@ -147,7 +147,7 @@ The flags fall into these groups:
|
||||
| --- | --- |
|
||||
| Console and early stop | `--console`, `--single-user`, `--early-stop-delay`, `--let-panic`, `--panic-serve` |
|
||||
| Input | `--input`, `--input-touchpad`, `--input-ptp`, `--input-active-us`, `--input-fnkeys` |
|
||||
| Present path | `--present-dartpool`, `--present-resident`, `--present-beam`, `--present-va-reclaim`, `--present-maint`, `--present-latch`, `--present-pipeline` |
|
||||
| Present path | `--present-dartpool`, `--present-resident`, `--present-beam`, `--present-va-reclaim`, `--present-maint`, `--present-latch`, `--present-pipeline`, `--present-early-done` |
|
||||
| GPU | `--ib-async`, `--gpu-recover`, `--gpu-va-wide`, `--gpu-coherent`, `--resident-mib`, `--adreno-fill-br`, `--adreno-fence-br`, `--adreno-cs-samplers`, `--gpu-x290-survey` |
|
||||
| Panel brightness | `--edp-backlight`, `--edp-backlight-mode` |
|
||||
| Clocks and bandwidth | `--thermal-guard`, `--gpu-dvfs`, `--gpu-dvfs-ceiling`, `--cpu-dvfs`, `--ddr-vote`, `--llcc`, `--bw-probe` |
|
||||
@@ -214,12 +214,31 @@ nits); either boot-arg moves the curve without rebuilding the monitor. The
|
||||
collection is built with
|
||||
`KEXT_PROFILE=desktop-next I2C_PROFILE=desktop-fnkeys tools/build-auxkc-desktop.sh
|
||||
NAME` for a monitor without `--gpu-coherent` (`desktop-next` is
|
||||
`desktop-nocoherent` plus the service); a `--gpu-coherent` run needs a
|
||||
`KEXT_PROFILE=desktop-backlight` collection pinned there instead. `--edp-backlight-mode` lets the first change switch a panel left in
|
||||
`desktop-nocoherent` plus the service). With `--gpu-coherent` or
|
||||
`--present-early-done` the run takes the collection pinned as
|
||||
`INPUT_AUXKC_DESKTOP_EARLY_DONE` instead (`KEXT_PROFILE=desktop-early-done
|
||||
I2C_PROFILE=desktop-fnkeys tools/build-auxkc-desktop.sh NAME`; `desktop-early-done` is
|
||||
`desktop-backlight`, the coherent `desktop` plus the service, with early
|
||||
completion), and is refused until that collection is pinned. `--edp-backlight-mode` lets the first change switch a panel left in
|
||||
PWM-pin mode to DPCD control. `tools/survey-edp-aux.py` checks, read-only from
|
||||
q1n1, that the clocks and the AUX controller the call needs are on.
|
||||
`tools/test-edp-backlight.py` is the offline test.
|
||||
|
||||
`--present-early-done MODE` (needs `--edp-backlight` and its chain) adds the
|
||||
guest boot-arg `adreno_present_early_done=MODE` for the `desktop-early-done`
|
||||
AdrenoX2 (`src/AdrenoDisplayEarlyDone.h` in the kext). AdrenoDisplay then
|
||||
completes a layer-0 swap right after its scanout copy instead of at the next
|
||||
frame tick. WindowServer keeps at most two swaps outstanding, so completing at
|
||||
the copy frees one of its entries a refresh earlier and a frame that runs past
|
||||
one refresh can still make the next latch; WindowServer may then run up to one
|
||||
frame further ahead. `1` completes at every copy, `2` only while WindowServer
|
||||
is late (for `--present-early-hold-ms`, default 2000, after each late frame),
|
||||
`0` only measures. Every mode adds one `AdrenoDisplay: perf done` line per perf
|
||||
window: completions at the copy, late frames, engagements, and histograms of
|
||||
WindowServer's arrivals after each tick and of each late frame's lateness. It
|
||||
excludes `--present-hold on`. `tools/test-present-early-done.py` is the offline
|
||||
test (with the presentation replay's early-done scenarios).
|
||||
|
||||
`--view-reclaim` gives the monitor `SNAP_VIEW_RECLAIM`. The kernel's per-CPU
|
||||
private page-table copies (`shadow/sprr-view.h`) hold 256 tables instead of
|
||||
128, and when a protecting RETYPE (read-only zone, TXM) finds them full, the
|
||||
|
||||
+1
-1
Submodule gpu/adreno-kext/kext updated: e1cf0729c6...a2d43d0c9d.
@@ -295,6 +295,16 @@ def parse(argv=None):
|
||||
help='with --present-latch: guest boot-arg adreno_hold (default off since b12v11: on completes the swap '
|
||||
'on screen just before the next latch and copies the pending one early; b12v11 showed early '
|
||||
'copies of frames whose IB had not finished)')
|
||||
p.add_argument('--present-early-done', type=int, choices=(0, 1, 2), default=None,
|
||||
help='with --edp-backlight and its chain: the AuxKC INPUT_AUXKC_DESKTOP_EARLY_DONE and guest '
|
||||
'boot-arg adreno_present_early_done: AdrenoDisplay completes a layer-0 swap right after its '
|
||||
'scanout copy instead of at the next tick, so WindowServer\'s two swap entries free a refresh '
|
||||
'earlier (1 always; 2 only while WindowServer is late, for --present-early-hold-ms after each late '
|
||||
'frame; 0 measures only). Every mode adds the perf done line (WindowServer\'s arrivals against '
|
||||
'the ticks). Not with --present-hold on; give it to every mode of the attempt')
|
||||
p.add_argument('--present-early-hold-ms', type=int, default=None,
|
||||
help='with --present-early-done 2: guest boot-arg adreno_present_early_hold_ms, how long a late frame '
|
||||
'keeps early completion on (default 2000; 100..10000)')
|
||||
p.add_argument('--present-gate-us', type=int, default=None,
|
||||
help='with --present-latch: guest boot-arg adreno_gate_us, the age under which a layer-0 swap is not '
|
||||
'copied while a GPU submission is in the kext (default 4000; 0 = no gate)')
|
||||
|
||||
@@ -244,6 +244,17 @@ INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA = 'ccf0c8f4da4f63fbe25c
|
||||
# (57965a84); without --edp-backlight the latch collection above (org-desktop-nocoherent-20261023, ccf0c8f4) is used.
|
||||
INPUT_AUXKC_DESKTOP_BACKLIGHT = 'build/current-assets/swap-path-20261024/adreno-input.kc'
|
||||
INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA = '9e916a4185bc609178fe80a3110e020d9f3167625da7a76a9bc9ff22412743d5'
|
||||
# --edp-backlight with --gpu-coherent or --present-early-done: the --edp-backlight collection rebuilt from
|
||||
# SnapAdreno's desktop-early-done profile, i.e. desktop-backlight (desktop's IO-coherent maintenance skipping plus
|
||||
# AdrenoBacklight) plus -DADRENO_PRESENT_EARLY_DONE=1 in AdrenoDisplay.o (src/AdrenoDisplayEarlyDone.h: a layer-0 swap
|
||||
# completed right after its scanout copy, boot-arg adreno_present_early_done 1 always / 2 while WindowServer is behind,
|
||||
# 0 measures only; the perf done line), and SnapI2CHID desktop-fnkeys. Link it with
|
||||
# KEXT_PROFILE=desktop-early-done I2C_PROFILE=desktop-fnkeys tools/build-auxkc-desktop.sh NAME (sudo for kmutil's staging)
|
||||
# and put the two lines it prints here. Every addition is inert without its switch (the monitor's coherent ON with
|
||||
# adreno_coherent=1; adreno_backlight=1; adreno_present_early_done), so the collection also serves --edp-backlight runs
|
||||
# without them. None until linked and pinned: those flag sets are refused rather than run on the nocoherent kext.
|
||||
INPUT_AUXKC_DESKTOP_EARLY_DONE = None
|
||||
INPUT_AUXKC_DESKTOP_EARLY_DONE_SHA = None
|
||||
# --guest-net: INPUT_AUXKC's objects with SnapintoshVirtualEthernet's datapath (docs/GUEST-NETWORK-20261019.md).
|
||||
# None until that collection is built and pinned; --guest-net refuses meanwhile.
|
||||
INPUT_AUXKC_NET = None
|
||||
@@ -787,6 +798,9 @@ def route_selected(args):
|
||||
edp_backlight = getattr(args, 'edp_backlight', False)
|
||||
need(not edp_backlight or latch,
|
||||
'--edp-backlight needs --present-latch and its chain (its AdrenoX2 is that collection\'s with ADRENO_BACKLIGHT)')
|
||||
early = getattr(args, 'present_early_done', None)
|
||||
need(early is None or edp_backlight,
|
||||
'--present-early-done needs --edp-backlight and its chain (its AdrenoX2 is the early-done collection)')
|
||||
need(not (vsync60 and (damage or cached or gpumap)),
|
||||
'--present-vsync60 already includes --present-gpumap\'s (and so --present-cached-read\'s) kext: give one of them')
|
||||
need(not (damage and cached), '--present-cached-read already includes --present-damage\'s present: give one of them')
|
||||
@@ -812,6 +826,14 @@ def route_selected(args):
|
||||
if vsync60:
|
||||
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_VSYNC60,
|
||||
INPUT_AUXKC_VSYNC60_SHA, INPUT_BUNDLES)
|
||||
if dartpool and ptp and resident and beam and va_reclaim and va_wide and latch and edp_backlight and (
|
||||
getattr(args, 'gpu_coherent', False) or early is not None):
|
||||
need(INPUT_AUXKC_DESKTOP_EARLY_DONE is not None and INPUT_AUXKC_DESKTOP_EARLY_DONE_SHA is not None,
|
||||
'--edp-backlight with --gpu-coherent or --present-early-done: its AuxKC is not pinned yet '
|
||||
'(INPUT_AUXKC_DESKTOP_EARLY_DONE_SHA; KEXT_PROFILE=desktop-early-done I2C_PROFILE=desktop-fnkeys '
|
||||
'tools/build-auxkc-desktop.sh NAME links it)')
|
||||
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_DESKTOP_EARLY_DONE, INPUT_AUXKC_DESKTOP_EARLY_DONE_SHA,
|
||||
INPUT_BUNDLES)
|
||||
if dartpool and ptp and resident and beam and va_reclaim and va_wide and latch and edp_backlight:
|
||||
need(INPUT_AUXKC_DESKTOP_BACKLIGHT is not None and INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA is not None,
|
||||
'--edp-backlight: its AuxKC is not pinned yet (INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA; '
|
||||
@@ -1244,6 +1266,20 @@ def boot_addition(args):
|
||||
if active_us is not None:
|
||||
need(active_us == 0 or 2000 <= active_us <= 100000, '--input-active-us must be 0 or 2000..100000')
|
||||
extra.append(f'snap_i2c_active_us={active_us}')
|
||||
# --present-early-done: AdrenoDisplay's early completion (src/AdrenoDisplayEarlyDone.h), read only by the
|
||||
# desktop-early-done AdrenoX2 that --edp-backlight then selects; the kext turns it off with the release hold.
|
||||
early, early_hold = getattr(args, 'present_early_done', None), getattr(args, 'present_early_hold_ms', None)
|
||||
need(early is None or getattr(args, 'edp_backlight', False),
|
||||
'--present-early-done needs --edp-backlight (the early-done collection is that chain\'s)')
|
||||
need(early is None or early in (0, 1, 2), '--present-early-done is 0 (measure only), 1 (always) or 2 (while behind)')
|
||||
need(early_hold is None or early == 2, '--present-early-hold-ms needs --present-early-done 2')
|
||||
need(not early or hold != 'on', '--present-early-done 1/2 and --present-hold on exclude each other (the hold '
|
||||
'keeps the swap current until its release)')
|
||||
if early is not None:
|
||||
extra.append(f'adreno_present_early_done={early}')
|
||||
if early_hold is not None:
|
||||
need(100 <= early_hold <= 10000, '--present-early-hold-ms must be 100..10000')
|
||||
extra.append(f'adreno_present_early_hold_ms={early_hold}')
|
||||
# --input-fnkeys: the keyboard's Fn-key layer (drivers/snap-i2c-hid/src/hid-keyboard.h HID_FNKEYS_*), read by a
|
||||
# SnapI2CHID built with SNAP_I2C_HID_FNKEYS; the same sets the kext refuses are refused here.
|
||||
fnkeys = getattr(args, 'input_fnkeys', None)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Early completion (gpu/adreno-kext/kext/src/AdrenoDisplayEarlyDone.h): included at the end of
|
||||
* tools/replay-adreno-pacing.c under -DADRENO_PRESENT_EARLY_DONE=1, after relatch_scenarios. Without the define this
|
||||
* file adds nothing; with it every other output is byte for byte the define-off build's (the knobs below are 0 there).
|
||||
*
|
||||
* The world is the present-pacing one (the A16 beam and latch, the sync gate, the relatch, the pipelined submit lock,
|
||||
* app IBs) with the deployed kext's switches: relatch and urgent polling on, early release EP1/EP2 on, EP3's assist off
|
||||
* (the sparse kext compiles it out). AdrenoDisplay's early completion is mirrored step for step in
|
||||
* replay-adreno-latency.c (l_present, l_swap_begin's arrival, l_tick); mode 1 completes every layer-0 swap at its copy,
|
||||
* mode 2 only while a late frame engaged it (hold 2 s).
|
||||
*
|
||||
* Calibration (the 2026-10-24 desktop runs):
|
||||
* - busy: installed-fresh-desktop-20261024-org2's 117 busy windows (gfx-probe load): 49.9 copies/s median, 18% of the
|
||||
* ticks without a new swap, q 6.2 ms, one 1.35 ms IB per frame, TO_DEVICE + FROM_DEVICE 3.4 ms per IB (98 MiB). Only
|
||||
* input-latency's SLOT WindowServer (two swaps outstanding, the next frame when one completes) reproduces that rate,
|
||||
* with a render time of 5-9 ms and 20% of the frames 14-30 ms (FREE gives 57-59 copies/s for the same frames).
|
||||
* - busy_coherent: the same frames with b12v23's maintenance (--gpu-coherent: 1.05 ms and 40 MiB per IB);
|
||||
* - quiet: the cursor moving over an idle desktop, a layer-0 swap every refresh and no IB (b12v20/b12v21: 59.8/s);
|
||||
* - sporadic: next2c's idle windows (57.9/s): the quiet frames plus 5% of frames with an IB whose kext time is 13 ms
|
||||
* (TO_DEVICE 7 ms over 208 MiB and 6 ms of preparation) and 3.4 ms of GPU;
|
||||
* - sporadic_coherent: the same with the coherent maintenance (the preparation stays: 6.5-8.5 ms in the kext).
|
||||
* The model has no guest-wide stalls, so its intervals over 34 ms stay under the hardware's (org2: 5.4%). */
|
||||
static void early_done_scenarios(const char *dir)
|
||||
{
|
||||
unsigned c0 = checks, f0 = failures, cc0 = cost_checks, cf0 = cost_failures;
|
||||
char path[4096]; snprintf(path, sizeof path, "%s/early-done.json", dir);
|
||||
RJ = fopen(path, "w"); assert(RJ); rfirst = 0;
|
||||
fprintf(RJ, "{\"scenarios\":{");
|
||||
const uint64_t M = MS;
|
||||
struct ecase { struct rcase c; unsigned spike_pct; uint64_t spike_min, spike_max; };
|
||||
const struct ecase cases[] = {
|
||||
{{"busy", WS_SLOT, 5 * M, 9 * M, 100, {900 * US, 1400 * US, 3 * M}, {1400 * US, 3 * M, 8 * M}, {75, 23, 2},
|
||||
2200 * US, 3400 * US, 500 * US, 1 * M, 1200 * M, {1 * M, 3 * M, 10 * M}, {3 * M, 10 * M, 30 * M}, {80, 15, 5},
|
||||
1 * M, 300 * US, 2, 1 * M, 6 * M, 3}, 20, 14 * M, 30 * M},
|
||||
{{"busy_coherent", WS_SLOT, 5 * M, 9 * M, 100, {900 * US, 1400 * US, 3 * M}, {1400 * US, 3 * M, 8 * M}, {75, 23, 2},
|
||||
400 * US, 900 * US, 300 * US, 700 * US, 1200 * M, {1 * M, 3 * M, 10 * M}, {3 * M, 10 * M, 30 * M}, {80, 15, 5},
|
||||
1 * M, 300 * US, 2, 1 * M, 6 * M, 3}, 20, 14 * M, 30 * M},
|
||||
{{"busy", WS_FREE, 5 * M, 9 * M, 100, {900 * US, 1400 * US, 3 * M}, {1400 * US, 3 * M, 8 * M}, {75, 23, 2},
|
||||
2200 * US, 3400 * US, 500 * US, 1 * M, 1200 * M, {1 * M, 3 * M, 10 * M}, {3 * M, 10 * M, 30 * M}, {80, 15, 5},
|
||||
1 * M, 300 * US, 2, 1 * M, 6 * M, 3}, 20, 14 * M, 30 * M},
|
||||
{{"quiet", WS_SLOT, 2 * M, 3600 * US, 0, {0, 0, 0}, {0, 0, 0}, {100, 0, 0},
|
||||
0, 0, 0, 0, 0, {0, 0, 0}, {0, 0, 0}, {100, 0, 0}, 0, 0, 0, 0, 0, 0}, 0, 0, 0},
|
||||
{{"sporadic", WS_SLOT, 2 * M, 3600 * US, 5, {3 * M, 3 * M, 3 * M}, {4 * M, 4 * M, 4 * M}, {100, 0, 0},
|
||||
12 * M, 14 * M, 1 * M, 2 * M, 0, {0, 0, 0}, {0, 0, 0}, {100, 0, 0}, 0, 0, 0, 0, 0, 0}, 0, 0, 0},
|
||||
{{"sporadic_coherent", WS_SLOT, 2 * M, 3600 * US, 5, {3 * M, 3 * M, 3 * M}, {4 * M, 4 * M, 4 * M}, {100, 0, 0},
|
||||
6500 * US, 8500 * US, 300 * US, 700 * US, 0, {0, 0, 0}, {0, 0, 0}, {100, 0, 0}, 0, 0, 0, 0, 0, 0}, 0, 0, 0},
|
||||
};
|
||||
const unsigned ncases = sizeof cases / sizeof cases[0];
|
||||
for (unsigned i = 0; i < ncases; i++) {
|
||||
const struct ecase *e = &cases[i];
|
||||
struct rres x[3];
|
||||
for (int mode = 0; mode < 3; mode++) {
|
||||
char tag[32]; snprintf(tag, sizeof tag, "_done%d", mode);
|
||||
rcase_noassist = 1; rcase_copydone = mode; rcase_ed_hold = 2000 * M;
|
||||
rcase_spike_pct = e->spike_pct; rcase_spike_min = e->spike_min; rcase_spike_max = e->spike_max;
|
||||
x[mode] = r_case(&e->c, RP_ALL, 1500, 1000, 0, tag);
|
||||
}
|
||||
rcase_noassist = 0; rcase_copydone = 0; rcase_ed_hold = 0; rcase_spike_pct = 0; rcase_spike_min = rcase_spike_max = 0;
|
||||
const struct rres *t = &x[0], *a = &x[1], *d = &x[2];
|
||||
/* mode 0 measures only: nothing completed at a copy; the others never complete without a copy */
|
||||
CHECK(t->ed.early == 0 && t->ed.engages == 0);
|
||||
CHECK(a->ed.early <= a->copies && d->ed.early <= d->copies);
|
||||
CHECK(a->ed.engages == 0); /* mode 1 has nothing to engage */
|
||||
/* never fewer copies than today (1% noise) */
|
||||
COST(a->cps_x10 * 100 >= t->cps_x10 * 99 && d->cps_x10 * 100 >= t->cps_x10 * 99);
|
||||
if (e->c.ws == WS_SLOT && t->ed.late * 50 > t->copies) {
|
||||
/* WindowServer late on more than 2% of the frames: both modes recover most of the missed refreshes */
|
||||
COST(a->cps_x10 >= t->cps_x10 + 30 && d->cps_x10 >= t->cps_x10 + 25);
|
||||
COST(d->ed.engages > 0 && d->ed.early > 0);
|
||||
}
|
||||
if (!t->ed.late) {
|
||||
/* nothing late: the adaptive mode never engages and the run is today's frame for frame */
|
||||
COST(d->ed.engages == 0 && d->ed.early == 0 && d->cps_x10 == t->cps_x10 && d->r.in_mean_us == t->r.in_mean_us);
|
||||
}
|
||||
char line[512];
|
||||
int n = adreno_perf_line_done(line, sizeof line, i + 1, &d->ed, 0);
|
||||
CHECK(n > 0 && n <= ADRENO_PERF_LINE_MAX);
|
||||
printf("early-done %s_%s: copies/s today %llu.%llu, always %llu.%llu, adaptive %llu.%llu; >34 ms %llu.%llu%% / "
|
||||
"%llu.%llu%% / %llu.%llu%%; late frames today %llu of %llu copies; input mean %llu / %llu / %llu us, "
|
||||
"p95 %llu / "
|
||||
"%llu / %llu us; q %llu / %llu / %llu us; adaptive engaged %llu times, %llu of %llu copies completed early\n",
|
||||
e->c.name, ws_name[e->c.ws], (unsigned long long)t->cps_x10 / 10, (unsigned long long)t->cps_x10 % 10,
|
||||
(unsigned long long)a->cps_x10 / 10, (unsigned long long)a->cps_x10 % 10,
|
||||
(unsigned long long)d->cps_x10 / 10, (unsigned long long)d->cps_x10 % 10,
|
||||
(unsigned long long)t->over34_pm / 10, (unsigned long long)t->over34_pm % 10,
|
||||
(unsigned long long)a->over34_pm / 10, (unsigned long long)a->over34_pm % 10,
|
||||
(unsigned long long)d->over34_pm / 10, (unsigned long long)d->over34_pm % 10,
|
||||
(unsigned long long)t->ed.late, (unsigned long long)t->copies,
|
||||
(unsigned long long)t->r.in_mean_us, (unsigned long long)a->r.in_mean_us, (unsigned long long)d->r.in_mean_us,
|
||||
(unsigned long long)t->r.in_p95_us, (unsigned long long)a->r.in_p95_us, (unsigned long long)d->r.in_p95_us,
|
||||
(unsigned long long)t->r.q_us, (unsigned long long)a->r.q_us, (unsigned long long)d->r.q_us,
|
||||
(unsigned long long)d->ed.engages, (unsigned long long)d->ed.early, (unsigned long long)d->copies);
|
||||
n = adreno_perf_line_done(line, sizeof line, i + 1, &t->ed, 0);
|
||||
printf("early-done %s_%s today's arrivals: %s\n", e->c.name, ws_name[e->c.ws], line);
|
||||
}
|
||||
fprintf(RJ, "},\"checks\":%u,\"failures\":%u,\"cost_checks\":%u,\"cost_failures\":%u}\n", checks - c0, failures - f0,
|
||||
cost_checks - cc0, cost_failures - cf0);
|
||||
fclose(RJ);
|
||||
printf("{\"early_done_checks\":%u,\"early_done_failures\":%u,\"early_done_cost_checks\":%u,"
|
||||
"\"early_done_cost_failures\":%u}\n",
|
||||
checks - c0, failures - f0, cost_checks - cc0, cost_failures - cf0);
|
||||
}
|
||||
@@ -57,6 +57,16 @@
|
||||
/* agents/present-pacing FAILURE-MODES.md: the b12v17 pipeline with its submit lock, and the relatch (relatch_scenarios) */
|
||||
#include "../gpu/adreno-kext/kext/src/AdrenoDisplayRelatch.h"
|
||||
#endif
|
||||
#ifndef ADRENO_PRESENT_EARLY_DONE
|
||||
#define ADRENO_PRESENT_EARLY_DONE 0
|
||||
#endif
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
#if !ADRENO_PRESENT_RELATCH
|
||||
#error "the early-completion scenarios run in the present-pacing world (-DADRENO_PRESENT_RELATCH=1)"
|
||||
#endif
|
||||
/* AdrenoDisplayEarlyDone.h: a layer-0 swap completed right after its copy, always or while WindowServer is behind (early_done_scenarios) */
|
||||
#include "../gpu/adreno-kext/kext/src/AdrenoDisplayEarlyDone.h"
|
||||
#endif
|
||||
|
||||
enum { WS_SLOT = 0, WS_EVENT = 1, WS_TIMER = 2, WS_FREE = 3 };
|
||||
enum { LP_BEAM = 0, LP_LATCH = 1, LP_HOLD = 2, LP_HOLD_NOGATE = 3 };
|
||||
@@ -144,6 +154,10 @@ struct lworld {
|
||||
uint64_t c_ovl, c_tmr, c_pre, c_ast, c_app, c_apps, c_polls, c_late_unmark;
|
||||
uint64_t ret_at[LMAX]; /* the submission's return: G1 busy until then, S returns then */
|
||||
#endif
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
/* AdrenoDisplay's fEarly: mode = complete_at_copy (1 always, as the pipeline what-if; 2 adaptive), its hold */
|
||||
struct adreno_early_done ed; uint64_t ed_hold;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* ---- WindowServer ---- */
|
||||
@@ -334,13 +348,25 @@ static void l_present(struct lworld *l, uint32_t id, uint8_t layers) /* presen
|
||||
l->presented = 1;
|
||||
if (layers & 1) l_copy(l, id);
|
||||
#if ADRENO_PIPELINE
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
/* earlyDoneNowGated by mode (1 is also gpu-pipeline's what-if, whose hold rows keep it: the kext refuses the hold
|
||||
* with early completion, the early-completion scenarios never run the hold) */
|
||||
if ((layers & 1) && l->complete_at_copy && adreno_early_active(&l->ed, l->w.now)) {
|
||||
l->early_done = id; adreno_early_completed(&l->ed); ws_completed(l, id);
|
||||
}
|
||||
#else
|
||||
if ((layers & 1) && l->complete_at_copy) { l->early_done = id; ws_completed(l, id); } /* what-if */
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
static void l_pop(struct lworld *l) { memmove(l->q, l->q + 1, (l->qn - 1) * sizeof l->q[0]); l->qn--; }
|
||||
static void l_swap_begin(struct lworld *l, int in_tick) /* swap_begin_gated */
|
||||
{
|
||||
struct bworld *w = &l->w;
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
if (!in_tick && l->qn && !l->pending_since && (l->q[0].layers & 1))
|
||||
adreno_early_arrival(&l->ed, w->now, l->qn == 1); /* swap_begin_gated, F1's read */
|
||||
#endif
|
||||
if (!in_tick && l->qn && !l->pending_since) l->pending_since = w->now; /* F1 */
|
||||
#if ADRENO_LATCH
|
||||
if (!in_tick && l->pending_since && l->h.released_at) adreno_hold_arrival(&l->h, tb(w, l->pending_since));
|
||||
@@ -462,12 +488,19 @@ static void l_tick(struct lworld *l) /* vsyncGated */
|
||||
w->in_tick = 0;
|
||||
if (!held) {
|
||||
if (l->rel_armed && l->cur && l->presented) l->rel_armed = 0;
|
||||
#endif
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
const int ed_had = l->cur && l->presented && (l->cur_layers & 1); /* completed here, or held since its copy */
|
||||
const uint64_t ed_copies = l->copies;
|
||||
#endif
|
||||
if (l->cur && l->presented) l_complete(l);
|
||||
w->in_tick = 1;
|
||||
if (l_gate(l)) { l->gated++; l_arm(l); } else l_swap_begin(l, 1);
|
||||
#if ADRENO_PRESENT_RELATCH
|
||||
if (l->p2) p2_relatch_arm(l); /* RL4: the GO's frame unused and its head deferred */
|
||||
#endif
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
adreno_early_tick(&l->ed, w->now, ed_had, l->copies != ed_copies || (l->qn && (l->q[0].layers & 1)));
|
||||
#endif
|
||||
w->b.go_row0 = 0; w->in_tick = 0;
|
||||
#if ADRENO_LATCH
|
||||
@@ -545,6 +578,9 @@ static void l_start(struct lworld *l)
|
||||
adreno_beam_latch(&w->b, l->policy == LP_BEAM ? 0 : 3 * ms);
|
||||
adreno_hold_init(&l->h, l->policy == LP_HOLD || l->policy == LP_HOLD_NOGATE, ms * 3 / 2, ms * ADRENO_HOLD_FLOOR_US / 1000);
|
||||
l->gate_ns = l->policy == LP_LATCH || l->policy == LP_HOLD ? 4 * MS : 0;
|
||||
#endif
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
adreno_early_init(&l->ed, (uint32_t)l->complete_at_copy, 16666667, l->ed_hold ? l->ed_hold : 2000 * MS, MS);
|
||||
#endif
|
||||
l->started_at = w->now;
|
||||
if (l->ws_kind == WS_EVENT) l->events = 1;
|
||||
|
||||
@@ -277,8 +277,19 @@ struct rres {
|
||||
uint64_t copies, iv[5], over34_pm, cps_x10, defer, swaps, max_wait_us, gpu_frames, marked, apps;
|
||||
uint64_t ovl, tmr, pre, ast, app_fin, polls, rl_armed, rl_go, rl_late, rl_gone, rl_gated, rl_ovt, rl_polls, rl_lead_min_us;
|
||||
uint64_t hist[ADRENO_RELATCH_BINS];
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
struct adreno_early_done ed; /* the run's early completions and arrivals (not windowed) */
|
||||
#endif
|
||||
};
|
||||
static FILE *RJ; static int rfirst;
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
/* replay-adreno-early-done.c's knobs, all 0 for every row above (their outputs stay byte for byte as without the define):
|
||||
* the swap completed right after its copy (lworld complete_at_copy, AdrenoDisplay's adreno_present_early_done=1),
|
||||
* WindowServer's render spikes, and EP3's assist off (the deployed sparse kext compiles it out). */
|
||||
static int rcase_copydone, rcase_noassist;
|
||||
static unsigned rcase_spike_pct;
|
||||
static uint64_t rcase_spike_min, rcase_spike_max, rcase_ed_hold;
|
||||
#endif
|
||||
static struct rres r_case(const struct rcase *c, int rp, unsigned swaps, uint64_t guard_us, unsigned seed, const char *tag)
|
||||
{
|
||||
static struct lworld l;
|
||||
@@ -297,6 +308,11 @@ static struct rres r_case(const struct rcase *c, int rp, unsigned swaps, uint64_
|
||||
l.early = rp == RP_EARLY || rp == RP_EARLY_NOASSIST || rp == RP_ALL || rp == RP_ALL_PIPE2;
|
||||
l.assist = l.early && rp != RP_EARLY_NOASSIST ? 250 * US : 0;
|
||||
l.tmr_period = 500 * US; l.urgent_period = relatch && rp != RP_RELATCH_NOURGENT ? 100 * US : 0;
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
l.complete_at_copy = rcase_copydone; l.ed_hold = rcase_ed_hold;
|
||||
l.spike_pct = rcase_spike_pct; l.spike_min = rcase_spike_min; l.spike_max = rcase_spike_max;
|
||||
if (rcase_noassist) l.assist = 0;
|
||||
#endif
|
||||
l.multi_pct = c->multi_pct;
|
||||
l.w.stall_pct = c->stall_pct; l.w.stall_min = c->stall_min; l.w.stall_max = c->stall_max;
|
||||
l.tmr_at = l.enter_at = l.rl_at = LINF;
|
||||
@@ -331,6 +347,9 @@ static struct rres r_case(const struct rcase *c, int rp, unsigned swaps, uint64_
|
||||
x.rl_armed = l.rl.armed; x.rl_go = l.rl.go; x.rl_late = l.rl.late; x.rl_gone = l.rl.gone; x.rl_gated = l.rl.gated;
|
||||
x.rl_ovt = l.rl.ovt; x.rl_polls = l.rl.polls; x.rl_lead_min_us = tb_ns(&l.w, l.w.tb_off + l.rl.lead_min) / 1000;
|
||||
memcpy(x.hist, l.rl.hist, sizeof x.hist);
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
x.ed = l.ed;
|
||||
#endif
|
||||
char name[128]; snprintf(name, sizeof name, "%s_%s_%s%s", c->name, ws_name[c->ws], rp_name[rp], tag ? tag : "");
|
||||
const struct lres *r = &x.r;
|
||||
fprintf(RJ, "%s\"%s\":{\"frames\":%llu,\"torn\":%llu,\"ovt\":%llu,\"beam_forced\":%llu,\"fps_x10\":%llu,\"copies_x10\":%llu,"
|
||||
@@ -493,4 +512,7 @@ static void relatch_scenarios(const char *dir)
|
||||
printf("{\"relatch_checks\":%u,\"relatch_failures\":%u,\"relatch_cost_checks\":%u,\"relatch_cost_failures\":%u}\n",
|
||||
checks - c0, failures - f0, cost_checks - cc0, cost_failures - cf0);
|
||||
}
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
#include "replay-adreno-early-done.c"
|
||||
#endif
|
||||
#endif
|
||||
@@ -1153,6 +1153,9 @@ int main(int argc,char **argv) {
|
||||
#endif
|
||||
#if ADRENO_PRESENT_RELATCH
|
||||
relatch_scenarios(argv[1]);
|
||||
#if ADRENO_PRESENT_EARLY_DONE
|
||||
early_done_scenarios(argv[1]);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Summarize a run's AdrenoDisplay perf windows: frame pacing and where a busy window's time went. Host only.
|
||||
|
||||
python3 tools/summarize-present-pacing.py build/PREFIX/console.txt [--report build/PREFIX/report.bin] [--json]
|
||||
|
||||
A perf window is the set of "AdrenoDisplay: perf <kind> s=<n>" lines one perfLineGated call prints (600 layer-0
|
||||
copies, AdrenoDisplay/stats.cpp). Windows are split into busy (at least --busy-ibs completed IBs, default 200: about
|
||||
20 IBs a second) and quiet ones, and each group is reduced to:
|
||||
- copies per second (median, min) and the copy intervals by the pace line's buckets (<= 17, <= 20, <= 34, <= 50,
|
||||
> 50 ms; <= 20 ms is on time, the tick firing late; 20-34 ms is one refresh shown twice; more is worse);
|
||||
- the ticks: ready (a layer-0 copy started in the tick), missed (a frame was on screen, nothing new copied), idle;
|
||||
missed less the relatch's copies (the head the tick deferred, copied into the same frame) is the share of refreshes
|
||||
WindowServer did not deliver in time ("true misses");
|
||||
- per IB: GPU time (the monitor's poll ticks at 19.2 MHz), the kext's submission time with the lock held, its cache
|
||||
maintenance time and bytes, its resource preparations; the copy time, the tick's lateness and q (swap to copy);
|
||||
- with the early-completion kext (the perf done line): completions at the copy, late frames, engagements, and the
|
||||
arrival and lateness histograms summed over the group.
|
||||
With --report, the GPU's time per clock from the DVFSREP1 record (tools/dvfs_report.py) is added."""
|
||||
import argparse
|
||||
import collections
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HOST_HZ = 19_200_000 # the monitor's IB tick counter (gtk)
|
||||
NEED = ('why', 'pace', 'map', 'gpu', 'pipe', 'sync', 'vsync')
|
||||
|
||||
|
||||
def windows(text):
|
||||
w = collections.defaultdict(dict)
|
||||
for line in text.split('\n'):
|
||||
m = re.search(r'AdrenoDisplay: perf why=(\w+) s=(\d+) (.*)', line)
|
||||
if m:
|
||||
w[int(m.group(2))]['why'] = dict(re.findall(r'(\w+)=(\S+)', m.group(3)), kind=m.group(1))
|
||||
continue
|
||||
m = re.search(r'AdrenoDisplay: perf (\w+) s=(\d+) (.*)', line)
|
||||
if m:
|
||||
w[int(m.group(2))][m.group(1)] = dict(re.findall(r'(\w+)=(\S+)', m.group(3)))
|
||||
return w
|
||||
|
||||
|
||||
def ints(text):
|
||||
return [int(x) for x in text.split('/')]
|
||||
|
||||
|
||||
def window_row(s, r):
|
||||
"""One periodic window's numbers, or None (an incomplete or non-periodic window)."""
|
||||
if not all(k in r for k in NEED) or r['why'].get('kind') != 'periodic':
|
||||
return None
|
||||
p, mp, g, pi, sy, v = (r[k] for k in ('pace', 'map', 'gpu', 'pipe', 'sync', 'vsync'))
|
||||
pp, tk = ints(p['pp']), ints(p['tk'])
|
||||
span = sum(pp) * int(p['mean']) / 1e6
|
||||
ib = int(mp['ib'])
|
||||
gtk, sub, cache, prep = ints(g['gtk']), ints(g['sub_us']), ints(g['cache']), ints(g['prep'])
|
||||
relatch = r.get('relatch', {})
|
||||
row = dict(s=s, pps=float(p['pps']), span=span, ib=ib, buckets=pp, ticks=tk[0], ready=tk[1], missed=tk[2], idle=tk[3],
|
||||
relatched=int(relatch.get('go', 0)), defer=int(sy['defer']),
|
||||
gpu_ms_ib=gtk[0] / HOST_HZ * 1e3 / ib if ib else 0.0,
|
||||
gpu_busy=gtk[0] / HOST_HZ / span if span else 0.0,
|
||||
sub_ms_ib=sub[0] / 1e3 / ib if ib else 0.0, cache_ms_ib=cache[2] / 1e3 / ib if ib else 0.0,
|
||||
cache_mib_ib=cache[1] / ib if ib else 0.0, prep_ms_ib=prep[1] / 1e3 / ib if ib else 0.0,
|
||||
copy_us=int(r['why']['mean_us']), late_us=ints(v['grid'])[0], q_us=int(p['q']))
|
||||
if 'done' in r:
|
||||
d = r['done']
|
||||
row['done'] = dict(mode=int(d['mode']), on=int(d['on']), early=int(d['done']), late=int(d['late']),
|
||||
engages=int(d['eng']), ahead=int(d['ahd']), arr=ints(d['arr']), lat=ints(d['lat']))
|
||||
return row
|
||||
|
||||
|
||||
def group(rows):
|
||||
if not rows:
|
||||
return None
|
||||
ticks = sum(r['ticks'] for r in rows) or 1
|
||||
buckets = [sum(r['buckets'][i] for r in rows) for i in range(5)]
|
||||
intervals = sum(buckets) or 1
|
||||
med = lambda k: round(statistics.median(r[k] for r in rows), 3)
|
||||
out = dict(windows=len(rows), pps_median=med('pps'), pps_min=min(r['pps'] for r in rows),
|
||||
ticks_per_s=round(ticks / sum(r['span'] for r in rows), 2),
|
||||
ready_pct=round(100 * sum(r['ready'] for r in rows) / ticks, 1),
|
||||
missed_pct=round(100 * sum(r['missed'] for r in rows) / ticks, 1),
|
||||
true_miss_pct=round(100 * sum(r['missed'] - r['relatched'] for r in rows) / ticks, 1),
|
||||
deferred_pct=round(100 * sum(r['defer'] for r in rows) / ticks, 1),
|
||||
intervals_pct=[round(100 * b / intervals, 1) for b in buckets],
|
||||
over34_pct=round(100 * (buckets[3] + buckets[4]) / intervals, 1),
|
||||
ibs_per_s=round(sum(r['ib'] for r in rows) / sum(r['span'] for r in rows), 1))
|
||||
for k in ('gpu_ms_ib', 'gpu_busy', 'sub_ms_ib', 'cache_ms_ib', 'cache_mib_ib', 'prep_ms_ib', 'copy_us', 'late_us',
|
||||
'q_us'):
|
||||
out[k + '_median'] = med(k)
|
||||
done = [r['done'] for r in rows if 'done' in r]
|
||||
if done:
|
||||
out['done'] = dict(modes=sorted({d['mode'] for d in done}), engaged_windows=sum(1 for d in done if d['on']),
|
||||
early=sum(d['early'] for d in done), late=sum(d['late'] for d in done),
|
||||
engages=sum(d['engages'] for d in done), ahead=sum(d['ahead'] for d in done),
|
||||
arrival_2ms_bins=[sum(d['arr'][i] for d in done) for i in range(len(done[0]['arr']))],
|
||||
late_bins_2_4_8_more=[sum(d['lat'][i] for d in done) for i in range(len(done[0]['lat']))])
|
||||
return out
|
||||
|
||||
|
||||
def summarize(text, busy_ibs=200):
|
||||
rows = [x for x in (window_row(s, r) for s, r in sorted(windows(text).items())) if x]
|
||||
return dict(busy=group([r for r in rows if r['ib'] >= busy_ibs]),
|
||||
quiet=group([r for r in rows if r['ib'] < busy_ibs]), busy_ibs=busy_ibs)
|
||||
|
||||
|
||||
def gpu_clocks(report):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import dvfs_report
|
||||
raw = Path(report).read_bytes()
|
||||
decoded = dvfs_report.decode_block(raw[dvfs_report.OFFSET:dvfs_report.OFFSET + 8 * 190]) # as its main reads it
|
||||
gpu = decoded.get('gpu', {}) if isinstance(decoded, dict) else {}
|
||||
keep = ('status', 'ceiling_mhz', 'seconds_at_mhz', 'mean_mhz', 'errors', 'thermal')
|
||||
return {k: gpu[k] for k in keep if k in gpu}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument('console', type=Path)
|
||||
ap.add_argument('--report', type=Path)
|
||||
ap.add_argument('--busy-ibs', type=int, default=200)
|
||||
ap.add_argument('--json', action='store_true')
|
||||
a = ap.parse_args()
|
||||
out = summarize(a.console.read_text(errors='replace'), a.busy_ibs)
|
||||
if a.report:
|
||||
out['gpu_clock'] = gpu_clocks(a.report)
|
||||
if a.json:
|
||||
print(json.dumps(out, indent=1))
|
||||
return
|
||||
for name in ('busy', 'quiet'):
|
||||
g = out[name]
|
||||
if not g:
|
||||
print(f'{name}: no windows')
|
||||
continue
|
||||
print(f"{name}: {g['windows']} windows, copies/s median {g['pps_median']} (min {g['pps_min']}), ticks/s "
|
||||
f"{g['ticks_per_s']}, IBs/s {g['ibs_per_s']}; ticks ready {g['ready_pct']}% missed {g['missed_pct']}% "
|
||||
f"(true misses {g['true_miss_pct']}%), deferred {g['deferred_pct']}%; intervals <=17/20/34/50/>50 ms "
|
||||
f"{'/'.join(str(x) for x in g['intervals_pct'])}%, over 34 ms {g['over34_pct']}%")
|
||||
print(f" per IB: GPU {g['gpu_ms_ib_median']} ms (GPU busy {g['gpu_busy_median']}), submit {g['sub_ms_ib_median']} ms, "
|
||||
f"cache {g['cache_ms_ib_median']} ms / {g['cache_mib_ib_median']} MiB, preparation {g['prep_ms_ib_median']} ms; "
|
||||
f"copy {g['copy_us_median']} us, tick late {g['late_us_median']} us, q {g['q_us_median']} us")
|
||||
if 'done' in g:
|
||||
d = g['done']
|
||||
print(f" early completion: modes {d['modes']}, engaged at {d['engaged_windows']} window ends, {d['early']} "
|
||||
f"completions at the copy, {d['late']} late frames ({d['engages']} engagements), {d['ahead']} arrivals "
|
||||
f"behind another; arrivals by 2 ms {d['arrival_2ms_bins']}, lateness <2/<4/<8/more "
|
||||
f"{d['late_bins_2_4_8_more']}")
|
||||
if 'gpu_clock' in out:
|
||||
print(f"gpu clock: {json.dumps(out['gpu_clock'])}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,157 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Host test for gpu/adreno-kext/kext/src/AdrenoDisplayEarlyDone.h (AdrenoDisplay's early completion and arrival
|
||||
* measurements). No kext, no A16. Times are plain units with one millisecond = 1000.
|
||||
*
|
||||
* cc -std=c11 -Wall -Wextra -Werror tools/test-adreno-early-done.c -o test && ./test
|
||||
* (-DEARLY_DONE_HEADER='"<path>"' tests another copy of the header: tools/test-present-early-done.py runs mutants)
|
||||
*
|
||||
* Exit 0 when every check passes; each failure is printed. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef EARLY_DONE_HEADER
|
||||
#define EARLY_DONE_HEADER "../gpu/adreno-kext/kext/src/AdrenoDisplayEarlyDone.h"
|
||||
#endif
|
||||
#include EARLY_DONE_HEADER
|
||||
|
||||
static unsigned checks, failures;
|
||||
#define CHECK(c) \
|
||||
do { checks++; if (!(c)) { failures++; fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #c); } } while (0)
|
||||
|
||||
enum { MSU = 1000, PERIOD = 16667, HOLD = 2000 * MSU };
|
||||
|
||||
static struct adreno_early_done fresh(uint32_t mode)
|
||||
{
|
||||
struct adreno_early_done e;
|
||||
adreno_early_init(&e, mode, PERIOD, HOLD, MSU);
|
||||
return e;
|
||||
}
|
||||
|
||||
/* Modes: OFF never completes early, ALWAYS always, ADAPTIVE only once engaged; an unknown mode is OFF. */
|
||||
static void modes(void)
|
||||
{
|
||||
struct adreno_early_done off = fresh(ADRENO_EARLY_OFF), always = fresh(ADRENO_EARLY_ALWAYS);
|
||||
struct adreno_early_done adaptive = fresh(ADRENO_EARLY_ADAPTIVE), bad = fresh(7);
|
||||
CHECK(!adreno_early_active(&off, 1) && !adreno_early_active(&off, 1u << 30));
|
||||
CHECK(adreno_early_active(&always, 0) && adreno_early_active(&always, 123456789));
|
||||
CHECK(!adreno_early_active(&adaptive, 5 * MSU));
|
||||
CHECK(bad.mode == ADRENO_EARLY_OFF && !adreno_early_active(&bad, 5));
|
||||
struct adreno_early_done z = fresh(ADRENO_EARLY_ADAPTIVE);
|
||||
adreno_early_init(&z, ADRENO_EARLY_ADAPTIVE, PERIOD, HOLD, 0);
|
||||
CHECK(z.ms == 1); /* never a zero millisecond (the bins divide by it) */
|
||||
}
|
||||
|
||||
/* Arrivals: the offset after the last proceeding tick in 2 ms bins, the last bin open; none before the first tick. */
|
||||
static void arrivals(void)
|
||||
{
|
||||
struct adreno_early_done e = fresh(ADRENO_EARLY_OFF);
|
||||
CHECK(!adreno_early_arrival(&e, 5 * MSU, 1) && e.arrivals == 0); /* no tick yet: nothing to measure from */
|
||||
adreno_early_tick(&e, 100 * MSU, 1, 1);
|
||||
const uint64_t offsets[] = {0, 1999, 2000, 7 * MSU, 15999, 16 * MSU, 40 * MSU};
|
||||
const unsigned bins[] = {0, 0, 1, 3, 7, 8, 8};
|
||||
for (unsigned i = 0; i < sizeof offsets / sizeof offsets[0]; i++) {
|
||||
struct adreno_early_done x = e;
|
||||
adreno_early_arrival(&x, 100 * MSU + offsets[i], 1);
|
||||
CHECK(x.arrivals == 1 && x.arr[bins[i]] == 1);
|
||||
}
|
||||
struct adreno_early_done x = e;
|
||||
CHECK(!adreno_early_arrival(&x, 99 * MSU, 1) && x.arrivals == 0); /* before the tick (a clock race): ignored */
|
||||
CHECK(!adreno_early_arrival(&x, 105 * MSU, 0) && x.ahead == 1 && x.arrivals == 0); /* behind another: ahead */
|
||||
}
|
||||
|
||||
/* Late frames: a tick that had a frame and neither copied nor kept a layer-0 head, then an arrival within one refresh.
|
||||
* Not late: a served tick, an idle tick (no frame on screen), an arrival a refresh or more later, a second arrival. */
|
||||
static void late_frames(void)
|
||||
{
|
||||
struct adreno_early_done e = fresh(ADRENO_EARLY_OFF);
|
||||
adreno_early_tick(&e, 1000 * MSU, 1, 0); /* missed */
|
||||
CHECK(adreno_early_arrival(&e, 1000 * MSU + 3 * MSU, 1) == 1);
|
||||
CHECK(e.late == 1 && e.lat[1] == 1 && e.engages == 0 && !adreno_early_active(&e, 1004 * MSU));
|
||||
CHECK(adreno_early_arrival(&e, 1000 * MSU + 5 * MSU, 1) == 0 && e.late == 1); /* once per missed tick */
|
||||
const uint64_t lateness[] = {500, 2500, 6000, 9000, PERIOD - 1};
|
||||
const unsigned lbins[] = {0, 1, 2, 3, 3};
|
||||
for (unsigned i = 0; i < sizeof lateness / sizeof lateness[0]; i++) {
|
||||
struct adreno_early_done x = fresh(ADRENO_EARLY_OFF);
|
||||
adreno_early_tick(&x, 50 * MSU, 1, 0);
|
||||
CHECK(adreno_early_arrival(&x, 50 * MSU + lateness[i], 1) == 1 && x.lat[lbins[i]] == 1);
|
||||
}
|
||||
struct adreno_early_done x = fresh(ADRENO_EARLY_OFF);
|
||||
adreno_early_tick(&x, 50 * MSU, 1, 0);
|
||||
CHECK(adreno_early_arrival(&x, 50 * MSU + PERIOD, 1) == 0 && x.late == 0); /* a refresh later: not this tick's */
|
||||
adreno_early_tick(&x, 80 * MSU, 1, 1); /* served: a copy, or a deferred head kept */
|
||||
CHECK(adreno_early_arrival(&x, 81 * MSU, 1) == 0 && x.late == 0);
|
||||
adreno_early_tick(&x, 90 * MSU, 0, 0); /* idle: nothing on screen for that refresh */
|
||||
CHECK(adreno_early_arrival(&x, 91 * MSU, 1) == 0 && x.late == 0);
|
||||
adreno_early_tick(&x, 100 * MSU, 1, 0);
|
||||
CHECK(adreno_early_arrival(&x, 101 * MSU, 0) == 0 && x.late == 0 && x.ahead == 1); /* behind another: no late */
|
||||
}
|
||||
|
||||
/* Adaptive: a late frame engages it for `hold`, every later late frame renews it, it ends by itself; ALWAYS and OFF
|
||||
* never count engagements. */
|
||||
static void adaptive(void)
|
||||
{
|
||||
struct adreno_early_done e = fresh(ADRENO_EARLY_ADAPTIVE);
|
||||
adreno_early_tick(&e, 10 * MSU, 1, 0);
|
||||
adreno_early_arrival(&e, 12 * MSU, 1);
|
||||
CHECK(e.engages == 1 && adreno_early_active(&e, 12 * MSU) && adreno_early_active(&e, 12 * MSU + HOLD - 1));
|
||||
CHECK(!adreno_early_active(&e, 12 * MSU + HOLD));
|
||||
adreno_early_tick(&e, 1000 * MSU, 1, 0); /* renewed while engaged: not a new engagement */
|
||||
adreno_early_arrival(&e, 1001 * MSU, 1);
|
||||
CHECK(e.engages == 1 && adreno_early_active(&e, 1001 * MSU + HOLD - 1) && !adreno_early_active(&e, 1001 * MSU + HOLD));
|
||||
adreno_early_tick(&e, 5000 * MSU, 1, 0); /* after it ended: a new engagement */
|
||||
adreno_early_arrival(&e, 5001 * MSU, 1);
|
||||
CHECK(e.engages == 2 && adreno_early_active(&e, 5002 * MSU));
|
||||
adreno_early_reset(&e); /* stop / mode set: nothing engaged, no tick */
|
||||
CHECK(!adreno_early_active(&e, 5002 * MSU) && e.last_tick == 0 && !e.tick_missed && e.mode == ADRENO_EARLY_ADAPTIVE);
|
||||
struct adreno_early_done a = fresh(ADRENO_EARLY_ALWAYS);
|
||||
adreno_early_tick(&a, 10 * MSU, 1, 0);
|
||||
adreno_early_arrival(&a, 11 * MSU, 1);
|
||||
CHECK(a.late == 1 && a.engages == 0);
|
||||
}
|
||||
|
||||
/* Windows: counters start over, the tick state and an engagement carry across. */
|
||||
static void windows(void)
|
||||
{
|
||||
struct adreno_early_done e = fresh(ADRENO_EARLY_ADAPTIVE);
|
||||
adreno_early_tick(&e, 10 * MSU, 1, 0);
|
||||
adreno_early_arrival(&e, 11 * MSU, 1);
|
||||
adreno_early_completed(&e);
|
||||
adreno_early_tick(&e, 27 * MSU, 1, 0);
|
||||
adreno_early_next_window(&e);
|
||||
CHECK(!e.early && !e.late && !e.engages && !e.arrivals && !e.ahead && !e.arr[0] && !e.lat[0]);
|
||||
CHECK(e.last_tick == 27 * MSU && e.tick_missed && adreno_early_active(&e, 30 * MSU));
|
||||
}
|
||||
|
||||
/* The perf done line: at most ADRENO_PERF_LINE_MAX bytes at every cap, and the exact text of a known window. */
|
||||
static void perf_line(void)
|
||||
{
|
||||
char line[512];
|
||||
struct adreno_early_done big;
|
||||
memset(&big, 0xff, sizeof big);
|
||||
int n = adreno_perf_line_done(line, sizeof line, ~0ull, &big, 1);
|
||||
CHECK(n > 0 && n <= ADRENO_PERF_LINE_MAX);
|
||||
printf("perf done line at caps: %d bytes\n", n);
|
||||
struct adreno_early_done e = fresh(ADRENO_EARLY_ADAPTIVE);
|
||||
adreno_early_tick(&e, 10 * MSU, 1, 0);
|
||||
adreno_early_arrival(&e, 13 * MSU, 1);
|
||||
adreno_early_completed(&e);
|
||||
adreno_early_tick(&e, 27 * MSU, 1, 1);
|
||||
adreno_early_arrival(&e, 36 * MSU, 1);
|
||||
adreno_early_arrival(&e, 37 * MSU, 0);
|
||||
n = adreno_perf_line_done(line, sizeof line, 42, &e, adreno_early_active(&e, 40 * MSU));
|
||||
CHECK(!strcmp(line, "AdrenoDisplay: perf done s=42 mode=2 on=1 done=1 late=1 eng=1 ahd=1 "
|
||||
"arr=0/1/0/0/1/0/0/0/0 lat=0/1/0/0"));
|
||||
printf("%s\n", line);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
modes();
|
||||
arrivals();
|
||||
late_frames();
|
||||
adaptive();
|
||||
windows();
|
||||
perf_line();
|
||||
printf("{\"checks\":%u,\"failures\":%u}\n", checks, failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Host test for --present-early-done: AdrenoDisplay completes a layer-0 swap right after its scanout copy
|
||||
(gpu/adreno-kext/kext/src/AdrenoDisplayEarlyDone.h, -DADRENO_PRESENT_EARLY_DONE=1, profile desktop-early-done). No A16.
|
||||
|
||||
- the header's unit test (tools/test-adreno-early-done.c, ASan/UBSan) passes, and fails on each deliberate mutant;
|
||||
- the kext uses the feature only inside ADRENO_PRESENT_EARLY_DONE blocks, and the desktop-early-done profile differs from
|
||||
desktop-backlight only in AdrenoDisplay.o (built when the private *26A428.h headers are present, else SKIP);
|
||||
- cycle.py: without the new flags the boot arguments and the AuxKC choice are what they were; with them, the boot-args,
|
||||
the refusals and the early-done collection (refused until pinned);
|
||||
- the presentation replay: without the define every output is the define-off build's; with it, the earlier outputs are
|
||||
unchanged and early-done.json has no failure and no cost failure (--no-replay skips this part, about 40 s).
|
||||
"""
|
||||
import argparse
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
KEXT = ROOT / 'gpu/adreno-kext/kext'
|
||||
HEADER = KEXT / 'src/AdrenoDisplayEarlyDone.h'
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
from cycle_lib import installed_fresh as fresh # noqa: E402
|
||||
from cycle_lib.common import Refused # noqa: E402
|
||||
|
||||
spec = importlib.util.spec_from_file_location('cycle_cli', ROOT / 'tools/cycle.py')
|
||||
cli = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(cli)
|
||||
|
||||
CC = ['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined']
|
||||
# (name, anchor in the header, replacement): each must make the unit test fail.
|
||||
MUTANTS = [
|
||||
('late counted twice', ' e->tick_missed = 0; /* one late frame per missed tick */\n', ''),
|
||||
('late window too wide', 'const int late = e->tick_missed && d < e->period;',
|
||||
'const int late = e->tick_missed && d < 2 * e->period;'),
|
||||
('idle ticks missed', 'e->tick_missed = had_frame && !served;', 'e->tick_missed = !served;'),
|
||||
('no renewal', ' e->until = now + e->hold;', ' if (!e->until) e->until = now + e->hold;'),
|
||||
('hold one tick long', 'e->until && now < e->until;', 'e->until && now <= e->until;'),
|
||||
('ahead not counted', 'if (!alone) { e->ahead++; return 0; }', 'if (!alone) return 0;'),
|
||||
('always is adaptive', 'if (e->mode == ADRENO_EARLY_ALWAYS) return 1;', ''),
|
||||
('arrival bins of 1 ms', 'two = 2 * e->ms;', 'two = e->ms;'),
|
||||
]
|
||||
REPLAY_DEFINES = ['-DADRENO_BEAM=1', '-DADRENO_PRESENT_CURSOR_STAGE=1', '-DADRENO_LATCH=1', '-DADRENO_PRESENT_SYNC=1',
|
||||
'-DADRENO_PIPELINE=1', '-DADRENO_PRESENT_RELATCH=1']
|
||||
RUN_REPLAY = True
|
||||
|
||||
|
||||
def run_unit(header=None, out=None):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
binary = Path(d) / 'early-done'
|
||||
extra = [f'-DEARLY_DONE_HEADER="{header}"'] if header else []
|
||||
build = subprocess.run(CC + extra + [str(ROOT / 'tools/test-adreno-early-done.c'), '-o', str(binary)],
|
||||
capture_output=True, text=True)
|
||||
if build.returncode:
|
||||
return 'build', build.stderr
|
||||
r = subprocess.run([str(binary)], capture_output=True, text=True, cwd=ROOT / 'tools')
|
||||
return r.returncode, r.stdout + r.stderr
|
||||
|
||||
|
||||
def scope_violations(path, pattern, define):
|
||||
"""Lines matching pattern outside every '#if...define' block (or in its #else)."""
|
||||
stack, bad = [], []
|
||||
for n, line in enumerate(path.read_text().split('\n'), 1):
|
||||
s = line.strip()
|
||||
if re.match(r'#\s*if', s):
|
||||
stack.append([define in s and not s.startswith('#ifndef'), s])
|
||||
continue
|
||||
if re.match(r'#\s*(else|elif)', s) and stack:
|
||||
stack[-1][0] = False
|
||||
continue
|
||||
if re.match(r'#\s*endif', s) and stack:
|
||||
stack.pop()
|
||||
continue
|
||||
if s.startswith('*') or s.startswith('/*') or s.startswith('//'):
|
||||
continue
|
||||
code = re.sub(r'//.*|/\*.*?(\*/|$)', '', s)
|
||||
if re.search(pattern, code) and not any(on for on, _ in stack) and not re.match(r'#\s*define', s):
|
||||
bad.append(f'{path.relative_to(ROOT)}:{n}: {s}')
|
||||
return bad
|
||||
|
||||
|
||||
class Header(unittest.TestCase):
|
||||
def test_unit_passes(self):
|
||||
code, out = run_unit()
|
||||
self.assertEqual(code, 0, out)
|
||||
self.assertIn('"failures":0', out)
|
||||
|
||||
def test_mutants_fail(self):
|
||||
text = HEADER.read_text()
|
||||
for name, old, new in MUTANTS:
|
||||
with self.subTest(name), tempfile.TemporaryDirectory() as d:
|
||||
self.assertEqual(text.count(old), 1, f'{name}: anchor not found once')
|
||||
mutant = Path(d) / 'AdrenoDisplayEarlyDone.h'
|
||||
mutant.write_text(text.replace(old, new))
|
||||
code, out = run_unit(mutant)
|
||||
self.assertNotEqual(code, 0, f'{name}: the unit test passed a mutant\n{out}')
|
||||
|
||||
|
||||
class Kext(unittest.TestCase):
|
||||
def test_scope(self):
|
||||
bad = []
|
||||
for path in sorted((KEXT / 'src').rglob('*')):
|
||||
if path.suffix in ('.cpp', '.h') and path.name != 'AdrenoDisplayEarlyDone.h':
|
||||
bad += scope_violations(path, r'\bfEarly\b|\bfScreenHeld\b|adreno_early_|earlyDoneNowGated|'
|
||||
r'AdrenoDisplayEarlyDone\.h', 'ADRENO_PRESENT_EARLY_DONE')
|
||||
self.assertEqual(bad, [])
|
||||
|
||||
def test_profile(self):
|
||||
text = (KEXT / 'profiles/desktop-early-done.sh').read_text()
|
||||
self.assertIn('. "$D/profiles/desktop-backlight.sh"', text)
|
||||
self.assertEqual(re.findall(r'^U_\w+=.*$', text, re.M),
|
||||
['U_AdrenoDisplay="$U_AdrenoDisplay -DADRENO_PRESENT_EARLY_DONE=1"'])
|
||||
|
||||
def test_profile_build(self):
|
||||
if not list((KEXT / 'include').glob('*26A428.h')) or not shutil.which('xcrun'):
|
||||
self.skipTest('SKIP: the private *26A428.h headers or Xcode are not present')
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outs = {}
|
||||
for profile in ('desktop-backlight', 'desktop-early-done'):
|
||||
out = Path(d) / profile
|
||||
r = subprocess.run(['sh', str(KEXT / 'build.sh')], capture_output=True, text=True,
|
||||
env=dict(os.environ, OUT=str(out), PROFILE=profile, ZERO_AR_DATE='1'))
|
||||
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
|
||||
outs[profile] = out
|
||||
a, b = outs['desktop-backlight'] / 'obj', outs['desktop-early-done'] / 'obj'
|
||||
differ = sorted(o.name for o in a.iterdir() if o.read_bytes() != (b / o.name).read_bytes())
|
||||
self.assertEqual(differ, ['AdrenoDisplay.o'])
|
||||
da = (outs['desktop-backlight'] / 'DEFINES.txt').read_text().split('\n')
|
||||
db = (outs['desktop-early-done'] / 'DEFINES.txt').read_text().split('\n')
|
||||
changed = [(x, y) for x, y in zip(da, db) if x != y]
|
||||
self.assertEqual(len(changed), 2) # the profile name and AdrenoDisplay's line
|
||||
self.assertTrue(changed[1][1].startswith('AdrenoDisplay: ') and
|
||||
changed[1][1] == changed[1][0] + ' -DADRENO_PRESENT_EARLY_DONE=1')
|
||||
|
||||
|
||||
def chain(**kw):
|
||||
base = dict(variant='fast', input=True, input_touchpad=True, input_ptp=True, present_dartpool=True,
|
||||
present_resident=True, present_beam=True, present_va_reclaim=True, gpu_va_wide=True,
|
||||
present_latch=True, edp_backlight=True)
|
||||
base.update(kw)
|
||||
return types.SimpleNamespace(**base)
|
||||
|
||||
|
||||
class Cycle(unittest.TestCase):
|
||||
def test_default_unchanged(self):
|
||||
args = chain()
|
||||
self.assertEqual(fresh.selected(args)[1], fresh.INPUT_AUXKC_DESKTOP_BACKLIGHT)
|
||||
self.assertNotIn('adreno_present_early', fresh.boot_addition(args))
|
||||
next3 = json.loads((ROOT.parent.parent / 'run/desktop-run-args-next3.json').read_text()) \
|
||||
if (ROOT.parent.parent / 'run/desktop-run-args-next3.json').is_file() else None
|
||||
if next3:
|
||||
a = cli.parse(next3 + ['--check'])
|
||||
self.assertNotIn('adreno_present_early', fresh.boot_addition(a))
|
||||
self.assertEqual(fresh.selected(a)[1], fresh.INPUT_AUXKC_DESKTOP_BACKLIGHT)
|
||||
|
||||
def test_boot_args(self):
|
||||
for mode in (0, 1, 2):
|
||||
self.assertTrue(fresh.boot_addition(chain(present_early_done=mode)).endswith(
|
||||
f' adreno_present_early_done={mode}'))
|
||||
self.assertTrue(fresh.boot_addition(chain(present_early_done=2, present_early_hold_ms=5000)).endswith(
|
||||
' adreno_present_early_done=2 adreno_present_early_hold_ms=5000'))
|
||||
self.assertIn('adreno_present_early_done=0', fresh.boot_addition(chain(present_early_done=0,
|
||||
present_hold='on')))
|
||||
|
||||
def test_refusals(self):
|
||||
for bad in (dict(present_early_done=3), dict(present_early_done=1, present_hold='on'),
|
||||
dict(present_early_done=2, present_hold='on'), dict(present_early_done=1, present_early_hold_ms=500),
|
||||
dict(present_early_hold_ms=500), dict(present_early_done=2, present_early_hold_ms=99),
|
||||
dict(present_early_done=2, present_early_hold_ms=10001), dict(present_early_done=1, edp_backlight=False)):
|
||||
with self.subTest(bad), self.assertRaises(Refused):
|
||||
fresh.boot_addition(chain(**bad))
|
||||
with self.assertRaises(Refused): # without the --edp-backlight chain: no collection has it
|
||||
fresh.selected(types.SimpleNamespace(variant='fast', input=True, present_early_done=1))
|
||||
|
||||
def test_collection(self):
|
||||
for kw in (dict(present_early_done=0), dict(present_early_done=2), dict(gpu_coherent=True)):
|
||||
with self.subTest(kw):
|
||||
if fresh.INPUT_AUXKC_DESKTOP_EARLY_DONE is None:
|
||||
with self.assertRaises(Refused):
|
||||
fresh.selected(chain(**kw))
|
||||
else:
|
||||
self.assertEqual(fresh.selected(chain(**kw))[1:3], (fresh.INPUT_AUXKC_DESKTOP_EARLY_DONE,
|
||||
fresh.INPUT_AUXKC_DESKTOP_EARLY_DONE_SHA))
|
||||
# --gpu-coherent without --edp-backlight keeps its collection (the latch chain's)
|
||||
self.assertEqual(fresh.selected(chain(edp_backlight=False, gpu_coherent=True))[1],
|
||||
fresh.INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH)
|
||||
|
||||
def test_cli(self):
|
||||
base = ['installed-fresh', '--prefix', 'x', '--variant', 'fast', '--check', '--edp-backlight']
|
||||
a = cli.parse(base + ['--present-early-done', '2', '--present-early-hold-ms', '3000'])
|
||||
self.assertEqual((a.present_early_done, a.present_early_hold_ms), (2, 3000))
|
||||
self.assertIsNone(cli.parse(base).present_early_done)
|
||||
with self.assertRaises(SystemExit), open(os.devnull, 'w') as quiet, contextlib.redirect_stderr(quiet):
|
||||
cli.parse(base + ['--present-early-done', '5'])
|
||||
|
||||
|
||||
class Replay(unittest.TestCase):
|
||||
def test_replay(self):
|
||||
if not RUN_REPLAY:
|
||||
self.skipTest('SKIP: --no-replay')
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outs = {}
|
||||
for name, extra in (('off', []), ('on', ['-DADRENO_PRESENT_EARLY_DONE=1'])):
|
||||
binary = Path(d) / f'replay-{name}'
|
||||
build = subprocess.run(['xcrun', 'clang', '-std=c11', '-O2', '-Wall', '-Wextra', '-Werror'] +
|
||||
REPLAY_DEFINES + extra + ['tools/replay-adreno-presentation.c', '-o', str(binary)],
|
||||
cwd=ROOT, capture_output=True, text=True)
|
||||
self.assertEqual(build.returncode, 0, build.stderr)
|
||||
out = Path(d) / name
|
||||
out.mkdir()
|
||||
r = subprocess.run([str(binary), str(out)], capture_output=True, text=True)
|
||||
self.assertEqual(r.returncode, 0, r.stdout[-2000:] + r.stderr[-2000:])
|
||||
outs[name] = (out, r.stdout)
|
||||
off, on = outs['off'][0], outs['on'][0]
|
||||
for j in sorted(off.glob('*.json')):
|
||||
self.assertEqual(j.read_bytes(), (on / j.name).read_bytes(), f'{j.name} changed with the define')
|
||||
self.assertFalse((off / 'early-done.json').exists())
|
||||
result = json.loads((on / 'early-done.json').read_text())
|
||||
self.assertEqual((result['failures'], result['cost_failures']), (0, 0))
|
||||
self.assertGreater(result['checks'], 0)
|
||||
for line in outs['on'][1].split('\n'):
|
||||
if line.startswith('early-done ') and 'arrivals' not in line:
|
||||
print(line)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ap = argparse.ArgumentParser(add_help=False)
|
||||
ap.add_argument('--no-replay', action='store_true')
|
||||
known, rest = ap.parse_known_args()
|
||||
RUN_REPLAY = not known.no_replay
|
||||
unittest.main(argv=[sys.argv[0]] + rest)
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Host test for tools/summarize-present-pacing.py on a synthetic console: two complete periodic windows (one busy, one
|
||||
quiet, the quiet one with the early-completion perf done line), one incomplete window and one reject window, which
|
||||
must both be left out."""
|
||||
import importlib.util
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location('summarize', ROOT / 'tools/summarize-present-pacing.py')
|
||||
tool = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(tool)
|
||||
|
||||
|
||||
def window(s, ib, pp, mean, tk, gtk, sub, cache, prep, go, defer, done=None, why='periodic'):
|
||||
lines = [
|
||||
f'AdrenoDisplay: perf why={why} s={s} presents=600 copies=600 win=600 mean_us=2000 max_us=3000 all_mean_us=2000 '
|
||||
'all_max_us=9000',
|
||||
f'AdrenoDisplay: perf pace s={s} pps=50.0 pp={pp} mean={mean} p95=34 tk={tk} late=700 off=0 gpu=1/2000 q=6000',
|
||||
f'AdrenoDisplay: perf vsync s={s} period_ns=16666666 grid=900/3000 early=1/100 skip=0 rs=0/0 lead=900 arm=1/0 '
|
||||
'unarmed=0 ticks=1',
|
||||
f'AdrenoDisplay: perf sync s={s} defer={defer} swaps={defer} forced=0 max_us=2000 marks=1 busy=1 ovf=0 stale=0 unm=0',
|
||||
f'AdrenoDisplay: perf pipe s={s} on=3 fl={ib} ovl=0 imm=0 sync=0 fold=0 drain=0 tmr=0 polls=0 spin_us=0 '
|
||||
'gap_us=0 max_ms=1 fail=0',
|
||||
f'AdrenoDisplay: perf relatch s={s} on=7 arm={go} go={go} late=0 gone=0 gate=0 polls=0 ovt=0 lead=0/0 wait=0 '
|
||||
'rel=0/0/0/0/0/0/0/0 ef=0/0/0 unm=0',
|
||||
f'AdrenoDisplay: perf map s={s} ins=0 pg=0/0/0 clr=0 sfree=0 pool=0/0/0/0 va=0/0/0/0 ib={ib}',
|
||||
f'AdrenoDisplay: perf gpu s={s} gtk={gtk}/0 sub_us={sub}/0 prep={prep} unw=0/0 cache={cache} res=0/0/0/0 '
|
||||
'mib=0/0',
|
||||
]
|
||||
if done:
|
||||
lines.append(f'AdrenoDisplay: perf done s={s} {done}')
|
||||
return lines
|
||||
|
||||
|
||||
class Summary(unittest.TestCase):
|
||||
def test_groups(self):
|
||||
text = '\n'.join(
|
||||
window(1, 600, '300/150/100/40/10', 20000, '600/400/180/20', 19200000, 1200000, '60000/60000/1800000',
|
||||
'120000/600000', 60, 70) +
|
||||
window(2, 30, '500/90/10/0/0', 16900, '600/590/8/2', 192000, 30000, '3000/1000/30000', '3000/60000', 0, 1,
|
||||
done='mode=2 on=1 done=5 late=2 eng=1 ahd=3 arr=1/2/3/4/5/6/7/8/9 lat=1/0/1/0') +
|
||||
window(3, 600, '1/0/0/0/0', 1, '1/1/0/0', 1, 1, '1/1/1', '1/1', 0, 0)[:3] + # incomplete: left out
|
||||
window(4, 600, '1/0/0/0/0', 1, '1/1/0/0', 1, 1, '1/1/1', '1/1', 0, 0, why='reject'))
|
||||
out = tool.summarize(text, 200)
|
||||
busy, quiet = out['busy'], out['quiet']
|
||||
self.assertEqual((busy['windows'], quiet['windows']), (1, 1))
|
||||
self.assertEqual(busy['intervals_pct'], [50.0, 25.0, 16.7, 6.7, 1.7])
|
||||
self.assertEqual(busy['over34_pct'], 8.3)
|
||||
self.assertEqual((busy['ready_pct'], busy['missed_pct'], busy['true_miss_pct']), (66.7, 30.0, 20.0))
|
||||
self.assertAlmostEqual(busy['gpu_ms_ib_median'], 1.667, places=3) # 1 s of host ticks over 600 IBs
|
||||
self.assertAlmostEqual(busy['sub_ms_ib_median'], 2.0, places=3)
|
||||
self.assertAlmostEqual(busy['cache_ms_ib_median'], 3.0, places=3)
|
||||
self.assertAlmostEqual(busy['cache_mib_ib_median'], 100.0, places=3)
|
||||
self.assertAlmostEqual(busy['prep_ms_ib_median'], 1.0, places=3)
|
||||
self.assertNotIn('done', busy)
|
||||
self.assertEqual(quiet['done'], dict(modes=[2], engaged_windows=1, early=5, late=2, engages=1, ahead=3,
|
||||
arrival_2ms_bins=[1, 2, 3, 4, 5, 6, 7, 8, 9], late_bins_2_4_8_more=[1, 0, 1, 0]))
|
||||
|
||||
def test_empty(self):
|
||||
self.assertEqual(tool.summarize('nothing here'), dict(busy=None, quiet=None, busy_ibs=200))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user