Individual app memory tracking added.
This commit is contained in:
parent
227b1179cc
commit
a793941357
15 changed files with 313 additions and 10 deletions
|
|
@ -30,6 +30,7 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <time.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Menu command IDs
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// App descriptor
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
|
|
|
|||
48
core/dvxMem.h
Normal file
48
core/dvxMem.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// dvxMem.h -- Per-app memory tracking for DVX
|
||||
//
|
||||
// REQUIRED: Every .c file compiled into a DXE (library, widget, shell,
|
||||
// or app) MUST include this header AFTER all system includes. This
|
||||
// ensures every malloc/free/calloc/realloc call goes through the
|
||||
// tracking wrappers, maintaining pointer consistency.
|
||||
//
|
||||
// The implementation (dvxPlatformDos.c) does NOT include this header,
|
||||
// so its dvxMalloc/dvxFree/dvxCalloc/dvxRealloc call the real libc
|
||||
// functions with no recursion.
|
||||
//
|
||||
// How it works:
|
||||
// 1. dvxMalloc prepends a 16-byte header (magic, appId, size) to
|
||||
// each allocation and returns a pointer past the header.
|
||||
// 2. dvxFree checks the magic value at ptr-16. If it matches, the
|
||||
// header is valid and the allocation is tracked. If not, the
|
||||
// pointer came from code outside DVX (libc internals, loader)
|
||||
// and is passed through to the real free() unchanged.
|
||||
// 3. The appId in the header comes from *dvxMemAppIdPtr, which the
|
||||
// shell points at ctx->currentAppId.
|
||||
//
|
||||
// The magic check makes cross-boundary frees safe: if DXE code frees
|
||||
// a pointer allocated by libc (e.g. from strdup, stb_ds internals),
|
||||
// dvxFree detects the missing header and falls through to real free.
|
||||
|
||||
#ifndef DVX_MEM_H
|
||||
#define DVX_MEM_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
extern int32_t *dvxMemAppIdPtr;
|
||||
|
||||
void *dvxMalloc(size_t size);
|
||||
void *dvxCalloc(size_t nmemb, size_t size);
|
||||
void *dvxRealloc(void *ptr, size_t size);
|
||||
void dvxFree(void *ptr);
|
||||
void dvxMemSnapshotLoad(int32_t appId);
|
||||
uint32_t dvxMemGetAppUsage(int32_t appId);
|
||||
void dvxMemResetApp(int32_t appId);
|
||||
|
||||
// Redirect standard allocator calls to tracking wrappers.
|
||||
// This MUST appear after <stdlib.h> so the real prototypes exist.
|
||||
#define malloc(s) dvxMalloc(s)
|
||||
#define calloc(n, s) dvxCalloc((n), (s))
|
||||
#define realloc(p, s) dvxRealloc((p), (s))
|
||||
#define free(p) dvxFree(p)
|
||||
|
||||
#endif // DVX_MEM_H
|
||||
|
|
@ -22,6 +22,8 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Widget class table
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -213,6 +213,38 @@ const char *platformValidateFilename(const char *name);
|
|||
// and free physical memory in kilobytes. Returns false if unavailable.
|
||||
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
|
||||
|
||||
// ============================================================
|
||||
// Per-app memory tracking
|
||||
// ============================================================
|
||||
//
|
||||
// Wraps malloc/free/calloc/realloc with a small header per allocation
|
||||
// that records the owning app ID and size. This lets DVX report per-app
|
||||
// memory usage in the Task Manager and detect leaks at app termination.
|
||||
//
|
||||
// The allocator reads *dvxMemAppIdPtr to determine which app to charge.
|
||||
// The shell sets this pointer to &ctx->currentAppId during init.
|
||||
// Tracked allocations carry a 16-byte header (magic + appId + size + pad).
|
||||
// Calls to dvxFree on non-tracked pointers (magic mismatch) fall through
|
||||
// to the real free() safely.
|
||||
|
||||
// Per-app memory tracking (see dvxMem.h for the full API).
|
||||
// These are declared here for the platform implementation; DXE
|
||||
// code should include dvxMem.h instead.
|
||||
extern int32_t *dvxMemAppIdPtr;
|
||||
|
||||
// Take a DPMI free-memory snapshot when an app is loaded.
|
||||
// Call BEFORE the app's DXE is opened so the snapshot captures
|
||||
// the free memory before the app allocates anything.
|
||||
void dvxMemSnapshotLoad(int32_t appId);
|
||||
|
||||
// Return estimated memory usage for an app (bytes).
|
||||
// Computed as the difference between the snapshot at load time
|
||||
// and the current DPMI free memory. Coarse but safe.
|
||||
uint32_t dvxMemGetAppUsage(int32_t appId);
|
||||
|
||||
// Clear the snapshot for an app (call when reaping/killing).
|
||||
void dvxMemResetApp(int32_t appId);
|
||||
|
||||
// Create a directory and all parent directories (like mkdir -p).
|
||||
// Returns 0 on success, -1 on failure. Existing directories are not
|
||||
// an error.
|
||||
|
|
|
|||
|
|
@ -1112,6 +1112,167 @@ bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Per-app memory tracking (header-based)
|
||||
// ============================================================
|
||||
//
|
||||
// Every DXE .c file includes dvxMem.h which #defines malloc/free/
|
||||
// calloc/realloc to dvxMalloc/dvxFree/dvxCalloc/dvxRealloc. This
|
||||
// file does NOT include dvxMem.h, so calls to malloc/free here go
|
||||
// directly to libc with no recursion.
|
||||
//
|
||||
// Each tracked allocation has a 16-byte header prepended. dvxFree
|
||||
// checks the magic before adjusting the pointer -- if it doesn't
|
||||
// match (pointer came from libc internals or loader code), it
|
||||
// falls through to the real free() unchanged.
|
||||
|
||||
#define DVX_ALLOC_MAGIC 0xDEADBEEFUL
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
int32_t appId;
|
||||
uint32_t size;
|
||||
uint32_t pad;
|
||||
} DvxAllocHeaderT;
|
||||
|
||||
int32_t *dvxMemAppIdPtr = NULL;
|
||||
static uint32_t *sAppMemUsed = NULL;
|
||||
static int32_t sAppMemCap = 0;
|
||||
|
||||
|
||||
static void dvxMemGrow(int32_t appId) {
|
||||
if (appId < sAppMemCap) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newCap = appId + 16;
|
||||
uint32_t *newArr = (uint32_t *)realloc(sAppMemUsed, newCap * sizeof(uint32_t));
|
||||
|
||||
if (!newArr) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(newArr + sAppMemCap, 0, (newCap - sAppMemCap) * sizeof(uint32_t));
|
||||
sAppMemUsed = newArr;
|
||||
sAppMemCap = newCap;
|
||||
}
|
||||
|
||||
|
||||
void *dvxMalloc(size_t size) {
|
||||
int32_t appId = dvxMemAppIdPtr ? *dvxMemAppIdPtr : 0;
|
||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)malloc(sizeof(DvxAllocHeaderT) + size);
|
||||
|
||||
if (!hdr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hdr->magic = DVX_ALLOC_MAGIC;
|
||||
hdr->appId = appId;
|
||||
hdr->size = (uint32_t)size;
|
||||
hdr->pad = 0;
|
||||
|
||||
if (appId >= 0) {
|
||||
dvxMemGrow(appId);
|
||||
if (appId < sAppMemCap) { sAppMemUsed[appId] += (uint32_t)size; }
|
||||
}
|
||||
|
||||
return hdr + 1;
|
||||
}
|
||||
|
||||
|
||||
void *dvxCalloc(size_t nmemb, size_t size) {
|
||||
size_t total = nmemb * size;
|
||||
void *ptr = dvxMalloc(total);
|
||||
|
||||
if (ptr) {
|
||||
memset(ptr, 0, total);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
void dvxFree(void *ptr) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;
|
||||
|
||||
if (hdr->magic != DVX_ALLOC_MAGIC) {
|
||||
// Not a tracked allocation (libc, stb_ds, loader) -- pass through
|
||||
free(ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t appId = hdr->appId;
|
||||
|
||||
if (appId >= 0 && appId < sAppMemCap) {
|
||||
sAppMemUsed[appId] -= hdr->size;
|
||||
}
|
||||
|
||||
hdr->magic = 0;
|
||||
free(hdr);
|
||||
}
|
||||
|
||||
|
||||
void *dvxRealloc(void *ptr, size_t size) {
|
||||
if (!ptr) {
|
||||
return dvxMalloc(size);
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
dvxFree(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;
|
||||
|
||||
if (hdr->magic != DVX_ALLOC_MAGIC) {
|
||||
// Not tracked -- pass through
|
||||
return realloc(ptr, size);
|
||||
}
|
||||
|
||||
int32_t appId = hdr->appId;
|
||||
uint32_t oldSize = hdr->size;
|
||||
|
||||
DvxAllocHeaderT *newHdr = (DvxAllocHeaderT *)realloc(hdr, sizeof(DvxAllocHeaderT) + size);
|
||||
|
||||
if (!newHdr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (appId >= 0 && appId < sAppMemCap) {
|
||||
sAppMemUsed[appId] -= oldSize;
|
||||
sAppMemUsed[appId] += (uint32_t)size;
|
||||
}
|
||||
|
||||
newHdr->size = (uint32_t)size;
|
||||
return newHdr + 1;
|
||||
}
|
||||
|
||||
|
||||
void dvxMemSnapshotLoad(int32_t appId) {
|
||||
(void)appId;
|
||||
}
|
||||
|
||||
|
||||
uint32_t dvxMemGetAppUsage(int32_t appId) {
|
||||
if (appId < 0 || appId >= sAppMemCap) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sAppMemUsed[appId];
|
||||
}
|
||||
|
||||
|
||||
void dvxMemResetApp(int32_t appId) {
|
||||
if (appId >= 0 && appId < sAppMemCap) {
|
||||
sAppMemUsed[appId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformMkdirRecursive
|
||||
// ============================================================
|
||||
|
|
@ -2161,6 +2322,14 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformChdir)
|
||||
DXE_EXPORT(platformFlushRect)
|
||||
DXE_EXPORT(platformGetMemoryInfo)
|
||||
DXE_EXPORT(dvxMemAppIdPtr)
|
||||
DXE_EXPORT(dvxMalloc)
|
||||
DXE_EXPORT(dvxCalloc)
|
||||
DXE_EXPORT(dvxRealloc)
|
||||
DXE_EXPORT(dvxFree)
|
||||
DXE_EXPORT(dvxMemSnapshotLoad)
|
||||
DXE_EXPORT(dvxMemGetAppUsage)
|
||||
DXE_EXPORT(dvxMemResetApp)
|
||||
DXE_EXPORT(platformGetSystemInfo)
|
||||
DXE_EXPORT(platformInit)
|
||||
DXE_EXPORT(platformInstallCrashHandler)
|
||||
|
|
|
|||
|
|
@ -172,8 +172,14 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Dispatch to per-widget onKey handler via vtable
|
||||
// Attribute allocations during event handling to the owning app
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t prevAppId = ctx->currentAppId;
|
||||
ctx->currentAppId = win->appId;
|
||||
|
||||
wclsOnKey(focus, key, mod);
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -195,6 +201,8 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
// are also in content-buffer space (set during layout), so no
|
||||
// coordinate transform is needed for hit testing.
|
||||
|
||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
|
||||
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||
WidgetT *root = win->widgetRoot;
|
||||
sClosedPopup = NULL;
|
||||
|
|
@ -203,6 +211,18 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Attribute allocations during event handling to the owning app
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t prevAppId = ctx->currentAppId;
|
||||
ctx->currentAppId = win->appId;
|
||||
|
||||
widgetOnMouseInner(win, root, x, y, buttons);
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
|
||||
|
||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons) {
|
||||
// Close popups from other windows
|
||||
if (sOpenPopup && sOpenPopup->window != win) {
|
||||
wclsClosePopup(sOpenPopup);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
@ -260,6 +261,7 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
|
|||
}
|
||||
|
||||
cleanupTempFile(app);
|
||||
dvxMemResetApp(app->appId);
|
||||
app->state = AppStateFreeE;
|
||||
dvxLog("Shell: force-killed app '%s'", app->name);
|
||||
}
|
||||
|
|
@ -332,6 +334,9 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
loadPath = tempPath;
|
||||
}
|
||||
|
||||
// Snapshot free memory before loading so we can estimate app usage
|
||||
dvxMemSnapshotLoad(id);
|
||||
|
||||
// Load the DXE
|
||||
void *handle = dlopen(loadPath, RTLD_GLOBAL);
|
||||
|
||||
|
|
@ -506,6 +511,7 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
|||
}
|
||||
|
||||
cleanupTempFile(app);
|
||||
dvxMemResetApp(app->appId);
|
||||
dvxLog("Shell: reaped app '%s'", app->name);
|
||||
app->state = AppStateFreeE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "dvxPlatform.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
@ -260,6 +261,10 @@ int shellMain(int argc, char *argv[]) {
|
|||
// Initialize app slot table
|
||||
shellAppInit();
|
||||
|
||||
// Point the memory tracker at currentAppId so allocations are
|
||||
// attributed to whichever app is currently executing.
|
||||
dvxMemAppIdPtr = &sCtx.currentAppId;
|
||||
|
||||
// Set up idle callback for cooperative yielding. When dvxUpdate has
|
||||
// no work to do (no input events, no dirty rects), it calls this
|
||||
// instead of busy-looping. This is the main mechanism for giving
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
#define TM_COL_COUNT 5
|
||||
#define TM_COL_COUNT 6
|
||||
#define TM_MAX_PATH 260
|
||||
#define TM_WIN_W 520
|
||||
#define TM_WIN_W 580
|
||||
#define TM_WIN_H 280
|
||||
#define TM_LIST_PREF_H 160
|
||||
#define TM_BTN_SPACING 8
|
||||
|
|
@ -52,6 +53,7 @@ typedef struct {
|
|||
char title[MAX_TITLE_LEN];
|
||||
char file[64];
|
||||
char type[12];
|
||||
char mem[16];
|
||||
} TmRowStringsT;
|
||||
|
||||
static AppContextT *sCtx = NULL;
|
||||
|
|
@ -226,6 +228,14 @@ static void refreshTaskList(void) {
|
|||
snprintf(row.file, sizeof(row.file), "%.63s", fname);
|
||||
snprintf(row.type, sizeof(row.type), "%s", app->hasMainLoop ? "Task" : "Callback");
|
||||
|
||||
uint32_t memKb = dvxMemGetAppUsage(app->appId) / 1024;
|
||||
|
||||
if (memKb >= 1024) {
|
||||
snprintf(row.mem, sizeof(row.mem), "%lu MB", (unsigned long)(memKb / 1024));
|
||||
} else {
|
||||
snprintf(row.mem, sizeof(row.mem), "%lu KB", (unsigned long)memKb);
|
||||
}
|
||||
|
||||
arrput(sRowStrs, row);
|
||||
arrput(appIds, i);
|
||||
}
|
||||
|
|
@ -241,6 +251,7 @@ static void refreshTaskList(void) {
|
|||
arrput(sCells, sRowStrs[r].title);
|
||||
arrput(sCells, sRowStrs[r].file);
|
||||
arrput(sCells, sRowStrs[r].type);
|
||||
arrput(sCells, sRowStrs[r].mem);
|
||||
arrput(sCells, "Running");
|
||||
}
|
||||
|
||||
|
|
@ -313,20 +324,23 @@ void shellTaskMgrOpen(AppContextT *ctx) {
|
|||
|
||||
static ListViewColT tmCols[TM_COL_COUNT];
|
||||
tmCols[0].title = "Name";
|
||||
tmCols[0].width = wgtPercent(20);
|
||||
tmCols[0].width = wgtPercent(18);
|
||||
tmCols[0].align = ListViewAlignLeftE;
|
||||
tmCols[1].title = "Title";
|
||||
tmCols[1].width = wgtPercent(30);
|
||||
tmCols[1].width = wgtPercent(26);
|
||||
tmCols[1].align = ListViewAlignLeftE;
|
||||
tmCols[2].title = "File";
|
||||
tmCols[2].width = wgtPercent(22);
|
||||
tmCols[2].width = wgtPercent(18);
|
||||
tmCols[2].align = ListViewAlignLeftE;
|
||||
tmCols[3].title = "Type";
|
||||
tmCols[3].width = wgtPercent(14);
|
||||
tmCols[3].width = wgtPercent(12);
|
||||
tmCols[3].align = ListViewAlignLeftE;
|
||||
tmCols[4].title = "Status";
|
||||
tmCols[4].width = wgtPercent(14);
|
||||
tmCols[4].align = ListViewAlignLeftE;
|
||||
tmCols[4].title = "Memory";
|
||||
tmCols[4].width = wgtPercent(12);
|
||||
tmCols[4].align = ListViewAlignRightE;
|
||||
tmCols[5].title = "Status";
|
||||
tmCols[5].width = wgtPercent(14);
|
||||
tmCols[5].align = ListViewAlignLeftE;
|
||||
|
||||
sTmListView = wgtListView(root);
|
||||
sTmListView->weight = 100;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue