blob: 67a189bd89949fd634d93251b7a2d0a8fde6f58c [file] [edit]
/* ----------------------------------------------------------------------------
Copyright (c) 2026-2026, Microsoft Research, Daniel Schwartz-Narbonne, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
// Tests for the mimalloc heap profiler (src/profile.c).
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include "mimalloc.h"
#include "mimalloc-profile.h"
#include "mimalloc/decl.h"
#include "testhelper.h"
// ---------------------------------------------------------------------------
// Shared callback state (not thread safe!)
// ---------------------------------------------------------------------------
typedef struct {
mi_profiler_t profiler;
uint64_t alloc_count;
uint64_t free_count;
size_t last_size;
uint64_t last_upscaled;
void* last_ptr;
} my_profiler_t;
static inline my_profiler_t* downcast( mi_profiler_t* prof ) {
return (my_profiler_t*)prof;
}
// We store ptr in user_data so on_free can verify the round-trip.
#define TEST_THRESHOLD (16 * 1024)
static size_t mi_cdecl on_alloc(mi_profiler_t* profiler, mi_profiler_sample_data_t* data, void* ptr, size_t requested_size, size_t threshold, uint64_t bytes_since_last_sample, mi_heap_t* heap) {
MI_UNUSED(threshold); MI_UNUSED(heap); MI_UNUSED(requested_size);
my_profiler_t* prof = downcast(profiler);
assert(bytes_since_last_sample >= requested_size);
prof->alloc_count++;
prof->last_ptr = ptr;
prof->last_size = requested_size;
prof->last_upscaled = bytes_since_last_sample;
// store ptr to verify round-trip
assert(data->user_data_size >= sizeof(void*));
assert(data->user_data_size >= prof->profiler.sample_data_size);
data->user_data[0] = ptr;
return TEST_THRESHOLD;
}
static void mi_cdecl on_free(mi_profiler_t* profiler, mi_profiler_sample_data_t* data, void* ptr, mi_heap_t* heap) {
MI_UNUSED(heap); MI_UNUSED_RELEASE(data); MI_UNUSED_RELEASE(ptr);
my_profiler_t* prof = downcast(profiler);
prof->free_count++;
// verify the user_data round-trip
assert(data->user_data[0] == ptr);
}
static my_profiler_t my_profiler = {
{ // profiler_t
NULL, // reserved
sizeof(void*), // needed sample data size
0, // initial sample rate (default)
&on_alloc,
&on_free,
NULL, // on_realloc
NULL, // on_snapshot
},
0, 0, 0, 0, NULL
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// Force at least one sample by allocating well over the threshold.
static void allocate_past_threshold(void) {
size_t total = 0;
while (total < TEST_THRESHOLD * 10) {
void* p = mi_malloc(4096);
mi_free(p);
total += 4096;
// mi_collect()
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
bool test_profiler_samples(void) {
CHECK_BODY("profiler: on_alloc called after threshold") {
uint64_t before = my_profiler.alloc_count;
allocate_past_threshold();
result = (my_profiler.alloc_count > before);
}
return true;
}
#define MAXLOOP 100000
bool test_profiler_record_fields(void) {
CHECK_BODY("profiler: record ptr and size are non-zero") {
uint64_t before = my_profiler.alloc_count;
int count;
for (count = 0; my_profiler.alloc_count == before && count < MAXLOOP; count++) {
void* p = mi_malloc(1024);
mi_free(p);
}
assert(count!=MAXLOOP);
result = (my_profiler.last_ptr != NULL && my_profiler.last_size > 0 && my_profiler.last_upscaled > 0 && count!=MAXLOOP);
}
return true;
}
bool test_profiler_on_free_called(void) {
CHECK_BODY("profiler: on_free called for sampled allocation") {
uint64_t alloc_before = my_profiler.alloc_count;
uint64_t free_before = my_profiler.free_count;
// Keep the pointer live until we confirm a sample was taken, then free it.
void* sampled = NULL;
int count;
for (count = 0; my_profiler.alloc_count == alloc_before && count < MAXLOOP; count++) {
if (sampled) { mi_free(sampled); }
sampled = mi_malloc(1024);
}
// At this point my_profiler.last_ptr is the sampled pointer.
// Free it and check on_free fires.
void* expected = my_profiler.last_ptr;
mi_free(expected);
sampled = NULL;
assert(count!=MAXLOOP);
result = (my_profiler.free_count > free_before && count!=MAXLOOP);
}
return true;
}
bool test_profiler_upscaled_at_least_size(void) {
CHECK_BODY("profiler: upscaled_size >= size") {
uint64_t before = my_profiler.alloc_count;
int count;
for (count = 0; my_profiler.alloc_count == before && count < MAXLOOP; count++) {
void* p = mi_malloc(256);
mi_free(p);
}
assert(count!=MAXLOOP);
result = (my_profiler.last_upscaled >= my_profiler.last_size && count!=MAXLOOP);
}
return true;
}
bool test_profiler_free_count_le_alloc_count(void) {
CHECK_BODY("profiler: on_free never called more times than on_alloc") {
// Free can only fire for sampled allocations, so free_count <= alloc_count
// must hold at all times.
allocate_past_threshold();
result = (my_profiler.free_count <= my_profiler.alloc_count);
}
return true;
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(void) {
mi_profile(&my_profiler.profiler);
mi_profiler_start(&my_profiler.profiler);
test_profiler_upscaled_at_least_size();
test_profiler_samples();
test_profiler_record_fields();
test_profiler_on_free_called();
test_profiler_free_count_le_alloc_count();
mi_profiler_stop(&my_profiler.profiler);
return print_test_summary();
}