Arena debug visualization

This commit is contained in:
2025-08-15 10:47:40 -04:00
parent 0b9bd2e819
commit 28fa96011f
4 changed files with 418 additions and 11 deletions

View File

@@ -4,9 +4,12 @@
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <new>
#include <type_traits>
#include <utility>
#include <vector>
/**
* @brief A high-performance arena allocator for bulk allocations.
@@ -366,6 +369,174 @@ public:
return current_block_ ? current_block_->block_count : 0;
}
/**
* @brief Debug function to visualize the arena's layout and contents.
*
* Prints a detailed breakdown of all blocks, memory usage, and allocation
* patterns. This is useful for understanding memory fragmentation and
* allocation behavior during development and debugging.
*
* Output includes:
* - Overall arena statistics (total allocated, used, blocks)
* - Per-block breakdown with sizes and usage
* - Memory utilization percentages
* - Block chain visualization
* - Optional memory content visualization
*
* @param out Output stream to write debug information to (default: std::cout)
* @param show_memory_map If true, shows a visual memory map of used/free
* space
* @param show_content If true, shows actual memory contents in hex and ASCII
* @param content_limit Maximum bytes of content to show per block (default:
* 256)
*
* ## Example Output:
* ```
* === Arena Debug Dump ===
* Total allocated: 3072 bytes across 2 blocks
* Currently used: 1500 bytes (48.8% utilization)
* Available in current: 572 bytes
*
* Block Chain (newest to oldest):
* Block #2: 2048 bytes [used: 572/2048 = 27.9%] <- current
* Block #1: 1024 bytes [used: 1024/1024 = 100.0%]
*
* Memory Contents:
* Block #2 (first 256 bytes):
* 0x0000: 48656c6c 6f20576f 726c6400 54657374 |Hello World.Test|
* ```
*/
void debug_dump(std::ostream &out = std::cout, bool show_memory_map = false,
bool show_content = false, size_t content_limit = 256) const {
out << "=== Arena Debug Dump ===" << std::endl;
if (!current_block_) {
out << "Arena is empty (no blocks allocated)" << std::endl;
out << "Initial block size: " << initial_block_size_ << " bytes"
<< std::endl;
return;
}
// Overall statistics
size_t total_alloc = this->total_allocated();
size_t used = used_bytes();
double utilization = total_alloc > 0 ? (100.0 * used / total_alloc) : 0.0;
out << "Total allocated: " << total_alloc << " bytes across "
<< num_blocks() << " blocks" << std::endl;
out << "Currently used: " << used << " bytes (" << std::fixed
<< std::setprecision(1) << utilization << "% utilization)" << std::endl;
out << "Available in current: " << available_in_current_block() << " bytes"
<< std::endl;
out << std::endl;
// Build list of blocks from current to first
std::vector<Block *> blocks;
Block *block = current_block_;
while (block) {
blocks.push_back(block);
block = block->prev;
}
out << "Block Chain (newest to oldest):" << std::endl;
// Display blocks in reverse order (current first)
for (size_t i = 0; i < blocks.size(); ++i) {
Block *b = blocks[i];
// Calculate used bytes in this specific block
size_t block_used;
if (i == 0) {
// Current block - use current_offset_
block_used = current_offset_;
} else {
// Previous blocks are fully used
block_used = b->size;
}
double block_util = b->size > 0 ? (100.0 * block_used / b->size) : 0.0;
out << "Block #" << (blocks.size() - i) << ": " << b->size << " bytes "
<< "[used: " << block_used << "/" << b->size << " = " << std::fixed
<< std::setprecision(1) << block_util << "%]";
if (i == 0) {
out << " <- current";
}
out << std::endl;
// Show memory map if requested
if (show_memory_map && b->size > 0) {
const size_t map_width = 60;
size_t used_chars = (map_width * block_used) / b->size;
used_chars = std::min(used_chars, map_width);
out << " [";
for (size_t j = 0; j < map_width; ++j) {
if (j < used_chars) {
out << "#";
} else {
out << ".";
}
}
out << "] (# = used, . = free)" << std::endl;
}
}
out << std::endl;
out << "Block addresses and relationships:" << std::endl;
for (size_t i = 0; i < blocks.size(); ++i) {
Block *b = blocks[i];
out << "Block #" << (blocks.size() - i) << " @ " << static_cast<void *>(b)
<< " -> data @ " << static_cast<void *>(b->data());
if (b->prev) {
out << " (prev: " << static_cast<void *>(b->prev) << ")";
} else {
out << " (first block)";
}
out << std::endl;
}
// Show memory contents if requested
if (show_content) {
out << std::endl;
out << "Memory Contents:" << std::endl;
for (size_t i = 0; i < blocks.size(); ++i) {
Block *b = blocks[i];
size_t block_num = blocks.size() - i;
// Calculate used bytes in this specific block
size_t block_used;
if (i == 0) {
// Current block - use current_offset_
block_used = current_offset_;
} else {
// Previous blocks are fully used
block_used = b->size;
}
if (block_used == 0) {
out << "Block #" << block_num << ": No content (empty)" << std::endl;
continue;
}
size_t bytes_to_show = std::min(block_used, content_limit);
out << "Block #" << block_num << " (first " << bytes_to_show << " of "
<< block_used << " used bytes):" << std::endl;
const char *data = b->data();
dump_memory_contents(out, data, bytes_to_show);
if (bytes_to_show < block_used) {
out << " ... (" << (block_used - bytes_to_show) << " more bytes)"
<< std::endl;
}
out << std::endl;
}
}
}
private:
/**
* @brief Add a new block with the specified size to the allocator.
@@ -416,6 +587,57 @@ private:
return (value + alignment - 1) & ~(alignment - 1);
}
/**
* @brief Dump memory contents in hex/ASCII format.
*
* Displays memory in the classic hex dump format with 16 bytes per line,
* showing both hexadecimal values and ASCII representation.
*
* @param out Output stream to write to
* @param data Pointer to the memory to dump
* @param size Number of bytes to dump
*/
static void dump_memory_contents(std::ostream &out, const char *data,
size_t size) {
const size_t bytes_per_line = 16;
for (size_t offset = 0; offset < size; offset += bytes_per_line) {
// Print offset
out << " 0x" << std::setfill('0') << std::setw(4) << std::hex << offset
<< ": ";
size_t bytes_in_line = std::min(bytes_per_line, size - offset);
// Print hex bytes
for (size_t i = 0; i < bytes_per_line; ++i) {
if (i < bytes_in_line) {
unsigned char byte = static_cast<unsigned char>(data[offset + i]);
out << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(byte);
} else {
out << " "; // Padding for incomplete lines
}
// Add space every 4 bytes for readability
if ((i + 1) % 4 == 0) {
out << " ";
}
}
// Print ASCII representation
out << " |";
for (size_t i = 0; i < bytes_in_line; ++i) {
char c = data[offset + i];
if (c >= 32 && c <= 126) { // Printable ASCII
out << c;
} else {
out << '.'; // Non-printable characters
}
}
out << "|" << std::dec << std::endl;
}
}
/// Size used for the first block and baseline for geometric growth
size_t initial_block_size_;
/// Pointer to the current (most recent) block, or nullptr if no blocks exist