Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
IlushaShurupov
6d0a4676ab Allocators Tools Initial 2023-07-07 00:10:00 +03:00
34 changed files with 3847 additions and 0 deletions

View file

@ -0,0 +1,42 @@
# Profiling
## Memory Leaks
Example program with memory leaks:
```c++
#include "allocators.h"
void test_call22() { new int; }
void test_call21() { new float; }
void test_call11() {
test_call21();
test_call22();
}
int main(char argc, char* argv[]) {
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocator, NULL };
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
TestModule.initialize();
test_call11();
TestModule.deinitialize();
}
```
If memory leaks were detected it will be loged in the output console.
![image](https://user-images.githubusercontent.com/63184036/222794298-3f238de4-c0b8-41fa-b7ec-c0c675da8f05.png)
Also debug.memleaks file will be generated in the working directory that can be viewved with MemLeaks Viewver.
![image](https://user-images.githubusercontent.com/63184036/222793169-a405effe-72be-42fc-b375-bb06dce0a735.png)
## Memory Usage Analisys
Currently outdated
## Benchmarks
Currently outdated

View file

@ -0,0 +1,26 @@
#pragma once
#include "HeapAllocator.hpp"
#include "ChunkAllocator.hpp"
#include "PoolAllocator.hpp"
namespace tp {
extern ModuleManifest gModuleAllocator;
};
inline void* operator new(size_t aSize, void* aWhere) noexcept { return aWhere; }
void* operator new(size_t aSize);
void* operator new[](size_t _Size);
void operator delete(void* aPtr);
void operator delete[](void* aPtr);
void* operator new(size_t aSize, tp::HeapAlloc& aAlloc);
void* operator new[](size_t _Size, tp::HeapAlloc& aAlloc);
void operator delete(void* aPtr, tp::HeapAlloc& aAlloc);
void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc);
void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc);
void* operator new[](size_t _Size, tp::HeapAllocGlobal& aAlloc);
void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc);
void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc);

View file

@ -0,0 +1,33 @@
#pragma once
#include "common.h"
#include "PublicConfig.hpp"
namespace tp {
// Chunk Allocator
// Constant time allocations and deallocations in any order.
// Memory blocks are fixed in size and number of blocks can not exceed given parameter.
struct ChunkAlloc {
ChunkAlloc(ualni aBlockSize, void* aMemory, ualni aMemSize);
ChunkAlloc(ualni aBlockSize, ualni aNBlocks);
void* allocate();
void deallocate(void* aPtr);
bool isFull();
bool isEmpty();
~ChunkAlloc();
private:
ualni mBSize; // Size of data in aligned units
ualni mNBlocks;
ualni* mBuff;
ualni* mNextBlock = 0;
ualni mNFreeBlocks;
ualni mNInitBlocks = 0;
bool mOwnBuff = false;
};
};

View file

@ -0,0 +1,20 @@
#pragma once
#include "HeapAllocatorGlobal.hpp"
namespace tp {
struct HeapAlloc {
#ifdef MEM_DEBUG
HeapAllocGlobal mAlloc;
ualni mNumAllocations = 0;
struct MemHeadLocal* mEntry = NULL;
#endif
void* allocate(ualni aBlockSize);
void deallocate(void* aPtr);
~HeapAlloc();
};
};

View file

@ -0,0 +1,19 @@
#pragma once
#include "common.h"
#include "PublicConfig.hpp"
namespace tp {
struct HeapAllocGlobal {
#ifdef MEM_DEBUG
static ualni mNumAllocations;
static struct MemHead* mEntry;
#endif
static void* allocate(ualni aBlockSize);
static void deallocate(void* aPtr);
~HeapAllocGlobal();
};
};

View file

@ -0,0 +1,13 @@
#pragma once
#include "HeapAllocatorGlobal.hpp"
namespace tp {
struct PickAlloc {
PickAlloc();
void* allocate(ualni aBlockSize);
void deallocate(void* aPtr);
~PickAlloc();
};
};

View file

@ -0,0 +1,42 @@
#pragma once
#include "ChunkAllocator.hpp"
namespace tp {
// Pool Allocator
// Overcomes chunk allocator fixed number of max allocations
struct PoolAlloc {
PoolAlloc(ualni aBlockSize, ualni aChunkSize);
void* allocate();
void deallocate(void* aPtr);
~PoolAlloc();
private:
struct Chunk : public ChunkAlloc {
Chunk(ualni aBlockSize, ualni aChunlSize) : ChunkAlloc(aBlockSize, aChunlSize) {}
Chunk* mNext = NULL;
Chunk* mPrev = NULL;
};
struct Chunks {
void add(Chunk*);
void remove(Chunk*);
Chunk* find(void* aPtr);
Chunk* findNotFull();
Chunk** mBuff = NULL;
ualni mUsedLen = 0;
ualni mLen = 0;
private:
Chunk** findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr);
};
Chunks mChunks;
Chunk* mFreeChunk = NULL;
ualni mBlockSize;
ualni mChunkSize;
};
};

View file

@ -0,0 +1,12 @@
#pragma once
#include "PublicConfig.hpp"
#define MEM_WRAP_SIZE 8 // Wrapping Size in aligned units
#define MEM_WRAP_FILL_VAL 0xBB // Wrapping Fill Value
#define MEM_CLEAR_ON_ALLOC // Clear data on allocation
#define MEM_CLEAR_ON_DEALLOC // Clear data on free
#define MEM_CLEAR_ON_DEALLOC_VAL 0xAA // Clear data on free
#define MEM_CLEAR_ON_ALLOC_VAL 0xCC // Clear data on free
#define MEM_STACK_TRACE // Save stack on allocation call
#define MEM_STACK_TRACE_MAX_DEPTH 32 // Call stack max depth

View file

@ -0,0 +1,3 @@
#pragma once
#define MEM_DEBUG // Memory Debugging

View file

@ -0,0 +1,43 @@
#pragma once
#include "common.h"
namespace tp {
struct CallStackSnapshots {
typedef alni FramePointer;
typedef FramePointer* StackShapshot; // NULL Terminated Frames
StackShapshot capture();
void saveToFile(StackShapshot* snapshots, const char* filepath);
StackShapshot* loadFromFile(const char* filepath);
private:
struct SnapshotsDict {
SnapshotsDict();
StackShapshot newStackSnapshot(ualni aDepth);
void put(StackShapshot aPtr);
StackShapshot getSlot(ualni aIdx);
alni presents(StackShapshot aPtr);
~SnapshotsDict();
private:
StackShapshot* mTable = NULL;
uhalni mSize = 512;
uhalni mEntries = 0;
ualni hash(StackShapshot snapshot);
bool compare(StackShapshot left, StackShapshot right);
alni findSlotRead(StackShapshot key);
ualni findSlotWrite(StackShapshot key);
void resize();
} mSnapshots;
StackShapshot getStack(ualni& len);
};
extern CallStackSnapshots gCallStackSnapshots;
};

View file

@ -0,0 +1,25 @@
#include "allocators.hpp"
#include "filesystem.h"
static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleFilesystem, NULL };
tp::ModuleManifest tp::gModuleAllocator = ModuleManifest("Allocators", NULL, NULL, sModuleDependencies);
//void* operator new(size_t aSize, void* aWhere) noexcept { return aWhere; }
void* operator new(size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
void* operator new[](size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
void operator delete(void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); }
void operator delete[](void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); }
void* operator new(size_t aSize, tp::HeapAlloc& aAlloc) { return aAlloc.allocate(aSize); }
void* operator new[](size_t aSize, tp::HeapAlloc& aAlloc) { return aAlloc.allocate(aSize); }
void operator delete(void* aPtr, tp::HeapAlloc& aAlloc) { aAlloc.deallocate(aPtr); }
void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc) { aAlloc.deallocate(aPtr); }
void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc) { return aAlloc.allocate(aSize); }
void* operator new[](size_t aSize, tp::HeapAllocGlobal& aAlloc) { return aAlloc.allocate(aSize); }
void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc) { aAlloc.deallocate(aPtr); }
void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc) { aAlloc.deallocate(aPtr); }

View file

