Appearance
aowlspt_profile.h
Source: abi/aowlspt_profile.h — 643 lines, 43 file-scope functions.
What this header owns
Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.
text
aowlspt_profile.h -- the per-mod / per-rider PROFILER's shared surface.
---------------------------------------------------------------------------
WHAT THIS IS, AND WHAT IT DELIBERATELY IS NOT
---------------------------------------------------------------------------
The feature request was "ModProfiler, but for aowlspt": see which mod is
costing frame time. ModProfiler is a BepInEx/Mono tool -- it enumerates
loaded plugins by reflection and wraps their MonoBehaviour.Update with a
Harmony patch. NONE of that exists on this build: there is no Mono, no
BepInEx, and IL2CPP reflection FAULTS here (fact: il2cpp_object_get_class,
il2cpp_class_get_name, il2cpp_value_box and field iteration all fault).
So this is not a port. It is the same IDEA reached by the only route this
architecture leaves open: **aowlspt already funnels every mod's and every
host feature's per-frame work through a small number of dispatch points that
we own the source of.** Instrumenting those is a pure-native, zero-reflection,
zero-by-name-lookup operation. Nothing in this file resolves an IL2CPP name,
calls a game method, or patches a byte of game code. It cannot: it is a
counter, a QueryPerformanceCounter pair, and a shared page.
That matters for fact #145 -- on this build EVERY by-NAME IL2CPP route is
fatal the moment it is USED. This header has no such route to be fatal.
The three dispatch points instrumented, and who owns them:
host/common/modhost.nim `tickMods` -> one slot per MOD (kind=MOD)
host/Aowlspt.Host.Il2Cpp/aowlhost.nim
`patchFired` /
`patchReturned` -> one slot per HOST RIDER (RIDER)
this header frame boundary -> the frame-time histogram
Backend ROUTE timing is deliberately NOT here. Another agent owns backend
route timing this session and two owners of one measurement is how a number
ends up meaning neither thing. `AOWL_PROF_KIND_ROUTE` is reserved so that
work can land without a version bump; nothing writes it today, and the mod
reports route cost as UNMEASURED rather than as zero.
---------------------------------------------------------------------------
THE MEASUREMENT, AND ITS HONEST ERROR BARS
---------------------------------------------------------------------------
Clock: QueryPerformanceCounter. On every machine this project runs on that is
the invariant TSC via the HPET-free fast path -- sub-100ns resolution, no
syscall. `QueryPerformanceFrequency` is read ONCE at init, not per sample.
Overhead: a begin/end pair is two QPC calls plus two adds. That is NOT free
and it is NOT subtracted. Subtracting an overhead estimate from a number you
then read back is a self-comparison -- exactly the "verification that cannot
fail" shape this project has lost days to. Instead `aowl_prof_calibrate()`
times 4096 EMPTY scopes once at startup and publishes the per-scope cost in
`overheadNs`, so a reader can judge whether a 0.004 ms slot is real. A slot
whose reported cost is within 3x of `overheadNs` is reported by the mod as
"at the noise floor", never as a measurement.
Attribution: a scope measures WALL time between two points on one thread,
so it includes any preemption the OS did in the middle. Over a rolling window
of frames that is what you want (a mod that gets descheduled IS costing you
frame time), but a single-frame max is not evidence of a mod being slow.
Both are published; the mod labels them differently.
Nesting: scopes are NOT nested by this design. Each dispatch point wraps one
leaf call. If a mod's on_update itself calls another mod, the inner cost is
attributed to the outer mod, and that is stated rather than corrected.
---------------------------------------------------------------------------
WHY THERE IS NO ALLOCATION ANYWHERE ON THIS PATH
---------------------------------------------------------------------------
A profiler that allocates per frame changes the thing it measures. So:
* the region is ONE fixed-size POD struct in a named file mapping, mapped
once per process and leaked on purpose;
* slot names are fixed `char[32]` written ONCE at registration, by
`aowl_prof_slot`, which is called from a `once` guard and never per frame;
* the frame-time history is a fixed ring of 512 `uint32` microsecond
samples, overwritten in place;
* percentiles are computed into a 512-entry stack array by an insertion-free
counting pass, and ONLY when a reader asks -- never per frame;
* the render path formats into caller-provided fixed buffers; this header
never calls malloc, never touches the managed heap, and never calls
il2cpp_string_new.
When `enabled` is 0, `aowl_prof_begin` is one relaxed volatile LONG load and
a return. That is the shipped default.
---------------------------------------------------------------------------
SAFETY
---------------------------------------------------------------------------
There is no pointer walk here to guard: the only pointer is the region, which
this file created, and every entry point returns immediately on NULL or on a
bad magic. There is no game memory read, so no VirtualQuery is needed and
adding one would be theatre. There is NO seh guard here EITHER -- and that is
deliberate: the callers (`patchFired`, `tickMods`) already run inside exactly
one `aowl_p_p_seh` and that guard is NOT re-entrant, so a nested guard here
would DISARM the outer one. This file is written so that it cannot fault:
every index is bounds-checked against a compile-time constant before use.
Self-disable: `faults` counts refusals (slot table full, bad index, clock
went backwards). At `AOWL_PROF_FAULT_LIMIT` the profiler clears `enabled`
itself and sets `disabledReason`, and nothing turns it back on but a restart
or an explicit write from the mod.Constants
AOWLSPT_PROFILE_HAOWL_PROF_FAULT_LIMITAOWL_PROF_MAGICAOWL_PROF_MAX_SLOTSAOWL_PROF_NAME_LENAOWL_PROF_REASON_LENAOWL_PROF_REGION_NAMEAOWL_PROF_RINGAOWL_PROF_VERSION
Types
struct AowlProfSharedstruct AowlProfSlot
Functions
| Signature | Line |
|---|---|
char aowl_prof_kind_name(int32_t k) | 132 |
AowlProfShared aowl_prof_map(void) | 227 |
AowlProfShared aowl_prof_get(void) | 256 |
void aowl_prof_fault(AowlProfShared* s, const char* why) | 262 |
LONG64 aowl_prof_now(void) | 276 |
LONG64 aowl_prof_ticks_to_ns(AowlProfShared* s, LONG64 ticks) | 282 |
int32_t aowl_prof_slot(const char* name, int32_t kind) | 299 |
void aowl_prof_begin(int32_t slot) | 328 |
void aowl_prof_end(int32_t slot) | 340 |
void aowl_prof_percentiles(AowlProfShared* s, int32_t n) | 365 |
void aowl_prof_roll(AowlProfShared* s) | 394 |
void aowl_prof_boundary(int32_t isFrameSource) | 413 |
void aowl_prof_frame(void) | 453 |
void aowl_prof_tick_boundary(void) | 461 |
LONG64 aowl_prof_window_ns(AowlProfShared* s) | 466 |
void aowl_prof_calibrate(void) | 484 |
int32_t aowl_prof_ready(void) | 516 |
int32_t aowl_prof_is_enabled(void) | 517 |
void aowl_prof_set_enabled(int32_t on) | 518 |
int32_t aowl_prof_panel_open(void) | 524 |
void aowl_prof_set_panel(int32_t on) | 525 |
void aowl_prof_set_window(int32_t n) | 526 |
int32_t aowl_prof_faults(void) | 533 |
int32_t aowl_prof_self_disabled(void) | 534 |
char aowl_prof_reason(void) | 535 |
int64_t aowl_prof_overhead_ns(void) | 536 |
int64_t aowl_prof_frames(void) | 537 |
int32_t aowl_prof_heartbeat(void) | 538 |
int32_t aowl_prof_live_slots(void) | 539 |
int32_t aowl_prof_frame_source(void) | 547 |
int32_t aowl_prof_slot_count(void) | 548 |
int32_t aowl_prof_slot_kind(int32_t i) | 549 |
char aowl_prof_slot_name(int32_t i) | 554 |
int64_t aowl_prof_slot_ns(int32_t i) | 559 |
int64_t aowl_prof_slot_max_ns(int32_t i) | 564 |
int64_t aowl_prof_slot_calls(int32_t i) | 569 |
int64_t aowl_prof_slot_total_ns(int32_t i) | 574 |
int32_t aowl_prof_win_us(int32_t which) | 579 |
int64_t aowl_prof_window_denom_ns(void) | 593 |
int32_t aowl_prof_slot_permille(int32_t i) | 600 |
int32_t aowl_prof_slot_name_char(int32_t i, int32_t k) | 621 |
int32_t aowl_prof_reason_char(int32_t k) | 628 |
int32_t aowl_prof_kind_char(int32_t kind, int32_t k) | 634 |

