Skip to content

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_H
  • AOWL_PROF_FAULT_LIMIT
  • AOWL_PROF_MAGIC
  • AOWL_PROF_MAX_SLOTS
  • AOWL_PROF_NAME_LEN
  • AOWL_PROF_REASON_LEN
  • AOWL_PROF_REGION_NAME
  • AOWL_PROF_RING
  • AOWL_PROF_VERSION

Types

  • struct AowlProfShared
  • struct AowlProfSlot

Functions

SignatureLine
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

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.