@ -0,0 +1,113 @@
/*
*
* Implementation uses embedded one-directional linked list to track free blocks.
* The embedded part ensures that there is no memory overhead on block specifically.
* Linked list is initialized iteratively on each allocation if it has not been already.
* Allocating:
* 1) updating list entry to stored in the entry itself next free pointer
* 2) returning entry before (1).
*
* Deallocating:
* 1) assigning list entry value to the deleted block
* 2) updating list entry to that block.
*
*/
#include "ChunkAllocator.hpp"
#include "PrivateConfig.hpp"
#include "HeapAllocatorGlobal.hpp"
using namespace tp;
enum : ualni {
ALIGNED_SIZE = ENV_ALNI_SIZE_B,
WRAP_SIZE_ALN = MEM_WRAP_SIZE,
WRAP_SIZE = WRAP_SIZE_ALN * ALIGNED_SIZE,
WRAP_VAL = MEM_WRAP_FILL_VAL,
CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL,
CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL,
};
ChunkAlloc::ChunkAlloc(ualni aBlockSize, void* aMemory, ualni aMemSize) {
auto const temp = aBlockSize / ALIGNED_SIZE;
mBSize = ((aBlockSize % ALIGNED_SIZE) ? temp + 1 : temp) + WRAP_SIZE_ALN * 2;
mBuff = (ualni*) aMemory;
mNBlocks = (aMemSize / ALIGNED_SIZE) / mBSize;
mNFreeBlocks = mNBlocks;
}
ChunkAlloc::ChunkAlloc(ualni aBlockSize, ualni aNBlocks) {
auto const temp = aBlockSize / ALIGNED_SIZE;
mBSize = ((aBlockSize % ALIGNED_SIZE) ? temp + 1 : temp) + WRAP_SIZE_ALN * 2;
mNBlocks = aNBlocks;
mNFreeBlocks = mNBlocks;
mBuff = (ualni*)HeapAllocGlobal::allocate(mNBlocks * mBSize * ALIGNED_SIZE);
mOwnBuff = true;
}
void* ChunkAlloc::allocate() {
RelAssert(mNFreeBlocks && "Out Of Memory");
// 1) PreInitialize blocks
if (mNInitBlocks < mNBlocks) {
*(mBuff + mNInitBlocks * mBSize) = (ualni)(mBuff + (mNInitBlocks++) * mBSize);
}
// 2) Find free block and update next free block
auto data = mNextBlock;
mNextBlock = (ualni*)(*data);
mNFreeBlocks--;
#ifdef MEM_DEBUG
// 3) Fill Wrap and offset data
auto wrap_top = data;
data += WRAP_SIZE_ALN;
auto wrap_bottom = data + mBSize;
memsetv(wrap_top, WRAP_SIZE, WRAP_VAL);
memsetv(wrap_bottom, WRAP_SIZE, WRAP_VAL);
// 4) Clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(data, mBSize * ALIGNED_SIZE, CLEAR_ALLOC_VAL);
#endif
#endif
return data;
}
void ChunkAlloc::deallocate(void* aPtr) {
auto block = (ualni*)aPtr;
#ifdef MEM_DEBUG
// 3) Check Wrap and offset data
auto wrap_bottom = block + mBSize;
auto wrap_top = block - WRAP_SIZE_ALN;
block = wrap_top;
// 3) Check the wrap
RelAssert(memequalv(wrap_top, WRAP_SIZE, WRAP_VAL) && memequalv(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!");
// 4) Clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(aPtr, mBSize * ALIGNED_SIZE, CLEAR_DEALLOC_VAL);
#endif
#endif
(*block) = (ualni)mNextBlock;
mNextBlock = block;
mNFreeBlocks++;
}
bool ChunkAlloc::isFull() { return !mNFreeBlocks; }
bool ChunkAlloc::isEmpty() { return mNFreeBlocks == mNBlocks; }
ChunkAlloc::~ChunkAlloc() {
if (mOwnBuff) {
HeapAllocGlobal::deallocate(mBuff);
}
}

View file

@ -0,0 +1,72 @@
#include "heapallocator.hpp"
#include "PrivateConfig.hpp"
#include <stddef.h>
#include <cstdlib>
using namespace tp;
#if not defined(MEM_DEBUG)
// ----------------------- Release Implementation ---------------------------- //
void* HeapAlloc::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
void HeapAlloc::deallocate(void* aPtr) { free(aPtr); }
HeapAlloc::~HeapAlloc() {}
#else
namespace tp {
struct MemHeadLocal {
MemHeadLocal* mPrev;
MemHeadLocal* mNext;
};
};
void* HeapAlloc::allocate(ualni aBlockSize) {
auto head = (MemHeadLocal*)mAlloc.allocate(aBlockSize + sizeof(MemHeadLocal));
auto out = head + 1;
mNumAllocations++;
if (mEntry) {
head->mNext = mEntry->mNext;
head->mPrev = mEntry->mPrev;
if (mEntry->mNext) mEntry->mNext->mPrev = head;
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
}
else {
head->mNext = NULL;
head->mPrev = NULL;
}
mEntry = head;
return out;
}
void HeapAlloc::deallocate(void* aPtr) {
auto head = ((MemHeadLocal*)(aPtr)) - 1;
mNumAllocations--;
if (mEntry->mNext) mEntry->mNext->mPrev = mEntry->mPrev;
if (mEntry->mPrev) mEntry->mPrev->mNext = mEntry->mNext;
if (head == mEntry) {
if (mEntry->mNext) {
mEntry = mEntry->mNext;
}
else {
mEntry = mEntry->mNext;
}
}
mAlloc.deallocate(head);
}
HeapAlloc::~HeapAlloc() {
if (mNumAllocations) {
DBG_BREAK("Destruction of not freed Allocator");
#ifdef MEM_STACK_TRACE
#endif
}
}
#endif

View file

@ -0,0 +1,146 @@
#include "heapallocator.hpp"
#include "PrivateConfig.hpp"
#include "StackTrace.hpp"
#include <stddef.h>
#include <cstdlib>
using namespace tp;
#if not defined(MEM_DEBUG)
// ----------------------- Release Implementation ---------------------------- //
void* HeapAllocGlobal::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
void HeapAllocGlobal::deallocate(void* aPtr) { free(aPtr); }
HeapAllocGlobal::~HeapAllocGlobal() {};
#else
tp::MemHead* tp::HeapAllocGlobal::mEntry = NULL;
tp::ualni tp::HeapAllocGlobal::mNumAllocations = NULL;
// ----------------------- Debug Implementation ---------------------------- //
// |----------------|
// | MemHead |
// |----------------|
// | wrap top |
// |----------------| - Allocated Block Layout
// | data |
// |----------------|
// | wrap bottom |
// |----------------|
namespace tp {
struct MemHead {
MemHead* mPrev;
MemHead* mNext;
ualni mBlockSize;
#ifdef MEM_STACK_TRACE
CallStackSnapshots::StackShapshot mCallStack;
#endif
};
};
enum : ualni {
ALIGNED_SIZE = ENV_ALNI_SIZE_B,
WRAP_SIZE = MEM_WRAP_SIZE * ALIGNED_SIZE,
WRAP_VAL = MEM_WRAP_FILL_VAL,
HEAD_SIZE = sizeof(MemHead),
CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL,
CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL,
};
void* HeapAllocGlobal::allocate(ualni aBlockSize) {
static_assert(HEAD_SIZE % ALIGNED_SIZE == 0, "Heap Allocator Configuration Error");
if (aBlockSize % ALIGNED_SIZE) {
aBlockSize = (aBlockSize / ALIGNED_SIZE + 1) * ALIGNED_SIZE;
}
// 1) Allocate the block
auto head = (MemHead*)malloc(aBlockSize + WRAP_SIZE * 2 + HEAD_SIZE);
auto wrap_top = (int1*)(head + 1);
auto data = wrap_top + WRAP_SIZE;
auto wrap_bottom = data + aBlockSize;
if (!head) { return NULL; }
head->mBlockSize = aBlockSize;
// 2) Link with existing blocks
mNumAllocations++;
if (mEntry) {
head->mNext = mEntry->mNext;
head->mPrev = mEntry->mPrev;
if (mEntry->mNext) mEntry->mNext->mPrev = head;
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
}
else {
head->mNext = NULL;
head->mPrev = NULL;
}
mEntry = head;
// 3) Wrap fill
memsetv(wrap_top, WRAP_SIZE, WRAP_VAL);
memsetv(wrap_bottom, WRAP_SIZE, WRAP_VAL);
// 4) Trace the stack
#ifdef MEM_STACK_TRACE
head->mCallStack = gCallStackSnapshots.capture();
#endif
// 5) clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(data, aBlockSize, CLEAR_ALLOC_VAL);
#endif
return data;
}
void HeapAllocGlobal::deallocate(void* aPtr) {
// 1) Restore the pointers
auto head = ((MemHead*)(aPtr)) - 1;
auto wrap_top = (int1*)(head + 1);
auto data = wrap_top + WRAP_SIZE;
auto wrap_bottom = data + head->mBlockSize;
// 2) Unlink with blocks
mNumAllocations--;
if (mEntry->mNext) mEntry->mNext->mPrev = mEntry->mPrev;
if (mEntry->mPrev) mEntry->mPrev->mNext = mEntry->mNext;
if (head == mEntry) {
if (mEntry->mNext) {
mEntry = mEntry->mNext;
}
else {
mEntry = mEntry->mNext;
}
}
// 3) Check the wrap
RelAssert(memequalv(wrap_top, WRAP_SIZE, WRAP_VAL) && memequalv(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!");
// 4) clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(data, head->mBlockSize, CLEAR_DEALLOC_VAL);
#endif
// 5) free the block
free(aPtr);
}
HeapAllocGlobal::~HeapAllocGlobal() {
// 1) Check for not deallocated memory
if (mNumAllocations) {
DBG_BREAK("Destruction of not freed Allocator");
#ifdef MEM_STACK_TRACE
#endif
}
}
#endif

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,132 @@
/*
*
Implementation:
* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk
* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on deallocation in log time
*
* Allocations:
* 1) allocate with chunk stored in list entry
* 2) ...
*
* Deallocations:
* 1) binary-search with delete pointer to find desired chunk
* 2) ...
*
*/
#include "allocators.hpp"
#include "PrivateConfig.hpp"
void tp::PoolAlloc::Chunks::add(Chunk* aChunk) {
// ensure order
auto smaller_address = findUtil(mBuff, mBuff + mUsedLen, aChunk);
for (auto iter = mBuff + mUsedLen; iter != smaller_address; iter--) { *(iter + 1) = *iter; }
*(smaller_address + 1) = aChunk;
mUsedLen++;
// check for buff overflow
if (mUsedLen == mLen) {
auto prevBuff = mBuff;
mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen * 2);
memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen);
mLen *= 2;
HeapAllocGlobal::deallocate(prevBuff);
}
}
void tp::PoolAlloc::Chunks::remove(Chunk* aChunk) {
// ensure order
auto del_address = findUtil(mBuff, mBuff + mUsedLen, aChunk);
for (auto iter = del_address; iter != mBuff + mUsedLen; iter++) { *iter = *(iter + 1); }
mUsedLen--;
// check for buff low usage
if ((halnf)mUsedLen / mLen < 0.25f) {
auto prevBuff = mBuff;
mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2);
memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen);
mLen /= 2;
HeapAllocGlobal::deallocate(prevBuff);
}
}
tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::find(void* aPtr) {
return *findUtil(mBuff, mBuff + mUsedLen, aPtr);
}
tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::findNotFull() {
for (ualni idx = 0; idx < mUsedLen; idx++) {
if (!mBuff[idx]->isFull()) {
return mBuff[idx];
}
}
return NULL;
}
tp::PoolAlloc::Chunk** tp::PoolAlloc::Chunks::findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) {
auto range = ualni(aRight - aLeft);
if (range == 1) { return aLeft; }
auto middle = aLeft + range / 2;
return (aPtr > *middle) ? findUtil(middle, aRight, aPtr) : findUtil(aLeft, middle, aPtr);
}
tp::PoolAlloc::PoolAlloc(ualni aBlockSize, ualni aChunkSize) : mBlockSize(aBlockSize), mChunkSize(aChunkSize) {}
void* tp::PoolAlloc::allocate() {
if (!mFreeChunk || mFreeChunk->isFull()) {
auto new_free_chunk = mChunks.findNotFull();
if (!new_free_chunk) {
new_free_chunk = new ((Chunk*)HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(mBlockSize, mChunkSize);
RelAssert(new_free_chunk);
mChunks.add(new_free_chunk);
if (mFreeChunk) {
new_free_chunk->mNext = mFreeChunk->mNext;
new_free_chunk->mPrev = mFreeChunk->mPrev;
if (mFreeChunk->mNext) mFreeChunk->mNext->mPrev = new_free_chunk;
if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = new_free_chunk;
}
else {
new_free_chunk->mNext = NULL;
new_free_chunk->mPrev = NULL;
}
mFreeChunk = new_free_chunk;
}
}
return mFreeChunk->allocate();
}
void tp::PoolAlloc::deallocate(void* aPtr) {
auto chunk = mChunks.find(aPtr);
chunk->deallocate(aPtr);
if (mFreeChunk->isEmpty()) {
Chunk* new_chunk = NULL;
for (ualni idx = 0; idx < mChunks.mUsedLen; idx++) {
if (!mChunks.mBuff[idx]->isFull() && new_chunk != mFreeChunk) {
new_chunk = mChunks.mBuff[idx];
}
}
if (new_chunk) {
if (mFreeChunk->mNext) mFreeChunk->mNext->mPrev = mFreeChunk->mPrev;
if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = mFreeChunk->mNext;
mChunks.remove(mFreeChunk);
HeapAllocGlobal::deallocate(mFreeChunk);
mFreeChunk = new_chunk;
}
}
}
tp::PoolAlloc::~PoolAlloc() {
}

View file

@ -0,0 +1,186 @@
#include "StackTrace.hpp"
#include "PrivateConfig.hpp"
#include <Windows.h>
#include <DbgHelp.h>
#include <iostream>
#include <cstdint>
#pragma comment(lib, "dbghelp.lib")
using namespace tp;
tp::CallStackSnapshots tp::gCallStackSnapshots;
// ----------------------- Dict ----------------------- //
CallStackSnapshots::SnapshotsDict::SnapshotsDict() {
mTable = (StackShapshot*)malloc(sizeof(StackShapshot) * mSize);
memsetv(mTable, sizeof(StackShapshot) * mSize, 0);
}
CallStackSnapshots::StackShapshot CallStackSnapshots::SnapshotsDict::getSlot(ualni aIdx) {
return mTable[aIdx];
}
alni CallStackSnapshots::SnapshotsDict::presents(StackShapshot aPtr) {
return findSlotRead(aPtr);
}
void CallStackSnapshots::SnapshotsDict::put(StackShapshot aPtr) {
auto idx = findSlotWrite(aPtr);
mTable[idx] = aPtr;
mEntries++;
if ((halnf)mEntries / mSize > 2.f / 3.f) {
resize();
}
}
void CallStackSnapshots::SnapshotsDict::resize() {
alni nslots_old = mSize;
auto table_old = mTable;
mSize *= 2;
mTable = (StackShapshot*)malloc(sizeof(StackShapshot) * mSize);
memsetv(mTable, sizeof(StackShapshot) * mSize, 0);
mEntries = 0;
for (alni i = 0; i < nslots_old; i++) {
if (!table_old[i]) {
continue;
}
alni idx = findSlotWrite(table_old[i]);
mTable[idx] = table_old[i];
mEntries++;
}
delete[] table_old;
}
alni CallStackSnapshots::SnapshotsDict::findSlotRead(StackShapshot key) {
ualni const hased_key = hash(key);
ualni const mask = mSize - 1;
ualni const shift = (hased_key >> 5) & ~1;
alni idx = hased_key & mask;
NEXT:
if (!mTable[idx]) {
return -1;
}
if (compare(mTable[idx], key)) {
return idx;
}
SKIP:
idx = ((5 * idx) + 1 + shift) & mask;
goto NEXT;
}
ualni CallStackSnapshots::SnapshotsDict::findSlotWrite(StackShapshot key) {
ualni const hased_key = hash(key);
ualni const mask = mSize - 1;
ualni const shift = (hased_key >> 5) & ~1;
ualni idx = hased_key & mask;
NEXT:
if (!mTable[idx]) {
return idx;
}
idx = ((5 * idx) + 1 + shift) & mask;
goto NEXT;
}
CallStackSnapshots::StackShapshot CallStackSnapshots::SnapshotsDict::newStackSnapshot(ualni aDepth) {
auto const size = sizeof(FramePointer) * (aDepth + 1);
auto out = (StackShapshot*)malloc(size);
memsetv(out, size, 0);
}
ualni CallStackSnapshots::SnapshotsDict::hash(StackShapshot snapshot) {
ualni out = 0;
for (FramePointer* iter = snapshot; iter; iter++) { out += *iter; }
return out;
}
bool CallStackSnapshots::SnapshotsDict::compare(StackShapshot left, StackShapshot right) {
FramePointer* iter_left = left;
FramePointer* iter_right = right;
do {
if (*iter_left != *iter_right) {
return false;
}
iter_left++;
iter_right++;
} while (iter_left && iter_right);
if (*iter_left != *iter_right) {
return false;
}
return true;
}
CallStackSnapshots::SnapshotsDict::~SnapshotsDict() {
for (ualni idx = 0; idx < mSize; idx++) {
if (mTable[idx]) {
free(mTable[idx]);
}
}
free(mTable);
}
// ----------------------- CallStackSnapshots ----------------------- //
CallStackSnapshots::StackShapshot CallStackSnapshots::getStack(ualni& len) {
enum { MAX_DEPTH = MEM_STACK_TRACE_MAX_DEPTH };
static FramePointer pointers[MAX_DEPTH];
len = 0;
CONTEXT context;
RtlCaptureContext(&context);
STACKFRAME64 stackFrame;
ZeroMemory(&stackFrame, sizeof(STACKFRAME64));
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Mode = AddrModeFlat;
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrFrame.Offset = context.Rbp;
stackFrame.AddrStack.Offset = context.Rsp;
HANDLE processHandle = GetCurrentProcess();
HANDLE threadHandle = GetCurrentThread();
while (
len < MAX_DEPTH &&
StackWalk64(IMAGE_FILE_MACHINE_AMD64, processHandle, threadHandle, &stackFrame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)
)
{
pointers[len] = stackFrame.AddrFrame.Offset;
len++;
}
return pointers;
}
CallStackSnapshots::StackShapshot CallStackSnapshots::capture() {
ualni len;
auto stack = getStack(len);
auto idx = mSnapshots.presents(stack);
if (idx == -1) {
auto new_snapshot = mSnapshots.newStackSnapshot(len + 1);
memcp(new_snapshot, stack, len * sizeof(FramePointer));
new_snapshot[len] = 0;
mSnapshots.put(new_snapshot);
return new_snapshot;
}
return mSnapshots.getSlot(idx);
}
void CallStackSnapshots::saveToFile(StackShapshot* snapshots, const char* filepath) {
}
CallStackSnapshots::StackShapshot* CallStackSnapshots::loadFromFile(const char* filepath) {
}

View file

@ -0,0 +1,291 @@
#include <math.h>
#include <stdio.h>
#include "allocators.h"
struct test_struct {
tp::alni val = 0;
test_struct() { val = 1; }
~test_struct() { val = -1; }
bool operator==(const test_struct& in) { return in.val == val; }
};
template <tp::alni size>
struct allocator_test {
test_struct data[size];
bool is_allocated[size];
tp::alni n_loaded = 0;
test_struct* allocations[size];
tp::AbstractAllocator* alloc;
tp::AbstractAllocator* parent_alloc;
const char* allocator_name = NULL;
tp::alni rand_idx(bool state) {
RAND:
tp::alni idx = (tp::alni)(tp::randf() * (size + 1));
CLAMP(idx, 0, size - 1);
if (state == is_allocated[idx]) {
goto RAND;
}
return idx;
}
allocator_test(tp::AbstractAllocator* palloc, const char* pallocator_name,
tp::AbstractAllocator* p_parent_alloc) {
allocator_name = pallocator_name;
parent_alloc = p_parent_alloc;
alloc = palloc;
for (tp::alni i = 0; i < size; i++) {
RAND:
tp::alni val = tp::alni(tp::randf() * (size + 100.f));
for (tp::alni check_idx = 0; check_idx < size; check_idx++) {
if (data[check_idx].val == val) {
goto RAND;
}
}
data[i].val = val;
is_allocated[i] = false;
allocations[i] = 0;
}
}
void verify_integrity() {
// verify data integrity
for (tp::alni i = 0; i < size; i++) {
if (is_allocated[i]) {
assert(*allocations[i] == data[i] && "data is currupted\n");
}
}
if (alloc->isWrapSupport()) assert(!alloc->isWrapCorrupted());
if (parent_alloc && parent_alloc->isWrapSupport())
assert(!parent_alloc->isWrapCorrupted());
verify_sizes();
}
void verify_sizes() {
return;
#ifdef MEM_TRACE
assert(alloc->sizeInuse() == n_loaded * sizeof(test_struct) &&
"invalid inuse size\n");
assert(alloc->sizeReserved() >= n_loaded * (tp::alni)sizeof(test_struct) &&
"invalid reserved size\n");
#endif
}
void load_item(tp::alni idx) {
if (!is_allocated[idx]) {
allocations[idx] = new (alloc) test_struct();
assert(allocations[idx] && "allocator returned NULL");
allocations[idx]->val = data[idx].val;
is_allocated[idx] = true;
n_loaded++;
verify_integrity();
}
}
void unload_item(tp::alni idx) {
if (is_allocated[idx]) {
verify_integrity();
delete allocations[idx];
is_allocated[idx] = false;
n_loaded--;
verify_integrity();
}
}
void change_states(tp::Range<tp::alni> rg, bool state, bool reversed = false,
bool random = false) {
for (auto i : rg) {
tp::alni idx = i;
if (random) {
idx = rand_idx(state);
}
else if (reversed) {
idx = size - i - 1;
}
(state) ? load_item(idx) : unload_item(idx);
}
}
// full down-up load then up-down unload
void test1() {
change_states({ 0, size }, 1);
change_states({ 0, size }, 0, true);
}
// full down-up load then down-up unload
void test2() {
change_states({ 0, size }, 1);
change_states({ 0, size }, 0);
}
// full random load then random unload
void test3() {
change_states({ 0, size }, 1, 0, 1);
change_states({ 0, size }, 0, 0, 1);
}
// multipul tests 1-3
void test4() {
test1();
test1();
test2();
test2();
test3();
test3();
}
static tp::alnf sineupf(tp::alnf asize, tp::alnf x, bool reverse) {
tp::alnf end = 4 * PI;
tp::alnf a = (2 / 7.f) * asize;
tp::alnf b = end / asize;
tp::alni c = ((-1 * reverse) + (1 * !reverse));
tp::alnf c1 = (x - (end * reverse)) / b;
tp::alnf c2 = (a * sin(x - (end * reverse)));
tp::alnf out = c1 + c2;
return c * out;
}
// sin load & sin unload with ~1/2 drop factor
void test5() {
tp::alnf end = 4 * PI;
tp::alnf step = end / 4.f;
for (char i = 0; i < 2; i++) {
for (tp::alnf x = 0; x <= end; x += step) {
tp::alni target_alloc_count = (tp::alni)ceil(sineupf(size, x, i));
CLAMP(target_alloc_count, 0, size);
while (n_loaded > target_alloc_count) {
unload_item(rand_idx(0));
}
while (n_loaded < target_alloc_count) {
load_item(rand_idx(1));
}
}
}
}
#ifdef MEM_WRAP
void check_wrap(tp::alni offset, bool after) {
CLAMP(offset, 1, WRAP_LEN);
test_struct* ts = allocations[rand_idx(0)];
tp::alni shift = (sizeof(test_struct) * after) + (offset - 1) * after -
offset * (!after);
tp::uint1* address = (((tp::uint1*)ts) + shift);
tp::uint1 val = *address;
*address = 5;
assert(alloc->isWrapCorrupted());
*address = val;
}
#endif
// mem guards test
void test6() {
change_states({ 0, size }, 1);
#ifdef MEM_WRAP
for (tp::alni after = 0; after < 2; after++) {
for (tp::alni offset = 1; offset <= WRAP_LEN; offset++) {
check_wrap(offset, after);
}
}
#endif
change_states({ 0, size }, 0);
}
void run_tests() {
try {
test1();
test2();
test3();
test4();
test5();
if (alloc->isWrapSupport()) {
test6();
}
printf("%s - passed\n", allocator_name);
if (!alloc->isWrapSupport()) {
printf(" WARNING: %s has no wrap support!! \n", allocator_name);
}
}
catch (...) {
printf("%s - failed\n", allocator_name);
}
}
};
void heap_alloc_test() {
tp::alloc_init(); {
try {
allocator_test<150> hatest(tp::heapalloc, "heap allocator", NULL);
hatest.run_tests();
}
catch (...) {
printf("heap alloc failed\n");
}
} tp::alloc_uninit();
}
void chunk_alloc_test() {
tp::alloc_init(); {
try {
tp::ChunkAlloc calloc(sizeof(test_struct), 50);
allocator_test<50> ca_test(&calloc, "chunk allocator", tp::heapalloc);
ca_test.run_tests();
}
catch (...) {
printf("chunk alloc failed\n");
}
} tp::alloc_uninit();
}
void pool_alloc_test() {
tp::alloc_init(); {
try {
tp::PoolAlloc palloc(sizeof(test_struct), 50);
allocator_test<150> pa_test(&palloc, "pool allocator", tp::heapalloc);
pa_test.run_tests();
}
catch (...) {
printf("chunk alloc failed\n");
}
} tp::alloc_uninit();
}
void allocators_test() {
printf("running tests on alocators:\n");
heap_alloc_test();
chunk_alloc_test();
pool_alloc_test();
}
CROSSPLATFORM_MAIN;
int main(int argc, char* argv[]) {
tp::print_env_info();
allocators_test();
}

View file

@ -0,0 +1,3 @@
#pragma once
void allocators_test();

View file

@ -0,0 +1,18 @@
#include "allocators.hpp"
int main() {
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocator, NULL };
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
if (!TestModule.initialize()) {
return 1;
}
tp::HeapAllocGlobal alloc;
int* val = new(alloc) int();
delete(alloc, val);
TestModule.deinitialize();
}

