Path: blob/main/system/lib/mimalloc/src/stats.c
6175 views
/* ----------------------------------------------------------------------------1Copyright (c) 2018-2021, Microsoft Research, Daan Leijen2This is free software; you can redistribute it and/or modify it under the3terms of the MIT license. A copy of the license can be found in the file4"LICENSE" at the root of this distribution.5-----------------------------------------------------------------------------*/6#include "mimalloc.h"7#include "mimalloc/internal.h"8#include "mimalloc/atomic.h"9#include "mimalloc/prim.h"1011#include <string.h> // memset1213#if defined(_MSC_VER) && (_MSC_VER < 1920)14#pragma warning(disable:4204) // non-constant aggregate initializer15#endif1617/* -----------------------------------------------------------18Statistics operations19----------------------------------------------------------- */2021static bool mi_is_in_main(void* stat) {22return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main23&& (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t)));24}2526static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {27if (amount == 0) return;28if (mi_is_in_main(stat))29{30// add atomically (for abandoned pages)31int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount);32mi_atomic_maxi64_relaxed(&stat->peak, current + amount);33if (amount > 0) {34mi_atomic_addi64_relaxed(&stat->allocated,amount);35}36else {37mi_atomic_addi64_relaxed(&stat->freed, -amount);38}39}40else {41// add thread local42stat->current += amount;43if (stat->current > stat->peak) stat->peak = stat->current;44if (amount > 0) {45stat->allocated += amount;46}47else {48stat->freed += -amount;49}50}51}5253void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {54if (mi_is_in_main(stat)) {55mi_atomic_addi64_relaxed( &stat->count, 1 );56mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount );57}58else {59stat->count++;60stat->total += amount;61}62}6364void _mi_stat_increase(mi_stat_count_t* stat, size_t amount) {65mi_stat_update(stat, (int64_t)amount);66}6768void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {69mi_stat_update(stat, -((int64_t)amount));70}7172// must be thread safe as it is called from stats_merge73static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) {74if (stat==src) return;75if (src->allocated==0 && src->freed==0) return;76mi_atomic_addi64_relaxed( &stat->allocated, src->allocated * unit);77mi_atomic_addi64_relaxed( &stat->current, src->current * unit);78mi_atomic_addi64_relaxed( &stat->freed, src->freed * unit);79// peak scores do not work across threads..80mi_atomic_addi64_relaxed( &stat->peak, src->peak * unit);81}8283static void mi_stat_counter_add(mi_stat_counter_t* stat, const mi_stat_counter_t* src, int64_t unit) {84if (stat==src) return;85mi_atomic_addi64_relaxed( &stat->total, src->total * unit);86mi_atomic_addi64_relaxed( &stat->count, src->count * unit);87}8889// must be thread safe as it is called from stats_merge90static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {91if (stats==src) return;92mi_stat_add(&stats->segments, &src->segments,1);93mi_stat_add(&stats->pages, &src->pages,1);94mi_stat_add(&stats->reserved, &src->reserved, 1);95mi_stat_add(&stats->committed, &src->committed, 1);96mi_stat_add(&stats->reset, &src->reset, 1);97mi_stat_add(&stats->purged, &src->purged, 1);98mi_stat_add(&stats->page_committed, &src->page_committed, 1);99100mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned, 1);101mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned, 1);102mi_stat_add(&stats->threads, &src->threads, 1);103104mi_stat_add(&stats->malloc, &src->malloc, 1);105mi_stat_add(&stats->segments_cache, &src->segments_cache, 1);106mi_stat_add(&stats->normal, &src->normal, 1);107mi_stat_add(&stats->huge, &src->huge, 1);108mi_stat_add(&stats->large, &src->large, 1);109110mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1);111mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1);112mi_stat_counter_add(&stats->commit_calls, &src->commit_calls, 1);113mi_stat_counter_add(&stats->reset_calls, &src->reset_calls, 1);114mi_stat_counter_add(&stats->purge_calls, &src->purge_calls, 1);115116mi_stat_counter_add(&stats->page_no_retire, &src->page_no_retire, 1);117mi_stat_counter_add(&stats->searches, &src->searches, 1);118mi_stat_counter_add(&stats->normal_count, &src->normal_count, 1);119mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1);120mi_stat_counter_add(&stats->large_count, &src->large_count, 1);121#if MI_STAT>1122for (size_t i = 0; i <= MI_BIN_HUGE; i++) {123if (src->normal_bins[i].allocated > 0 || src->normal_bins[i].freed > 0) {124mi_stat_add(&stats->normal_bins[i], &src->normal_bins[i], 1);125}126}127#endif128}129130/* -----------------------------------------------------------131Display statistics132----------------------------------------------------------- */133134// unit > 0 : size in binary bytes135// unit == 0: count as decimal136// unit < 0 : count in binary137static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) {138char buf[32]; buf[0] = 0;139int len = 32;140const char* suffix = (unit <= 0 ? " " : "B");141const int64_t base = (unit == 0 ? 1000 : 1024);142if (unit>0) n *= unit;143144const int64_t pos = (n < 0 ? -n : n);145if (pos < base) {146if (n!=1 || suffix[0] != 'B') { // skip printing 1 B for the unit column147_mi_snprintf(buf, len, "%lld %-3s", (long long)n, (n==0 ? "" : suffix));148}149}150else {151int64_t divider = base;152const char* magnitude = "K";153if (pos >= divider*base) { divider *= base; magnitude = "M"; }154if (pos >= divider*base) { divider *= base; magnitude = "G"; }155const int64_t tens = (n / (divider/10));156const long whole = (long)(tens/10);157const long frac1 = (long)(tens%10);158char unitdesc[8];159_mi_snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix);160_mi_snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);161}162_mi_fprintf(out, arg, (fmt==NULL ? "%12s" : fmt), buf);163}164165166static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {167mi_printf_amount(n,unit,out,arg,NULL);168}169170static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {171if (unit==1) _mi_fprintf(out, arg, "%12s"," ");172else mi_print_amount(n,0,out,arg);173}174175static void mi_stat_print_ex(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg, const char* notok ) {176_mi_fprintf(out, arg,"%10s:", msg);177if (unit != 0) {178if (unit > 0) {179mi_print_amount(stat->peak, unit, out, arg);180mi_print_amount(stat->allocated, unit, out, arg);181mi_print_amount(stat->freed, unit, out, arg);182mi_print_amount(stat->current, unit, out, arg);183mi_print_amount(unit, 1, out, arg);184mi_print_count(stat->allocated, unit, out, arg);185}186else {187mi_print_amount(stat->peak, -1, out, arg);188mi_print_amount(stat->allocated, -1, out, arg);189mi_print_amount(stat->freed, -1, out, arg);190mi_print_amount(stat->current, -1, out, arg);191if (unit == -1) {192_mi_fprintf(out, arg, "%24s", "");193}194else {195mi_print_amount(-unit, 1, out, arg);196mi_print_count((stat->allocated / -unit), 0, out, arg);197}198}199if (stat->allocated > stat->freed) {200_mi_fprintf(out, arg, " ");201_mi_fprintf(out, arg, (notok == NULL ? "not all freed" : notok));202_mi_fprintf(out, arg, "\n");203}204else {205_mi_fprintf(out, arg, " ok\n");206}207}208else {209mi_print_amount(stat->peak, 1, out, arg);210mi_print_amount(stat->allocated, 1, out, arg);211_mi_fprintf(out, arg, "%11s", " "); // no freed212mi_print_amount(stat->current, 1, out, arg);213_mi_fprintf(out, arg, "\n");214}215}216217static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {218mi_stat_print_ex(stat, msg, unit, out, arg, NULL);219}220221static void mi_stat_peak_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {222_mi_fprintf(out, arg, "%10s:", msg);223mi_print_amount(stat->peak, unit, out, arg);224_mi_fprintf(out, arg, "\n");225}226227static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {228_mi_fprintf(out, arg, "%10s:", msg);229mi_print_amount(stat->total, -1, out, arg);230_mi_fprintf(out, arg, "\n");231}232233234static void mi_stat_counter_print_avg(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) {235const int64_t avg_tens = (stat->count == 0 ? 0 : (stat->total*10 / stat->count));236const long avg_whole = (long)(avg_tens/10);237const long avg_frac1 = (long)(avg_tens%10);238_mi_fprintf(out, arg, "%10s: %5ld.%ld avg\n", msg, avg_whole, avg_frac1);239}240241242static void mi_print_header(mi_output_fun* out, void* arg ) {243_mi_fprintf(out, arg, "%10s: %11s %11s %11s %11s %11s %11s\n", "heap stats", "peak ", "total ", "freed ", "current ", "unit ", "count ");244}245246#if MI_STAT>1247static void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) {248bool found = false;249char buf[64];250for (size_t i = 0; i <= max; i++) {251if (bins[i].allocated > 0) {252found = true;253int64_t unit = _mi_bin_size((uint8_t)i);254_mi_snprintf(buf, 64, "%s %3lu", fmt, (long)i);255mi_stat_print(&bins[i], buf, unit, out, arg);256}257}258if (found) {259_mi_fprintf(out, arg, "\n");260mi_print_header(out, arg);261}262}263#endif264265266267//------------------------------------------------------------268// Use an output wrapper for line-buffered output269// (which is nice when using loggers etc.)270//------------------------------------------------------------271typedef struct buffered_s {272mi_output_fun* out; // original output function273void* arg; // and state274char* buf; // local buffer of at least size `count+1`275size_t used; // currently used chars `used <= count`276size_t count; // total chars available for output277} buffered_t;278279static void mi_buffered_flush(buffered_t* buf) {280buf->buf[buf->used] = 0;281_mi_fputs(buf->out, buf->arg, NULL, buf->buf);282buf->used = 0;283}284285static void mi_cdecl mi_buffered_out(const char* msg, void* arg) {286buffered_t* buf = (buffered_t*)arg;287if (msg==NULL || buf==NULL) return;288for (const char* src = msg; *src != 0; src++) {289char c = *src;290if (buf->used >= buf->count) mi_buffered_flush(buf);291mi_assert_internal(buf->used < buf->count);292buf->buf[buf->used++] = c;293if (c == '\n') mi_buffered_flush(buf);294}295}296297//------------------------------------------------------------298// Print statistics299//------------------------------------------------------------300301static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept {302// wrap the output function to be line buffered303char buf[256];304buffered_t buffer = { out0, arg0, NULL, 0, 255 };305buffer.buf = buf;306mi_output_fun* out = &mi_buffered_out;307void* arg = &buffer;308309// and print using that310mi_print_header(out,arg);311#if MI_STAT>1312mi_stats_print_bins(stats->normal_bins, MI_BIN_HUGE, "normal",out,arg);313#endif314#if MI_STAT315mi_stat_print(&stats->normal, "normal", (stats->normal_count.count == 0 ? 1 : -(stats->normal.allocated / stats->normal_count.count)), out, arg);316mi_stat_print(&stats->large, "large", (stats->large_count.count == 0 ? 1 : -(stats->large.allocated / stats->large_count.count)), out, arg);317mi_stat_print(&stats->huge, "huge", (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg);318mi_stat_count_t total = { 0,0,0,0 };319mi_stat_add(&total, &stats->normal, 1);320mi_stat_add(&total, &stats->large, 1);321mi_stat_add(&total, &stats->huge, 1);322mi_stat_print(&total, "total", 1, out, arg);323#endif324#if MI_STAT>1325mi_stat_print(&stats->malloc, "malloc req", 1, out, arg);326_mi_fprintf(out, arg, "\n");327#endif328mi_stat_print_ex(&stats->reserved, "reserved", 1, out, arg, "");329mi_stat_print_ex(&stats->committed, "committed", 1, out, arg, "");330mi_stat_peak_print(&stats->reset, "reset", 1, out, arg );331mi_stat_peak_print(&stats->purged, "purged", 1, out, arg );332mi_stat_print(&stats->page_committed, "touched", 1, out, arg);333mi_stat_print(&stats->segments, "segments", -1, out, arg);334mi_stat_print(&stats->segments_abandoned, "-abandoned", -1, out, arg);335mi_stat_print(&stats->segments_cache, "-cached", -1, out, arg);336mi_stat_print(&stats->pages, "pages", -1, out, arg);337mi_stat_print(&stats->pages_abandoned, "-abandoned", -1, out, arg);338mi_stat_counter_print(&stats->pages_extended, "-extended", out, arg);339mi_stat_counter_print(&stats->page_no_retire, "-noretire", out, arg);340mi_stat_counter_print(&stats->arena_count, "arenas", out, arg);341mi_stat_counter_print(&stats->arena_crossover_count, "-crossover", out, arg);342mi_stat_counter_print(&stats->arena_rollback_count, "-rollback", out, arg);343mi_stat_counter_print(&stats->mmap_calls, "mmaps", out, arg);344mi_stat_counter_print(&stats->commit_calls, "commits", out, arg);345mi_stat_counter_print(&stats->reset_calls, "resets", out, arg);346mi_stat_counter_print(&stats->purge_calls, "purges", out, arg);347mi_stat_print(&stats->threads, "threads", -1, out, arg);348mi_stat_counter_print_avg(&stats->searches, "searches", out, arg);349_mi_fprintf(out, arg, "%10s: %5zu\n", "numa nodes", _mi_os_numa_node_count());350351size_t elapsed;352size_t user_time;353size_t sys_time;354size_t current_rss;355size_t peak_rss;356size_t current_commit;357size_t peak_commit;358size_t page_faults;359mi_process_info(&elapsed, &user_time, &sys_time, ¤t_rss, &peak_rss, ¤t_commit, &peak_commit, &page_faults);360_mi_fprintf(out, arg, "%10s: %5ld.%03ld s\n", "elapsed", elapsed/1000, elapsed%1000);361_mi_fprintf(out, arg, "%10s: user: %ld.%03ld s, system: %ld.%03ld s, faults: %lu, rss: ", "process",362user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, (unsigned long)page_faults );363mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s");364if (peak_commit > 0) {365_mi_fprintf(out, arg, ", commit: ");366mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s");367}368_mi_fprintf(out, arg, "\n");369}370371static mi_msecs_t mi_process_start; // = 0372373static mi_stats_t* mi_stats_get_default(void) {374mi_heap_t* heap = mi_heap_get_default();375return &heap->tld->stats;376}377378static void mi_stats_merge_from(mi_stats_t* stats) {379if (stats != &_mi_stats_main) {380mi_stats_add(&_mi_stats_main, stats);381memset(stats, 0, sizeof(mi_stats_t));382}383}384385void mi_stats_reset(void) mi_attr_noexcept {386mi_stats_t* stats = mi_stats_get_default();387if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); }388memset(&_mi_stats_main, 0, sizeof(mi_stats_t));389if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); };390}391392void mi_stats_merge(void) mi_attr_noexcept {393mi_stats_merge_from( mi_stats_get_default() );394}395396void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done`397mi_stats_merge_from(stats);398}399400void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {401mi_stats_merge_from(mi_stats_get_default());402_mi_stats_print(&_mi_stats_main, out, arg);403}404405void mi_stats_print(void* out) mi_attr_noexcept {406// for compatibility there is an `out` parameter (which can be `stdout` or `stderr`)407mi_stats_print_out((mi_output_fun*)out, NULL);408}409410void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {411_mi_stats_print(mi_stats_get_default(), out, arg);412}413414415// ----------------------------------------------------------------416// Basic timer for convenience; use milli-seconds to avoid doubles417// ----------------------------------------------------------------418419static mi_msecs_t mi_clock_diff;420421mi_msecs_t _mi_clock_now(void) {422return _mi_prim_clock_now();423}424425mi_msecs_t _mi_clock_start(void) {426if (mi_clock_diff == 0.0) {427mi_msecs_t t0 = _mi_clock_now();428mi_clock_diff = _mi_clock_now() - t0;429}430return _mi_clock_now();431}432433mi_msecs_t _mi_clock_end(mi_msecs_t start) {434mi_msecs_t end = _mi_clock_now();435return (end - start - mi_clock_diff);436}437438439// --------------------------------------------------------440// Basic process statistics441// --------------------------------------------------------442443mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept444{445mi_process_info_t pinfo;446_mi_memzero_var(pinfo);447pinfo.elapsed = _mi_clock_end(mi_process_start);448pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current));449pinfo.peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak));450pinfo.current_rss = pinfo.current_commit;451pinfo.peak_rss = pinfo.peak_commit;452pinfo.utime = 0;453pinfo.stime = 0;454pinfo.page_faults = 0;455456_mi_prim_process_info(&pinfo);457458if (elapsed_msecs!=NULL) *elapsed_msecs = (pinfo.elapsed < 0 ? 0 : (pinfo.elapsed < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.elapsed : PTRDIFF_MAX));459if (user_msecs!=NULL) *user_msecs = (pinfo.utime < 0 ? 0 : (pinfo.utime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.utime : PTRDIFF_MAX));460if (system_msecs!=NULL) *system_msecs = (pinfo.stime < 0 ? 0 : (pinfo.stime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.stime : PTRDIFF_MAX));461if (current_rss!=NULL) *current_rss = pinfo.current_rss;462if (peak_rss!=NULL) *peak_rss = pinfo.peak_rss;463if (current_commit!=NULL) *current_commit = pinfo.current_commit;464if (peak_commit!=NULL) *peak_commit = pinfo.peak_commit;465if (page_faults!=NULL) *page_faults = pinfo.page_faults;466}467468469