diff --git a/docs/building.md b/docs/building.md index 4ec11a7a..6aaf6949 100644 --- a/docs/building.md +++ b/docs/building.md @@ -723,9 +723,9 @@ and that the lines of older logs, without those fields, still parse. Any Python, A change to what the frame-time summary charges to an address-space walk of `NX2NAVI_MEM_CENSUS` (`runtime/perf_census.h` `census_charge`, `runtime/perf.c` `ft_frame`) passes `python tests\census_charge_check.py`, -which builds the header alone: a walk before, after or around a frame, one still running, the charge taken from the -replay part first, capped by the overlap and the parts, and 100,000 random frames in which the census part never passes -the frame and the parts keep their sum. No GPU, no game files. +which builds the header alone: a walk before, after or around a frame, one still running, a torn read of the last walk's +start (taken as no walk), the charge taken from the replay part first, capped by the overlap and the parts, and 100,000 +random frames in which the census part never passes the frame and the parts keep their sum. No GPU, no game files. A change to how the on/off switches read their value (`runtime/perf.c` `rt_knob`) passes `python tests\rt_knob_check.py`, which cuts the function out of the file with its warning table and builds it with stand-ins for the settings and the diff --git a/runtime/perf_census.h b/runtime/perf_census.h index 594b11b0..1aac306e 100644 --- a/runtime/perf_census.h +++ b/runtime/perf_census.h @@ -8,12 +8,18 @@ #pragma once #include +// A walk takes 100-260 ms, so one seen running for longer than this is a torn read: perf.c ft_frame loaded the last +// walk's start, then the 0 that perf.c perf_census_walk(1) stores as the end before it stores the next start. Taken as +// running, that walk would cover the whole frame. +#define CENSUS_WALK_MAX_NS 2000000000ull + // The frame is [now - d, now]; the walk [w0, w1]: w0 0 when no walk was seen, w1 0 (or older than w0, the end of the // walk before) while it runs, then reaching now. Returns the time moved into *census. static inline uint64_t census_charge(uint64_t now, uint64_t d, uint64_t w0, uint64_t w1, uint64_t *replay, uint64_t *other, uint64_t *census) { if (!w0 || !d || d > now) return 0; if (w1 && w1 < w0) w1 = 0; + if (!w1 && now - w0 > CENSUS_WALK_MAX_NS) return 0; uint64_t lo = now - d > w0 ? now - d : w0, hi = w1 && w1 < now ? w1 : now; if (hi <= lo) return 0; uint64_t ov = hi - lo, room = d > *census ? d - *census : 0; diff --git a/tests/census_charge_check.c b/tests/census_charge_check.c index f9aed5ac..7d309902 100644 --- a/tests/census_charge_check.c +++ b/tests/census_charge_check.c @@ -46,6 +46,16 @@ int main(void) { p = charge(1000, 40, 985, 700, 30, 10, 0); CHECK(p.moved == 15 * MS && p.replay == 15 * MS, "a running walk with the last one's end: moved %llu", (unsigned long long)p.moved); + // a torn read (the last walk's start, 10 s back, with the 0 stored before the next start) moves nothing; a walk + // running for 1.9 s still counts, one running for just over 2 s (CENSUS_WALK_MAX_NS) does not + p = charge(20000, 40, 10000, 0, 30, 10, 0); + CHECK(p.moved == 0 && p.replay == 30 * MS && p.other == 10 * MS && p.census == 0, "a torn read: moved %llu", + (unsigned long long)p.moved); + p = charge(3000, 40, 1100, 0, 30, 10, 0); + CHECK(p.moved == 40 * MS && p.replay == 0 && p.other == 0 && p.census == 40 * MS, + "a walk running for 1.9 s: moved %llu", (unsigned long long)p.moved); + p = charge(3000, 40, 999, 0, 30, 10, 0); + CHECK(p.moved == 0 && p.census == 0, "a walk running for 2.001 s: moved %llu", (unsigned long long)p.moved); // the overlap caps the charge; the parts cap it too (nothing taken from the other parts of the frame) p = charge(1000, 100, 900, 0, 5, 3, 0); CHECK(p.moved == 8 * MS && p.replay == 0 && p.other == 0 && p.census == 8 * MS, "parts smaller than the overlap: " @@ -54,17 +64,21 @@ int main(void) { p = charge(1000, 40, 900, 0, 30, 10, 35); CHECK(p.moved == 5 * MS && p.census == 40 * MS && p.replay == 25 * MS, "census at the frame's length: moved %llu, " "census %llu", (unsigned long long)p.moved, (unsigned long long)p.census); - // random frames: the census part never passes the frame, the moved time never passes the overlap, and the sum of - // the three parts is kept + // random frames: the census part never passes the frame, the moved time is the overlap capped by the frame's room + // and its replay and unattributed parts (none for a walk seen running for over 2 s), and the sum of the three parts + // is kept srand(7); for (int i = 0; i < 100000; i++) { - uint64_t now = 1000 + (uint64_t)(rand() % 1000), d = 1 + (uint64_t)(rand() % 300); - uint64_t w0 = (uint64_t)(rand() % 2000), w1 = rand() % 3 ? w0 + (uint64_t)(rand() % 400) : 0; + uint64_t now = 1000 + (uint64_t)(rand() % 4000), d = 1 + (uint64_t)(rand() % 300); + uint64_t w0 = (uint64_t)(rand() % 5000), w1 = rand() % 3 ? w0 + (uint64_t)(rand() % 400) : 0; uint64_t census = (uint64_t)(rand() % (int)d), replay = (uint64_t)(rand() % 300), other = (uint64_t)(rand() % 300); p = charge(now, d, w0, w1, replay, other, census); uint64_t lo = now - d > w0 ? now - d : w0, hi = w1 && w1 < now ? w1 : now; - uint64_t ov = w0 && hi > lo ? (hi - lo) * MS : 0; - int ok = p.census <= d * MS && p.moved <= ov && p.replay + p.other + p.census == (replay + other + census) * MS && + int torn = !w1 && (w0 > now || now - w0 > 2000); + uint64_t ov = w0 && !torn && hi > lo ? (hi - lo) * MS : 0, want = ov; + if (want > (d - census) * MS) want = (d - census) * MS; + if (want > (replay + other) * MS) want = (replay + other) * MS; + int ok = p.census <= d * MS && p.moved == want && p.replay + p.other + p.census == (replay + other + census) * MS && p.census == census * MS + p.moved && (p.other == other * MS || p.replay == 0); CHECK(ok, "case %d: now %llu d %llu walk %llu-%llu: moved %llu of overlap %llu, census %llu", i, (unsigned long long)now, (unsigned long long)d, (unsigned long long)w0, (unsigned long long)w1,