mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-23 22:53:58 +00:00
malloc: Keep some stats and dump them at process exit if LIBC_DUMP_MALLOC_STATS is set
Very bare-bones and barely useful. Can go away once the perf_event-based malloc tracking is further along.
This commit is contained in:
parent
c44d2515b4
commit
22f701d657
Notes:
sideshowbarker
2024-07-19 03:30:03 +09:00
Author: https://github.com/nico Commit: https://github.com/SerenityOS/serenity/commit/22f701d6570 Pull-request: https://github.com/SerenityOS/serenity/pull/3186
3 changed files with 72 additions and 2 deletions
|
@ -25,6 +25,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/InlineLinkedList.h>
|
#include <AK/InlineLinkedList.h>
|
||||||
|
#include <AK/LogStream.h>
|
||||||
#include <AK/ScopedValueRollback.h>
|
#include <AK/ScopedValueRollback.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibThread/Lock.h>
|
#include <LibThread/Lock.h>
|
||||||
|
@ -72,6 +73,29 @@ static bool s_profiling = false;
|
||||||
static unsigned short size_classes[] = { 8, 16, 32, 64, 128, 252, 508, 1016, 2036, 4090, 8188, 16376, 32756, 0 };
|
static unsigned short size_classes[] = { 8, 16, 32, 64, 128, 252, 508, 1016, 2036, 4090, 8188, 16376, 32756, 0 };
|
||||||
static constexpr size_t num_size_classes = sizeof(size_classes) / sizeof(unsigned short);
|
static constexpr size_t num_size_classes = sizeof(size_classes) / sizeof(unsigned short);
|
||||||
|
|
||||||
|
struct MallocStats {
|
||||||
|
size_t number_of_malloc_calls;
|
||||||
|
|
||||||
|
size_t number_of_big_allocator_hits;
|
||||||
|
size_t number_of_big_allocator_purge_hits;
|
||||||
|
size_t number_of_big_allocs;
|
||||||
|
|
||||||
|
size_t number_of_empty_block_hits;
|
||||||
|
size_t number_of_empty_block_purge_hits;
|
||||||
|
size_t number_of_block_allocs;
|
||||||
|
size_t number_of_blocks_full;
|
||||||
|
|
||||||
|
size_t number_of_free_calls;
|
||||||
|
|
||||||
|
size_t number_of_big_allocator_keeps;
|
||||||
|
size_t number_of_big_allocator_frees;
|
||||||
|
|
||||||
|
size_t number_of_freed_full_blocks;
|
||||||
|
size_t number_of_keeps;
|
||||||
|
size_t number_of_frees;
|
||||||
|
};
|
||||||
|
static MallocStats g_malloc_stats = {};
|
||||||
|
|
||||||
constexpr size_t block_size = 64 * KiB;
|
constexpr size_t block_size = 64 * KiB;
|
||||||
constexpr size_t block_mask = ~(block_size - 1);
|
constexpr size_t block_mask = ~(block_size - 1);
|
||||||
|
|
||||||
|
@ -207,6 +231,8 @@ static void* malloc_impl(size_t size)
|
||||||
if (!size)
|
if (!size)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
g_malloc_stats.number_of_malloc_calls++;
|
||||||
|
|
||||||
size_t good_size;
|
size_t good_size;
|
||||||
auto* allocator = allocator_for_size(size, good_size);
|
auto* allocator = allocator_for_size(size, good_size);
|
||||||
|
|
||||||
|
@ -215,6 +241,7 @@ static void* malloc_impl(size_t size)
|
||||||
#ifdef RECYCLE_BIG_ALLOCATIONS
|
#ifdef RECYCLE_BIG_ALLOCATIONS
|
||||||
if (auto* allocator = big_allocator_for_size(real_size)) {
|
if (auto* allocator = big_allocator_for_size(real_size)) {
|
||||||
if (!allocator->blocks.is_empty()) {
|
if (!allocator->blocks.is_empty()) {
|
||||||
|
g_malloc_stats.number_of_big_allocator_hits++;
|
||||||
auto* block = allocator->blocks.take_last();
|
auto* block = allocator->blocks.take_last();
|
||||||
int rc = madvise(block, real_size, MADV_SET_NONVOLATILE);
|
int rc = madvise(block, real_size, MADV_SET_NONVOLATILE);
|
||||||
bool this_block_was_purged = rc == 1;
|
bool this_block_was_purged = rc == 1;
|
||||||
|
@ -226,14 +253,17 @@ static void* malloc_impl(size_t size)
|
||||||
perror("mprotect");
|
perror("mprotect");
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
if (this_block_was_purged)
|
if (this_block_was_purged) {
|
||||||
|
g_malloc_stats.number_of_big_allocator_purge_hits++;
|
||||||
new (block) BigAllocationBlock(real_size);
|
new (block) BigAllocationBlock(real_size);
|
||||||
|
}
|
||||||
|
|
||||||
ue_notify_malloc(&block->m_slot[0], size);
|
ue_notify_malloc(&block->m_slot[0], size);
|
||||||
return &block->m_slot[0];
|
return &block->m_slot[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
g_malloc_stats.number_of_big_allocs++;
|
||||||
auto* block = (BigAllocationBlock*)os_alloc(real_size, "malloc: BigAllocationBlock");
|
auto* block = (BigAllocationBlock*)os_alloc(real_size, "malloc: BigAllocationBlock");
|
||||||
new (block) BigAllocationBlock(real_size);
|
new (block) BigAllocationBlock(real_size);
|
||||||
ue_notify_malloc(&block->m_slot[0], size);
|
ue_notify_malloc(&block->m_slot[0], size);
|
||||||
|
@ -248,6 +278,7 @@ static void* malloc_impl(size_t size)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!block && allocator->empty_block_count) {
|
if (!block && allocator->empty_block_count) {
|
||||||
|
g_malloc_stats.number_of_empty_block_hits++;
|
||||||
block = allocator->empty_blocks[--allocator->empty_block_count];
|
block = allocator->empty_blocks[--allocator->empty_block_count];
|
||||||
int rc = madvise(block, block_size, MADV_SET_NONVOLATILE);
|
int rc = madvise(block, block_size, MADV_SET_NONVOLATILE);
|
||||||
bool this_block_was_purged = rc == 1;
|
bool this_block_was_purged = rc == 1;
|
||||||
|
@ -260,12 +291,15 @@ static void* malloc_impl(size_t size)
|
||||||
perror("mprotect");
|
perror("mprotect");
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
if (this_block_was_purged)
|
if (this_block_was_purged) {
|
||||||
|
g_malloc_stats.number_of_empty_block_purge_hits++;
|
||||||
new (block) ChunkedBlock(good_size);
|
new (block) ChunkedBlock(good_size);
|
||||||
|
}
|
||||||
allocator->usable_blocks.append(block);
|
allocator->usable_blocks.append(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!block) {
|
if (!block) {
|
||||||
|
g_malloc_stats.number_of_block_allocs++;
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
snprintf(buffer, sizeof(buffer), "malloc: ChunkedBlock(%zu)", good_size);
|
snprintf(buffer, sizeof(buffer), "malloc: ChunkedBlock(%zu)", good_size);
|
||||||
block = (ChunkedBlock*)os_alloc(block_size, buffer);
|
block = (ChunkedBlock*)os_alloc(block_size, buffer);
|
||||||
|
@ -278,6 +312,7 @@ static void* malloc_impl(size_t size)
|
||||||
void* ptr = block->m_freelist;
|
void* ptr = block->m_freelist;
|
||||||
block->m_freelist = block->m_freelist->next;
|
block->m_freelist = block->m_freelist->next;
|
||||||
if (block->is_full()) {
|
if (block->is_full()) {
|
||||||
|
g_malloc_stats.number_of_blocks_full++;
|
||||||
#ifdef MALLOC_DEBUG
|
#ifdef MALLOC_DEBUG
|
||||||
dbgprintf("Block %p is now full in size class %zu\n", block, good_size);
|
dbgprintf("Block %p is now full in size class %zu\n", block, good_size);
|
||||||
#endif
|
#endif
|
||||||
|
@ -302,6 +337,8 @@ static void free_impl(void* ptr)
|
||||||
if (!ptr)
|
if (!ptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
g_malloc_stats.number_of_free_calls++;
|
||||||
|
|
||||||
LOCKER(malloc_lock());
|
LOCKER(malloc_lock());
|
||||||
|
|
||||||
void* block_base = (void*)((FlatPtr)ptr & block_mask);
|
void* block_base = (void*)((FlatPtr)ptr & block_mask);
|
||||||
|
@ -312,6 +349,7 @@ static void free_impl(void* ptr)
|
||||||
#ifdef RECYCLE_BIG_ALLOCATIONS
|
#ifdef RECYCLE_BIG_ALLOCATIONS
|
||||||
if (auto* allocator = big_allocator_for_size(block->m_size)) {
|
if (auto* allocator = big_allocator_for_size(block->m_size)) {
|
||||||
if (allocator->blocks.size() < number_of_big_blocks_to_keep_around_per_size_class) {
|
if (allocator->blocks.size() < number_of_big_blocks_to_keep_around_per_size_class) {
|
||||||
|
g_malloc_stats.number_of_big_allocator_keeps++;
|
||||||
allocator->blocks.append(block);
|
allocator->blocks.append(block);
|
||||||
size_t this_block_size = block->m_size;
|
size_t this_block_size = block->m_size;
|
||||||
if (mprotect(block, this_block_size, PROT_NONE) < 0) {
|
if (mprotect(block, this_block_size, PROT_NONE) < 0) {
|
||||||
|
@ -326,6 +364,7 @@ static void free_impl(void* ptr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
g_malloc_stats.number_of_big_allocator_frees++;
|
||||||
os_free(block, block->m_size);
|
os_free(block, block->m_size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -350,6 +389,7 @@ static void free_impl(void* ptr)
|
||||||
#ifdef MALLOC_DEBUG
|
#ifdef MALLOC_DEBUG
|
||||||
dbgprintf("Block %p no longer full in size class %u\n", block, good_size);
|
dbgprintf("Block %p no longer full in size class %u\n", block, good_size);
|
||||||
#endif
|
#endif
|
||||||
|
g_malloc_stats.number_of_freed_full_blocks++;
|
||||||
allocator->full_blocks.remove(block);
|
allocator->full_blocks.remove(block);
|
||||||
allocator->usable_blocks.prepend(block);
|
allocator->usable_blocks.prepend(block);
|
||||||
}
|
}
|
||||||
|
@ -363,6 +403,7 @@ static void free_impl(void* ptr)
|
||||||
#ifdef MALLOC_DEBUG
|
#ifdef MALLOC_DEBUG
|
||||||
dbgprintf("Keeping block %p around for size class %u\n", block, good_size);
|
dbgprintf("Keeping block %p around for size class %u\n", block, good_size);
|
||||||
#endif
|
#endif
|
||||||
|
g_malloc_stats.number_of_keeps++;
|
||||||
allocator->usable_blocks.remove(block);
|
allocator->usable_blocks.remove(block);
|
||||||
allocator->empty_blocks[allocator->empty_block_count++] = block;
|
allocator->empty_blocks[allocator->empty_block_count++] = block;
|
||||||
mprotect(block, block_size, PROT_NONE);
|
mprotect(block, block_size, PROT_NONE);
|
||||||
|
@ -372,6 +413,7 @@ static void free_impl(void* ptr)
|
||||||
#ifdef MALLOC_DEBUG
|
#ifdef MALLOC_DEBUG
|
||||||
dbgprintf("Releasing block %p for size class %u\n", block, good_size);
|
dbgprintf("Releasing block %p for size class %u\n", block, good_size);
|
||||||
#endif
|
#endif
|
||||||
|
g_malloc_stats.number_of_frees++;
|
||||||
allocator->usable_blocks.remove(block);
|
allocator->usable_blocks.remove(block);
|
||||||
--allocator->block_count;
|
--allocator->block_count;
|
||||||
os_free(block, block_size);
|
os_free(block, block_size);
|
||||||
|
@ -454,4 +496,27 @@ void __malloc_init()
|
||||||
|
|
||||||
new (&big_allocators()[0])(BigAllocator);
|
new (&big_allocators()[0])(BigAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void serenity_dump_malloc_stats()
|
||||||
|
{
|
||||||
|
dbg() << "# malloc() calls: " << g_malloc_stats.number_of_malloc_calls;
|
||||||
|
dbg();
|
||||||
|
dbg() << "big alloc hits: " << g_malloc_stats.number_of_big_allocator_hits;
|
||||||
|
dbg() << "big alloc hits that were purged: " << g_malloc_stats.number_of_big_allocator_purge_hits;
|
||||||
|
dbg() << "big allocs: " << g_malloc_stats.number_of_big_allocs;
|
||||||
|
dbg();
|
||||||
|
dbg() << "empty block hits: " << g_malloc_stats.number_of_empty_block_hits;
|
||||||
|
dbg() << "empty block hits that were purged: " << g_malloc_stats.number_of_empty_block_purge_hits;
|
||||||
|
dbg() << "block allocs: " << g_malloc_stats.number_of_block_allocs;
|
||||||
|
dbg() << "filled blocks: " << g_malloc_stats.number_of_blocks_full;
|
||||||
|
dbg();
|
||||||
|
dbg() << "# free() calls: " << g_malloc_stats.number_of_free_calls;
|
||||||
|
dbg();
|
||||||
|
dbg() << "big alloc keeps: " << g_malloc_stats.number_of_big_allocator_keeps;
|
||||||
|
dbg() << "big alloc frees: " << g_malloc_stats.number_of_big_allocator_frees;
|
||||||
|
dbg();
|
||||||
|
dbg() << "full block frees: " << g_malloc_stats.number_of_freed_full_blocks;
|
||||||
|
dbg() << "number of keeps: " << g_malloc_stats.number_of_keeps;
|
||||||
|
dbg() << "number of frees: " << g_malloc_stats.number_of_frees;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,6 +201,10 @@ extern "C" {
|
||||||
void exit(int status)
|
void exit(int status)
|
||||||
{
|
{
|
||||||
__cxa_finalize(nullptr);
|
__cxa_finalize(nullptr);
|
||||||
|
|
||||||
|
if (getenv("LIBC_DUMP_MALLOC_STATS"))
|
||||||
|
serenity_dump_malloc_stats();
|
||||||
|
|
||||||
extern void _fini();
|
extern void _fini();
|
||||||
_fini();
|
_fini();
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
|
@ -41,6 +41,7 @@ __BEGIN_DECLS
|
||||||
__attribute__((malloc)) __attribute__((alloc_size(1))) void* malloc(size_t);
|
__attribute__((malloc)) __attribute__((alloc_size(1))) void* malloc(size_t);
|
||||||
__attribute__((malloc)) __attribute__((alloc_size(1, 2))) void* calloc(size_t nmemb, size_t);
|
__attribute__((malloc)) __attribute__((alloc_size(1, 2))) void* calloc(size_t nmemb, size_t);
|
||||||
size_t malloc_size(void*);
|
size_t malloc_size(void*);
|
||||||
|
void serenity_dump_malloc_stats(void);
|
||||||
void free(void*);
|
void free(void*);
|
||||||
__attribute__((alloc_size(2))) void* realloc(void* ptr, size_t);
|
__attribute__((alloc_size(2))) void* realloc(void* ptr, size_t);
|
||||||
char* getenv(const char* name);
|
char* getenv(const char* name);
|
||||||
|
|
Loading…
Reference in a new issue