View file

@ -0,0 +1,172 @@
#include "allocators.h"
#include <stdio.h>
#include "timer.h"
struct AllocSpeedTets {
const char* test_desc = NULL;
virtual void exec() {};
};
struct Test1 : AllocSpeedTets {
Test1() {
test_desc = "allocate lots of memory of same sizes, then free it all";
}
const int len = 1000;
int size = 100;
void* buff[1000];
void exec_util(tp::AbstractAllocator* alloc) {
for (size_t i = 0; i < len; i++) {
buff[i] = alloc->Alloc(size);
}
for (size_t i = 0; i < len; i++) {
alloc->Free(buff[i]);
}
}
void exec() {
tp::time_ms start, end;
tp::HeapAlloc malloc;
tp::PoolAlloc pool(0, 0);
tp::ChunkAlloc chunck(0, 0);
tp::PickAlloc pick;
start = tp::get_time();
end = tp::get_time();
printf("Malloc : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pool : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Chunk : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pick : %lli ms \n", end - start);
}
};
struct Test2 : AllocSpeedTets {
Test2() {
test_desc = "allocate lots of memory of different sizes, then free it all";
}
void exec() {
tp::time_ms start, end;
tp::HeapAlloc malloc;
tp::PoolAlloc pool(0, 0);
tp::ChunkAlloc chunck(0, 0);
tp::PickAlloc pick;
start = tp::get_time();
end = tp::get_time();
printf("Malloc : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pool : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Chunk : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pick : %lli ms \n", end - start);
}
};
struct Test3 : AllocSpeedTets {
Test3() {
test_desc = "allocate only a few blocks of memory, free them, and repeat this loop several times \
\n (repeat for same - sized blocks and different - sized blocks)\n";
}
void exec() {
tp::time_ms start, end;
tp::HeapAlloc malloc;
tp::PoolAlloc pool(0, 0);
tp::ChunkAlloc chunck(0, 0);
tp::PickAlloc pick;
start = tp::get_time();
end = tp::get_time();
printf("Malloc : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pool : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Chunk : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pick : %lli ms \n", end - start);
}
};
struct Test4 : AllocSpeedTets {
Test4() {
test_desc = "allocate lots of memory of different sizes, free half of it(e.g.the even allocations), then allocateand free memory in a loop\n";
}
void exec() {
tp::time_ms start, end;
tp::HeapAlloc malloc;
tp::PoolAlloc pool(0, 0);
tp::ChunkAlloc chunck(0, 0);
tp::PickAlloc pick;
start = tp::get_time();
end = tp::get_time();
printf("Malloc : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pool : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Chunk : %lli ms \n", end - start);
start = tp::get_time();
end = tp::get_time();
printf("Pick : %lli ms \n", end - start);
}
};
void banckmark() {
tp::alloc_init();
const int tests_len = 1;
AllocSpeedTets* tests[] = {
new Test1(),
new Test2(),
new Test3(),
new Test4(),
};
for (int i = 0; i < tests_len; i++) {
printf("Test %i: \n %s\n", i, tests[i]->test_desc);
tests[i]->exec();
delete tests[i];
}
tp::alloc_uninit();
}

View file

