kobodl/prof.h
Ville Lindholm 869056c5d2
Phase 0: profiling scaffolding for DOD refactor
Adds a tiny zone-based profiler (prof.{h,cpp}) enabled via KOBO_PROF=1.
Instruments run_game with zones for myship.move, enemies.move (split into
realize and move_pass), hit_structure, and hit_bolt. Adds KOBO_STRESS=N
to keep the enemy pool topped up with rocks for stable per-frame timing.

Why: the entity/render pipeline refactor in BENCH.md needs a comparable
baseline before any layout changes. Without a stress workload the natural
gameplay enemy count is too noisy to compare across commits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-29 13:31:31 +03:00

95 lines
2.5 KiB
C

// Lightweight zone-based profiler for measuring the DOD refactor.
// Enabled at runtime via KOBO_PROF=1 in the environment.
// See plans: .claude/plans/can-you-explain-the-noble-wilkes.md (Phase 0).
#ifndef KOBO_PROF_H
#define KOBO_PROF_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PROF_MAX_ZONES 32
#define PROF_DUMP_EVERY_FRAMES 600
typedef struct {
const char *name;
uint64_t calls;
uint64_t total_ns;
} prof_zone_t;
extern prof_zone_t prof_zones[PROF_MAX_ZONES];
extern int prof_nzones;
extern int prof_enabled;
extern uint64_t prof_frame_count;
static inline uint64_t prof_now_ns(void)
{
#if defined(__APPLE__) || defined(__linux__)
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
#else
return 0;
#endif
}
// Returns the index of the zone with the given name, creating it on first use.
// Names must be string literals (we compare by pointer for speed).
static inline int prof_zone_id(const char *name)
{
for (int i = 0; i < prof_nzones; ++i)
if (prof_zones[i].name == name)
return i;
if (prof_nzones >= PROF_MAX_ZONES)
return PROF_MAX_ZONES - 1;
int id = prof_nzones++;
prof_zones[id].name = name;
prof_zones[id].calls = 0;
prof_zones[id].total_ns = 0;
return id;
}
static inline void prof_accumulate(int id, uint64_t ns)
{
prof_zones[id].calls += 1;
prof_zones[id].total_ns += ns;
}
void prof_init(void);
void prof_frame_tick(void);
void prof_dump(FILE *f);
#ifdef __cplusplus
}
// RAII helper for C++ scoping.
struct ProfScope {
int id;
uint64_t t0;
inline ProfScope(int zone_id) : id(zone_id), t0(prof_enabled ? prof_now_ns() : 0) {}
inline ~ProfScope() {
if (prof_enabled) prof_accumulate(id, prof_now_ns() - t0);
}
};
// Pastes the line number to make the static unique per use-site.
#define PROF_ZONE_CONCAT2(a,b) a##b
#define PROF_ZONE_CONCAT(a,b) PROF_ZONE_CONCAT2(a,b)
#define PROF_ZONE(name) \
static int PROF_ZONE_CONCAT(_prof_zid_, __LINE__) = -1; \
if (PROF_ZONE_CONCAT(_prof_zid_, __LINE__) < 0) \
PROF_ZONE_CONCAT(_prof_zid_, __LINE__) = prof_zone_id(name); \
ProfScope PROF_ZONE_CONCAT(_prof_scope_, __LINE__)( \
PROF_ZONE_CONCAT(_prof_zid_, __LINE__))
#endif
#endif // KOBO_PROF_H