mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-24 18:01:53 +02:00
perf(debug): debug.memorystats switch for the heap counting (off by default) and the places of the large heap blocks in the memory section
This commit is contained in:
@@ -28,6 +28,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/translation.h"
|
||||
#include "vehicle/Train.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/memorystats.h"
|
||||
#include "utilities/dictionary.h"
|
||||
#include "version_info.h"
|
||||
#include <chrono>
|
||||
@@ -1146,6 +1147,10 @@ int eu07_application::init_settings(int Argc, char *Argv[])
|
||||
{
|
||||
Global.LoadIniFile("eu07.ini");
|
||||
}
|
||||
if (Global.debug_memorystats)
|
||||
{
|
||||
start_heap_counting();
|
||||
}
|
||||
|
||||
// process command line arguments
|
||||
bool sceneryfromcommandline{false};
|
||||
|
||||
@@ -1177,6 +1177,34 @@ void opengl46_renderer::Update_Memory_Info()
|
||||
text += "process: " + mb( process.resident ) + " resident (peak " + mb( process.peak ) + ")"
|
||||
+ ( process.committed > 0 ? ", " + mb( process.committed ) + " private" : std::string{} ) + "\n";
|
||||
}
|
||||
// the memory of the card, all processes: GL_NVX_gpu_memory_info (in kB)
|
||||
if( m_nvxmemory < 0 ) {
|
||||
m_nvxmemory = 0;
|
||||
GLint extensions { 0 };
|
||||
::glGetIntegerv( GL_NUM_EXTENSIONS, &extensions );
|
||||
for( GLint index = 0; index < extensions; ++index ) {
|
||||
auto const *name { reinterpret_cast<char const *>( ::glGetStringi( GL_EXTENSIONS, static_cast<GLuint>( index ) ) ) };
|
||||
if( ( name != nullptr ) && ( std::strcmp( name, "GL_NVX_gpu_memory_info" ) == 0 ) ) {
|
||||
m_nvxmemory = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( m_nvxmemory > 0 ) {
|
||||
GLint dedicated { 0 }, available { 0 }, evicted { 0 };
|
||||
::glGetIntegerv( 0x9047, &dedicated ); // GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX
|
||||
::glGetIntegerv( 0x9049, &available ); // GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX
|
||||
::glGetIntegerv( 0x904B, &evicted ); // GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX
|
||||
text += "card memory, all programs: " + mb( static_cast<std::uint64_t>( std::max( dedicated - available, 0 ) ) * 1024 ) + " used of "
|
||||
+ mb( static_cast<std::uint64_t>( dedicated ) * 1024 ) + ( evicted > 0 ? ", " + mb( static_cast<std::uint64_t>( evicted ) * 1024 ) + " evicted" : std::string{} ) + "\n";
|
||||
}
|
||||
|
||||
// the rest needs new and delete counted, which slows the loading down: only with debug.memorystats
|
||||
if( false == heap_counting() ) {
|
||||
text += "the memory of the parts of the program: off (debug.memorystats yes in eu07.ini, it slows the loading down)";
|
||||
m_memorytext = std::move( text );
|
||||
return;
|
||||
}
|
||||
// what the program took with new: the rest of the private memory is of the drivers, libraries and the c runtime
|
||||
auto const heap { query_heap_memory() };
|
||||
text += "taken with new: " + mb( heap.bytes ) + " in " + std::to_string( heap.blocks ) + " blocks"
|
||||
@@ -1200,28 +1228,28 @@ void opengl46_renderer::Update_Memory_Info()
|
||||
}
|
||||
text += "\n";
|
||||
}
|
||||
// the memory of the card, all processes: GL_NVX_gpu_memory_info (in kB)
|
||||
if( m_nvxmemory < 0 ) {
|
||||
m_nvxmemory = 0;
|
||||
GLint extensions { 0 };
|
||||
::glGetIntegerv( GL_NUM_EXTENSIONS, &extensions );
|
||||
for( GLint index = 0; index < extensions; ++index ) {
|
||||
auto const *name { reinterpret_cast<char const *>( ::glGetStringi( GL_EXTENSIONS, static_cast<GLuint>( index ) ) ) };
|
||||
if( ( name != nullptr ) && ( std::strcmp( name, "GL_NVX_gpu_memory_info" ) == 0 ) ) {
|
||||
m_nvxmemory = 1;
|
||||
break;
|
||||
// where the large blocks come from
|
||||
{
|
||||
auto const large { query_large_blocks() };
|
||||
text += " blocks of " + std::to_string( large_blocks::LARGE_BLOCK / 1024 ) + " KB and more by the place which took them:\n";
|
||||
std::size_t const shown { 15 };
|
||||
std::uint64_t restbytes { 0 }, restblocks { 0 };
|
||||
for( std::size_t index = 0; index < large.sites.size(); ++index ) {
|
||||
auto const &site { large.sites[ index ] };
|
||||
if( index >= shown ) {
|
||||
restbytes += site.bytes;
|
||||
restblocks += site.blocks;
|
||||
continue;
|
||||
}
|
||||
text += " " + mb( site.bytes ) + " in " + std::to_string( site.blocks ) + ": " + site.place + "\n";
|
||||
}
|
||||
if( restblocks > 0 ) {
|
||||
text += " " + mb( restbytes ) + " in " + std::to_string( restblocks ) + ": " + std::to_string( large.sites.size() - shown ) + " other places\n";
|
||||
}
|
||||
if( large.untracked > 0 ) {
|
||||
text += " " + std::to_string( large.untracked ) + " blocks not followed, the table of them was full\n";
|
||||
}
|
||||
}
|
||||
if( m_nvxmemory > 0 ) {
|
||||
GLint dedicated { 0 }, available { 0 }, evicted { 0 };
|
||||
::glGetIntegerv( 0x9047, &dedicated ); // GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX
|
||||
::glGetIntegerv( 0x9049, &available ); // GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX
|
||||
::glGetIntegerv( 0x904B, &evicted ); // GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX
|
||||
text += "card memory, all programs: " + mb( static_cast<std::uint64_t>( std::max( dedicated - available, 0 ) ) * 1024 ) + " used of "
|
||||
+ mb( static_cast<std::uint64_t>( dedicated ) * 1024 ) + ( evicted > 0 ? ", " + mb( static_cast<std::uint64_t>( evicted ) * 1024 ) + " evicted" : std::string{} ) + "\n";
|
||||
}
|
||||
|
||||
std::uint64_t accounted { 0 };
|
||||
// textures: the copies of the data kept in the process after the upload
|
||||
auto const textures { m_textures.memory() };
|
||||
|
||||
@@ -1150,6 +1150,12 @@ bool global_settings::ConfigParseDebug(cParser& Parser, const std::string& token
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "debug.memorystats")
|
||||
{
|
||||
ParseOne(Parser, debug_memorystats);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "debuglog")
|
||||
{
|
||||
std::string value;
|
||||
@@ -1915,6 +1921,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "physics.roadlod", physics_roadlod );
|
||||
export_as_text( Output, "physics.pipewake", physics_pipewake );
|
||||
export_as_text( Output, "performance.multithreading", performance_multithreading );
|
||||
export_as_text( Output, "debug.memorystats", debug_memorystats );
|
||||
export_as_text( Output, "debuglog", iWriteLogEnabled );
|
||||
export_as_text( Output, "multiplelogs", MultipleLogs );
|
||||
export_as_text( Output, "showsystemconsole", ShowSystemConsole );
|
||||
|
||||
@@ -118,6 +118,7 @@ struct global_settings {
|
||||
bool physics_roadlod{ true }; // road vehicles far from the camera go by one step of the physics in a frame, see vehicle_table::update()
|
||||
float physics_pipewake{ 0.001f }; // bar: air flowing along a pipe wakes the physics of a vehicle when it changes its pressure by more than this. 0: any flow does
|
||||
bool performance_multithreading{ true }; // work split over the logical cores of the machine (worker_pool), as many as each job is worth
|
||||
bool debug_memorystats{ false }; // the memory of the parts of the program in the debug panel, with new and delete counted (start_heap_counting()). slows the loading down
|
||||
bool bnewAirCouplers{ true };
|
||||
float fMoveLight{ 0.f }; // numer dnia w roku albo -1
|
||||
bool FakeLight{ false }; // toggle between fixed and dynamic daylight
|
||||
|
||||
@@ -15,29 +15,76 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <malloc.h>
|
||||
#include <psapi.h>
|
||||
#include <dbghelp.h>
|
||||
#pragma comment(lib, "psapi.lib")
|
||||
#elif defined(__APPLE__)
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
#else
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <malloc/malloc.h>
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <malloc.h>
|
||||
#include <string>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// the global new and delete of the executable count the memory of the blocks they hand out (query_heap_memory()). the blocks come
|
||||
// from malloc() as with the default operators; the aligned forms are left to the runtime
|
||||
// the global new and delete of the executable count the memory of the blocks they hand out (query_heap_memory()), once
|
||||
// start_heap_counting() turns it on. the blocks come from malloc() as with the default operators; the aligned forms are left to the
|
||||
// runtime
|
||||
namespace
|
||||
{
|
||||
|
||||
std::atomic<std::uint64_t> g_heapbytes{0};
|
||||
std::atomic<std::uint64_t> g_heapblocks{0};
|
||||
std::atomic<bool> g_counting{false};
|
||||
// signed: the blocks taken before the counting began are given back with it on
|
||||
std::atomic<std::int64_t> g_heapbytes{0};
|
||||
std::atomic<std::int64_t> g_heapblocks{0};
|
||||
// by the size of the blocks, see heap_memory
|
||||
std::array<std::atomic<std::uint64_t>, heap_memory::CLASSES> g_classbytes{};
|
||||
std::array<std::atomic<std::uint64_t>, heap_memory::CLASSES> g_classblocks{};
|
||||
std::array<std::atomic<std::int64_t>, heap_memory::CLASSES> g_classbytes{};
|
||||
std::array<std::atomic<std::int64_t>, heap_memory::CLASSES> g_classblocks{};
|
||||
|
||||
// the large blocks live now, with the call stacks which took them. a fixed table: new can't take memory for it
|
||||
constexpr std::size_t LARGE_FRAMES{16};
|
||||
constexpr std::size_t LARGE_CAPACITY{16384};
|
||||
|
||||
struct large_record
|
||||
{
|
||||
void *block{nullptr};
|
||||
std::uint64_t size{0};
|
||||
std::array<void *, LARGE_FRAMES> frames{};
|
||||
std::uint32_t framecount{0};
|
||||
};
|
||||
|
||||
std::array<large_record, LARGE_CAPACITY> g_large; // [0, g_largeused) holds the records, with gaps of the blocks given back
|
||||
std::size_t g_largeused{0};
|
||||
std::uint64_t g_largeuntracked{0};
|
||||
std::uint32_t g_largeskip{0}; // the frames of new itself at the top of the call stacks, see calibrate_frames()
|
||||
std::atomic_flag g_largelock = ATOMIC_FLAG_INIT;
|
||||
|
||||
struct large_lock
|
||||
{
|
||||
large_lock()
|
||||
{
|
||||
while (g_largelock.test_and_set(std::memory_order_acquire))
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
~large_lock()
|
||||
{
|
||||
g_largelock.clear(std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t size_class(std::size_t const Size)
|
||||
{
|
||||
@@ -55,6 +102,59 @@ std::size_t block_size(void *Block)
|
||||
#endif
|
||||
}
|
||||
|
||||
std::uint32_t capture_frames(std::array<void *, LARGE_FRAMES> &Frames)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return ::RtlCaptureStackBackTrace(0, static_cast<DWORD>(Frames.size()), Frames.data(), nullptr);
|
||||
#else
|
||||
return static_cast<std::uint32_t>(std::max(0, ::backtrace(Frames.data(), static_cast<int>(Frames.size()))));
|
||||
#endif
|
||||
}
|
||||
|
||||
void note_large(void *Block, std::size_t const Size)
|
||||
{
|
||||
large_record record;
|
||||
record.block = Block;
|
||||
record.size = Size;
|
||||
// taken outside the lock: it may take memory with malloc the first time
|
||||
record.framecount = capture_frames(record.frames);
|
||||
|
||||
large_lock lock;
|
||||
std::size_t slot{0};
|
||||
while ((slot < g_largeused) && (g_large[slot].block != nullptr))
|
||||
{
|
||||
++slot;
|
||||
}
|
||||
if (slot == g_largeused)
|
||||
{
|
||||
if (g_largeused == g_large.size())
|
||||
{
|
||||
++g_largeuntracked;
|
||||
return;
|
||||
}
|
||||
++g_largeused;
|
||||
}
|
||||
g_large[slot] = record;
|
||||
}
|
||||
|
||||
void forget_large(void *Block)
|
||||
{
|
||||
large_lock lock;
|
||||
for (std::size_t slot = 0; slot < g_largeused; ++slot)
|
||||
{
|
||||
if (g_large[slot].block != Block)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
g_large[slot].block = nullptr;
|
||||
while ((g_largeused > 0) && (g_large[g_largeused - 1].block == nullptr))
|
||||
{
|
||||
--g_largeused;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void *counted_new(std::size_t Size)
|
||||
{
|
||||
if (Size == 0)
|
||||
@@ -65,11 +165,18 @@ void *counted_new(std::size_t Size)
|
||||
{
|
||||
if (auto *block{std::malloc(Size)})
|
||||
{
|
||||
auto const size{block_size(block)};
|
||||
g_heapbytes.fetch_add(size, std::memory_order_relaxed);
|
||||
g_heapblocks.fetch_add(1, std::memory_order_relaxed);
|
||||
g_classbytes[size_class(size)].fetch_add(size, std::memory_order_relaxed);
|
||||
g_classblocks[size_class(size)].fetch_add(1, std::memory_order_relaxed);
|
||||
if (g_counting.load(std::memory_order_relaxed))
|
||||
{
|
||||
auto const size{block_size(block)};
|
||||
g_heapbytes.fetch_add(static_cast<std::int64_t>(size), std::memory_order_relaxed);
|
||||
g_heapblocks.fetch_add(1, std::memory_order_relaxed);
|
||||
g_classbytes[size_class(size)].fetch_add(static_cast<std::int64_t>(size), std::memory_order_relaxed);
|
||||
g_classblocks[size_class(size)].fetch_add(1, std::memory_order_relaxed);
|
||||
if (size >= large_blocks::LARGE_BLOCK)
|
||||
{
|
||||
note_large(block, size);
|
||||
}
|
||||
}
|
||||
return block;
|
||||
}
|
||||
auto const handler{std::get_new_handler()};
|
||||
@@ -87,11 +194,18 @@ void counted_delete(void *Block) noexcept
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const size{block_size(Block)};
|
||||
g_heapbytes.fetch_sub(size, std::memory_order_relaxed);
|
||||
g_heapblocks.fetch_sub(1, std::memory_order_relaxed);
|
||||
g_classbytes[size_class(size)].fetch_sub(size, std::memory_order_relaxed);
|
||||
g_classblocks[size_class(size)].fetch_sub(1, std::memory_order_relaxed);
|
||||
if (g_counting.load(std::memory_order_relaxed))
|
||||
{
|
||||
auto const size{block_size(Block)};
|
||||
g_heapbytes.fetch_sub(static_cast<std::int64_t>(size), std::memory_order_relaxed);
|
||||
g_heapblocks.fetch_sub(1, std::memory_order_relaxed);
|
||||
g_classbytes[size_class(size)].fetch_sub(static_cast<std::int64_t>(size), std::memory_order_relaxed);
|
||||
g_classblocks[size_class(size)].fetch_sub(1, std::memory_order_relaxed);
|
||||
if (size >= large_blocks::LARGE_BLOCK)
|
||||
{
|
||||
forget_large(Block);
|
||||
}
|
||||
}
|
||||
std::free(Block);
|
||||
}
|
||||
|
||||
@@ -163,17 +277,277 @@ void operator delete[](void *Block, std::nothrow_t const &) noexcept
|
||||
|
||||
heap_memory query_heap_memory()
|
||||
{
|
||||
auto const value = [](std::atomic<std::int64_t> const &Counter) {
|
||||
return static_cast<std::uint64_t>(std::max<std::int64_t>(0, Counter.load(std::memory_order_relaxed)));
|
||||
};
|
||||
heap_memory result;
|
||||
result.bytes = g_heapbytes.load(std::memory_order_relaxed);
|
||||
result.blocks = g_heapblocks.load(std::memory_order_relaxed);
|
||||
result.bytes = value(g_heapbytes);
|
||||
result.blocks = value(g_heapblocks);
|
||||
for (std::size_t index = 0; index < heap_memory::CLASSES; ++index)
|
||||
{
|
||||
result.classbytes[index] = g_classbytes[index].load(std::memory_order_relaxed);
|
||||
result.classblocks[index] = g_classblocks[index].load(std::memory_order_relaxed);
|
||||
result.classbytes[index] = value(g_classbytes[index]);
|
||||
result.classblocks[index] = value(g_classblocks[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// how many frames at the top of the stack of a block are of new itself: a block is taken here and its stack is compared with the one
|
||||
// of this function. the names of the functions, which tell the same, aren't there in every build (linux without -rdynamic)
|
||||
#if defined(_MSC_VER)
|
||||
__declspec(noinline)
|
||||
#else
|
||||
__attribute__((noinline))
|
||||
#endif
|
||||
void calibrate_frames()
|
||||
{
|
||||
std::array<void *, LARGE_FRAMES> own;
|
||||
auto const owncount{capture_frames(own)};
|
||||
auto *block{::operator new(large_blocks::LARGE_BLOCK)};
|
||||
large_record record;
|
||||
{
|
||||
large_lock lock;
|
||||
for (std::size_t slot = 0; slot < g_largeused; ++slot)
|
||||
{
|
||||
if (g_large[slot].block == block)
|
||||
{
|
||||
record = g_large[slot];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
::operator delete(block);
|
||||
// the first frame of the block's stack which is in this one too (with the next one, to be sure) is the caller of this function:
|
||||
// the frame before it is this function, the ones before that are new
|
||||
for (std::uint32_t frame = 1; frame + 1 < record.framecount; ++frame)
|
||||
{
|
||||
for (std::uint32_t index = 0; index + 1 < owncount; ++index)
|
||||
{
|
||||
if ((record.frames[frame] == own[index]) && (record.frames[frame + 1] == own[index + 1]))
|
||||
{
|
||||
g_largeskip = frame - 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void start_heap_counting()
|
||||
{
|
||||
if (g_counting.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
// the first backtrace() loads the library which walks the stack: done here, not in the middle of a new
|
||||
std::array<void *, LARGE_FRAMES> frames;
|
||||
capture_frames(frames);
|
||||
#endif
|
||||
g_counting.store(true, std::memory_order_relaxed);
|
||||
calibrate_frames();
|
||||
}
|
||||
|
||||
bool heap_counting()
|
||||
{
|
||||
return g_counting.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// a frame of a call stack, as the symbols of the program name it
|
||||
struct frame_name
|
||||
{
|
||||
std::string function;
|
||||
std::string line; // file:line, where known and not in a header of the standard library
|
||||
bool allocator{false}; // a part of new or of the standard containers: the place which took the block is further down
|
||||
};
|
||||
|
||||
frame_name name_frame(void *Address)
|
||||
{
|
||||
frame_name result;
|
||||
// the address the call returns to: the call itself is a byte before it
|
||||
auto const address{reinterpret_cast<std::uintptr_t>(Address) - 1};
|
||||
#if defined(_WIN32)
|
||||
auto const process{::GetCurrentProcess()};
|
||||
static bool const symbols{[process]() {
|
||||
::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
|
||||
return ::SymInitialize(process, nullptr, TRUE) != FALSE;
|
||||
}()};
|
||||
if (symbols)
|
||||
{
|
||||
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + 512];
|
||||
auto *symbol{reinterpret_cast<SYMBOL_INFO *>(buffer)};
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = 511;
|
||||
DWORD64 displacement{0};
|
||||
if (::SymFromAddr(process, address, &displacement, symbol))
|
||||
{
|
||||
result.function = symbol->Name;
|
||||
}
|
||||
IMAGEHLP_LINE64 line{};
|
||||
line.SizeOfStruct = sizeof(line);
|
||||
DWORD linedisplacement{0};
|
||||
if (::SymGetLineFromAddr64(process, address, &linedisplacement, &line) && (line.FileName != nullptr))
|
||||
{
|
||||
std::string file{line.FileName};
|
||||
file = file.substr(file.find_last_of("\\/") + 1);
|
||||
// the headers of the standard library have no extension
|
||||
if (file.find('.') != std::string::npos)
|
||||
{
|
||||
result.line = file + ":" + std::to_string(line.LineNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.function.empty())
|
||||
{
|
||||
HMODULE module{nullptr};
|
||||
char path[MAX_PATH]{};
|
||||
if (::GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCSTR>(address), &module) &&
|
||||
::GetModuleFileNameA(module, path, MAX_PATH))
|
||||
{
|
||||
std::string file{path};
|
||||
std::ostringstream name;
|
||||
name << file.substr(file.find_last_of("\\/") + 1) << "+0x" << std::hex << (address - reinterpret_cast<std::uintptr_t>(module));
|
||||
result.function = name.str();
|
||||
}
|
||||
}
|
||||
#else
|
||||
Dl_info info{};
|
||||
if (::dladdr(reinterpret_cast<void *>(address), &info) != 0)
|
||||
{
|
||||
if (info.dli_sname != nullptr)
|
||||
{
|
||||
int status{0};
|
||||
auto *demangled{abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status)};
|
||||
result.function = (status == 0 && demangled != nullptr) ? demangled : info.dli_sname;
|
||||
std::free(demangled);
|
||||
}
|
||||
else if (info.dli_fname != nullptr)
|
||||
{
|
||||
// without the names of the functions of the executable (no -rdynamic): addr2line -f -C -e <file> <offset> tells them
|
||||
std::string file{info.dli_fname};
|
||||
std::ostringstream name;
|
||||
name << file.substr(file.find_last_of('/') + 1) << "+0x" << std::hex << (address - reinterpret_cast<std::uintptr_t>(info.dli_fbase));
|
||||
result.function = name.str();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (result.function.empty())
|
||||
{
|
||||
std::ostringstream name;
|
||||
name << "0x" << std::hex << address;
|
||||
result.function = name.str();
|
||||
}
|
||||
// the qualified name, without what the demangled names of gcc have around it: the type returned, the parameters
|
||||
auto function{result.function};
|
||||
{
|
||||
int depth{0};
|
||||
std::size_t start{0};
|
||||
for (std::size_t index = 0; index < function.size(); ++index)
|
||||
{
|
||||
auto const character{function[index]};
|
||||
if (character == '<')
|
||||
{
|
||||
++depth;
|
||||
}
|
||||
else if (character == '>')
|
||||
{
|
||||
--depth;
|
||||
}
|
||||
else if ((depth == 0) && (character == ' ') && ((index < 8) || (function.compare(index - 8, 8, "operator") != 0)))
|
||||
{
|
||||
start = index + 1;
|
||||
}
|
||||
else if ((depth == 0) && (character == '(') && (index > start))
|
||||
{
|
||||
function = function.substr(start, index - start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.allocator = (function.rfind("std::", 0) == 0) || (function.rfind("operator new", 0) == 0) || (function.rfind("__gnu_cxx::", 0) == 0) ||
|
||||
(function.find("counted_new") != std::string::npos) || (function.find("note_large") != std::string::npos) ||
|
||||
(function.find("capture_frames") != std::string::npos) || (function.find("RtlCaptureStackBackTrace") != std::string::npos) ||
|
||||
(function.find("backtrace") != std::string::npos) || (function.find("malloc") != std::string::npos);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
large_blocks query_large_blocks()
|
||||
{
|
||||
large_blocks result;
|
||||
// a copy of the table, so the lock isn't held while the stacks get named. static: it's too large for the stack, and a block taken
|
||||
// for it would land in the table itself
|
||||
static std::array<large_record, LARGE_CAPACITY> records;
|
||||
std::size_t count{0};
|
||||
{
|
||||
large_lock lock;
|
||||
for (std::size_t slot = 0; slot < g_largeused; ++slot)
|
||||
{
|
||||
if (g_large[slot].block != nullptr)
|
||||
{
|
||||
records[count++] = g_large[slot];
|
||||
}
|
||||
}
|
||||
result.untracked = g_largeuntracked;
|
||||
}
|
||||
|
||||
static std::mutex namesmutex;
|
||||
static std::unordered_map<void *, frame_name> names;
|
||||
std::scoped_lock namelock(namesmutex);
|
||||
auto const name = [](void *Address) -> frame_name const & {
|
||||
auto lookup{names.find(Address)};
|
||||
if (lookup == names.end())
|
||||
{
|
||||
lookup = names.emplace(Address, name_frame(Address)).first;
|
||||
}
|
||||
return lookup->second;
|
||||
};
|
||||
auto const place = [](frame_name const &Frame) { return Frame.line.empty() ? Frame.function : Frame.function + " (" + Frame.line + ")"; };
|
||||
|
||||
std::unordered_map<std::string, std::size_t> sites;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
auto const &record{records[index]};
|
||||
// the first frame outside new and the containers, and the one which called it
|
||||
std::string site;
|
||||
auto frame{g_largeskip};
|
||||
while ((frame < record.framecount) && name(record.frames[frame]).allocator)
|
||||
{
|
||||
++frame;
|
||||
}
|
||||
if (frame < record.framecount)
|
||||
{
|
||||
site = place(name(record.frames[frame]));
|
||||
if (frame + 1 < record.framecount)
|
||||
{
|
||||
site += " <- " + place(name(record.frames[frame + 1]));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
site = "?";
|
||||
}
|
||||
auto lookup{sites.find(site)};
|
||||
if (lookup == sites.end())
|
||||
{
|
||||
lookup = sites.emplace(site, result.sites.size()).first;
|
||||
result.sites.push_back({site});
|
||||
}
|
||||
auto &target{result.sites[lookup->second]};
|
||||
target.bytes += record.size;
|
||||
++target.blocks;
|
||||
}
|
||||
std::sort(result.sites.begin(), result.sites.end(), [](heap_site const &Left, heap_site const &Right) { return Left.bytes > Right.bytes; });
|
||||
return result;
|
||||
}
|
||||
|
||||
process_memory query_process_memory()
|
||||
{
|
||||
process_memory result;
|
||||
|
||||
@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// memory of the process, as the system counts it, for the debug panel
|
||||
@@ -38,6 +39,31 @@ struct heap_memory
|
||||
|
||||
heap_memory query_heap_memory();
|
||||
|
||||
// the counting of new and delete is off until this turns it on (debug.memorystats in the ini): it costs time with every block the
|
||||
// program takes, which shows most in the loading of a scenery. once on, it stays on; the blocks taken before aren't counted
|
||||
void start_heap_counting();
|
||||
bool heap_counting();
|
||||
|
||||
// where the large blocks taken with new come from: the blocks of LARGE_BLOCK bytes or more, live now, summed up by the place in the
|
||||
// program which took them (the first function of the call stack outside the standard library, and the one which called it)
|
||||
struct heap_site
|
||||
{
|
||||
std::string place;
|
||||
std::uint64_t bytes{0};
|
||||
std::uint64_t blocks{0};
|
||||
};
|
||||
|
||||
struct large_blocks
|
||||
{
|
||||
static constexpr std::uint64_t LARGE_BLOCK{256 * 1024};
|
||||
std::vector<heap_site> sites; // the most memory first
|
||||
std::uint64_t untracked{0}; // blocks taken while the table of them was full, since the counting began
|
||||
};
|
||||
|
||||
// goes through the call stacks of the blocks, which takes a while the first time (the symbols of the program get loaded). not for
|
||||
// more threads at once
|
||||
large_blocks query_large_blocks();
|
||||
|
||||
// what a part of the program keeps in memory: things and bytes
|
||||
struct memory_count
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user