@ -0,0 +1,563 @@
#include "benchmarker.h"
#include "implot.h"
#include "ImGuiUtils.h"
tp::alni hash(const tp::string& val) {
return tp::hash(val.cstr());
}
config glb_cfg = {
.live_update = false,
.heap = true,
.pool = true,
.chunk = true,
.current_sizing_pattern = 0,
.current_loading_pattern = 0,
.current_ordering_pattern = 0,
.update = 0,
.time_per_instruction = true,
.mem_per_instruction = true,
.avreging = 1,
.chunk_bsize = 100,
.chunk_blen = 100,
.pool_bsize = 100,
.pool_blen = 100,
};
benchmarker::benchmarker() /*: ImGui::CompleteApp()*/ {
i_count = 0;
pattern_out = NULL;
out.reserve(3);
patterns.put("constant", new const_pattern());
patterns.put("linear", new linear_pattern());
patterns.put("random", new random_pattern());
}
void benchmarker::output_draw() {
if (ImGui::Begin("Overview")) {
if (is_output) {
if (ImGui::TreeNode("total time")) {
for (auto& i : tp::Range(0, 3)) {
if (out[i]) {
if (out[i]->total_time < 1000) {
ImGui::Text("%s : %f 10e-9", out[i]->alloc_type, out[i]->total_time);
} else if (out[i]->total_time < 1000000) {
ImGui::Text("%s : %f 10e-6", out[i]->alloc_type, out[i]->total_time / 1000.f);
} else if (out[i]->total_time < 1000000000) {
ImGui::Text("%s : %f 10e-3", out[i]->alloc_type, out[i]->total_time / 1000000.f);
} else {
ImGui::Text("%s : %f ", out[i]->alloc_type, out[i]->total_time / 1000000000.f);
}
}
}
ImGui::TreePop();
}
if (ImGui::TreeNode("failures")) {
if ((out[0] && out[0]->failed) || (out[1] && out[1]->failed) || (out[2] && out[2]->failed)) {
for (auto& i : tp::Range(0, 3)) {
if (out[i] && out[i]->failed)
ImGui::Text("%s failed", out[i]->alloc_type);
}
} else {
ImGui::Text("all succeeded");
}
ImGui::TreePop();
}
}
}
ImGui::End();
if (cfg.time_per_instruction) {
if (ImGui::Begin("Graphs")) {
if (is_output) {
if (ImPlot::BeginPlot("Time(ns) vs Alloc / Free inst")) {
if (cfg.heap) ImPlot::PlotLine("halloc", x_axis, out[0]->time.buff(), (int) i_count);
if (cfg.pool) ImPlot::PlotLine("pool", x_axis, out[1]->time.buff(), (int) i_count);
if (cfg.chunk) ImPlot::PlotLine("chunk", x_axis, out[2]->time.buff(), (int) i_count);
ImPlot::EndPlot();
}
}
}
ImGui::End();
}
if (cfg.mem_per_instruction) {
if (ImGui::Begin("Graphs")) {
if (is_output) {
if (ImPlot::BeginPlot("Memory (bytes) at Instruction state")) {
if (cfg.heap) ImPlot::PlotLine("halloc", x_axis, out[0]->mem.buff(), (int) i_count);
if (cfg.pool) ImPlot::PlotLine("pool", x_axis, out[1]->mem.buff(), (int) i_count);
if (cfg.chunk) ImPlot::PlotLine("chunk", x_axis, out[2]->mem.buff(), (int) i_count);
ImPlot::EndPlot();
}
}
}
ImGui::End();
}
if (ImGui::Begin("Pattern")) {
if (is_output) {
if (ImPlot::BeginPlot("Alloc/Free instruction info vs instruction idx")) {
if (cfg.current_sizing_pattern) ImPlot::PlotLine("allocated item size", x_axis, pattern_out->alloc_size.buff(), (int) i_count);
if (cfg.current_ordering_pattern) ImPlot::PlotLine("used data item idx", x_axis, pattern_out->data_idx.buff(), (int) i_count);
if (cfg.current_loading_pattern) ImPlot::PlotLine("total items allocated", x_axis, pattern_out->items_loaded.buff(), (int) i_count);
ImPlot::EndPlot();
}
}
}
ImGui::End();
}
benchmarker::~benchmarker() {
for (auto iter : patterns) {
delete iter->val;
}
clear_out();
}
void benchmarker::select_pattern() {
if (patterns.size()) {
const char** pattern_names = new const char* [patterns.size()];
const char* current_opattern_name = NULL;
const char* current_lpattern_name = NULL;
const char* current_spattern_name = NULL;
for (auto pattern_name : patterns) {
pattern_names[pattern_name.entry_idx] = pattern_name->key.cstr();
if (pattern_name->val == glb_cfg.current_ordering_pattern)
current_opattern_name = pattern_name->key.cstr();
if (pattern_name->val == glb_cfg.current_sizing_pattern)
current_spattern_name = pattern_name->key.cstr();
if (pattern_name->val == glb_cfg.current_loading_pattern)
current_lpattern_name = pattern_name->key.cstr();
}
if (ImGui::BeginCombo("Ordering", current_opattern_name)) {
for (int n = 0; n < patterns.size(); n++) {
bool is_selected = (current_opattern_name == pattern_names[n]);
if (ImGui::Selectable(pattern_names[n], is_selected)) {
current_opattern_name = pattern_names[n];
glb_cfg.current_ordering_pattern = patterns.get(pattern_names[n]);
}
}
ImGui::EndCombo();
}
if (ImGui::BeginCombo("Loading", current_lpattern_name)) {
for (int n = 0; n < patterns.size(); n++) {
bool is_selected = (current_lpattern_name == pattern_names[n]);
if (ImGui::Selectable(pattern_names[n], is_selected)) {
current_lpattern_name = pattern_names[n];
glb_cfg.current_loading_pattern = patterns.get(pattern_names[n]);
}
}
ImGui::EndCombo();
}
if (ImGui::BeginCombo("Sizing", current_spattern_name)) {
for (int n = 0; n < patterns.size(); n++) {
bool is_selected = (current_spattern_name == pattern_names[n]);
if (ImGui::Selectable(pattern_names[n], is_selected)) {
current_spattern_name = pattern_names[n];
glb_cfg.current_sizing_pattern = patterns.get(pattern_names[n]);
}
}
ImGui::EndCombo();
}
delete pattern_names;
}
}
void benchmarker::pattern_combo(const char*& current) {
if (patterns.size()) {
const char** pattern_names = new const char* [patterns.size()];
for (auto pattern_name : patterns) {
pattern_names[pattern_name.entry_idx] = pattern_name->key.cstr();
}
if (ImGui::BeginCombo("Patterns", current)) {
for (int n = 0; n < patterns.size(); n++) {
bool is_selected = (current == pattern_names[n]);
if (ImGui::Selectable(pattern_names[n], is_selected)) {
current = pattern_names[n];
}
}
ImGui::EndCombo();
}
delete pattern_names;
}
}
void benchmarker::pattern_generator() {
static tp::alni selected_idx = -1;
static pattern* child_pattern_active = NULL;
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.1f);
pattern* pattern_edit = NULL;
if (ImGui::Begin("Pattern Generator")) {
if (ImGui::SubMenuBegin("Selector", 1)) {
const char* prev_pattern = pattern_generator_active;
pattern_combo(pattern_generator_active);
child_pattern_active = (prev_pattern == pattern_generator_active) ? child_pattern_active : NULL;
if (pattern_generator_active) {
tp::alni idx = patterns.presents(pattern_generator_active);
if (!MAP_VALID_IDX(idx)) {
pattern_edit = 0;
pattern_generator_active = 0;
} else {
pattern_edit = patterns[idx];
}
}
//ImGui::Separator();
static char create_name[100] = {"new pattern name"};
ImGui::InputText("pattern name", create_name, 100);
if (ImGui::Button("Create")) {
if (!patterns.presents(create_name)) {
pattern* new_pt = new pattern();
new_pt->build_in = false;
new_pt->pattern_name = create_name;
tp::string id = create_name;
id.capture();
patterns.put(id, new_pt);
} else {
ImGui::Notify("Such Pattern Already Exists", 3);
}
}
if (pattern_edit) {
ImGui::SameLine();
if (ImGui::Button("Delete")) {
if (pattern_edit->build_in) {
ImGui::Notify("Cant Remove Built-in Patterns", 3);
} else {
patterns.remove(pattern_generator_active);
delete pattern_edit;
pattern_edit = NULL;
pattern_generator_active = NULL;
}
}
ImGui::SameLine();
if (ImGui::Button("Rename")) {
if (pattern_edit->build_in) {
ImGui::Notify("Cant Rename Built-in Patterns", 3);
} else {
patterns.remove(pattern_generator_active);
patterns.put(create_name, pattern_edit);
}
}
}
ImGui::SubMenuEnd(1);
}
if (pattern_generator_active) {
tp::alni idx = patterns.presents(pattern_generator_active);
if (!MAP_VALID_IDX(idx)) {
pattern_edit = 0;
pattern_generator_active = 0;
} else {
pattern_edit = patterns[idx];
}
}
if (!pattern_edit) {
ImGui::Text("Select pattern to edit or create one");
ImGui::End();
return;
}
if (ImGui::SubMenuBegin("Preview", 1)) {
static float preview_res_scale = 0.05f;
int resolution = (int) (1000 * preview_res_scale);
CLAMP(resolution, 3, 10000);
float* x_axis = new float[resolution];
float* y_axis = new float[resolution];
float x = 0;
float step = 1.f / (resolution - 1);
for (tp::alni idx = 0; idx < resolution; idx++) {
x_axis[idx] = x;
y_axis[idx] = (tp::flt4) pattern_edit->get_y(&patterns, x);
x += step;
}
if (ImPlot::BeginPlot("Pattern")) {
ImPlot::PlotLine("toggle", x_axis, y_axis, resolution);
ImPlot::EndPlot();
}
delete x_axis;
delete y_axis;
ImGui::SliderFloat("graph resolution", &preview_res_scale, 0.f, 1.f);
ImGui::SubMenuEnd(1);
}
if (ImGui::SubMenuBegin("Compositor", 1)) {
if (!pattern_edit->build_in) {
if (ImGui::SubMenuBegin("Child Patterns", 2)) {
ImGui::BeginListBox("");
for (tp::alni idx = 0; idx < pattern_edit->regions.length(); idx++) {
ImGui::PushID((int) idx);
if (ImGui::Button(pattern_edit->regions[idx].name.cstr())) {
child_pattern_active = patterns.get(pattern_edit->regions[idx].name);
selected_idx = idx;
}
if (selected_idx == idx) {
ImGui::SameLine(); ImGui::Text(" - Active");
}
ImGui::PopID();
}
ImGui::EndListBox();
if (selected_idx >= pattern_edit->regions.length()) {
selected_idx = -1;
}
static const char* append_pattern = NULL;
bool add = ImGui::Button(" + ");
ImGui::SameLine();
pattern_combo(append_pattern);
if (add) {
if (append_pattern) {
pattern_edit->regions.pushBack(child_pattern(append_pattern));
} else {
ImGui::Notify("Select a Pattern to Append");
}
}
if (child_pattern_active && selected_idx != -1) {
if (ImGui::Button(" Up ")) {
if (selected_idx > 0) {
tp::string tmp = pattern_edit->regions[selected_idx].name;
pattern_edit->regions[selected_idx] = pattern_edit->regions[selected_idx - 1];
pattern_edit->regions[selected_idx - 1] = tmp;
selected_idx--;
}
}
ImGui::SameLine();
if (ImGui::Button("Down")) {
if (selected_idx < pattern_edit->regions.length() - 1) {
tp::string tmp = pattern_edit->regions[selected_idx].name;
pattern_edit->regions[selected_idx] = pattern_edit->regions[selected_idx + 1];
pattern_edit->regions[selected_idx + 1] = tmp;
selected_idx++;
}
}
ImGui::SameLine();
if (ImGui::Button("Remove")) {
pattern_edit->regions.remove(selected_idx);
selected_idx = pattern_edit->regions.length() - 1;
}
} else {
ImGui::Text("Select Child Pattern");
}
ImGui::SubMenuEnd(2);
}
if (child_pattern_active && selected_idx != -1) {
if (ImGui::SubMenuBegin("child Pattern Properties", 2)) {
ImGui::SliderFloat("Point", &pattern_edit->regions[selected_idx].point, 0.f, 1.f);
ImGui::SliderFloat("Lower lim", &pattern_edit->regions[selected_idx].lowerlim, 0.f, 1.f);
ImGui::SliderFloat("Upper lim", &pattern_edit->regions[selected_idx].uppernlim, 0.f, 1.f);
if (child_pattern_active->build_in) {
}
ImGui::SubMenuEnd(2);
}
}
} else {
ImGui::Text("Can't Edit Built-In Patterns");
}
ImGui::SubMenuEnd(1);
}
ImGui::Separator();
}
ImGui::End();
}
void benchmarker::MainDrawTick() {
analize(glb_cfg);
ImGui::BeginGroup();
if (ImGui::WindowEditor("Properties")) {
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f);
{
if (ImGui::Button("Run")) {
glb_cfg.update = true;
}
bool prev_val = glb_cfg.live_update;
ImGui::SameLine(); ImGui::Checkbox("Live Update", &glb_cfg.live_update);
if (prev_val != glb_cfg.live_update) {
glb_cfg.update = true;
}
}
if (ImGui::SubMenuBegin("General", 1)) {
ImGui::InputInt("Averaging", &glb_cfg.avreging, 1, 100); ImGui::ToolTip("Number of tests to be averaged");
ImGui::Checkbox("Collect time per instruction", &glb_cfg.time_per_instruction);
ImGui::Checkbox("Collect mem per instruction", &glb_cfg.mem_per_instruction);
ImGui::SubMenuEnd(1);
}
if (ImGui::SubMenuBegin("Testing Pattern", 1)) {
select_pattern();
ImGui::InputInt("size", &glb_cfg.pt_scale.size, 1, 100); ImGui::ToolTip("Y Scale of sizing pattern");
ImGui::InputInt("items", &glb_cfg.pt_scale.items, 1, 100); ImGui::ToolTip("Y Scale of ordering and loading patterns");
ImGui::InputInt("iterations", &glb_cfg.pt_scale.iterations, 1, 100); ImGui::ToolTip("X Scale of all patterns");
ImGui::SubMenuEnd(1);
}
if (ImGui::SubMenuBegin("Allocators", 1)) {
ImGui::Checkbox("heap", &glb_cfg.heap); ImGui::SameLine(); ImGui::Checkbox("pool", &glb_cfg.pool); ImGui::SameLine(); ImGui::Checkbox("chunk", &glb_cfg.chunk);
if (glb_cfg.chunk && ImGui::SubMenuBegin("Chunk", 2)) {
ImGui::InputInt("size", &glb_cfg.chunk_bsize, 1, 100); ImGui::ToolTip("Size of a slot in the chunk buffer");
ImGui::InputInt("length", &glb_cfg.chunk_blen, 1, 100); ImGui::ToolTip("Number of slots in the chunk buffer");
ImGui::SubMenuEnd(2);
}
if (glb_cfg.pool && ImGui::SubMenuBegin("Pool", 2)) {
ImGui::ToolTip("Generalized use of chunk allocator");
ImGui::InputInt("size", &glb_cfg.pool_bsize, 1, 100);
ImGui::InputInt("length", &glb_cfg.pool_blen, 1, 100);
ImGui::SubMenuEnd(2);
}
ImGui::SubMenuEnd(1);
}
//ImGui::PopItemWidth();
}
ImGui::End();
output_draw();
pattern_generator();
ImGui::EndGroup();
}
void benchmarker::analize(config pcfg) {
if (!((pcfg.update) || (!(this->cfg == pcfg) && cfg.live_update))) {
return;
}
this->cfg = pcfg;
clear_out();
if (!pattern_analizer.init(&patterns, cfg.current_loading_pattern, cfg.current_ordering_pattern, cfg.current_sizing_pattern, &cfg.pt_scale)) {
if (pcfg.update) ImGui::Notify("invalid pattern configuration", 3);
glb_cfg.update = false;
cfg.update = false;
is_output = false;
return;
}
reserve_out(&pattern_analizer);
for (tp::alni iter = 0; iter < pcfg.avreging; iter++) {
init_allocators(pcfg);
if (cfg.heap) collect(&pattern_analizer, halloc, out[0]);
if (cfg.pool) collect(&pattern_analizer, palloc, out[1]);
if (cfg.chunk) collect(&pattern_analizer, calloc, out[2]);
dest_allocators();
}
for (auto& i : tp::Range(0, 3)) {
if (out[i]) out[i]->scale_all(1.f / pcfg.avreging);
}
i_count = pattern_analizer.max_iterations();
if (x_axis) {
delete x_axis;
x_axis = NULL;
}
x_axis = new tp::alnf[i_count];
for (tp::alni iter = 0; iter < i_count; iter++) {
x_axis[iter] = (tp::alnf) iter;
}
glb_cfg.update = false;
cfg.update = false;
is_output = true;
}
void benchmarker::init_allocators(config& pcfg) {
if (cfg.heap) halloc = new tp::HeapAlloc();
if (cfg.pool) palloc = new tp::PoolAlloc(pcfg.pool_bsize, pcfg.pool_blen);
if (cfg.chunk) calloc = new tp::ChunkAlloc(pcfg.chunk_bsize, pcfg.chunk_blen);
}
void benchmarker::dest_allocators() {
try {
if (cfg.heap) delete halloc;
if (cfg.pool) delete palloc;
if (cfg.chunk) delete calloc;
} catch (...) {
}
halloc = NULL;
palloc = NULL;
calloc = NULL;
}
void benchmarker::clear_out() {
for (auto i : tp::Range(0, 3)) {
if (out[i]) {
delete out[i];
out[i] = NULL;
}
}
if (pattern_out) delete pattern_out;
pattern_out = NULL;
}
void benchmarker::reserve_out(test_pattern* pattern) {
pattern_out = new pattern_histogram(pattern);
if (cfg.heap) out[0] = new allocator_histogram(pattern, "heap", cfg.time_per_instruction, cfg.mem_per_instruction);
if (cfg.pool) out[1] = new allocator_histogram(pattern, "pool", cfg.time_per_instruction, cfg.mem_per_instruction);
if (cfg.chunk) out[2] = new allocator_histogram(pattern, "chunk", cfg.time_per_instruction, cfg.mem_per_instruction);
}

View file

@ -0,0 +1,100 @@
#pragma once
#include "patterns.h"
#include "allocators.h"
#include "array.h"
#include "gl.h"
#include "glcommon.h"
#include "window.h"
enum class load_type {
LINEAR,
SINE,
STEPS,
RANDOM
};
enum class order_type {
LINEAR,
LINEAR_REVERSED,
RANDOM,
};
struct config {
// general
bool live_update = false;
bool heap = true;
bool pool = true;
bool chunk = true;
// pattern
pattern* current_sizing_pattern = 0;
pattern* current_loading_pattern = 0;
pattern* current_ordering_pattern = 0;
pattern_scale pt_scale;
tp::alni update = 0;
bool time_per_instruction = 0;
bool mem_per_instruction = 0;
int avreging;
// allocators
int chunk_bsize = 0;
int chunk_blen = 0;
int pool_bsize = 0;
int pool_blen = 0;
bool operator==(const config& in) {
return tp::memequal(this, (config*)(&in), sizeof(config));
}
};
struct benchmarker {
config cfg;
bool is_output = false;
// allocators
tp::HeapAlloc* halloc = NULL;
tp::PoolAlloc* palloc = NULL;
tp::ChunkAlloc* calloc = NULL;
tp::Array<allocator_histogram*> out;
pattern_histogram* pattern_out;
tp::alni i_count;
tp::alnf* x_axis = NULL;
tp::HashMap<pattern*, tp::string> patterns;
pattern_reader pattern_analizer;
const char* pattern_generator_active = NULL;
benchmarker();
~benchmarker();
test_pattern* get_pattern(config& cfg);
void pattern_combo(const char*&);
void analize(config cfg);
void select_pattern();
void output_draw();
void MainDrawTick();
void pattern_generator();
void init_allocators(config& cfg);
void dest_allocators();
void clear_out();
void reserve_out(test_pattern* pattern);
};

View file

@ -0,0 +1,119 @@
#include "collector.h"
#include <chrono>
allocator_histogram::allocator_histogram(test_pattern* pt, const char* alloc_type, bool p_time_per_inst, bool p_mem_per_inst) {
this->alloc_type = alloc_type;
time_per_inst = p_time_per_inst;
mem_per_inst = p_mem_per_inst;
if (time_per_inst) {
time.reserve(pt->max_iterations());
}
if (mem_per_inst) {
mem.reserve(pt->max_iterations());
}
data.reserve(pt->data_count());
total_time = 0;
failed = false;
}
void allocator_histogram::scale_all(tp::alnf fac) {
for (auto& iter : time) {
iter.data() = fac * iter.data();
}
for (auto& iter : mem) {
iter.data() = fac * iter.data();
}
total_time = fac * total_time;
}
allocator_histogram::~allocator_histogram() {
}
void allocator_histogram::mark_resourses_usage(tp::alni idx, tp::alnf p_time, tp::alni p_mem, bool add) {
if (mem.length() > idx)
add ? mem[idx] += (tp::alnf)p_mem : mem[idx] = (tp::alnf)p_mem;
if (time.length() > idx)
add ? time[idx] += p_time : time[idx] = p_time;
}
bool execute_instruction(tp::AbstractAllocator* alloc, bool load, tp::alni size, tp::uint1*& data) {
bool failed = false;
try {
if (load) {
if (!data) {
data = (tp::uint1*)alloc->Alloc(size);
if (!data) {
failed = true;
}
}
}
else {
if (data) {
alloc->Free(data);
data = NULL;
}
}
}
catch (...) {
failed = true;
}
return !failed;
}
void collect(test_pattern* pattern, tp::AbstractAllocator* alloc, allocator_histogram* histogram) {
tp::alni iter_idx = 0;
tp::alni nimtems_loaded = 0;
auto total_st = std::chrono::high_resolution_clock::now();
for (iter_idx = 0; iter_idx < pattern->max_iterations(); iter_idx++) {
tp::alni target_nimtems_loaded = pattern->pick_alloc_count(iter_idx);
tp::alni data_idx = pattern->pick_idx(iter_idx);
tp::alni load_size = pattern->pick_size(iter_idx);
auto iter_st = std::chrono::high_resolution_clock::now();
while (nimtems_loaded != target_nimtems_loaded) {
bool load = nimtems_loaded < target_nimtems_loaded;
if (!execute_instruction(alloc, load, load_size, histogram->data[data_idx])) {
histogram->mark_resourses_usage(iter_idx, 0, 0, 0);
histogram->failed = true;
break;
}
nimtems_loaded += (tp::alni)load + (-1 * (tp::alni)(!load));
}
auto iter_nd = std::chrono::high_resolution_clock::now();
tp::alni dur = std::chrono::duration_cast<std::chrono::nanoseconds>(iter_nd - iter_st).count();
histogram->mark_resourses_usage(iter_idx, tp::alnf(dur), alloc->sizeReserved(), 1);
}
// clear all out
for (iter_idx = 0; iter_idx < pattern->max_iterations(); iter_idx++) {
if (histogram->data[iter_idx]) {
execute_instruction(alloc, false, 0, histogram->data[iter_idx]);
}
}
auto total_nd = std::chrono::high_resolution_clock::now();
tp::alni dur = std::chrono::duration_cast<std::chrono::nanoseconds>(total_nd - total_st).count();
histogram->total_time += dur;
}
pattern_histogram::pattern_histogram(test_pattern* pt) {
alloc_size.reserve(pt->max_iterations());
data_idx.reserve(pt->max_iterations());
items_loaded.reserve(pt->max_iterations());
for (auto& i : tp::Range(0, pt->max_iterations())) {
alloc_size[i] = (tp::alnf) pt->pick_size(i);
data_idx[i] = (tp::alnf)pt->pick_idx(i);
items_loaded[i] = (tp::alnf)pt->pick_alloc_count(i);
}
}

View file

@ -0,0 +1,43 @@
#pragma once
#include "array.h"
class test_pattern {
public:
virtual tp::alni pick_size(tp::alni iter) { return 0; };
virtual tp::alni max_size() { return 0; };
virtual tp::alni pick_alloc_count(tp::alni iter) { return 0; };
virtual tp::alni max_iterations() { return 0; };
virtual tp::alni pick_idx(tp::alni iter) { return 0; };
virtual tp::alni data_count() { return 0; };
};
struct pattern_histogram {
tp::Array<tp::alnf> alloc_size;
tp::Array<tp::alnf> data_idx;
tp::Array<tp::alnf> items_loaded;
pattern_histogram(test_pattern* pt);
};
struct allocator_histogram {
const char* alloc_type;
tp::alnf total_time;
tp::Array<tp::alnf> time;
tp::Array<tp::alnf> mem;
bool failed;
tp::Array<tp::uint1*> data;
bool time_per_inst;
bool mem_per_inst;
allocator_histogram(test_pattern* pt, const char* alloc_type, bool time_per_inst, bool mem_per_inst);
~allocator_histogram();
void mark_resourses_usage(tp::alni idx, tp::alnf time, tp::alni mem, bool add);
void scale_all(tp::alnf fac);
};
void collect(test_pattern* pattern, tp::AbstractAllocator* alloc, allocator_histogram* histogram);

View file

@ -0,0 +1,174 @@
#pragma once
#include "strings.h"
#include <iostream>
#include "collector.h"
tp::alni hash(const tp::string& val);
#include "map.h"
enum class leav_pattern_type {
LINEAR,
RANDOM,
SINE,
CONST,
};
struct child_pattern {
child_pattern() {}
child_pattern(tp::string _name) { name = _name; }
float uppernlim = 1.f;
float lowerlim = 0.f;
float point = 1.f;
tp::string name;
};
struct pattern {
leav_pattern_type type = leav_pattern_type::CONST;
tp::string pattern_name;
tp::Array<child_pattern> regions;
bool build_in = true;
pattern() {
}
tp::alnf get_y(tp::HashMap<pattern*, tp::string>* patterns, tp::alnf x) {
assert(x <= 1.0001f && x >= -0.00001f);
if (!regions.length()) {
return pure_get_y(x);
}
float offset = 0.f;
for (tp::alni i = 0; i < regions.length(); i++) {
tp::alni idx = patterns->presents(regions[i].name);
if (!MAP_VALID_IDX(idx)) {
return 0.f;
}
pattern* child = (*patterns)[idx];
assert(child);
float range = regions[i].point * (1.f - offset);
if (offset + range > x) {
return regions[i].lowerlim +
(child->get_y(patterns, (x - offset) / range) *
(regions[i].uppernlim - regions[i].lowerlim));
}
offset += range;
}
return 0;
}
virtual tp::alnf pure_get_y(tp::alnf x) { return 0; }
~pattern() {}
};
// -------------------- build-in patterns ---------------------------- //
struct const_pattern : pattern {
float val = 1.f;
const_pattern() {
type = leav_pattern_type::CONST;
pattern_name = "const";
build_in = true;
}
tp::alnf pure_get_y(tp::alnf x) override { return val; }
};
struct linear_pattern : pattern {
bool reversed = false;
linear_pattern() {
type = leav_pattern_type::LINEAR;
pattern_name = "linear";
build_in = true;
}
tp::alnf pure_get_y(tp::alnf x) override { return reversed ? 1.f - x : x; }
};
struct random_pattern : pattern {
random_pattern() {
type = leav_pattern_type::RANDOM;
build_in = true;
}
tp::alnf pure_get_y(tp::alnf x) override { return tp::randf(); }
};
// -------------------- build-in patterns end ---------------------------- //
struct pattern_scale {
int items = 0;
int size = 0;
int iterations = 0;
};
class pattern_reader : public test_pattern {
public:
bool init(tp::HashMap<pattern*, tp::string>* p_patterns, pattern* p_lpattern,
pattern* p_opattern, pattern* p_spattern, pattern_scale* p_scale) {
lpattern = p_lpattern;
opattern = p_opattern;
spattern = p_spattern;
scale = p_scale;
patterns = p_patterns;
return verify_rulles();
}
bool verify_rulles() {
if (!lpattern || !opattern || !spattern) {
return false;
}
bool out = true;
out &= scale->items > 0;
out &= scale->size > 0;
out &= scale->iterations > 0;
return out;
}
tp::HashMap<pattern*, tp::string>* patterns;
pattern* lpattern = NULL;
pattern* opattern = NULL;
pattern* spattern = NULL;
pattern_scale* scale = 0;
tp::alnf get_x_val(tp::alni iter) {
tp::alnf out = iter / (tp::alnf)scale->iterations;
return out > 1 ? 1.f : out;
}
tp::alni pick_size(tp::alni iter) override {
return (tp::alni)(scale->size * spattern->get_y(patterns, get_x_val(iter)));
}
tp::alni pick_alloc_count(tp::alni iter) override {
return (tp::alni)(scale->items * lpattern->get_y(patterns, get_x_val(iter)));
}
tp::alni pick_idx(tp::alni iter) override {
tp::alni out = (tp::alni) (scale->items * opattern->get_y(patterns, get_x_val(iter)));
CLAMP(out, 0, scale->items - 1);
return out;
}
tp::alni max_size() override { return scale->size; }
tp::alni max_iterations() override { return scale->iterations; }
tp::alni data_count() override { return scale->iterations; }
};

View file

@ -0,0 +1,93 @@
#include "SimpleGui.h"
#include "debugui.h"
#include "memleaks.h"
#include "timer.h"
struct MemLeaksGUI {
tp::glw::Window window;
tp::glw::DebugUI ui;
tp::glw::Canvas canvas;
tp::glw::WindowSimpleInputs inputs;
tp::glw::WindowsHeaderWidget header;
tp::MemLeaksData mLeaks;
tp::MemLeaksTreeView mView;
tp::halnf mHeaderHeight = 5.5f;
tp::Timer timer;
MemLeaksGUI(const char* path) : window(), ui(window, debuguiCallBack, this), mLeaks(path), timer(10) {
canvas.setCol1({ 0.5f, 0.5f, 0.5f, 0.5f });
window.mAppearence.mHiden = false;
mView.setTarget(&mLeaks);
}
void proc() {
window.pollEvents();
inputs.update(window, 1.f);
header.header_rec = { (inputs.mWindowSizeMM.x - 60.f) / 2.f, 3.f, 60.f, mHeaderHeight };
mView.rect = { 0.f, 0.f, inputs.mWindowSizeMM.x, inputs.mWindowSizeMM.y };
mView.proc();
if (inputs.MoveUp()) {
mView.zoom_factor -= 0.2f;
}
else if (inputs.MoveDown()) {
mView.zoom_factor += 0.2f;
}
mView.zoom_factor = tp::clamp(mView.zoom_factor, 0.f, 1.f);
mView.vieport_crs = inputs.mCrs;
mView.vieport_crs_delta = inputs.mCrsmDelta * -1.f;
mView.mouse_down = inputs.Activated();
mView.mouse_hold = inputs.Anticipating();
mView.mouse_up = inputs.Selected();
header.proc(window, inputs);
}
void draw() {
window.beginDraw(); {
canvas.beginDraw({ { 0, 0 }, inputs.mWindowSizeMM }, window.mDevice.mDPMM, 1.f); {
mView.draw(&canvas);
header.draw(canvas);
} canvas.endDraw();
ui.drawDebugUI(window.mDevice.mDPMM);
} window.endDraw();
}
void run() {
while (!window.mEvents.mClose) {
proc();
if (window.mEvents.mRedraw) {
draw();
}
timer.wait();
timer.reset();
}
}
void debugui() {}
static void debuguiCallBack(void* self) { ((MemLeaksGUI*)self)->debugui(); }
~MemLeaksGUI() {}
};
int main(char argc, char* argv[]) {
tp::set_working_dir();
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleGlw, NULL };
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
if (!TestModule.initialize()) {
return 1;
}
{
MemLeaksGUI gui(argc > 1 ? argv[1] : "rsc/debug.memleaks");
gui.run();
}
TestModule.deinitialize();
}

View file

@ -0,0 +1,403 @@
#include "memleaks.h"
using namespace tp;
void MemLeaksData::Frame::dfs(Frame& frame, void (*exec)(Frame& frame, void* custom), void* custom) {
if (frame.flag == Frame::Flags::INUSE) { return; }
frame.flag = Frame::Flags::INUSE;
exec(frame, custom);
for (auto call_info : frame.callers) {
dfs(*call_info->val.caller, exec, custom);
}
frame.flag = Frame::Flags::NONE;
}
MemLeaksData::MemLeaksData(tp::string afilename) {
filename = afilename;
load_leaks();
rootframe.name = "root";
}
void MemLeaksData::increase_caller_count(Frame& frame, FrameId caller_id) {
auto idx = frame.callers.presents(caller_id);
if (idx) {
frame.callers.getSlotVal(idx).count++;
}
else {
Frame* caller_frame = &frames.get(caller_id);
frame.callers.put(caller_id, { caller_frame, 1 });
}
}
void MemLeaksData::calc_max_level(Frame& frame, tp::alni& max_level) {
frame.flag = Frame::Flags::INUSE;
for (auto call_info : frame.callers) {
auto caller = call_info->val.caller;
if (caller->flag != Frame::Flags::INUSE) {
if (caller->depth_level < frame.depth_level + 1) {
caller->depth_level = frame.depth_level + 1;
max_level = MAX(caller->depth_level, max_level);
calc_max_level(*call_info->val.caller, max_level);
}
}
}
frame.flag = Frame::Flags::NONE;
}
void MemLeaksData::count_level_users(Frame& frame, tp::alni& max_level, tp::Array<LevelInfo>& levels) {
if (frame.flag2 == 1) {
return;
}
frame.flag2 = 1;
levels[frame.depth_level].count_users += 1;
for (auto call_info : frame.callers) {
count_level_users(*call_info->val.caller, max_level, levels);
}
}
void MemLeaksData::apply_position(Frame& frame, tp::Array<LevelInfo>& levels) {
if (frame.flag2 == 1) {
return;
}
frame.flag2 = 1;
auto x = -(levels[frame.depth_level].count_users - 1) * sapacing.x / 2 + sapacing.x * levels[frame.depth_level].used_idx;
frame.tree_view_pos.assign(x, frame.depth_level * sapacing.y);
levels[frame.depth_level].used_idx++;
for (auto call_info : frame.callers) {
apply_position(*call_info->val.caller, levels);
}
};
void MemLeaksData::construct_tree() {
using namespace tp;
// construct tree
for (auto leak : leaks) {
increase_caller_count(rootframe, leak.data()[0]);
for (auto i : Range<alni>(leak->length() - 1)) {
increase_caller_count(frames.get(leak.data()[i]), leak.data()[i + 1]);
}
}
tp::alni max_depth_level = 0;
calc_max_level(rootframe, max_depth_level);
levels.reserve(max_depth_level + 1);
count_level_users(rootframe, max_depth_level, levels);
Frame::dfs(rootframe, [](Frame& frame, void*) { frame.flag2 = 0; });
apply_position(rootframe, levels);
Frame::dfs(rootframe, [](Frame& frame, void*) { frame.flag2 = 0; });
}
void MemLeaksData::make_connections() {
tp::halni len = 0;
for (auto& frame : frames) {
len += frame.iter->val.callers.size();
}
mConnections.reserve(len);
tp::halni idx = 0;
for (auto& frame : frames) {
for (auto& caller : frame.iter->val.callers) {
caller.iter->val.caller;
mConnections[idx] = { caller.iter->val.caller, &frame.iter->val, caller.iter->val.count };
idx++;
}
}
}
void MemLeaksData::load_leaks() {
using namespace tp;
File log(filename.cstr(), osfile_openflags::LOAD);
if (!log.opened) {
status = LoadStatus::INVALID_FILE_PATH;
return;
}
log.Preload();
const char logo[] = "memleaks\0";
const char logo_len = 10;
char logo_loaded[logo_len];
log.read_bytes(logo_loaded, logo_len);
if (!tp::memequal(logo_loaded, logo, logo_len)) {
status = LoadStatus::INVALID_FILE_FORMAT;
return;
}
try {
alni leaks_len;
log.read<alni>(&leaks_len);
leaks.reserve(leaks_len);
Frame frame;
for (alni idx = 0; idx < leaks_len; idx++) {
tp::alni frames_len;
log.read<tp::alni>(&frames_len);
leaks[idx].reserve(frames_len);
for (alni frame_idx = 0; frame_idx < frames_len; frame_idx++) {
FrameId id;
log.read<tp::ualni>(&id);
leaks[idx][frame_idx] = id;
//if (frames.presents(id)) {
//continue;
//}
frame.name.load(&log);
frame.file.load(&log);
log.read<tp::ualni>(&frame.line);
frame.id = id;
frames.put(id, frame);
}
}
construct_tree();
make_connections();
}
catch (...) {
status = LoadStatus::INTERNAL_ERROR;
return;
}
status = LoadStatus::DONE;
}
// ------------------------- Tree View Drawer -------------------------------------- //
void MemLeaksTreeView::setTarget(MemLeaksData* aLeaks) {
leaks = aLeaks;
selected_node = NULL;
}
void MemLeaksTreeView::proc() {
if (!leaks) {
return;
}
// handle selection
if (mouse_down) {
selected_node = NULL;
void (*find_selected)(Frame & frame, void* vec) = [](Frame& frame, void* self_ptr) {
auto self = (MemLeaksTreeView*)self_ptr;
auto node_rec = self->nodeBoundsScaled(frame);
//auto vieport = self->rect;
if (node_rec.inside(self->vieport_crs)) {
self->selected_node = &frame;
}
};
Frame::dfs(leaks->rootframe, find_selected, this);
}
// handle mouse drag
if (vieport_crs_delta != 0.f && rect.inside(vieport_crs) && mouse_hold) {
if (selected_node) {
selected_node->tree_view_pos -= vieport_crs_delta / (tp::halnf)scaleval;
}
else {
tree_view_pos -= vieport_crs_delta / (tp::halnf)scaleval;
}
}
// calc scale fac
{
// zoom_factor :
// 0 - 3 nodes visiable
// 1 - all nodes visiable
auto max_tree_size = MAX(tree_size.x, tree_size.y);
auto max_node_size = MAX(node_size.x, node_size.y);
auto min_view_size = MIN(rect.z, rect.w) - 100;
auto max = min_view_size / max_tree_size;
auto min = min_view_size / max_node_size;
scaleval = (max - min) * zoom_factor + min;
}
// calc tree size
void (*execf)(Frame & frame, void* vec) = [](Frame& frame, void* vecp) {
auto tree_min_max_pos = (tp::rectf*)vecp;
// if smaller than min
if (tree_min_max_pos->v1.x > frame.tree_view_pos.x) {
tree_min_max_pos->v1.x = frame.tree_view_pos.x;
}
if (tree_min_max_pos->v1.y > frame.tree_view_pos.y) {
tree_min_max_pos->v1.y = frame.tree_view_pos.y;
}
// if bigger than max
if (tree_min_max_pos->v2.x < frame.tree_view_pos.x) {
tree_min_max_pos->v2.x = frame.tree_view_pos.x;
}
if (tree_min_max_pos->v2.y < frame.tree_view_pos.y) {
tree_min_max_pos->v2.y = frame.tree_view_pos.y;
}
};
tree_size = 0;
tp::rectf tree_min_max_pos = { FLT_MAX, FLT_MIN };
Frame::dfs(leaks->rootframe, execf, &tree_min_max_pos);
tree_size = tree_min_max_pos.v2 - tree_min_max_pos.v1;
tree_size += node_size;
}
void MemLeaksTreeView::draw(tp::glw::Canvas* drawer) {
drawer->setCol1(col.bg);
drawer->rect(rect, 2.f);
drawer->setCol1(col.text);
if (!leaks) {
drawer->text("Not Loaded", rect, 5);
return;
}
switch (leaks->status) {
case MemLeaksData::LoadStatus::INVALID_FILE_PATH:
drawer->text("INVALID_FILE_PATH", rect, 5);
return;
case MemLeaksData::LoadStatus::INVALID_FILE_FORMAT:
drawer->text("INVALID_FILE_FORMAT", rect, 5);
return;
case MemLeaksData::LoadStatus::INTERNAL_ERROR:
drawer->text("INTERNAL_ERROR", rect, 5);
return;
}
for (auto& frame : leaks->frames) {
drawNode(frame.iter->val, drawer);
}
for (auto& connection : leaks->mConnections) {
drawConnection(*connection.data().caller, *connection.data().target, connection.data().count, drawer);
}
if (selected_node) {
auto flag = tp::glw::Canvas::Align(tp::glw::Canvas::LEFT | tp::glw::Canvas::TOP);
tp::rectf info_rec = rect;
info_rec.pos.x += 5;
info_rec.pos.y += 25;
drawer->text(selected_node->name, info_rec, 4, flag);
info_rec.pos.y += 10;
drawer->text(selected_node->file, info_rec, 4, flag);
info_rec.pos.y += 10;
drawer->text(tp::halni(selected_node->line), info_rec, 4, flag);
}
}
void MemLeaksTreeView::drawNode(Frame& node, tp::glw::Canvas* drawer) {
auto rec = nodeBoundsScaled(node);
// outside
if (!rec.overlap(rect)) {
node.flag = Frame::Flags::NONE;
return;
}
rec.clamp(rect);
drawer->setCol1((&node == selected_node) ? col.node_active : col.node);
drawer->setCol2(col.node_outline);
drawer->rect(rec, node_rounding, outline_size);
drawer->setCol1(col.text);
auto prev = drawer->mClamping;
rec.pos.x += 2;
rec.size.x -= 4;
drawer->setClamping(rec);
drawer->text(node.name, rec, text_size, tp::glw::Canvas::LEFT_MIDDLE);
drawer->setClamping(prev);
}
void MemLeaksTreeView::drawConnection(Frame& from, Frame& to, tp::halni call_count, tp::glw::Canvas* drawer) {
node_size *= 1.1;
auto rec1 = nodeBoundsScaled(from);
auto rec2 = nodeBoundsScaled(to);
node_size /= 1.1;
auto c1 = scalePoint(from.tree_view_pos + tree_view_pos);
auto c2 = scalePoint(to.tree_view_pos + tree_view_pos);
rec1.clamp_outside(c1, c2);
rec2.clamp_outside(c1, c2);
auto viewport = rect;
if (!viewport.clamp_inside(c1, c2)) {
return;
}
auto dir = (c2 - c1).unitv();
auto side = dir.normal() * arrow_size * scaleval;
auto ab = c2 - dir * arrow_size * scaleval;
auto ae = c2;
auto al = ab + side;
auto ar = ab - side;
if (&from == selected_node || &to == selected_node) {
//col = ImColor(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]);
}
drawer->setCol1(col.arrow);
drawer->trig({ ae.x, ae.y }, { al.x, al.y }, { ar.x, ar.y });
drawer->line({ c1.x, c1.y }, { c2.x, c2.y }, line_thik);
if (&from == selected_node || &to == selected_node) {
auto text_pos = c1 + (dir * (tp::halnf)(c1 - c2).length() * 0.8);
tp::string count = call_count;
drawer->setCol1(col.text);
drawer->text(count.cstr(), { text_pos.x, text_pos.y, 0, 0 }, text_size);
}
}
tp::vec2f MemLeaksTreeView::scalePoint(const tp::vec2f in) {
return in * (tp::halnf)scaleval + rect.pos + rect.size / 2;
}
tp::rectf MemLeaksTreeView::nodeBounds(Frame& node) {
tp::rectf out;
out.pos = node.tree_view_pos + tree_view_pos;
out.pos -= node_size / 2;
out.size = node_size;
return out;
}
tp::rectf MemLeaksTreeView::nodeBoundsScaled(Frame& node) {
auto out = nodeBounds(node);
auto p1 = scalePoint(out.pos);
auto p3 = scalePoint(out.p3());
out.pos = p1;
out.size = p3 - p1;
return out;
}

