mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 09:51:02 +00:00
Shared runtime code now goes through runtime/platform/platform.h, implemented by platform/win32.c and a new platform/linux.c: futex locks, a userfaultfd and PAGEMAP_SCAN write watch, signal-based async preemption, our own fiber switch, and a SIGSEGV handler for the thaw and crash hooks. The window, input, menu and software keyboard get SDL3 back ends beside the Win32 ones, with the swapchain (gfx_present.c) and input merging (input.c) shared. SHA-256 replaces BCrypt, ELF builds get their own override sections, and the build tools gain Linux branches. On Linux the selftest and the gfx-abi, astc, nvn and window probes pass. probe-vkmem still fails: imported guest memory cannot be write-watched on Linux, which the shadow buffers in vk_memory.c will address.
372 lines
18 KiB
C
372 lines
18 KiB
C
// Sampling profiler (TOTK_PROFILE=<path>) and per-frame timing (TOTK_TRACE=perf). tools/prof_report.py reads the profile.
|
|
// A host thread wakes every 1 ms and, for every other thread, suspends it, reads Rip and resumes it. The sampler never
|
|
// allocates or takes a lock between SuspendThread and ResumeThread and never runs guest code. The profile is text:
|
|
// totk-prof 1 / base <hex> / period_us / samples N / thread <tid> <samples> <in-exe samples> / module <base> <size> <path> /
|
|
// rip <hex> <count> (the top PROF_TOP Rips) / stack <count> <rip> <caller> ... (TOTK_PROFILE_RENDER or
|
|
// TOTK_PROFILE_CHAINS only, the top PROF_STACKS call chains)
|
|
// With TOTK_PROFILE_RENDER (the render thread) or TOTK_PROFILE_CHAINS=1 (every thread, chains of all threads summed: for
|
|
// short frame ranges, as each sample copies a stack) the sampler also copies up to STK_COPY bytes of the suspended
|
|
// thread's stack and, after
|
|
// ResumeThread, unwinds the copy with the .pdata tables (RtlVirtualUnwind), so it takes no lock while the thread is
|
|
// suspended. The unwind stops at the first frame outside the copy; deeper callers are cut.
|
|
// TOTK_PROFILE_SLOW=<ms> keeps every thread's chains of the last half second in a ring (a thread outside the exe, mostly
|
|
// waiting, copies 8 KiB of stack, one in it STK_COPY; a thread that ran no cycle since its previous sample is not
|
|
// suspended and repeats its previous chain; a sample that took over 2 ms to stop its thread, which happens while the
|
|
// thread is in a long kernel call, is written as `tstall <tid> <ms into the window> <ms stopping> <rip>...` with the
|
|
// chain where the thread stopped) and, after a present at least <ms> after the previous one, writes
|
|
// that interval's samples (plus the 20 ms before) to <TOTK_PROFILE>.f<frame>: the profile format, one `stack 1` line per
|
|
// sample, and `tstack <tid> <ms into the window> <rip>...` lines that keep the thread and the time. At most 64 dumps.
|
|
#include <windows.h>
|
|
#include <psapi.h>
|
|
#include <tlhelp32.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cfg.h"
|
|
#include "flight.h"
|
|
#include "gfx.h"
|
|
#include "host.h"
|
|
#include "perfmon.h"
|
|
#include "prof.h"
|
|
|
|
#define render_tid prof_render_tid
|
|
#define main_tid prof_main_tid
|
|
#define replay_tid prof_replay_tid
|
|
|
|
#define PROF_SLOTS (1u << 16) // Rip hash table, open addressing; a full table drops new Rips
|
|
#define PROF_TOP 4000 // more than the 200 asked for: the report sums per function
|
|
#define PROF_THREADS 512
|
|
#define STK_SLOTS (1u << 15) // call-chain hash table, open addressing; a full table drops new chains
|
|
#define PROF_STACKS 6000
|
|
#define STK_COPY (64u << 10) // stack bytes copied per sample
|
|
#define STK_SLACK (64u << 10) // zeros after the copy: a frame reaching past it reads zeros, and a zero Rip ends the chain
|
|
|
|
typedef struct { uint64_t rip, n; } RipCount;
|
|
#define PROF_DEPTH 24 // frames kept per call chain, the sampled Rip first
|
|
// cycles, depth, last: TOTK_PROFILE_SLOW only, the thread's cycle count and chain at its previous sample
|
|
typedef struct { DWORD tid; HANDLE h; uint64_t n, in_exe, cycles; uint32_t depth; uint64_t last[PROF_DEPTH]; } ThreadSlot;
|
|
|
|
static RipCount table[PROF_SLOTS];
|
|
typedef struct { uint64_t hash, n; uint32_t depth; uint64_t rip[PROF_DEPTH]; } StackCount;
|
|
static StackCount stacks[STK_SLOTS];
|
|
static uint64_t stacks_dropped;
|
|
static uint8_t stk_buf[STK_COPY + STK_SLACK] __attribute__((aligned(16))); // the slack is never written
|
|
static ThreadSlot threads[PROF_THREADS];
|
|
static int n_threads;
|
|
static uint64_t total, dropped;
|
|
static const char *out_path;
|
|
static HANDLE sampler;
|
|
static volatile LONG stop, written;
|
|
static uintptr_t exe_lo, exe_hi;
|
|
static int only_render; // TOTK_PROFILE_RENDER=1: sample the render thread only
|
|
static int only_main; // TOTK_PROFILE_MAIN=1: sample the guest main thread only
|
|
static int chains; // call chains are kept: TOTK_PROFILE_RENDER or TOTK_PROFILE_CHAINS
|
|
static volatile uint64_t cur_frame; // last presented frame
|
|
static uint64_t frame_lo, frame_hi; // TOTK_PROFILE_FRAMES=<lo>-<hi>: sample only while presents lo..hi-1 are pending
|
|
static int64_t world_lo, world_hi; // TOTK_PROFILE_FRAMES=w<lo>-<hi>: only while the world clock is in [lo, hi) s (us)
|
|
|
|
#define SLOW_RING (1u << 15) // TOTK_PROFILE_SLOW: about 0.5 s of samples of every thread
|
|
#define SLOW_DUMPS 64
|
|
typedef struct { uint64_t t; DWORD tid; uint32_t depth; uint64_t rip[PROF_DEPTH]; } Sample;
|
|
static Sample ring[SLOW_RING];
|
|
static uint64_t ring_n;
|
|
#define STALL_RING 1024
|
|
static Sample stalls[STALL_RING]; // t: when the stop began; rip[PROF_DEPTH - 1] holds the stop's ns
|
|
static uint64_t stall_n;
|
|
static uint64_t slow_ns; // TOTK_PROFILE_SLOW in ns, 0 off
|
|
static volatile LONG64 dump_lo, dump_hi, dump_frame; // the render thread asks for a window, the sampler writes it
|
|
|
|
static void count_rip(uint64_t rip) {
|
|
uint32_t i = (uint32_t)((rip * 0x9E3779B97F4A7C15ull) >> 48);
|
|
for (uint32_t k = 0; k < 64; k++, i = (i + 1) & (PROF_SLOTS - 1)) {
|
|
if (table[i].rip == rip || !table[i].rip) {
|
|
table[i].rip = rip;
|
|
table[i].n++;
|
|
return;
|
|
}
|
|
}
|
|
dropped++;
|
|
}
|
|
|
|
static void count_stack(const uint64_t *rip, uint32_t depth) {
|
|
uint64_t h = 0xcbf29ce484222325ull;
|
|
for (uint32_t d = 0; d < depth; d++) h = (h ^ rip[d]) * 0x100000001b3ull;
|
|
h |= 1; // 0 marks a free slot
|
|
uint32_t i = (uint32_t)(h >> 49);
|
|
for (uint32_t k = 0; k < 64; k++, i = (i + 1) & (STK_SLOTS - 1)) {
|
|
StackCount *s = &stacks[i];
|
|
if (!s->hash) {
|
|
s->hash = h, s->depth = depth;
|
|
memcpy(s->rip, rip, depth * sizeof *rip);
|
|
} else if (s->hash != h || s->depth != depth || memcmp(s->rip, rip, depth * sizeof *rip)) {
|
|
continue;
|
|
}
|
|
s->n++;
|
|
return;
|
|
}
|
|
stacks_dropped++;
|
|
}
|
|
|
|
// Unwinds the stack copied to stk_buf from real address lo (n bytes). Registers that point into the copied range are
|
|
// moved into the copy, so frame-pointer frames read the copy too. Returns the number of frames.
|
|
static uint32_t unwind_copy(CONTEXT *c, uintptr_t lo, size_t n, uint64_t *rip) {
|
|
uintptr_t buf = (uintptr_t)stk_buf, end = buf + n;
|
|
DWORD64 *regs[] = {&c->Rbx, &c->Rbp, &c->Rsi, &c->Rdi, &c->R12, &c->R13, &c->R14, &c->R15};
|
|
for (unsigned i = 0; i < sizeof regs / sizeof *regs; i++)
|
|
if (*regs[i] >= lo && *regs[i] < lo + n) *regs[i] = *regs[i] - lo + buf;
|
|
c->Rsp = buf;
|
|
uint32_t d = 0;
|
|
while (d < PROF_DEPTH && c->Rip && c->Rsp >= buf && c->Rsp + 8 <= end) {
|
|
rip[d++] = c->Rip;
|
|
DWORD64 base;
|
|
PRUNTIME_FUNCTION f = RtlLookupFunctionEntry(c->Rip, &base, NULL);
|
|
if (f) {
|
|
void *hd;
|
|
DWORD64 est;
|
|
RtlVirtualUnwind(UNW_FLAG_NHANDLER, base, c->Rip, f, c, &hd, &est, NULL);
|
|
} else { // a leaf function: the return address is on top
|
|
c->Rip = *(DWORD64 *)c->Rsp;
|
|
c->Rsp += 8;
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
|
|
// Re-lists the process's threads: keeps the handle of a thread seen before, opens new ones, closes the gone ones.
|
|
static void refresh_threads(DWORD self) {
|
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
|
if (snap == INVALID_HANDLE_VALUE) return;
|
|
static ThreadSlot next[PROF_THREADS];
|
|
int n = 0;
|
|
THREADENTRY32 te = {.dwSize = sizeof te};
|
|
DWORD pid = GetCurrentProcessId();
|
|
for (BOOL ok = Thread32First(snap, &te); ok; ok = Thread32Next(snap, &te)) {
|
|
if (te.th32OwnerProcessID != pid || te.th32ThreadID == self || n == PROF_THREADS) continue;
|
|
ThreadSlot *old = NULL;
|
|
for (int i = 0; i < n_threads; i++)
|
|
if (threads[i].tid == te.th32ThreadID) old = &threads[i];
|
|
if (old) {
|
|
next[n++] = *old;
|
|
old->h = NULL;
|
|
} else {
|
|
HANDLE h = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, te.th32ThreadID);
|
|
if (h) next[n++] = (ThreadSlot){.tid = te.th32ThreadID, .h = h};
|
|
}
|
|
}
|
|
CloseHandle(snap);
|
|
for (int i = 0; i < n_threads; i++)
|
|
if (threads[i].h) CloseHandle(threads[i].h); // exited; its counts are lost with the slot
|
|
memcpy(threads, next, sizeof(ThreadSlot) * n);
|
|
n_threads = n;
|
|
}
|
|
|
|
static void write_modules(FILE *f) {
|
|
HMODULE mods[1024];
|
|
DWORD need = 0;
|
|
if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof mods, &need)) return;
|
|
for (DWORD i = 0; i < need / sizeof(HMODULE) && i < 1024; i++) {
|
|
MODULEINFO mi;
|
|
char path[MAX_PATH];
|
|
if (GetModuleInformation(GetCurrentProcess(), mods[i], &mi, sizeof mi) && GetModuleFileNameA(mods[i], path, sizeof path))
|
|
fprintf(f, "module %llx %lx %s\n", (unsigned long long)(uintptr_t)mi.lpBaseOfDll, mi.SizeOfImage, path);
|
|
}
|
|
}
|
|
|
|
// The sampler, between ticks: the samples of the window the render thread asked for.
|
|
static void slow_dump(void) {
|
|
uint64_t lo = (uint64_t)dump_lo, hi = (uint64_t)dump_hi, frame = (uint64_t)dump_frame;
|
|
static int dumps;
|
|
char path[1100];
|
|
snprintf(path, sizeof path, "%s.f%llu", out_path, (unsigned long long)frame);
|
|
FILE *f = dumps < SLOW_DUMPS ? fopen(path, "w") : NULL;
|
|
if (f) {
|
|
dumps++;
|
|
fprintf(f, "totk-prof 1\nbase %llx\nperiod_us 1000\nframe %llu interval_ms %.1f\nmain %u render %u replay %u\n",
|
|
(unsigned long long)(uintptr_t)GetModuleHandleW(NULL), (unsigned long long)frame, (hi - lo) / 1e6 - 20,
|
|
main_tid, render_tid, replay_tid);
|
|
write_modules(f);
|
|
uint64_t first = ring_n > SLOW_RING ? ring_n - SLOW_RING : 0;
|
|
for (int pass = 0; pass < 2; pass++)
|
|
for (uint64_t k = first; k < ring_n; k++) {
|
|
const Sample *s = &ring[k & (SLOW_RING - 1)];
|
|
if (s->t < lo || s->t > hi) continue;
|
|
if (pass) fprintf(f, "tstack %lu %.1f", s->tid, (s->t - lo) / 1e6);
|
|
else fprintf(f, "stack 1");
|
|
for (uint32_t d = 0; d < s->depth; d++) fprintf(f, " %llx", (unsigned long long)s->rip[d]);
|
|
fputc('\n', f);
|
|
}
|
|
for (uint64_t k = stall_n > STALL_RING ? stall_n - STALL_RING : 0; k < stall_n; k++) {
|
|
const Sample *s = &stalls[k & (STALL_RING - 1)];
|
|
if (s->t + s->rip[PROF_DEPTH - 1] < lo || s->t > hi) continue;
|
|
fprintf(f, "tstall %lu %.1f %.1f", s->tid, ((double)s->t - (double)lo) / 1e6, s->rip[PROF_DEPTH - 1] / 1e6);
|
|
for (uint32_t d = 0; d < s->depth; d++) fprintf(f, " %llx", (unsigned long long)s->rip[d]);
|
|
fputc('\n', f);
|
|
}
|
|
fclose(f);
|
|
rt_log("prof: slow frame %llu (%.1f ms) -> %s", (unsigned long long)frame, (hi - lo) / 1e6 - 20, path);
|
|
}
|
|
InterlockedExchange64(&dump_hi, 0);
|
|
}
|
|
|
|
static DWORD WINAPI sampler_main(void *arg) {
|
|
(void)arg;
|
|
DWORD self = GetCurrentThreadId();
|
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
|
HANDLE timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
|
static CONTEXT ctx __attribute__((aligned(16)));
|
|
for (unsigned tick = 0; !stop; tick++) {
|
|
if (timer) {
|
|
LARGE_INTEGER due = {.QuadPart = -10000}; // 1 ms
|
|
SetWaitableTimer(timer, &due, 0, NULL, NULL, FALSE);
|
|
WaitForSingleObject(timer, 10);
|
|
} else {
|
|
Sleep(1);
|
|
}
|
|
if (tick % 64 == 0) refresh_threads(self);
|
|
if (dump_hi) slow_dump();
|
|
if (frame_hi && (cur_frame < frame_lo || cur_frame >= frame_hi)) continue;
|
|
if (world_hi) { // -1 before the world clock starts
|
|
int64_t w = frame_step_world_us();
|
|
if (w < world_lo || w >= world_hi) continue;
|
|
}
|
|
uint64_t now = slow_ns ? kern_now_ns() : 0;
|
|
for (int i = 0; i < n_threads; i++) {
|
|
ThreadSlot *t = &threads[i];
|
|
if ((only_render && t->tid != render_tid) || (only_main && t->tid != main_tid)) continue;
|
|
uint64_t cyc = 0;
|
|
if (slow_ns && t->depth && QueryThreadCycleTime(t->h, &cyc) && cyc == t->cycles) { // still waiting
|
|
Sample *s = &ring[ring_n++ & (SLOW_RING - 1)];
|
|
s->t = now, s->tid = t->tid, s->depth = t->depth;
|
|
memcpy(s->rip, t->last, t->depth * sizeof *t->last);
|
|
continue;
|
|
}
|
|
ctx.ContextFlags = chains ? CONTEXT_CONTROL | CONTEXT_INTEGER : CONTEXT_CONTROL;
|
|
uint64_t stop0 = slow_ns ? kern_now_ns() : 0;
|
|
if (SuspendThread(t->h) == (DWORD)-1) continue;
|
|
BOOL ok = GetThreadContext(t->h, &ctx); // returns once the thread has stopped in user mode
|
|
uint64_t stop_ns = slow_ns ? kern_now_ns() - stop0 : 0;
|
|
size_t n = 0;
|
|
MEMORY_BASIC_INFORMATION mb;
|
|
// a thread stopped between its sub rsp and the stack probe has Rsp in the guard page: touching it would fault
|
|
if (ok && chains && VirtualQuery((void *)ctx.Rsp, &mb, sizeof mb) && mb.State == MEM_COMMIT &&
|
|
!(mb.Protect & (PAGE_GUARD | PAGE_NOACCESS))) {
|
|
n = (uintptr_t)mb.BaseAddress + mb.RegionSize - ctx.Rsp; // the committed stack above Rsp
|
|
size_t cap = slow_ns && (ctx.Rip < exe_lo || ctx.Rip >= exe_hi) ? 8u << 10 : STK_COPY;
|
|
if (n > cap) n = cap;
|
|
memcpy(stk_buf, (void *)ctx.Rsp, n);
|
|
}
|
|
ResumeThread(t->h);
|
|
if (!ok) continue;
|
|
count_rip(ctx.Rip);
|
|
uint64_t chain[PROF_DEPTH] = {ctx.Rip};
|
|
uint32_t d = n ? unwind_copy(&ctx, ctx.Rsp, n, chain) : 0;
|
|
if (d) count_stack(chain, d);
|
|
if (slow_ns) {
|
|
Sample *s = &ring[ring_n++ & (SLOW_RING - 1)];
|
|
s->t = now, s->tid = t->tid, s->depth = d ? d : 1;
|
|
memcpy(s->rip, chain, s->depth * sizeof *chain);
|
|
if (stop_ns > 2000000) {
|
|
Sample *w = &stalls[stall_n++ & (STALL_RING - 1)];
|
|
*w = *s;
|
|
w->t = stop0;
|
|
if (w->depth > PROF_DEPTH - 1) w->depth = PROF_DEPTH - 1;
|
|
w->rip[PROF_DEPTH - 1] = stop_ns;
|
|
}
|
|
QueryThreadCycleTime(t->h, &t->cycles); // after ResumeThread: the cycles the sample itself cost
|
|
t->depth = s->depth;
|
|
memcpy(t->last, chain, s->depth * sizeof *chain);
|
|
}
|
|
total++;
|
|
t->n++;
|
|
if (ctx.Rip >= exe_lo && ctx.Rip < exe_hi) t->in_exe++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_desc(const void *a, const void *b) {
|
|
uint64_t x = ((const RipCount *)a)->n, y = ((const RipCount *)b)->n;
|
|
return (x < y) - (x > y);
|
|
}
|
|
|
|
static int cmp_stack_desc(const void *a, const void *b) {
|
|
uint64_t x = ((const StackCount *)a)->n, y = ((const StackCount *)b)->n;
|
|
return (x < y) - (x > y);
|
|
}
|
|
|
|
void prof_sampler_write(void) {
|
|
if (!out_path || InterlockedExchange(&written, 1)) return;
|
|
InterlockedExchange(&stop, 1);
|
|
if (WaitForSingleObject(sampler, 2000) != WAIT_OBJECT_0) return; // sampler stuck: no half-written counts
|
|
FILE *f = fopen(out_path, "w");
|
|
if (!f) {
|
|
rt_log("prof: cannot write %s", out_path);
|
|
return;
|
|
}
|
|
fprintf(f, "totk-prof 1\nbase %llx\nperiod_us 1000\nsamples %llu\ndropped %llu\n", (unsigned long long)(uintptr_t)GetModuleHandleW(NULL),
|
|
(unsigned long long)total, (unsigned long long)dropped);
|
|
for (int i = 0; i < n_threads; i++)
|
|
fprintf(f, "thread %lu %llu %llu\n", threads[i].tid, (unsigned long long)threads[i].n, (unsigned long long)threads[i].in_exe);
|
|
write_modules(f);
|
|
int cnt = 0;
|
|
for (uint32_t i = 0; i < PROF_SLOTS; i++)
|
|
if (table[i].rip) table[cnt++] = table[i];
|
|
qsort(table, cnt, sizeof *table, cmp_desc);
|
|
for (int i = 0; i < cnt && i < PROF_TOP; i++) fprintf(f, "rip %llx %llu\n", (unsigned long long)table[i].rip, (unsigned long long)table[i].n);
|
|
int ns = 0;
|
|
for (uint32_t i = 0; i < STK_SLOTS; i++)
|
|
if (stacks[i].hash) stacks[ns++] = stacks[i];
|
|
qsort(stacks, ns, sizeof *stacks, cmp_stack_desc);
|
|
for (int i = 0; i < ns && i < PROF_STACKS; i++) {
|
|
fprintf(f, "stack %llu", (unsigned long long)stacks[i].n);
|
|
for (uint32_t d = 0; d < stacks[i].depth; d++) fprintf(f, " %llx", (unsigned long long)stacks[i].rip[d]);
|
|
fputc('\n', f);
|
|
}
|
|
if (stacks_dropped) fprintf(f, "stacks_dropped %llu\n", (unsigned long long)stacks_dropped);
|
|
fclose(f);
|
|
rt_log("prof: %llu samples, %d Rips -> %s", (unsigned long long)total, cnt, out_path);
|
|
}
|
|
|
|
int prof_sampler_start(void) {
|
|
out_path = cfg_get("TOTK_PROFILE");
|
|
if (!out_path || !*out_path) {
|
|
out_path = NULL;
|
|
return 0;
|
|
}
|
|
only_render = cfg_get("TOTK_PROFILE_RENDER") != NULL;
|
|
only_main = !only_render && cfg_get("TOTK_PROFILE_MAIN") != NULL;
|
|
const char *ch = cfg_get("TOTK_PROFILE_CHAINS");
|
|
const char *sl = cfg_get("TOTK_PROFILE_SLOW");
|
|
slow_ns = sl && atoi(sl) > 0 ? (uint64_t)atoi(sl) * 1000000ull : 0;
|
|
chains = only_render || only_main || slow_ns || (ch && *ch && strcmp(ch, "0"));
|
|
const char *fr = cfg_get("TOTK_PROFILE_FRAMES");
|
|
double wl, wh;
|
|
if (fr && fr[0] == 'w' && sscanf(fr + 1, "%lf-%lf", &wl, &wh) == 2 && wl >= 0 && wl < wh)
|
|
world_lo = (int64_t)(wl * 1e6), world_hi = (int64_t)(wh * 1e6);
|
|
else if (fr && sscanf(fr, "%llu-%llu", (unsigned long long *)&frame_lo, (unsigned long long *)&frame_hi) != 2) frame_hi = 0;
|
|
MODULEINFO mi;
|
|
if (GetModuleInformation(GetCurrentProcess(), GetModuleHandleW(NULL), &mi, sizeof mi)) {
|
|
exe_lo = (uintptr_t)mi.lpBaseOfDll;
|
|
exe_hi = exe_lo + mi.SizeOfImage;
|
|
}
|
|
sampler = CreateThread(NULL, 0, sampler_main, NULL, 0, NULL);
|
|
if (!sampler) {
|
|
rt_log("prof: cannot start the sampler (error %lu)", GetLastError());
|
|
out_path = NULL;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// perf_present, every present: the frame the sampler's TOTK_PROFILE_FRAMES window counts, and a TOTK_PROFILE_SLOW
|
|
// dump of a present at least slow_ns after the previous one (d 0 on the first present).
|
|
void prof_sampler_frame(uint64_t frame, uint64_t now, uint64_t d) {
|
|
cur_frame = frame;
|
|
if (slow_ns && d >= slow_ns && !dump_hi) {
|
|
dump_lo = (LONG64)(now - d - 20000000ull);
|
|
dump_frame = (LONG64)frame;
|
|
InterlockedExchange64(&dump_hi, (LONG64)now);
|
|
}
|
|
}
|