I've written a memory tracker for use in my game engine. I based it on this slide in a talk by Nicolai Josuttis.
One problem I'm having is that, during reporting, the implicit string conversion and output stream operator functions allocate. This results in calls to new
. Since I've overloaded new
the tracker tries to track the new allocations and ends up invalidating the results.
Is there a way to output the results without allocating?
Memory.hpp
#pragma once
//https://www.youtube.com/watch?v=e2ZQyYr0Oi0
//C++17 - The Best Features - Nicolai Josuttis [ACCU 2018]
#include "Engine/Core/BuildConfig.hpp"
#include "Engine/Core/ErrorWarningAssert.hpp"
//#include "Engine/Profiling/StackTrace.hpp"
#include <new>
#include <sstream>
#include <iostream>
#include <ostream>
class Memory {
public:
struct status_t {
std::size_t leaked_objs = 0;
std::size_t leaked_bytes = 0;
operator bool() const noexcept {
return leaked_objs || leaked_bytes;
}
operator std::string() const noexcept {
#ifdef TRACK_MEMORY
std::ostringstream ss;
std::string s = ss.str();
ss << "Leaked objects: " << leaked_objs << " for " << leaked_bytes << " bytes.\n";
return s;
#else
return {};
#endif
}
friend std::ostream& operator<<(std::ostream& os, [[maybe_unused]]const status_t& s) noexcept {
#ifdef TRACK_MEMORY
os << "Leaked objects: " << s.leaked_objs << " for " << s.leaked_bytes << " bytes.\n";
#endif
return os;
}
};
struct status_frame_t {
std::size_t frame_id = 0;
std::size_t leaked_objs = 0;
std::size_t leaked_bytes = 0;
operator bool() const noexcept {
return leaked_objs || leaked_bytes;
}
operator std::string() const noexcept {
#ifdef TRACK_MEMORY
std::ostringstream ss;
ss << "Frame " << frame_id << ": Leaked objects: " << leaked_objs << " for " << leaked_bytes << " bytes.\n";
std::string s = ss.str();
return s;
#else
return {};
#endif
}
friend std::ostream& operator<<(std::ostream& os, [[maybe_unused]]const status_frame_t& s) noexcept {
#ifdef TRACK_MEMORY
os << "Frame " << s.frame_id << ": Leaked objects: " << s.leaked_objs << " for " << s.leaked_bytes << " bytes.\n";
#endif
return os;
}
};
[[nodiscard]] static void* allocate(std::size_t n) noexcept {
if(is_enabled()) {
++frameCount;
frameSize += n;
++allocCount;
allocSize += n;
if(maxSize < allocSize) {
maxSize = allocSize;
}
if(maxCount < allocCount) {
maxCount = allocCount;
}
}
return std::malloc(n);
}
static void deallocate(void* ptr, std::size_t size) noexcept {
if(is_enabled()) {
++framefreeCount;
framefreeSize += size;
++freeCount;
freeSize += size;
}
std::free(ptr);
}
static void enable([[maybe_unused]]bool e) noexcept {
#ifdef TRACK_MEMORY
_active = e;
#endif
}
static bool is_enabled() noexcept {
#ifdef TRACK_MEMORY
return _active;
#else
return false;
#endif
}
static void trace([[maybe_unused]]bool doTrace) noexcept {
#ifdef TRACK_MEMORY
_trace = doTrace;
#endif
}
static void tick() noexcept {
#ifdef TRACK_MEMORY
if(auto f = Memory::frame_status()) {
std::cout << f << '\n';
}
++frameCounter;
resetframecounters();
#endif
}
static void resetframecounters() noexcept {
#ifdef TRACK_MEMORY
frameSize = 0;
frameCount = 0;
framefreeCount = 0;
framefreeSize = 0;
#endif
}
static status_t status() noexcept {
return { allocCount - freeCount, allocSize - freeSize };
}
static status_frame_t frame_status() noexcept {
return { frameCounter, frameCount - framefreeCount, frameSize - framefreeSize };
}
inline static std::size_t maxSize = 0;
inline static std::size_t maxCount = 0;
inline static std::size_t allocSize = 0;
inline static std::size_t allocCount = 0;
inline static std::size_t frameSize = 0;
inline static std::size_t frameCount = 0;
inline static std::size_t frameCounter = 0;
inline static std::size_t freeCount = 0;
inline static std::size_t freeSize = 0;
inline static std::size_t framefreeCount = 0;
inline static std::size_t framefreeSize = 0;
protected:
private:
inline static bool _active = false;
inline static bool _trace = false;
};
#ifdef TRACK_MEMORY
void* operator new(std::size_t size);
void* operator new[](std::size_t size);
void operator delete(void* ptr, std::size_t size) noexcept;
void operator delete[](void* ptr, std::size_t size) noexcept;
#endif
Memory.cpp
#include "Engine/Profiling/Memory.hpp"
#ifdef TRACK_MEMORY
void* operator new(std::size_t size) {
return Memory::allocate(size);
}
void* operator new[](std::size_t size) {
return Memory::allocate(size);
}
void operator delete(void* ptr, std::size_t size) noexcept {
Memory::deallocate(ptr, size);
}
void operator delete[](void* ptr, std::size_t size) noexcept {
Memory::deallocate(ptr, size);
}
#endif
std::string
. Less elegant, but side-steps the allocation issue. \$\endgroup\$