View file

@ -0,0 +1,137 @@
#include "map.h"
#include "array.h"
#include "stringt.h"
#include "rect.h"
#include "color.h"
#include "canvas.h"
namespace tp {
struct MemLeaksData {
typedef tp::ualni FrameId;
typedef tp::Array<FrameId> MemLeak;
struct Frame;
struct CallerInfo {
Frame* caller = NULL;
tp::ualni count = 0;
};
struct Frame {
tp::string name;
tp::string file;
tp::ualni line = 0;
tp::ualni id = 0;
tp::HashMap<CallerInfo, FrameId> callers;
tp::vec2f tree_view_pos = 0;
bool collapced = true;
enum class Flags {
NONE,
INUSE,
} flag = Flags::NONE;
tp::alni flag2 = 0;
tp::alni depth_level = 0;
static void dfs(Frame& frame, void (*exec)(Frame& frame, void* custom), void* custom = 0);
};
Frame rootframe;
tp::HashMap<Frame, FrameId> frames;
tp::Array<MemLeak> leaks;
struct Connection {
Frame* caller = NULL;
Frame* target = NULL;
tp::ualni count = 0;
};
tp::Array<Connection> mConnections;
tp::string filename;
tp::vec2f sapacing = { 60.f, 15.f };
enum class LoadStatus {
DONE,
INVALID_FILE_PATH,
INVALID_FILE_FORMAT,
INTERNAL_ERROR,
} status;
MemLeaksData(tp::string afilename);
private:
struct LevelInfo {
tp::alni count_users;
tp::alni used_idx;
};
tp::Array<LevelInfo> levels;
void increase_caller_count(Frame& frame, FrameId caller_id);
void calc_max_level(Frame& frame, tp::alni& max_level);
void count_level_users(Frame& frame, tp::alni& max_level, tp::Array<LevelInfo>& levels);
void apply_position(Frame& frame, tp::Array<LevelInfo>& levels);
void construct_tree();
void load_leaks();
void make_connections();
};
struct MemLeaksTreeView {
// inputs
tp::halnf zoom_factor = 1.f;
tp::rectf rect = 0.f;
tp::vec2f vieport_crs = 0.f;
tp::vec2f vieport_crs_delta = 0.f;
bool mouse_down = false;
bool mouse_hold = false;
bool mouse_up = false;
// appearence
struct Colors {
tp::rgba node = { 0.1f, 0.1f, 0.1f, 1.f };
tp::rgba node_outline = { 0.5f, 0.5f, 0.5f, 1.f };
tp::rgba node_active = { 0.2f, 0.2f, 0.2f, 1.f };
tp::rgba bg = { 0.15f, 0.15f, 0.15f, 1.f };
tp::rgba text = { 0.9f, 0.9f, 0.9f, 1.f };
tp::rgba arrow = { 0.5f, 0.5f, 0.5f, 1.f };
} col;
tp::halnf text_size = 3;
tp::halnf arrow_size = 2;
tp::halnf outline_size = 0.5f;
tp::halnf line_thik = 0.2f;
tp::halnf node_rounding = 1;
void setTarget(MemLeaksData* aLeaks);
void proc();
void draw(tp::glw::Canvas* drawer);
private:
typedef MemLeaksData::Frame Frame;
MemLeaksData* leaks = NULL;
Frame* selected_node = NULL;
bool inside_node = false;
tp::alnf scaleval = 1.0;
tp::vec2f tree_size = 0.f;
tp::vec2f tree_view_pos = { 0.f, -50.f };
tp::vec2f node_size = { 50, 10 };
void drawNode(Frame& node, tp::glw::Canvas* drawer);
void drawConnection(Frame& from, Frame& to, tp::halni call_count, tp::glw::Canvas* drawer);
tp::vec2f scalePoint(const tp::vec2f in);
tp::rectf nodeBounds(Frame& node);
tp::rectf nodeBoundsScaled(Frame& node);
};
};

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,401 @@
#include "GuiWindow.h"
#include "imgui.h"
#include "implot.h"
#include "nanovg.h"
#include "strings.h"
#include "filesystem.h"
#include "array.h"
#include "list.h"
#include "map.h"
#include "intersections.h"
#include "pickalloc_analizer.h"
#include "pickalloc_cfg.h"
#include <stdio.h>
class Memusage : public tp::GuiWindow {
tp::string filename;
static const char logo_len = 16;
const char logo[logo_len] = "memusage\0\0\0\0\0\0\0";
char logo_loaded[logo_len];
PickAllocConfig cfg;
enum class LoadStatus {
NONE,
DONE,
INVALID_FILE_PATH,
INVALID_FILE_FORMAT,
INTERNAL_ERROR,
} status;
typedef tp::PickAllocDataAnalizer::Event Event;
typedef tp::alni SizeKey;
struct Timeline {
tp::Array<tp::alnf> size;
tp::Array<tp::alnf> time;
tp::ualni peak = 0;
tp::ualni avereging = 0;
void recordEvents(tp::Array<Event>& events) {
tp::ualni total_mem_usage = 0;
size.extend(events.length());
time.extend(events.length());
for (auto ev : events) {
if (ev->dealloc) {
total_mem_usage -= ev->size;
} else {
total_mem_usage += ev->size;
}
time[ev.idx()] = (tp::alnf) ev->time;
size[ev.idx()] = (tp::alnf) total_mem_usage;
if (peak < total_mem_usage) {
peak = total_mem_usage;
}
}
avereging = 1;
}
void to_seconds() {
for (auto tm : time) {
tm.data() /= 1000;
}
}
};
// Contains events of fixed size
struct Group {
tp::ualni group_size = 0;
tp::Array<Event> events;
tp::ualni flag = 0;
Timeline timeline;
Group(tp::ualni grp_size) : group_size(grp_size) {}
};
tp::HashMap<Group*, SizeKey> groups;
tp::Array<Group*> groups_sorted;
public:
Memusage(const char* filename) : GuiWindow() {
this->mHeader.mAppTitle = "Mamusage Analizer";
this->mHeader.mPadding = 10;
tp::string file_path = __FILE__;
tp::make_dir_str(file_path.get_writable());
tp::make_dir_str(file_path.get_writable());
this->mRscDirectory = file_path + "/";
this->mGuiRootIsWindow = false;
LoadFonts();
this->filename = filename;
}
~Memusage() {
for (auto tl : groups) {
delete tl->val;
}
if (cfg.mAllocationSizes) delete[] cfg.mAllocationSizes;
if (cfg.mChunkLengths) delete[] cfg.mChunkLengths;
}
void run() {
// display status
for (auto i : tp::Range(2)) {
procInputs();
beginDraw();
if (mNativeWindow.mEvents.mRedraw) {
DrawCallback();
}
endDraw();
mNativeWindow.mEvents.mRedraw = true;
}
while (!mNativeWindow.mEvents.mTerminate) {
tp::Timer timer(5);
procInputs();
beginDraw();
if (mNativeWindow.mEvents.mRedraw) {
DrawCallback();
}
endDraw();
timer.wait();
if (status == LoadStatus::NONE) {
openFile();
mNativeWindow.mEvents.mRedraw = true;
}
}
}
private:
tp::ualni fileContentSize(tp::File& file) {
return file.size() - fileContentStartIdx();
}
tp::ualni fileContentStartIdx() {
return logo_len;
}
void readFile(tp::File& file) {
using namespace tp;
// read cfg
file.read(&cfg.mCapacity);
cfg.mAllocationSizes = new tp::int4[cfg.mCapacity];
cfg.mChunkLengths = new tp::int4[cfg.mCapacity];
file.read_bytes((tp::int1*)cfg.mAllocationSizes, sizeof(tp::int4) * cfg.mCapacity);
file.read_bytes((tp::int1*)cfg.mChunkLengths, sizeof(tp::int4) * cfg.mCapacity);
// read evens
tp::ualni events_length;
file.read(&events_length);
// one pass to collect data about groups in 'flag'
auto events_adress = file.adress;
for (tp::ualni i = 0; i < events_length; i++) {
Event evnt;
file.read(&evnt);
auto idx = groups.presents(evnt.size);
Group* group = NULL;
if (idx) {
group = groups.getSlotVal(idx);
} else {
group = new Group(evnt.size);
groups.put(evnt.size, group);
}
group->flag++;
}
for (auto grp : groups) {
grp->val->events.reserve(grp->val->flag);
grp->val->flag = 0;
}
file.adress = events_adress;
// read actual events
for (tp::ualni idx = 0; idx < events_length; idx++) {
Event evnt;
file.read(&evnt);
auto grp = groups.get(evnt.size);
grp->events[grp->flag] = evnt;
grp->flag++;
}
// initialize additional structure for sorting
groups_sorted.reserve(groups.size());
for (auto grp : groups) {
groups_sorted[grp.entry_idx] = grp->val;
}
// generate timelines
for (auto grp : groups_sorted) {
grp.data()->timeline.recordEvents(grp.data()->events);
grp.data()->timeline.to_seconds();
}
}
void openFile() {
using namespace tp;
File log(filename.cstr(), osfile_openflags::LOAD);
if (!log.opened) {
status = LoadStatus::INVALID_FILE_PATH;
return;
}
log.read_bytes(logo_loaded, logo_len);
if (!tp::memequal(logo_loaded, logo, logo_len)) {
status = LoadStatus::INVALID_FILE_FORMAT;
return;
}
log.Preload();
try {
readFile(log);
status = LoadStatus::DONE;
} catch (...) {
status = LoadStatus::INTERNAL_ERROR;
return;
}
}
void HeaderDrawCallback() override {
using namespace ImGui;
Button("Analize");
Button("Save To Header File");
}
void DrawCallback() {
auto pos = this->mNativeWindow.mAppearence.mSize / 2;
if (status != LoadStatus::DONE) {
nvgFillColor(mNvg, nvgRGB(250, 250, 250));
nvgFontSize(mNvg, 24);
nvgTextAlign(mNvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER);
switch (status) {
case LoadStatus::NONE:
nvgText(mNvg, pos.x, pos.y, "Loading...", 0);
break;
case LoadStatus::INVALID_FILE_PATH:
nvgText(mNvg, pos.x, pos.y, "Invalid File Path", 0);
break;
case LoadStatus::INVALID_FILE_FORMAT:
nvgText(mNvg, pos.x, pos.y, "Invalid File Format", 0);
break;
case LoadStatus::INTERNAL_ERROR:
nvgText(mNvg, pos.x, pos.y, "Internal Error", 0);
}
return;
}
drawEditor();
}
enum class GroupSorting {
LARGER_SIZE,
SMALLER_SIZE,
LARGER_TOTAL_SIZE,
SMALLER_TOTAL_SIZE,
} sorting = GroupSorting::LARGER_SIZE;
void sortGroups(GroupSorting type) {
if (type == sorting) {
return;
}
switch (type) {
case Memusage::GroupSorting::LARGER_SIZE:
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
return i1->group_size > i2->group_size;
});
break;
case Memusage::GroupSorting::SMALLER_SIZE:
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
return i1->group_size < i2->group_size;
});
break;
case Memusage::GroupSorting::LARGER_TOTAL_SIZE:
break;
case Memusage::GroupSorting::SMALLER_TOTAL_SIZE:
break;
default:
break;
}
sorting = type;
}
Group* mGroupActive = NULL;
void groupView() {
using namespace ImGui;
int tmp;
const char* names[] = {
{"Larger Group Size"},
{"Smaller Group Size"},
//{"LARGER_TOTAL_SIZE"},
//{"SMALER_TOTAL_SIZE"},
};
tmp = (int) sorting;
Combo(" ", &tmp, names, 2);
Separator();
sortGroups((GroupSorting) tmp);
for (auto grp : groups_sorted) {
if (Selectable(tp::string((tp::alni)grp.data()->group_size).cstr())) {
mGroupActive = grp.data();
}
}
}
void drawTimeline(const Timeline& timeline) {
if (ImPlot::BeginPlot(" Plot ", {-1, -1}, ImPlotFlags_CanvasOnly | ImPlotFlags_AntiAliased)) {
ImPlot::PlotLine("Memory Usage", timeline.time.buff(), timeline.size.buff(), (tp::halni) timeline.time.length());
ImPlot::EndPlot();
}
}
void infoView() {
using namespace ImGui;
if (!mGroupActive) {
Text("No Group Selected");
return;
}
Text("Total Events : %i", mGroupActive->events.length());
Separator();
drawTimeline(mGroupActive->timeline);
}
void drawEditor() {
using namespace ImGui;
Begin("Groups View");
groupView();
End();
Begin("Info");
infoView();
End();
}
};
int main(char argc, char* argv[]) {
if (argc != 2) {
printf("invalid arguments");
return 0;
}
//ImPlot::
Memusage::InitializeTypes();
{
Memusage app(argv[1]);
ImPlot::CreateContext();
app.run();
ImPlot::DestroyContext();
printf("internal error");
}
Memusage::UnInitializeTypes();
tp::terminate();
}

