diff --git a/CMakeLists.txt b/CMakeLists.txt
index dbd400c8..68153468 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -641,6 +641,7 @@ endif()
install(FILES include/mimalloc.h DESTINATION ${mi_install_incdir})
install(FILES include/mimalloc-override.h DESTINATION ${mi_install_incdir})
install(FILES include/mimalloc-new-delete.h DESTINATION ${mi_install_incdir})
+install(FILES include/mimalloc-stats.h DESTINATION ${mi_install_incdir})
install(FILES cmake/mimalloc-config.cmake DESTINATION ${mi_install_cmakedir})
install(FILES cmake/mimalloc-config-version.cmake DESTINATION ${mi_install_cmakedir})
diff --git a/ide/vs2022/mimalloc-lib.vcxproj b/ide/vs2022/mimalloc-lib.vcxproj
index c6c2cb5f..abdac1d1 100644
--- a/ide/vs2022/mimalloc-lib.vcxproj
+++ b/ide/vs2022/mimalloc-lib.vcxproj
@@ -486,6 +486,7 @@
+
diff --git a/ide/vs2022/mimalloc-lib.vcxproj.filters b/ide/vs2022/mimalloc-lib.vcxproj.filters
index 3acf4015..f88907ba 100644
--- a/ide/vs2022/mimalloc-lib.vcxproj.filters
+++ b/ide/vs2022/mimalloc-lib.vcxproj.filters
@@ -93,6 +93,9 @@
Headers
+
+ Headers
+
diff --git a/include/mimalloc-stats.h b/include/mimalloc-stats.h
new file mode 100644
index 00000000..4dc4c381
--- /dev/null
+++ b/include/mimalloc-stats.h
@@ -0,0 +1,102 @@
+/* ----------------------------------------------------------------------------
+Copyright (c) 2018-2025, Microsoft Research, 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.
+-----------------------------------------------------------------------------*/
+#pragma once
+#ifndef MIMALLOC_STATS_H
+#define MIMALLOC_STATS_H
+
+#include
+#include
+
+#define MI_STAT_VERSION 1 // increased on every backward incompatible change
+
+// count allocation over time
+typedef struct mi_stat_count_s {
+ int64_t total; // total allocated
+ int64_t peak; // peak allocation
+ int64_t current; // current allocation
+} mi_stat_count_t;
+
+// counters only increase
+typedef struct mi_stat_counter_s {
+ int64_t total; // total count
+} mi_stat_counter_t;
+
+#define MI_STAT_FIELDS() \
+ MI_STAT_COUNT(pages) /* count of mimalloc pages */ \
+ MI_STAT_COUNT(reserved) /* reserved memory bytes */ \
+ MI_STAT_COUNT(committed) /* committed bytes */ \
+ MI_STAT_COUNT(reset) /* reset bytes */ \
+ MI_STAT_COUNT(purged) /* purged bytes */ \
+ MI_STAT_COUNT(page_committed) /* committed memory inside pages */ \
+ MI_STAT_COUNT(pages_abandoned) /* abandonded pages count */ \
+ MI_STAT_COUNT(threads) /* number of threads */ \
+ MI_STAT_COUNT(malloc_normal) /* allocated bytes <= MI_LARGE_OBJ_SIZE_MAX */ \
+ MI_STAT_COUNT(malloc_huge) /* allocated bytes in huge pages */ \
+ MI_STAT_COUNT(malloc_requested) /* malloc requested bytes */ \
+ \
+ MI_STAT_COUNTER(mmap_calls) \
+ MI_STAT_COUNTER(commit_calls) \
+ MI_STAT_COUNTER(reset_calls) \
+ MI_STAT_COUNTER(purge_calls) \
+ MI_STAT_COUNTER(arena_count) /* number of memory arena's */ \
+ MI_STAT_COUNTER(malloc_normal_count) /* number of blocks <= MI_LARGE_OBJ_SIZE_MAX */ \
+ MI_STAT_COUNTER(malloc_huge_count) /* number of huge bloks */ \
+ MI_STAT_COUNTER(malloc_guarded_count) /* number of allocations with guard pages */ \
+ \
+ /* internal statistics */ \
+ MI_STAT_COUNTER(arena_rollback_count) \
+ MI_STAT_COUNTER(pages_extended) /* number of page extensions */ \
+ MI_STAT_COUNTER(pages_retire) /* number of pages that are retired */ \
+ MI_STAT_COUNTER(page_searches) /* searches for a fresh page */ \
+ /* only on v1 and v2 */ \
+ MI_STAT_COUNT(segments) \
+ MI_STAT_COUNT(segments_abandoned) \
+ MI_STAT_COUNT(segments_cache) \
+ MI_STAT_COUNT(_segments_reserved) \
+ /* only on v3 */ \
+ MI_STAT_COUNTER(pages_reclaim_on_alloc) \
+ MI_STAT_COUNTER(pages_reclaim_on_free) \
+ MI_STAT_COUNTER(pages_reabandon_full) \
+ MI_STAT_COUNTER(pages_unabandon_busy_wait) \
+
+
+// Define the statistics structure
+#define MI_BIN_HUGE (73U) // see types.h
+#define MI_STAT_COUNT(stat) mi_stat_count_t stat;
+#define MI_STAT_COUNTER(stat) mi_stat_counter_t stat;
+
+typedef struct mi_stats_s
+{
+ int version;
+
+ MI_STAT_FIELDS()
+
+ // future extension
+ mi_stat_count_t _stat_reserved[4];
+ mi_stat_counter_t _stat_counter_reserved[4];
+
+ // size segregated statistics
+ mi_stat_count_t malloc_bins[MI_BIN_HUGE+1]; // allocation per size bin
+ mi_stat_count_t page_bins[MI_BIN_HUGE+1]; // pages allocated per size bin
+} mi_stats_t;
+
+#undef MI_STAT_COUNT
+#undef MI_STAT_COUNTER
+
+// Exported definitions
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+mi_decl_export void mi_stats_get( size_t stats_size, mi_stats_t* stats ) mi_attr_noexcept;
+mi_decl_export const char* mi_stats_get_json( size_t buf_size, char* buf ) mi_attr_noexcept; // use mi_free to free the result if the input buf == NULL
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MIMALLOC_STATS_H
\ No newline at end of file
diff --git a/include/mimalloc/types.h b/include/mimalloc/types.h
index c27489d0..4c912107 100644
--- a/include/mimalloc/types.h
+++ b/include/mimalloc/types.h
@@ -21,6 +21,7 @@ terms of the MIT license. A copy of the license can be found in the file
// --------------------------------------------------------------------------
+#include
#include // ptrdiff_t
#include // uintptr_t, uint16_t, etc
#include "atomic.h" // _Atomic
@@ -515,135 +516,10 @@ struct mi_heap_s {
};
-
-// ------------------------------------------------------
-// Debug
-// ------------------------------------------------------
-
-#if !defined(MI_DEBUG_UNINIT)
-#define MI_DEBUG_UNINIT (0xD0)
-#endif
-#if !defined(MI_DEBUG_FREED)
-#define MI_DEBUG_FREED (0xDF)
-#endif
-#if !defined(MI_DEBUG_PADDING)
-#define MI_DEBUG_PADDING (0xDE)
-#endif
-
-#if (MI_DEBUG)
-// use our own assertion to print without memory allocation
-void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func );
-#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__))
-#else
-#define mi_assert(x)
-#endif
-
-#if (MI_DEBUG>1)
-#define mi_assert_internal mi_assert
-#else
-#define mi_assert_internal(x)
-#endif
-
-#if (MI_DEBUG>2)
-#define mi_assert_expensive mi_assert
-#else
-#define mi_assert_expensive(x)
-#endif
-
-// ------------------------------------------------------
-// Statistics
-// ------------------------------------------------------
-
-#ifndef MI_STAT
-#if (MI_DEBUG>0)
-#define MI_STAT 2
-#else
-#define MI_STAT 0
-#endif
-#endif
-
-typedef struct mi_stat_count_s {
- int64_t total;
- int64_t peak;
- int64_t current;
-} mi_stat_count_t;
-
-typedef struct mi_stat_counter_s {
- int64_t total;
-} mi_stat_counter_t;
-
-typedef struct mi_stats_s {
- mi_stat_count_t pages; // count of mimalloc pages
- mi_stat_count_t reserved; // reserved memory bytes
- mi_stat_count_t committed; // committed bytes
- mi_stat_count_t reset; // reset bytes
- mi_stat_count_t purged; // purged bytes
- mi_stat_count_t page_committed; // committed memory inside pages
- mi_stat_count_t pages_abandoned; // abandonded pages count
- mi_stat_count_t threads; // number of threads
- mi_stat_count_t malloc_normal; // allocated bytes <= MI_LARGE_OBJ_SIZE_MAX
- mi_stat_count_t malloc_huge; // allocated bytes in huge pages
- mi_stat_count_t malloc_requested; // malloc requested bytes
-
- mi_stat_counter_t mmap_calls;
- mi_stat_counter_t commit_calls;
- mi_stat_counter_t reset_calls;
- mi_stat_counter_t purge_calls;
- mi_stat_counter_t arena_count; // number of memory arena's
- mi_stat_counter_t malloc_normal_count; // number of blocks <= MI_LARGE_OBJ_SIZE_MAX
- mi_stat_counter_t malloc_huge_count; // number of huge bloks
- mi_stat_counter_t malloc_guarded_count; // number of allocations with guard pages
-
- // internal statistics
- mi_stat_counter_t arena_rollback_count;
- mi_stat_counter_t pages_extended; // number of page extensions
- mi_stat_counter_t pages_retire; // number of pages that are retired
- mi_stat_counter_t page_searches; // searches for a fresh page
- // only on v1 and v2
- mi_stat_count_t segments;
- mi_stat_count_t segments_abandoned;
- mi_stat_count_t segments_cache;
- mi_stat_count_t _segments_reserved;
- // only on v3
- mi_stat_counter_t pages_reclaim_on_alloc;
- mi_stat_counter_t pages_reclaim_on_free;
- mi_stat_counter_t pages_reabandon_full;
- mi_stat_counter_t pages_unabandon_busy_wait;
-
- // future extension
- mi_stat_count_t _stat_reserved[4];
- mi_stat_counter_t _stat_counter_reserved[4];
-
- // size segregated statistics
- mi_stat_count_t malloc_bins[MI_BIN_HUGE+1]; // allocation per size bin
- mi_stat_count_t page_bins[MI_BIN_HUGE+1]; // pages allocated per size bin
-} mi_stats_t;
-
-
-// add to stat keeping track of the peak
-void _mi_stat_increase(mi_stat_count_t* stat, size_t amount);
-void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount);
-// counters can just be increased
-void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
-
-#if (MI_STAT)
-#define mi_stat_increase(stat,amount) _mi_stat_increase( &(stat), amount)
-#define mi_stat_decrease(stat,amount) _mi_stat_decrease( &(stat), amount)
-#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount)
-#else
-#define mi_stat_increase(stat,amount) ((void)0)
-#define mi_stat_decrease(stat,amount) ((void)0)
-#define mi_stat_counter_increase(stat,amount) ((void)0)
-#endif
-
-#define mi_heap_stat_counter_increase(heap,stat,amount) mi_stat_counter_increase( (heap)->tld->stats.stat, amount)
-#define mi_heap_stat_increase(heap,stat,amount) mi_stat_increase( (heap)->tld->stats.stat, amount)
-#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount)
-
-
// ------------------------------------------------------
// Sub processes do not reclaim or visit segments
-// from other sub processes
+// from other sub processes. These are essentially the
+// static variables of a process.
// ------------------------------------------------------
struct mi_subproc_s {
@@ -656,6 +532,7 @@ struct mi_subproc_s {
mi_memid_t memid; // provenance of this memory block
};
+
// ------------------------------------------------------
// Thread Local data
// ------------------------------------------------------
@@ -693,4 +570,73 @@ struct mi_tld_s {
mi_stats_t stats; // statistics
};
+
+
+// ------------------------------------------------------
+// Debug
+// ------------------------------------------------------
+
+#if !defined(MI_DEBUG_UNINIT)
+#define MI_DEBUG_UNINIT (0xD0)
+#endif
+#if !defined(MI_DEBUG_FREED)
+#define MI_DEBUG_FREED (0xDF)
+#endif
+#if !defined(MI_DEBUG_PADDING)
+#define MI_DEBUG_PADDING (0xDE)
+#endif
+
+#if (MI_DEBUG)
+// use our own assertion to print without memory allocation
+void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func );
+#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__))
+#else
+#define mi_assert(x)
+#endif
+
+#if (MI_DEBUG>1)
+#define mi_assert_internal mi_assert
+#else
+#define mi_assert_internal(x)
+#endif
+
+#if (MI_DEBUG>2)
+#define mi_assert_expensive mi_assert
+#else
+#define mi_assert_expensive(x)
+#endif
+
+
+// ------------------------------------------------------
+// Statistics
+// ------------------------------------------------------
+#ifndef MI_STAT
+#if (MI_DEBUG>0)
+#define MI_STAT 2
+#else
+#define MI_STAT 0
+#endif
+#endif
+
+// add to stat keeping track of the peak
+void _mi_stat_increase(mi_stat_count_t* stat, size_t amount);
+void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount);
+// counters can just be increased
+void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
+
+#if (MI_STAT)
+#define mi_stat_increase(stat,amount) _mi_stat_increase( &(stat), amount)
+#define mi_stat_decrease(stat,amount) _mi_stat_decrease( &(stat), amount)
+#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount)
+#else
+#define mi_stat_increase(stat,amount) ((void)0)
+#define mi_stat_decrease(stat,amount) ((void)0)
+#define mi_stat_counter_increase(stat,amount) ((void)0)
+#endif
+
+#define mi_heap_stat_counter_increase(heap,stat,amount) mi_stat_counter_increase( (heap)->tld->stats.stat, amount)
+#define mi_heap_stat_increase(heap,stat,amount) mi_stat_increase( (heap)->tld->stats.stat, amount)
+#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount)
+
+
#endif
diff --git a/src/heap.c b/src/heap.c
index 28161266..7c235a7b 100644
--- a/src/heap.c
+++ b/src/heap.c
@@ -167,6 +167,11 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
// collect arenas (this is program wide so don't force purges on abandonment of threads)
_mi_arenas_collect(collect == MI_FORCE /* force purge? */);
+
+ // merge statistics
+ if (collect <= MI_FORCE) {
+ mi_stats_merge();
+ }
}
void _mi_heap_collect_abandon(mi_heap_t* heap) {
diff --git a/src/init.c b/src/init.c
index f91e279b..f4fc6798 100644
--- a/src/init.c
+++ b/src/init.c
@@ -134,7 +134,7 @@ static mi_decl_cache_align mi_tld_t tld_main = {
0, 0, 0, 0, 0, &mi_subproc_default,
&tld_main.stats
}, // segments
- { MI_STATS_NULL } // stats
+ { MI_STAT_VERSION, MI_STATS_NULL } // stats
};
mi_decl_cache_align mi_heap_t _mi_heap_main = {
@@ -159,7 +159,7 @@ mi_decl_cache_align mi_heap_t _mi_heap_main = {
bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`.
-mi_stats_t _mi_stats_main = { MI_STATS_NULL };
+mi_stats_t _mi_stats_main = { MI_STAT_VERSION, MI_STATS_NULL };
#if MI_GUARDED
mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) {
diff --git a/src/page-queue.c b/src/page-queue.c
index 9fc5f924..3507505d 100644
--- a/src/page-queue.c
+++ b/src/page-queue.c
@@ -136,10 +136,15 @@ static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t*
}
#endif
+static size_t mi_page_bin(const mi_page_t* page) {
+ const size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));
+ mi_assert_internal(bin <= MI_BIN_FULL);
+ return bin;
+}
+
static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) {
mi_assert_internal(heap!=NULL);
- size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));
- mi_assert_internal(bin <= MI_BIN_FULL);
+ const size_t bin = mi_page_bin(page);
mi_page_queue_t* pq = &heap->pages[bin];
mi_assert_internal((mi_page_block_size(page) == pq->block_size) ||
(mi_page_is_huge(page) && mi_page_queue_is_huge(pq)) ||
diff --git a/src/page.c b/src/page.c
index df802c7d..ea6535bd 100644
--- a/src/page.c
+++ b/src/page.c
@@ -290,6 +290,7 @@ static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size
mi_assert_internal(full_block_size >= block_size);
mi_page_init(heap, page, full_block_size, heap->tld);
mi_heap_stat_increase(heap, pages, 1);
+ mi_heap_stat_increase(heap, page_bins[mi_page_bin(page)], 1);
if (pq != NULL) { mi_page_queue_push(heap, pq, page); }
mi_assert_expensive(_mi_page_is_valid(page));
return page;
@@ -438,10 +439,12 @@ void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
// remove from the page list
// (no need to do _mi_heap_delayed_free first as all blocks are already free)
- mi_segments_tld_t* segments_tld = &mi_page_heap(page)->tld->segments;
+ mi_heap_t* heap = mi_page_heap(page);
+ mi_segments_tld_t* segments_tld = &heap->tld->segments;
mi_page_queue_remove(pq, page);
// and free it
+ mi_heap_stat_decrease(heap, page_bins[mi_page_bin(page)], 1);
mi_page_set_heap(page,NULL);
_mi_segment_page_free(page, force, segments_tld);
}
diff --git a/src/stats.c b/src/stats.c
index 65d6d00b..36c7f23c 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -63,7 +63,7 @@ void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
// must be thread safe as it is called from stats_merge
-static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src) {
+static void mi_stat_count_add(mi_stat_count_t* stat, const mi_stat_count_t* src) {
if (stat==src) return;
if (src->total!=0) { mi_atomic_addi64_relaxed(&stat->total, src->total); }
if (src->current!=0) { mi_atomic_addi64_relaxed(&stat->current, src->current); }
@@ -78,58 +78,29 @@ static void mi_stat_counter_add(mi_stat_counter_t* stat, const mi_stat_counter_t
if (src->total!=0) { mi_atomic_addi64_relaxed(&stat->total, src->total); }
}
+#define MI_STAT_COUNT(stat) mi_stat_count_add(&stats->stat, &src->stat);
+#define MI_STAT_COUNTER(stat) mi_stat_counter_add(&stats->stat, &src->stat);
+
// must be thread safe as it is called from stats_merge
static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
if (stats==src) return;
- mi_stat_add(&stats->pages, &src->pages);
- mi_stat_add(&stats->reserved, &src->reserved);
- mi_stat_add(&stats->committed, &src->committed);
- mi_stat_add(&stats->reset, &src->reset);
- mi_stat_add(&stats->purged, &src->purged);
- mi_stat_add(&stats->page_committed, &src->page_committed);
- mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned);
- mi_stat_add(&stats->threads, &src->threads);
- mi_stat_add(&stats->malloc_normal, &src->malloc_normal);
- mi_stat_add(&stats->malloc_huge, &src->malloc_huge);
- mi_stat_add(&stats->malloc_requested, &src->malloc_requested);
- mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls);
- mi_stat_counter_add(&stats->commit_calls, &src->commit_calls);
- mi_stat_counter_add(&stats->reset_calls, &src->reset_calls);
- mi_stat_counter_add(&stats->purge_calls, &src->purge_calls);
- mi_stat_counter_add(&stats->arena_count, &src->arena_count);
- mi_stat_counter_add(&stats->malloc_normal_count, &src->malloc_normal_count);
- mi_stat_counter_add(&stats->malloc_huge_count, &src->malloc_huge_count);
- mi_stat_counter_add(&stats->malloc_guarded_count, &src->malloc_guarded_count);
+ // copy all fields
+ MI_STAT_FIELDS()
- mi_stat_counter_add(&stats->arena_rollback_count, &src->arena_rollback_count);
- mi_stat_counter_add(&stats->pages_extended, &src->pages_extended);
- mi_stat_counter_add(&stats->pages_retire, &src->pages_retire);
- mi_stat_counter_add(&stats->page_searches, &src->page_searches);
-
- mi_stat_add(&stats->segments, &src->segments);
- mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned);
- mi_stat_add(&stats->segments_cache, &src->segments_cache);
-
- mi_stat_counter_add(&stats->pages_reclaim_on_alloc, &src->pages_reclaim_on_alloc);
- mi_stat_counter_add(&stats->pages_reclaim_on_free, &src->pages_reclaim_on_free);
- mi_stat_counter_add(&stats->pages_reabandon_full, &src->pages_reabandon_full);
- mi_stat_counter_add(&stats->pages_unabandon_busy_wait, &src->pages_unabandon_busy_wait);
-
-#if MI_STAT>1
+ #if MI_STAT>1
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
- // if (src->normal_bins[i].total != 0 && src->normal_bins[i].current != 0) {
- mi_stat_add(&stats->malloc_bins[i], &src->malloc_bins[i]);
- //}
+ mi_stat_count_add(&stats->malloc_bins[i], &src->malloc_bins[i]);
}
+ #endif
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
- // if (src->normal_bins[i].total != 0 && src->normal_bins[i].current != 0) {
- mi_stat_add(&stats->page_bins[i], &src->page_bins[i]);
- //}
- }
-#endif
+ mi_stat_count_add(&stats->page_bins[i], &src->page_bins[i]);
+ }
}
+#undef MI_STAT_COUNT
+#undef MI_STAT_COUNTER
+
/* -----------------------------------------------------------
Display statistics
----------------------------------------------------------- */
@@ -318,8 +289,8 @@ static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0)
mi_stat_print(&stats->malloc_normal, "normal", (stats->malloc_normal_count.total == 0 ? 1 : -1), out, arg);
mi_stat_print(&stats->malloc_huge, "huge", (stats->malloc_huge_count.total == 0 ? 1 : -1), out, arg);
mi_stat_count_t total = { 0,0,0 };
- mi_stat_add(&total, &stats->malloc_normal);
- mi_stat_add(&total, &stats->malloc_huge);
+ mi_stat_count_add(&total, &stats->malloc_normal);
+ mi_stat_count_add(&total, &stats->malloc_huge);
mi_stat_print_ex(&total, "total", 1, out, arg, "");
#endif
#if MI_STAT>1
@@ -466,3 +437,159 @@ mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, s
if (peak_commit!=NULL) *peak_commit = pinfo.peak_commit;
if (page_faults!=NULL) *page_faults = pinfo.page_faults;
}
+
+
+// --------------------------------------------------------
+// Return statistics
+// --------------------------------------------------------
+
+void mi_stats_get(size_t stats_size, mi_stats_t* stats) mi_attr_noexcept {
+ if (stats == NULL || stats_size == 0) return;
+ _mi_memzero(stats, stats_size);
+ const size_t size = (stats_size > sizeof(mi_stats_t) ? sizeof(mi_stats_t) : stats_size);
+ _mi_memcpy(stats, &_mi_stats_main, size);
+ stats->version = MI_STAT_VERSION;
+}
+
+
+// --------------------------------------------------------
+// Statics in json format
+// --------------------------------------------------------
+
+typedef struct mi_heap_buf_s {
+ char* buf;
+ size_t size;
+ size_t used;
+ bool can_realloc;
+} mi_heap_buf_t;
+
+static bool mi_heap_buf_expand(mi_heap_buf_t* hbuf) {
+ if (hbuf==NULL) return false;
+ if (hbuf->buf != NULL && hbuf->size>0) {
+ hbuf->buf[hbuf->size-1] = 0;
+ }
+ if (hbuf->size > SIZE_MAX/2 || !hbuf->can_realloc) return false;
+ const size_t newsize = (hbuf->size == 0 ? 2*MI_KiB : 2*hbuf->size);
+ char* const newbuf = (char*)mi_rezalloc(hbuf->buf, newsize);
+ if (newbuf == NULL) return false;
+ hbuf->buf = newbuf;
+ hbuf->size = newsize;
+ return true;
+}
+
+static void mi_heap_buf_print(mi_heap_buf_t* hbuf, const char* msg) {
+ if (msg==NULL || hbuf==NULL) return;
+ if (hbuf->used + 1 >= hbuf->size && !hbuf->can_realloc) return;
+ for (const char* src = msg; *src != 0; src++) {
+ char c = *src;
+ if (hbuf->used + 1 >= hbuf->size) {
+ if (!mi_heap_buf_expand(hbuf)) return;
+ }
+ mi_assert_internal(hbuf->used < hbuf->size);
+ hbuf->buf[hbuf->used++] = c;
+ }
+ mi_assert_internal(hbuf->used < hbuf->size);
+ hbuf->buf[hbuf->used] = 0;
+}
+
+static void mi_heap_buf_print_count_bin(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, size_t bin, bool add_comma) {
+ const size_t binsize = _mi_bin_size(bin);
+ const size_t pagesize = (binsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_SMALL_PAGE_SIZE :
+ (binsize <= MI_MEDIUM_OBJ_SIZE_MAX ? MI_MEDIUM_PAGE_SIZE :
+ (binsize <= MI_LARGE_OBJ_SIZE_MAX ? MI_LARGE_PAGE_SIZE : 0)));
+ char buf[128];
+ _mi_snprintf(buf, 128, "%s{ \"total\": %lld, \"peak\": %lld, \"current\": %lld, \"block_size\": %zu, \"page_size\": %zu }%s\n", prefix, stat->total, stat->peak, stat->current, binsize, pagesize, (add_comma ? "," : ""));
+ buf[127] = 0;
+ mi_heap_buf_print(hbuf, buf);
+}
+
+static void mi_heap_buf_print_count(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, bool add_comma) {
+ char buf[128];
+ _mi_snprintf(buf, 128, "%s{ \"total\": %lld, \"peak\": %lld, \"current\": %lld }%s\n", prefix, stat->total, stat->peak, stat->current, (add_comma ? "," : ""));
+ buf[127] = 0;
+ mi_heap_buf_print(hbuf, buf);
+}
+
+static void mi_heap_buf_print_count_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_count_t* stat) {
+ char buf[128];
+ _mi_snprintf(buf, 128, " \"%s\": ", name);
+ buf[127] = 0;
+ mi_heap_buf_print(hbuf, buf);
+ mi_heap_buf_print_count(hbuf, "", stat, true);
+}
+
+static void mi_heap_buf_print_value(mi_heap_buf_t* hbuf, const char* name, int64_t val) {
+ char buf[128];
+ _mi_snprintf(buf, 128, " \"%s\": %lld,\n", name, val);
+ buf[127] = 0;
+ mi_heap_buf_print(hbuf, buf);
+}
+
+static void mi_heap_buf_print_size(mi_heap_buf_t* hbuf, const char* name, size_t val, bool add_comma) {
+ char buf[128];
+ _mi_snprintf(buf, 128, " \"%s\": %zu%s\n", name, val, (add_comma ? "," : ""));
+ buf[127] = 0;
+ mi_heap_buf_print(hbuf, buf);
+}
+
+static void mi_heap_buf_print_counter_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_counter_t* stat) {
+ mi_heap_buf_print_value(hbuf, name, stat->total);
+}
+
+#define MI_STAT_COUNT(stat) mi_heap_buf_print_count_value(&hbuf, #stat, &stats->stat);
+#define MI_STAT_COUNTER(stat) mi_heap_buf_print_counter_value(&hbuf, #stat, &stats->stat);
+
+const char* mi_stats_get_json(size_t output_size, char* output_buf) mi_attr_noexcept {
+ mi_heap_buf_t hbuf = { NULL, 0, 0, true };
+ if (output_size > 0 && output_buf != NULL) {
+ _mi_memzero(output_buf, output_size);
+ hbuf.buf = output_buf;
+ hbuf.size = output_size;
+ hbuf.can_realloc = false;
+ }
+ else {
+ if (!mi_heap_buf_expand(&hbuf)) return NULL;
+ }
+ mi_heap_buf_print(&hbuf, "{\n");
+ mi_heap_buf_print_value(&hbuf, "version", MI_STAT_VERSION);
+ mi_heap_buf_print_value(&hbuf, "mimalloc_version", MI_MALLOC_VERSION);
+
+ // process info
+ mi_heap_buf_print(&hbuf, " \"process\": {\n");
+ size_t elapsed;
+ size_t user_time;
+ size_t sys_time;
+ size_t current_rss;
+ size_t peak_rss;
+ size_t current_commit;
+ size_t peak_commit;
+ size_t page_faults;
+ mi_process_info(&elapsed, &user_time, &sys_time, ¤t_rss, &peak_rss, ¤t_commit, &peak_commit, &page_faults);
+ mi_heap_buf_print_size(&hbuf, "elapsed_msecs", elapsed, true);
+ mi_heap_buf_print_size(&hbuf, "user_msecs", user_time, true);
+ mi_heap_buf_print_size(&hbuf, "system_msecs", sys_time, true);
+ mi_heap_buf_print_size(&hbuf, "page_faults", page_faults, true);
+ mi_heap_buf_print_size(&hbuf, "rss_current", current_rss, true);
+ mi_heap_buf_print_size(&hbuf, "rss_peak", peak_rss, true);
+ mi_heap_buf_print_size(&hbuf, "commit_current", current_commit, true);
+ mi_heap_buf_print_size(&hbuf, "commit_peak", peak_commit, false);
+ mi_heap_buf_print(&hbuf, " },\n");
+
+ // statistics
+ mi_stats_t* stats = &_mi_stats_main;
+ MI_STAT_FIELDS()
+
+ // size bins
+ mi_heap_buf_print(&hbuf, " \"malloc_bins\": [\n");
+ for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
+ mi_heap_buf_print_count_bin(&hbuf, " ", &stats->malloc_bins[i], i, i!=MI_BIN_HUGE);
+ }
+ mi_heap_buf_print(&hbuf, " ],\n");
+ mi_heap_buf_print(&hbuf, " \"page_bins\": [\n");
+ for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
+ mi_heap_buf_print_count_bin(&hbuf, " ", &stats->page_bins[i], i, i!=MI_BIN_HUGE);
+ }
+ mi_heap_buf_print(&hbuf, " ]\n");
+ mi_heap_buf_print(&hbuf, "}\n");
+ return hbuf.buf;
+}
diff --git a/test/test-stress.c b/test/test-stress.c
index c41954d3..bf95a0f5 100644
--- a/test/test-stress.c
+++ b/test/test-stress.c
@@ -36,7 +36,7 @@ static int ITER = 400;
static int THREADS = 8;
static int SCALE = 25;
static int ITER = 20;
-#elif defined(MI_GUARDED) // with debug guard pages reduce parameters to stay within the azure pipeline limits
+#elif 1 || defined(MI_GUARDED) // with debug guard pages reduce parameters to stay within the azure pipeline limits
static int THREADS = 8;
static int SCALE = 10;
static int ITER = 10;
@@ -61,6 +61,7 @@ static bool main_participates = false; // main thread participates as a
#define custom_free(p) free(p)
#else
#include
+#include
#define custom_calloc(n,s) mi_calloc(n,s)
#define custom_realloc(p,s) mi_realloc(p,s)
#define custom_free(p) mi_free(p)
@@ -330,8 +331,15 @@ int main(int argc, char** argv) {
#ifndef NDEBUG
mi_debug_show_arenas();
mi_collect(true);
+
+ const char* json = mi_stats_get_json(0, NULL);
+ if (json != NULL) {
+ puts(json);
+ mi_free(json);
+ }
+
#endif
- mi_stats_print(NULL);
+ mi_stats_print(NULL);
#endif
//bench_end_program();
return 0;