// MemDebugStats.cpp : Implementation of the heap memory-dump library
// Author: Juan Carlos "JCAB" Arévalo Baeza <mailto:jcab@roningames.com>
// (C) 2000-2001 Ronin Entertainment <http://www.roningames.com>
// You may use this as you like, as long as you don't remove the copyright.
#include "stdafx.h"
#include "MemDebugStats.h"
#pragma warning(disable: 4786) // identifier was truncated to '255' characters in the debug information
#include <windows.h>
#include <CrtDbg.h>
#include <TLHelp32.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <map>
#include <string>
// Unrelated common definitions
typedef unsigned char uchar;
typedef unsigned int uint;
#define RONIN_PRINTF(s,n) { \
char ronin__s[n]; \
va_list ronin__list; \
\
va_start(ronin__list, s); \
vsprintf(ronin__s, s, ronin__list); \
va_end(ronin__list); \
s = ronin__s; \
}
// Prints out to the file AND to OutputDebugString().
static void MultiPrintf(FILE *f, const char *fmt, ...)
{
RONIN_PRINTF(fmt, 4096);
if (f) {
fprintf(f, "%s", fmt);
}
OutputDebugString(fmt);
}
static void DumpProcessInfo(FILE *f)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
MultiPrintf(f, "\n-------------------- Current process info\n\n");
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE, 0);
DWORD dataAlloc = 0;
MODULEENTRY32 me;
me.dwSize = sizeof(me);
bool moduleValid = !!Module32First(snapshot, &me);
while (moduleValid) {
if ((DWORD)me.modBaseAddr >= (DWORD)si.lpMinimumApplicationAddress && (DWORD)me.modBaseAddr < (DWORD)si.lpMaximumApplicationAddress) {
MultiPrintf(f, "Process module: %8x, %13.3f Kbytes, %-32s : %s\n", me.modBaseAddr, me.modBaseSize / 1024.F, me.szModule, me.szExePath);
dataAlloc += me.modBaseSize;
}
moduleValid = !!Module32Next(snapshot, &me);
}
MultiPrintf(f, "\nTotal process data: %13.3f Kbytes\n\n", dataAlloc / 1024.F);
me.dwSize = sizeof(me);
moduleValid = !!Module32First(snapshot, &me);
while (moduleValid) {
if (!((DWORD)me.modBaseAddr >= (DWORD)si.lpMinimumApplicationAddress && (DWORD)me.modBaseAddr < (DWORD)si.lpMaximumApplicationAddress)) {
MultiPrintf(f, "System module: %8x, %13.3f Kbytes, %-32s : %s\n", me.modBaseAddr, me.modBaseSize / 1024.F, me.szModule, me.szExePath);
}
moduleValid = !!Module32Next(snapshot, &me);
}
// The following is VERY slow on Win2000, so I usually keep it disabled.
// Win98 is reasonably fast, thoguh. Dunno why.
/*
DWORD totalAlloc = dataAlloc;
DWORD totalFree = 0;
HEAPLIST32 hl;
hl.dwSize = sizeof(hl);
bool heapValid = !!Heap32ListFirst(snapshot, &hl);
while (heapValid) {
if (hl.dwFlags & HF32_DEFAULT) {
MultiPrintf(f, "Default heap found!!\n");
} else {
MultiPrintf(f, "Heap found!!\n");
}
DWORD heapAlloc = 0;
DWORD heapFree = 0;
DWORD numHeapAlloc = 0;
DWORD numHeapFree = 0;
HEAPENTRY32 he;
he.dwSize = sizeof(he);
bool entryValid = !!Heap32First(&he, hl.th32ProcessID, hl.th32HeapID);
while (entryValid) {
if (he.dwFlags == LF32_FIXED) {
// Uncomment to log out every single "fixed" block.
// MultiPrintf(f, " Block: %8x, %13.3f Kbytes, fixed\n", he.dwAddress, he.dwBlockSize / 1024.F);
heapAlloc += he.dwBlockSize;
++numHeapAlloc;
} else if (he.dwFlags == LF32_FREE) {
// Uncomment to log out every single "freed" block.
// MultiPrintf(f, " Block: %8x, %13.3f Kbytes, free\n", he.dwAddress, he.dwBlockSize / 1024.F);
heapFree += he.dwBlockSize;
++numHeapFree;
} else if (he.dwFlags == LF32_MOVEABLE) {
// Uncomment to log out every single "moveable" block.
// MultiPrintf(f, " Block: %8x, %13.3f Kbytes, moveable\n", he.dwAddress, he.dwBlockSize / 1024.F);
heapAlloc += he.dwBlockSize;
++numHeapAlloc;
} else {
MultiPrintf(f, " Block: %8x, %13.3f Kbytes, <unknown: %8x>\n", he.dwAddress, he.dwBlockSize / 1024.F, he.dwFlags);
}
entryValid = !!Heap32Next(&he);
}
MultiPrintf(f, " Heap: %13.3f Kbytes allocated, %13.3f Kbytes free\n", heapAlloc / 1024.F, heapFree / 1024.F);
totalAlloc += heapAlloc;
totalFree += heapFree;
heapValid = !!Heap32ListNext(snapshot, &hl);
}
MultiPrintf(f, "Total: %13.3f Kbytes allocated, %13.3f Kbytes free\n", totalAlloc / 1024.F, totalFree / 1024.F);
//*/
CloseHandle(snapshot);
}
static void DumpGlobalInfo(FILE *f)
{
MEMORYSTATUS ms;
GlobalMemoryStatus(&ms);
MultiPrintf(f, "\n-------------------- System info\n\n");
MultiPrintf(f, "Memory load: %8d %%\n", ms.dwMemoryLoad);
MultiPrintf(f, "Memory total: %12.3f MB\n", ms.dwTotalPhys / (1024.F * 1024.F));
MultiPrintf(f, "Memory used: %12.3f MB\n", (ms.dwTotalPhys - ms.dwAvailPhys) / (1024.F * 1024.F));
MultiPrintf(f, "Memory available: %12.3f MB\n", ms.dwAvailPhys / (1024.F * 1024.F));
MultiPrintf(f, "Swap file total: %12.3f MB\n", ms.dwTotalPageFile / (1024.F * 1024.F));
MultiPrintf(f, "Swap file in use: %12.3f MB\n", (ms.dwTotalPageFile - ms.dwAvailPageFile) / (1024.F * 1024.F));
MultiPrintf(f, "Swap file available: %12.3f MB\n", ms.dwAvailPageFile / (1024.F * 1024.F));
MultiPrintf(f, "Virtual total: %12.3f MB\n", ms.dwTotalVirtual / (1024.F * 1024.F));
MultiPrintf(f, "Virtual in use: %12.3f MB\n", (ms.dwTotalVirtual - ms.dwAvailVirtual) / (1024.F * 1024.F));
MultiPrintf(f, "Virtual available: %12.3f MB\n", ms.dwAvailVirtual / (1024.F * 1024.F));
MultiPrintf(f, "\n");
}
void GetMemSnapshot(MemDebugSnapshot &snap)
{
delete snap.hookedBlock;
snap.hookedBlock = new int(0xBAADF0F0);
_CrtMemCheckpoint(&snap.memState);
}
#ifndef NDEBUG
struct FileLine {
std::string name;
int line;
FileLine(): name(), line(0) {}
FileLine(const std::string &name_, int line_): name(name_), line(line_) {}
friend bool operator <(const FileLine &fl1, const FileLine &fl2) {
const int comp = strcmp(fl1.name.c_str(), fl2.name.c_str());
if (comp < 0) return true;
if (comp > 0) return false;
return fl1.line < fl2.line;
}
};
struct SizeInfo {
int numBlocksPerSize[32];
int totalSizePerSize[32];
int totalBlocks;
int totalSize;
SizeInfo(): totalBlocks(0), totalSize(0) {
memset(&numBlocksPerSize, 0, sizeof(numBlocksPerSize));
memset(&totalSizePerSize, 0, sizeof(totalSizePerSize));
}
SizeInfo &operator+=(const SizeInfo &other) {
uint i;
for (i = 0; i < 32; ++i) {
numBlocksPerSize[i] += other.numBlocksPerSize[i];
totalSizePerSize[i] += other.totalSizePerSize[i];
}
totalBlocks += other.totalBlocks;
totalSize += other.totalSize;
return *this;
}
SizeInfo &operator-=(const SizeInfo &other) {
uint i;
for (i = 0; i < 32; ++i) {
numBlocksPerSize[i] -= other.numBlocksPerSize[i];
totalSizePerSize[i] -= other.totalSizePerSize[i];
}
totalBlocks -= other.totalBlocks;
totalSize -= other.totalSize;
return *this;
}
};
struct FileLineSizeInfo: FileLine, SizeInfo {
FileLineSizeInfo() {}
FileLineSizeInfo(const FileLine &fl, const SizeInfo &si):
FileLine(fl),
SizeInfo(si)
{}
};
struct FileSizeInfo: SizeInfo {
std::string name;
FileSizeInfo() {}
FileSizeInfo(const std::string &n, const SizeInfo &si):
name(n),
SizeInfo(si)
{}
};
// There's no way to do without the file-scope variables,
// because the report hook function doesn't have the extra
// "custom data" pointer that EVERY C-style callback must have.
// Bad move by the boys at Microsoft.
static std::string g_LastFName = std::string();
static int g_LastLine = 0;
static SizeInfo g_UnnamedSizeInfo;
static SizeInfo g_TotalSizeInfo;
static std::map<FileLine, SizeInfo> g_FileLineMap;
static FILE *g_Fileo = NULL;
static void ClearFileData()
{
g_LastFName = std::string();
g_LastLine = 0;
g_UnnamedSizeInfo;
g_TotalSizeInfo;
g_FileLineMap.clear();
g_Fileo = NULL;
}
static int GatherStatsReportHook(int reportType, char *message, int *returnValue)
{
if (returnValue) {
*returnValue = 0;
}
int n = strlen(message) - 4;
if (n > 0 && message[n] == ')' && message[n+2] == ':') {
int e = n-1;
while (e > 0 && message[e] != '(') {
--e;
}
if (message[e] == '(') {
g_LastFName.assign(message, e);
sscanf(message+e+1, "%d", &g_LastLine);
return true;
}
}
void *ptr;
int size;
int subType = 0;
if (sscanf(message, "normal block at 0x%x, %d", &ptr, &size) == 2 ||
sscanf(message, "crt block at 0x%x, subtype %d, %d", &ptr, &subType, &size) == 3) {
SizeInfo *si;
if (!g_LastFName.empty()) {
si = &g_FileLineMap[FileLine(g_LastFName, g_LastLine)];
} else {
si = &g_UnnamedSizeInfo;
}
int block = 0;
int bsize = size;
while (bsize > 0 && block < 31) {
bsize >>= 1;
++block;
}
++si->numBlocksPerSize[block];
si->totalSizePerSize[block] += size;
++si->totalBlocks;
si->totalSize += size;
++g_TotalSizeInfo.numBlocksPerSize[block];
g_TotalSizeInfo.totalSizePerSize[block] += size;
++g_TotalSizeInfo.totalBlocks;
g_TotalSizeInfo.totalSize += size;
g_LastFName[0] = 0;
return true;
}
return true;
}
static int OutputReportHook(int reportType, char *message, int *returnValue)
{
if (returnValue) {
*returnValue = 0;
}
MultiPrintf(g_Fileo, "%s", message);
return true;
}
static void ReportBlock(FILE *f, const SizeInfo &si)
{
if (si.numBlocksPerSize[0] > 0) {
MultiPrintf(f, " Blocks of size 0 : %6d blocks\n", si.numBlocksPerSize[0]);
}
if (si.numBlocksPerSize[1] > 0) {
MultiPrintf(f, " Blocks of size 1 : %6d blocks\n", si.numBlocksPerSize[1]);
}
int bsize = 2;
int i;
for (i = 2; i < 32; ++i) {
if (si.numBlocksPerSize[i] > 0) {
MultiPrintf(f, " Between %7d and %7d: %6d blocks, %10d bytes total, %11.2f avg. bytes/block\n", bsize, (bsize << 1) - 1, si.numBlocksPerSize[i], si.totalSizePerSize[i], (float) si.totalSizePerSize[i] / si.numBlocksPerSize[i]);
}
bsize <<= 1;
}
}
static void DumpSingleBlock(FILE *f, const char *header, const SizeInfo &si, bool fmt)
{
if (fmt) {
if (si.totalBlocks > 0) {
MultiPrintf(f, "%-75s : %6d | %8d | %11.2f\n", header, si.totalBlocks, si.totalSize, (float)si.totalSize / si.totalBlocks);
} else {
MultiPrintf(f, "%-75s : %6d | %8d | %11.2f\n", header, 0, 0, 0);
}
} else {
if (si.totalBlocks > 0) {
MultiPrintf(f, "%s : Total %d blocks, %d bytes, %.2f avg bytes/block\n", header, si.totalBlocks, si.totalSize, (float)si.totalSize / si.totalBlocks);
} else {
MultiPrintf(f, "%s : Total 0 blocks, 0 bytes, 0 avg bytes/block\n", header);
}
}
if (!fmt) {
int n = 0;
int j;
for (j = 0; j < 32; ++j) {
if (si.numBlocksPerSize[j] > 0) {
++n;
}
}
if (n > 1) {
ReportBlock(f, si);
}
}
}
#endif
static FILE *OpenLogFile()
{
int j;
for (j = 0; j < 0x10000; ++j) {
char fname[4096];
sprintf(fname, "c:\\MEM%04x.log", j);
FILE *f = fopen(fname, "r");
if (f == NULL) {
return fopen(fname, "wt");
} else {
fclose(f);
}
}
return NULL;
}
void DumpMemStats(MemDebugSnapshot &snap, const char *file, unsigned int line)
{
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
FILE *f = OpenLogFile();
MultiPrintf(f, "\n-------------------------------------------------------------------------------\n");
if (file) {
MultiPrintf(f, "\n%s(%d): Code line that executed the dump\n\n", file, line);
} else {
MultiPrintf(f, "\nPlease, use the macros to execute the dump!!\n\n");
}
#ifndef NDEBUG
{
MultiPrintf(f, "-------------------- Begin DumpMemStats\n\n");
_CRT_REPORT_HOOK g_OldReportHook = _CrtSetReportHook(GatherStatsReportHook);
_CrtMemDumpAllObjectsSince(&snap.memState);
_CrtSetReportHook(g_OldReportHook);
{
DumpSingleBlock(f, "Total blocks", g_TotalSizeInfo, false);
DumpSingleBlock(f, "Unnamed blocks", g_UnnamedSizeInfo, false);
SizeInfo namedSizeInfo = g_TotalSizeInfo;
namedSizeInfo += g_UnnamedSizeInfo;
DumpSingleBlock(f, "Named blocks", namedSizeInfo, false);
}
MultiPrintf(f, "\n-------------------- Stats sorted by file name and line number\n\n");
std::map<std::string, SizeInfo> FileMap;
{
SizeInfo FileAccum;
std::string lastFile;
int FileLines = 0;
SizeInfo totals;
std::map<FileLine, SizeInfo>::const_iterator i;
for (i = g_FileLineMap.begin(); i != g_FileLineMap.end(); ++i) {
if (lastFile != i->first.name) {
if (FileLines > 0) {
FileMap[lastFile] = FileAccum;
}
FileAccum = SizeInfo();
lastFile = i->first.name;
FileLines = 0;
}
char header[4096];
sprintf(header, "%s(%d)", i->first.name.c_str(), i->first.line);
DumpSingleBlock(f, header, i->second, false);
++FileLines;
FileAccum += i->second;
totals += i->second;
}
if (FileLines > 0) {
FileMap[lastFile] = FileAccum;
}
if (totals.totalBlocks > 0) {
MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
} else {
MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
}
}
{
MultiPrintf(f, "\n--------- Formatted\n\n");
MultiPrintf(f, "%-75s : blocks | bytes | avg bytes/block\n", " ");
MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
SizeInfo totals;
std::map<FileLine, SizeInfo>::const_iterator i;
for (i = g_FileLineMap.begin(); i != g_FileLineMap.end(); ++i) {
char header[4096];
sprintf(header, "%-68s(%5d)", i->first.name.c_str(), i->first.line);
DumpSingleBlock(f, header, i->second, true);
totals += i->second;
}
if (totals.totalBlocks > 0) {
MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
} else {
MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
}
}
std::multimap<int, FileSizeInfo> BlockFilesMap;
{
MultiPrintf(f, "\n-------------------- Stats sorted by file name\n\n");
MultiPrintf(f, "%-75s : blocks | bytes | avg bytes/block\n", " ");
MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
SizeInfo totals;
std::map<std::string, SizeInfo>::const_iterator i;
for (i = FileMap.begin(); i != FileMap.end(); ++i) {
char header[4096];
sprintf(header, "%-75s", i->first.c_str());
DumpSingleBlock(f, header, i->second, true);
BlockFilesMap.insert(std::pair<const int, FileSizeInfo>(i->second.totalBlocks, FileSizeInfo(i->first, i->second)));
totals += i->second;
}
if (totals.totalBlocks > 0) {
MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
} else {
MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
}
}
std::multimap<int, FileSizeInfo> SizeFilesMap;
{
MultiPrintf(f, "\n-------------------- File stats sorted by number of blocks\n\n");
MultiPrintf(f, "%-75s : blocks | bytes | avg bytes/block\n", " ");
MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
SizeInfo totals;
std::multimap<int, FileSizeInfo>::const_iterator i;
for (i = BlockFilesMap.begin(); i != BlockFilesMap.end(); ++i) {
char header[4096];
sprintf(header, "%-75s", i->second.name.c_str());
DumpSingleBlock(f, header, i->second, true);
SizeFilesMap.insert(std::pair<const int, FileSizeInfo>(i->second.totalSize, i->second));
totals += i->second;
}
if (totals.totalBlocks > 0) {
MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
} else {
MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
}
}
std::map<std::string, SizeInfo> DirMap;
{
MultiPrintf(f, "\n-------------------- File stats sorted by total size\n\n");
MultiPrintf(f, "%-75s : blocks | bytes | avg bytes/block\n", " ");
MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
SizeInfo totals;
std::multimap<int, FileSizeInfo>::const_iterator i;
for (i = SizeFilesMap.begin(); i != SizeFilesMap.end(); ++i) {
char header[4096];
sprintf(header, "%-75s", i->second.name.c_str());
DumpSingleBlock(f, header, i->second, true);
{
const char * const name = i->second.name.c_str();
const char *d = name + i->second.name.size()-1;
while (d > name && *d != '\\' && *d != '/') {
--d;
}
while (*d == '\\' || *d == '/') {
std::string dir(name, d-name);
std::map<std::string, SizeInfo>::iterator it = DirMap.find(dir);
if (it == DirMap.end()) {
DirMap.insert(std::pair<std::string, SizeInfo>(dir, i->second));
} else {
it->second += i->second;
}
--d;
while (d > name && *d != '\\' && *d != '/') {
--d;
}
}
}
totals += i->second;
}
if (totals.totalBlocks > 0) {
MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
} else {
MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
}
}
{
MultiPrintf(f, "\n-------------------- Directory stats\n\n");
MultiPrintf(f, "%-75s : blocks | bytes | avg bytes/block\n", " ");
MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
std::map<std::string, SizeInfo>::const_iterator i;
for (i = DirMap.begin(); i != DirMap.end(); ++i) {
char header[4096];
sprintf(header, "%-75s", i->first.c_str());
DumpSingleBlock(f, header, i->second, true);
}
}
MultiPrintf(f, "\n-------------------- End DumpMemStats\n\n");
_CrtMemState dif;
if (&snap) {
_CrtMemState cur;
_CrtMemCheckpoint(&cur);
_CrtMemDifference(&dif, &snap.memState, &cur);
} else {
_CrtMemCheckpoint(&dif);
}
g_Fileo = f;
_CrtSetReportHook(OutputReportHook);
_CrtMemDumpStatistics(&dif);
_CrtSetReportHook(g_OldReportHook);
g_Fileo = NULL;
memset(&g_UnnamedSizeInfo, 0, sizeof(g_UnnamedSizeInfo));
memset(&g_TotalSizeInfo, 0, sizeof(g_TotalSizeInfo));
g_LastFName[0] = 0;
g_FileLineMap.clear();
}
ClearFileData();
#else
MultiPrintf(f, "-------------------- Executed from Release mode, so no debug heap stats\n\n");
#endif
DumpProcessInfo(f);
DumpGlobalInfo(f);
fclose(f);
}
void DumpShortMemStats(const char *file, unsigned int line)
{
FILE *f = OpenLogFile();
if (file) {
MultiPrintf(f, "\n%s(%d): Code line that executed the short dump\n\n", file, line);
} else {
MultiPrintf(f, "\nPlease, use the macros to execute the short dump!!\n\n");
}
DumpGlobalInfo(f);
fclose(f);
}
void DumpMemStats(const char *file, unsigned int line)
{
DumpMemStats(*(MemDebugSnapshot *)0, file, line);
}
|