View file

@ -0,0 +1,401 @@
#include "GuiWindow.h"
#include "imgui.h"
#include "implot.h"
#include "nanovg.h"
#include "strings.h"
#include "filesystem.h"
#include "array.h"
#include "list.h"
#include "map.h"
#include "intersections.h"
#include "pickalloc_analizer.h"
#include "pickalloc_cfg.h"
#include <stdio.h>
class Memusage : public tp::GuiWindow {
tp::string filename;
static const char logo_len = 16;
const char logo[logo_len] = "memusage\0\0\0\0\0\0\0";
char logo_loaded[logo_len];
PickAllocConfig cfg;
enum class LoadStatus {
NONE,
DONE,
INVALID_FILE_PATH,
INVALID_FILE_FORMAT,
INTERNAL_ERROR,
} status;
typedef tp::PickAllocDataAnalizer::Event Event;
typedef tp::alni SizeKey;
struct Timeline {
tp::Array<tp::alnf> size;
tp::Array<tp::alnf> time;
tp::ualni peak = 0;
tp::ualni avereging = 0;
void recordEvents(tp::Array<Event>& events) {
tp::ualni total_mem_usage = 0;
size.extend(events.length());
time.extend(events.length());
for (auto ev : events) {
if (ev->dealloc) {
total_mem_usage -= ev->size;
} else {
total_mem_usage += ev->size;
}
time[ev.idx()] = (tp::alnf) ev->time;
size[ev.idx()] = (tp::alnf) total_mem_usage;
if (peak < total_mem_usage) {
peak = total_mem_usage;
}
}
avereging = 1;
}
void to_seconds() {
for (auto tm : time) {
tm.data() /= 1000;
}
}
};
// Contains events of fixed size
struct Group {
tp::ualni group_size = 0;
tp::Array<Event> events;
tp::ualni flag = 0;
Timeline timeline;
Group(tp::ualni grp_size) : group_size(grp_size) {}
};
tp::HashMap<Group*, SizeKey> groups;
tp::Array<Group*> groups_sorted;
public:
Memusage(const char* filename) : GuiWindow() {
this->mHeader.mAppTitle = "Mamusage Analizer";
this->mHeader.mPadding = 10;
tp::string file_path = __FILE__;
tp::make_dir_str(file_path.get_writable());
tp::make_dir_str(file_path.get_writable());
this->mRscDirectory = file_path + "/";
this->mGuiRootIsWindow = false;
LoadFonts();
this->filename = filename;
}
~Memusage() {
for (auto tl : groups) {
delete tl->val;
}
if (cfg.mAllocationSizes) delete[] cfg.mAllocationSizes;
if (cfg.mChunkLengths) delete[] cfg.mChunkLengths;
}
void run() {
// display status
for (auto i : tp::Range(2)) {
procInputs();
beginDraw();
if (mNativeWindow.mEvents.mRedraw) {
DrawCallback();
}
endDraw();
mNativeWindow.mEvents.mRedraw = true;
}
while (!mNativeWindow.mEvents.mTerminate) {
tp::Timer timer(5);
procInputs();
beginDraw();
if (mNativeWindow.mEvents.mRedraw) {
DrawCallback();
}
endDraw();
timer.wait();
if (status == LoadStatus::NONE) {
openFile();
mNativeWindow.mEvents.mRedraw = true;
}
}
}
private:
tp::ualni fileContentSize(tp::File& file) {
return file.size() - fileContentStartIdx();
}
tp::ualni fileContentStartIdx() {
return logo_len;
}
void readFile(tp::File& file) {
using namespace tp;
// read cfg
file.read(&cfg.mCapacity);
cfg.mAllocationSizes = new tp::int4[cfg.mCapacity];
cfg.mChunkLengths = new tp::int4[cfg.mCapacity];
file.read_bytes((tp::int1*)cfg.mAllocationSizes, sizeof(tp::int4) * cfg.mCapacity);
file.read_bytes((tp::int1*)cfg.mChunkLengths, sizeof(tp::int4) * cfg.mCapacity);
// read evens
tp::ualni events_length;
file.read(&events_length);
// one pass to collect data about groups in 'flag'
auto events_adress = file.adress;
for (tp::ualni i = 0; i < events_length; i++) {
Event evnt;
file.read(&evnt);
auto idx = groups.presents(evnt.size);
Group* group = NULL;
if (idx) {
group = groups.getSlotVal(idx);
} else {
group = new Group(evnt.size);
groups.put(evnt.size, group);
}
group->flag++;
}
for (auto grp : groups) {
grp->val->events.reserve(grp->val->flag);
grp->val->flag = 0;
}
file.adress = events_adress;
// read actual events
for (tp::ualni idx = 0; idx < events_length; idx++) {
Event evnt;
file.read(&evnt);
auto grp = groups.get(evnt.size);
grp->events[grp->flag] = evnt;
grp->flag++;
}
// initialize additional structure for sorting
groups_sorted.reserve(groups.size());
for (auto grp : groups) {
groups_sorted[grp.entry_idx] = grp->val;
}
// generate timelines
for (auto grp : groups_sorted) {
grp.data()->timeline.recordEvents(grp.data()->events);
grp.data()->timeline.to_seconds();
}
}
void openFile() {
using namespace tp;
File log(filename.cstr(), osfile_openflags::LOAD);
if (!log.opened) {
status = LoadStatus::INVALID_FILE_PATH;
return;
}
log.read_bytes(logo_loaded, logo_len);
if (!tp::memequal(logo_loaded, logo, logo_len)) {
status = LoadStatus::INVALID_FILE_FORMAT;
return;
}
log.Preload();
try {
readFile(log);
status = LoadStatus::DONE;
} catch (...) {
status = LoadStatus::INTERNAL_ERROR;
return;
}
}
void HeaderDrawCallback() override {
using namespace ImGui;
Button("Analize");
Button("Save To Header File");
}
void DrawCallback() {
auto pos = this->mNativeWindow.mAppearence.mSize / 2;
if (status != LoadStatus::DONE) {
nvgFillColor(mNvg, nvgRGB(250, 250, 250));
nvgFontSize(mNvg, 24);
nvgTextAlign(mNvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER);
switch (status) {
case LoadStatus::NONE:
nvgText(mNvg, pos.x, pos.y, "Loading...", 0);
break;
case LoadStatus::INVALID_FILE_PATH:
nvgText(mNvg, pos.x, pos.y, "Invalid File Path", 0);
break;
case LoadStatus::INVALID_FILE_FORMAT:
nvgText(mNvg, pos.x, pos.y, "Invalid File Format", 0);
break;
case LoadStatus::INTERNAL_ERROR:
nvgText(mNvg, pos.x, pos.y, "Internal Error", 0);
}
return;
}
drawEditor();
}
enum class GroupSorting {
LARGER_SIZE,
SMALLER_SIZE,
LARGER_TOTAL_SIZE,
SMALLER_TOTAL_SIZE,
} sorting = GroupSorting::LARGER_SIZE;
void sortGroups(GroupSorting type) {
if (type == sorting) {
return;
}
switch (type) {
case Memusage::GroupSorting::LARGER_SIZE:
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
return i1->group_size > i2->group_size;
});
break;
case Memusage::GroupSorting::SMALLER_SIZE:
groups_sorted.sort([](Group* const& i1, Group* const& i2) {
return i1->group_size < i2->group_size;
});
break;
case Memusage::GroupSorting::LARGER_TOTAL_SIZE:
break;
case Memusage::GroupSorting::SMALLER_TOTAL_SIZE:
break;
default:
break;
}
sorting = type;
}
Group* mGroupActive = NULL;
void groupView() {
using namespace ImGui;
int tmp;
const char* names[] = {
{"Larger Group Size"},
{"Smaller Group Size"},
//{"LARGER_TOTAL_SIZE"},
//{"SMALER_TOTAL_SIZE"},
};
tmp = (int) sorting;
Combo(" ", &tmp, names, 2);
Separator();
sortGroups((GroupSorting) tmp);
for (auto grp : groups_sorted) {
if (Selectable(tp::string((tp::alni)grp.data()->group_size).cstr())) {
mGroupActive = grp.data();
}
}
}
void drawTimeline(const Timeline& timeline) {
if (ImPlot::BeginPlot(" Plot ", {-1, -1}, ImPlotFlags_CanvasOnly | ImPlotFlags_AntiAliased)) {
ImPlot::PlotLine("Memory Usage", timeline.time.buff(), timeline.size.buff(), (tp::halni) timeline.time.length());
ImPlot::EndPlot();
}
}
void infoView() {
using namespace ImGui;
if (!mGroupActive) {
Text("No Group Selected");
return;
}
Text("Total Events : %i", mGroupActive->events.length());
Separator();
drawTimeline(mGroupActive->timeline);
}
void drawEditor() {
using namespace ImGui;
Begin("Groups View");
groupView();
End();
Begin("Info");
infoView();
End();
}
};
int main(char argc, char* argv[]) {
if (argc != 2) {
printf("invalid arguments");
return 0;
}
//ImPlot::
Memusage::InitializeTypes();
{
Memusage app(argv[1]);
ImPlot::CreateContext();
app.run();
ImPlot::DestroyContext();
printf("internal error");
}
Memusage::UnInitializeTypes();
tp::terminate();
}