mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
304 lines
8.8 KiB
C++
Executable File
304 lines
8.8 KiB
C++
Executable File
#include <list>
|
|
#include <map>
|
|
#include <stdarg.h>
|
|
#include <string>
|
|
#include <time.h>
|
|
|
|
#include "KString.h"
|
|
|
|
#include "KMemory.h" // last include
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// KANJI_DUMP_LEAKED_MEM will print out the memory block that was leaked.
|
|
// This is helpful when leaked objects have string identifiers.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//#define KANJI_DUMP_LEAKED_MEM
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// KANJI_TRACK_MEM_USAGE will print out all memory allocations.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//#define KANJI_TRACK_MEM_USAGE
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Our memory system is thread-safe, but instead of linking massive libraries,
|
|
// we attempt to use C++11 std::mutex.
|
|
#ifdef USE_CPP11_MUTEX_DISABLED
|
|
#include <mutex>
|
|
typedef std::recursive_mutex KSysLock; // rentrant
|
|
struct KAutoLock {
|
|
KAutoLock(KSysLock& lock) :mLock(lock) { mLock.lock(); } // acquire
|
|
~KAutoLock() { mLock.unlock(); } // release
|
|
|
|
KSysLock& mLock;
|
|
};
|
|
#else // Fallback to unsafe. don't spawn threads
|
|
typedef int KSysLock;
|
|
struct KAutoLock {
|
|
KAutoLock(KSysLock) {} // acquire
|
|
~KAutoLock() {} // release
|
|
};
|
|
#endif
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
struct KANJI_ALLOC_INFO
|
|
{
|
|
size_t size;
|
|
std::string file;
|
|
int line;
|
|
};
|
|
static bool showLeaks = false;
|
|
class KAllocMap : public std::map<void*, KANJI_ALLOC_INFO>
|
|
{
|
|
public:
|
|
KSysLock crit;
|
|
static bool allocMapValid;
|
|
|
|
public:
|
|
KAllocMap() { allocMapValid = true; }
|
|
~KAllocMap()
|
|
{
|
|
if (showLeaks)
|
|
KMemoryDumpUnfreed();
|
|
|
|
allocMapValid = false;
|
|
}
|
|
};
|
|
bool KAllocMap::allocMapValid = false;
|
|
static KAllocMap allocMap; // once this static object destructs, it dumps unfreed memory
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#ifdef KANJI_TRACK_MEM_USAGE
|
|
void KMemoryDumpUsage(); // forward declaration
|
|
class KAllocStat
|
|
{
|
|
public:
|
|
typedef std::map<int, int> allocCount; // [size] = count
|
|
typedef std::map<std::pair<std::string, int>, allocCount> allocInfo; // [file, line] = allocCount
|
|
|
|
allocInfo memInfo;
|
|
static bool allocMapValid;
|
|
|
|
public:
|
|
|
|
KAllocStat()
|
|
{
|
|
allocMapValid = true;
|
|
}
|
|
~KAllocStat()
|
|
{
|
|
if (showLeaks)
|
|
KMemoryDumpUsage();
|
|
|
|
allocMapValid = false;
|
|
}
|
|
void addTrack(const char* fname, int lnum, int asize)
|
|
{
|
|
allocCount& info = memInfo[std::pair<std::string, int>(fname, lnum)];
|
|
info[asize]++;
|
|
}
|
|
};
|
|
bool KAllocStat::allocMapValid = false;
|
|
static KAllocStat allocStat;
|
|
#endif // KANJI_TRACK_MEM_USAGE
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
extern void KMemoryAddTrack( void* addr, size_t asize, const char* fname, int lnum )
|
|
{
|
|
if (!KAllocMap::allocMapValid || asize == 0)
|
|
return;
|
|
|
|
KAutoLock aCrit(allocMap.crit);
|
|
showLeaks = true;
|
|
|
|
KANJI_ALLOC_INFO& info = allocMap[addr];
|
|
info.file = fname;
|
|
info.line = lnum;
|
|
info.size = asize;
|
|
|
|
#ifdef KANJI_TRACK_MEM_USAGE
|
|
if (KAllocStat::allocMapValid)
|
|
allocStat.addTrack(fname, lnum, asize);
|
|
#endif
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void KMemoryRemoveTrack(void* addr)
|
|
{
|
|
if (!KAllocMap::allocMapValid)
|
|
return;
|
|
|
|
KAutoLock aCrit(allocMap.crit);
|
|
KAllocMap::iterator anItr = allocMap.find(addr);
|
|
if (anItr != allocMap.end())
|
|
allocMap.erase(anItr);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void KMemoryDumpUnfreed()
|
|
{
|
|
if (!KAllocMap::allocMapValid)
|
|
return;
|
|
|
|
KAutoLock aCrit(allocMap.crit); // prevent modification of the map while iterating
|
|
|
|
size_t totalSize = 0;
|
|
char buf[8192];
|
|
|
|
FILE* f = fopen("mem_leaks.txt", "wt");
|
|
if (!f)
|
|
return;
|
|
|
|
time_t aTime = time(NULL);
|
|
sprintf(buf, "Memory Leak Report for %s\n", asctime(localtime(&aTime)));
|
|
fprintf(f, "%s", buf);
|
|
KOutputDebug(DEBUGLVL, "\n");
|
|
KOutputDebug(INFOLVL, buf);
|
|
for(KAllocMap::iterator i = allocMap.begin(); i != allocMap.end(); ++i)
|
|
{
|
|
sprintf(buf, "%s(%d) : Leak %u byte%s @0x%08X\n", i->second.file.c_str(), i->second.line, i->second.size, i->second.size > 1 ? "s" : "", (size_t)i->first);
|
|
KOutputDebug(ERRORLVL, buf);
|
|
fprintf(f, "%s", buf);
|
|
|
|
#ifdef KANJI_DUMP_LEAKED_MEM
|
|
unsigned char* data = (unsigned char*)i->first;
|
|
int count = 0;
|
|
char hex_dump[1024];
|
|
char ascii_dump[1024];
|
|
|
|
for (int index = 0; index < i->second.size; index++)
|
|
{
|
|
unsigned char _c = *data;
|
|
|
|
if (count == 0)
|
|
sprintf(hex_dump, "\t%02X ", _c);
|
|
else
|
|
sprintf(hex_dump, "%s%02X ", hex_dump, _c); // technically, this is undefined behavior
|
|
|
|
if ((_c < 32) || (_c > 126))
|
|
_c = '.';
|
|
|
|
if (count == 7)
|
|
sprintf(ascii_dump, "%s%c ", ascii_dump, _c);
|
|
else
|
|
sprintf(ascii_dump, "%s%c", count == 0 ? "\t" : ascii_dump, _c); // technically, this is undefined behavior
|
|
|
|
|
|
if (++count == 16)
|
|
{
|
|
count = 0;
|
|
sprintf(buf, "%s\t%s\n", hex_dump, ascii_dump);
|
|
fprintf(f, buf);
|
|
|
|
memset((void*)hex_dump, 0, 1024);
|
|
memset((void*)ascii_dump, 0, 1024);
|
|
}
|
|
|
|
data++;
|
|
}
|
|
|
|
if (count != 0)
|
|
{
|
|
fprintf(f, hex_dump);
|
|
for (int index = 0; index < 16 - count; index++)
|
|
fprintf(f, "\t");
|
|
|
|
fprintf(f, ascii_dump);
|
|
|
|
for (int index = 0; index < 16 - count; index++)
|
|
fprintf(f, ".");
|
|
}
|
|
|
|
count = 0;
|
|
fprintf(f, "\n\n");
|
|
memset((void*)hex_dump, 0, 1024);
|
|
memset((void*)ascii_dump, 0, 1024);
|
|
|
|
#endif // KANJI_DUMP_LEAKED_MEM
|
|
|
|
totalSize += i->second.size;
|
|
}
|
|
|
|
ErrorLevel lvl = (totalSize > 0) ? ERRORLVL : INFOLVL;
|
|
|
|
sprintf(buf, "-----------------------------------------------------------\n");
|
|
fprintf(f, "%s", buf);
|
|
KOutputDebug(lvl, buf);
|
|
sprintf(buf, "Total Unfreed: %u bytes (%luKB)\n\n", totalSize, totalSize / 1024);
|
|
KOutputDebug(lvl, buf);
|
|
fprintf(f, "%s", buf);
|
|
fclose(f);
|
|
}
|
|
|
|
#ifdef KANJI_TRACK_MEM_USAGE
|
|
void KMemoryDumpUsage()
|
|
{
|
|
if (!KAllocStat::allocMapValid)
|
|
return;
|
|
|
|
char buf[8192];
|
|
FILE* f = fopen("mem_usage.txt", "wt");
|
|
|
|
time_t aTime = time(NULL);
|
|
sprintf(buf, "Memory Usage Report for %s\n", asctime(localtime(&aTime)));
|
|
if (f) fprintf(f, "%s", buf);
|
|
KOutputDebug("\n");
|
|
KOutputDebug(buf);
|
|
|
|
for(KAllocStat::allocInfo::iterator i = allocStat.memInfo.begin(); i != allocStat.memInfo.end(); ++i)
|
|
{
|
|
int aBytesTotal = 0;
|
|
int aCallsTotal = 0;
|
|
for (KAllocStat::allocCount::iterator index = i->second.begin(); index != i->second.end(); ++index)
|
|
{
|
|
aBytesTotal += index->first;
|
|
aCallsTotal += index->second;
|
|
sprintf(buf, "%s(%d) : %d bytes (%d %s)\n", i->first.first.c_str(), i->first.second, index->first, index->second, index->second == 1 ? "call" : "calls");
|
|
if (f) fprintf(f, "%s", buf);
|
|
KOutputDebug(buf);
|
|
}
|
|
|
|
if (i->second.size() > 1)
|
|
{
|
|
sprintf(buf, " %s(%d) : %d KB total (%d calls)\n", i->first.first.c_str(), i->first.second, aBytesTotal / 1024, aCallsTotal);
|
|
if (f) fprintf(f, "%s", buf);
|
|
KOutputDebug(buf);
|
|
}
|
|
}
|
|
if (f) fclose(f);
|
|
}
|
|
#endif // KANJI_TRACK_MEM_USAGE
|
|
|
|
size_t KMemoryAllocated()
|
|
{
|
|
if (!KAllocMap::allocMapValid)
|
|
return 0;
|
|
|
|
KAutoLock aCrit(allocMap.crit);
|
|
|
|
size_t size = 0;
|
|
for(auto i = allocMap.begin(); i != allocMap.end(); ++i)
|
|
{
|
|
KANJI_ALLOC_INFO& info = i->second;
|
|
size += info.size;
|
|
}
|
|
return size;
|
|
}
|