From 98e9d8bcfb08932ed7a0f8fc4fd15037dc7ad55d Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sun, 6 Sep 2026 11:06:41 +0900 Subject: [PATCH] Debugger: Add cache statistics command Add `info cache` command that reports cache usage and cumulative hit rates for glyph, CG, and archive data. --- src/ald_manager.c | 4 ++++ src/ald_manager.h | 3 ++- src/cache.c | 19 ++++++++++++++++++- src/cache.h | 11 +++++++++++ src/cache_test.c | 20 ++++++++++++++++++++ src/cg.c | 4 ++++ src/cg.h | 2 ++ src/debugger_cui.c | 32 ++++++++++++++++++++++++++++++++ src/font.c | 4 ++++ src/font.h | 2 ++ 10 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/ald_manager.c b/src/ald_manager.c index 07495e1..6239a04 100644 --- a/src/ald_manager.c +++ b/src/ald_manager.c @@ -148,3 +148,7 @@ int ald_get_maxno(DRIFILETYPE type) { return 0; return dri[type]->maxno; } + +CacheStats ald_get_cache_stats(void) { + return cache_get_stats(dri_cache); +} diff --git a/src/ald_manager.h b/src/ald_manager.h index 3852228..b8f7d2b 100644 --- a/src/ald_manager.h +++ b/src/ald_manager.h @@ -25,6 +25,7 @@ #define __ALD_MANAGER__ #include "portab.h" +#include "cache.h" #include "dri.h" #define DRIFILETYPEMAX 7 @@ -44,6 +45,6 @@ bool ald_exists(DRIFILETYPE type, int no); dridata *ald_getdata(DRIFILETYPE type, int no); void ald_freedata(dridata *data); int ald_get_maxno(DRIFILETYPE type); +CacheStats ald_get_cache_stats(void); #endif /* !__ALD_MANAGER__ */ - diff --git a/src/cache.c b/src/cache.c index 47196aa..b0edb58 100644 --- a/src/cache.c +++ b/src/cache.c @@ -41,6 +41,8 @@ struct Cache { size_t capacity; size_t size; size_t count; + size_t hits; + size_t misses; }; static bool is_pinned(const Cache *cache, const CacheEntry *entry) { @@ -112,8 +114,11 @@ void *cache_get(Cache *cache, const void *key) { if (!cache || !key) return NULL; CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key)); - if (!entry) + if (!entry) { + cache->misses++; return NULL; + } + cache->hits++; lru_remove(cache, entry); lru_prepend(cache, entry); return entry->data; @@ -176,3 +181,15 @@ size_t cache_clear(Cache *cache) { } return cache->count; } + +CacheStats cache_get_stats(const Cache *cache) { + if (!cache) + return (CacheStats){0}; + return (CacheStats){ + .count = cache->count, + .size = cache->size, + .capacity = cache->capacity, + .hits = cache->hits, + .misses = cache->misses, + }; +} diff --git a/src/cache.h b/src/cache.h index a56ce05..a2f0dfc 100644 --- a/src/cache.h +++ b/src/cache.h @@ -40,6 +40,14 @@ typedef enum { CACHE_INSERT_NOMEM, } CacheInsertResult; +typedef struct { + size_t count; + size_t size; + size_t capacity; + size_t hits; + size_t misses; +} CacheStats; + Cache *cache_new(size_t capacity, const CacheOps *ops); void cache_destroy(Cache *cache); @@ -55,4 +63,7 @@ bool cache_remove(Cache *cache, const void *key); /* Removes all unpinned entries and returns the number of entries left. */ size_t cache_clear(Cache *cache); +/* Returns zero-filled statistics when cache is NULL. */ +CacheStats cache_get_stats(const Cache *cache); + #endif /* XSYSTEM35_CACHE_H */ diff --git a/src/cache_test.c b/src/cache_test.c index c6c7982..952503b 100644 --- a/src/cache_test.c +++ b/src/cache_test.c @@ -64,14 +64,27 @@ void cache_test(void) { Cache *cache = cache_new(2, &ops); ASSERT_TRUE(cache); destroyed = 0; + CacheStats stats = cache_get_stats(cache); + ASSERT_EQUAL(stats.count, 0); + ASSERT_EQUAL(stats.size, 0); + ASSERT_EQUAL(stats.capacity, 2); + ASSERT_EQUAL(stats.hits, 0); + ASSERT_EQUAL(stats.misses, 0); // A lookup makes k1 most-recently used, so inserting k3 evicts k2. int k1 = 1, k2 = 2, k3 = 3, k4 = 4, k5 = 5; ASSERT_EQUAL(cache_insert(cache, &k1, new_value(1), 1), CACHE_INSERT_OK); ASSERT_EQUAL(cache_insert(cache, &k2, new_value(2), 1), CACHE_INSERT_OK); + stats = cache_get_stats(cache); + ASSERT_EQUAL(stats.count, 2); + ASSERT_EQUAL(stats.size, 2); + ASSERT_EQUAL(stats.capacity, 2); ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1); ASSERT_EQUAL(cache_insert(cache, &k3, new_value(3), 1), CACHE_INSERT_OK); ASSERT_NULL(cache_get(cache, &k2)); + stats = cache_get_stats(cache); + ASSERT_EQUAL(stats.hits, 1); + ASSERT_EQUAL(stats.misses, 1); // Pinned entries survive both capacity eviction and cache_clear(). ((TestValue *)cache_get(cache, &k1))->pinned = true; @@ -94,4 +107,11 @@ void cache_test(void) { ASSERT_EQUAL(cache_clear(cache), 0); ASSERT_EQUAL(destroyed, 4); cache_destroy(cache); + + stats = cache_get_stats(NULL); + ASSERT_EQUAL(stats.count, 0); + ASSERT_EQUAL(stats.size, 0); + ASSERT_EQUAL(stats.capacity, 0); + ASSERT_EQUAL(stats.hits, 0); + ASSERT_EQUAL(stats.misses, 0); } diff --git a/src/cg.c b/src/cg.c index 9dcff0c..35ee8e6 100644 --- a/src/cg.c +++ b/src/cg.c @@ -354,6 +354,10 @@ void cg_reset(void) { memset(&loc_where0, 0, sizeof(loc_where0)); } +CacheStats cg_get_cache_stats(void) { + return cache_get_stats(cg_cache); +} + /* * Set cg display location * x : display location x diff --git a/src/cg.h b/src/cg.h index 6549e87..ef8c95c 100644 --- a/src/cg.h +++ b/src/cg.h @@ -25,6 +25,7 @@ #define __CG__ #include +#include "cache.h" #include "portab.h" struct SDL_Surface; @@ -88,6 +89,7 @@ extern void cgdata_free(cgdata *cg); extern struct SDL_Surface *cg_load_as_sdlsurface(int no); struct SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mosaic, bool as_alpha); extern void load_censor_list(const char *path); +extern CacheStats cg_get_cache_stats(void); extern int cg_vspPB; extern int cg_fflg; diff --git a/src/debugger_cui.c b/src/debugger_cui.c index 2ef02a5..014be20 100644 --- a/src/debugger_cui.c +++ b/src/debugger_cui.c @@ -23,6 +23,9 @@ #include "debugger.h" #include "debugger_private.h" #include "debug_symbol.h" +#include "ald_manager.h" +#include "cg.h" +#include "font.h" #include "nact.h" #include "variable.h" #ifdef HAVE_SIGACTION @@ -207,6 +210,34 @@ static const char * const help_help = NULL; static CommandResult cmd_help(void); +static const char desc_info[] = "Print information about the program."; +static const char help_info[] = + "Syntax: info cache\n" + "\n" + "Displays usage and lookup statistics for each cache."; + +static void print_cache_stats(const char *name, CacheStats stats) { + size_t lookups = stats.hits + stats.misses; + double hit_rate = lookups ? 100.0 * stats.hits / lookups : 0.0; + printf("%-8s %8zu %12zu %12zu %8zu %8zu %8.1f%%\n", + name, stats.count, stats.size, stats.capacity, + stats.hits, stats.misses, hit_rate); +} + +static CommandResult cmd_info(void) { + char *arg = strtok(NULL, whitespaces); + if (!arg || strcmp(arg, "cache") || strtok(NULL, whitespaces)) { + puts(help_info); + return CONTINUE_REPL; + } + + puts("Cache Entries Size Capacity Hits Misses Hit rate"); + print_cache_stats("Glyph", font_get_cache_stats()); + print_cache_stats("CG", cg_get_cache_stats()); + print_cache_stats("Archive", ald_get_cache_stats()); + return CONTINUE_REPL; +} + static const char desc_list[] = "List specified function or line."; static const char help_list[] = "Syntax: list\n" @@ -361,6 +392,7 @@ const Command dbg_cui_commands[] = { {"continue", "c", desc_continue, help_continue, cmd_continue}, {"delete", "d", desc_delete, help_delete, cmd_delete}, {"help", "h", desc_help, help_help, cmd_help}, + {"info", "i", desc_info, help_info, cmd_info}, {"list", "l", desc_list, help_list, cmd_list}, {"step", "s", desc_step, help_step, cmd_step}, {"finish", NULL, desc_finish, help_finish, cmd_finish}, diff --git a/src/font.c b/src/font.c index d4b4a52..1ff4a9a 100644 --- a/src/font.c +++ b/src/font.c @@ -416,6 +416,10 @@ bool font_get_antialias(void) { return this.antialiase_on; } +CacheStats font_get_cache_stats(void) { + return cache_get_stats(glyph_cache); +} + #ifdef __EMSCRIPTEN__ EM_ASYNC_JS(bool, load_mincho_font, (void), { return await xsystem35.load_mincho_font(); diff --git a/src/font.h b/src/font.h index f4a5ea5..fb8a232 100644 --- a/src/font.h +++ b/src/font.h @@ -25,6 +25,7 @@ #define __FONT_H__ #include +#include "cache.h" #include "config.h" #include "portab.h" @@ -55,6 +56,7 @@ extern void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool // Rows by which a rendered surface extends above the character cell. Subtract // it from a Y coordinate that denotes the cell top before blitting. extern int font_cell_overhang(FontSpec spec); +extern CacheStats font_get_cache_stats(void); #ifdef __EMSCRIPTEN__ extern bool load_mincho_font(void);