Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/mimalloc/stats.c
Line
Count
Source
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2018-2021, Microsoft Research, Daan Leijen
3
This is free software; you can redistribute it and/or modify it under the
4
terms of the MIT license. A copy of the license can be found in the file
5
"LICENSE" at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include "mimalloc.h"
8
#include "mimalloc/internal.h"
9
#include "mimalloc/atomic.h"
10
#include "mimalloc/prim.h"
11
12
#include <string.h> // memset
13
14
#if defined(_MSC_VER) && (_MSC_VER < 1920)
15
#pragma warning(disable:4204)  // non-constant aggregate initializer
16
#endif
17
18
/* -----------------------------------------------------------
19
  Statistics operations
20
----------------------------------------------------------- */
21
22
0
static bool mi_is_in_main(void* stat) {
23
0
  return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main
24
0
         && (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t)));
25
0
}
26
27
0
static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {
28
0
  if (amount == 0) return;
29
0
  if mi_unlikely(mi_is_in_main(stat))
30
0
  {
31
    // add atomically (for abandoned pages)
32
0
    int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount);
33
    // if (stat == &_mi_stats_main.committed) { mi_assert_internal(current + amount >= 0); };
34
0
    mi_atomic_maxi64_relaxed(&stat->peak, current + amount);
35
0
    if (amount > 0) {
36
0
      mi_atomic_addi64_relaxed(&stat->total,amount);
37
0
    }
38
0
  }
39
0
  else {
40
    // add thread local
41
0
    stat->current += amount;
42
0
    if (stat->current > stat->peak) { stat->peak = stat->current; }
43
0
    if (amount > 0) { stat->total += amount; }
44
0
  }
45
0
}
46
47
0
void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {
48
0
  if (mi_is_in_main(stat)) {
49
0
    mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount );
50
0
  }
51
0
  else {
52
0
    stat->total += amount;
53
0
  }
54
0
}
55
56
0
void _mi_stat_increase(mi_stat_count_t* stat, size_t amount) {
57
0
  mi_stat_update(stat, (int64_t)amount);
58
0
}
59
60
0
void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
61
0
  mi_stat_update(stat, -((int64_t)amount));
62
0
}
63
64
65
0
static void mi_stat_adjust(mi_stat_count_t* stat, int64_t amount) {
66
0
  if (amount == 0) return;
67
0
  if mi_unlikely(mi_is_in_main(stat))
68
0
  {
69
    // adjust atomically
70
0
    mi_atomic_addi64_relaxed(&stat->current, amount);
71
0
    mi_atomic_addi64_relaxed(&stat->total,amount);
72
0
  }
73
0
  else {
74
    // adjust local
75
0
    stat->current += amount;
76
0
    stat->total += amount;
77
0
  }
78
0
}
79
80
0
void _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount) {
81
0
  mi_stat_adjust(stat, -((int64_t)amount));
82
0
}
83
84
85
// must be thread safe as it is called from stats_merge
86
0
static void mi_stat_count_add_mt(mi_stat_count_t* stat, const mi_stat_count_t* src) {
87
0
  if (stat==src) return;
88
0
  mi_atomic_void_addi64_relaxed(&stat->total, &src->total);
89
0
  const int64_t prev_current = mi_atomic_addi64_relaxed(&stat->current, src->current);
90
91
  // Global current plus thread peak approximates new global peak
92
  // note: peak scores do really not work across threads.
93
  // we used to just add them together but that often overestimates in practice.
94
  // similarly, max does not seem to work well. The current approach
95
  // by Artem Kharytoniuk (@artem-lunarg) seems to work better, see PR#1112
96
  // for a longer description.
97
0
  mi_atomic_maxi64_relaxed(&stat->peak, prev_current + src->peak);
98
0
}
99
100
0
static void mi_stat_counter_add_mt(mi_stat_counter_t* stat, const mi_stat_counter_t* src) {
101
0
  if (stat==src) return;
102
0
  mi_atomic_void_addi64_relaxed(&stat->total, &src->total);
103
0
}
104
105
0
#define MI_STAT_COUNT(stat)    mi_stat_count_add_mt(&stats->stat, &src->stat);
106
0
#define MI_STAT_COUNTER(stat)  mi_stat_counter_add_mt(&stats->stat, &src->stat);
107
108
// must be thread safe as it is called from stats_merge
109
0
static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
110
0
  if (stats==src) return;
111
112
  // copy all fields
113
0
  MI_STAT_FIELDS()
114
115
  #if MI_STAT>1
116
  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
117
    mi_stat_count_add_mt(&stats->malloc_bins[i], &src->malloc_bins[i]);
118
  }
119
  #endif
120
0
  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
121
0
    mi_stat_count_add_mt(&stats->page_bins[i], &src->page_bins[i]);
122
0
  }
123
0
}
124
125
#undef MI_STAT_COUNT
126
#undef MI_STAT_COUNTER
127
128
/* -----------------------------------------------------------
129
  Display statistics
130
----------------------------------------------------------- */
131
132
// unit > 0 : size in binary bytes
133
// unit == 0: count as decimal
134
// unit < 0 : count in binary
135
0
static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, bool limitwidth) {
136
0
  char buf[32]; buf[0] = 0;
137
0
  int  len = 32;
138
0
  const char* suffix = (unit <= 0 ? " " : "B");
139
0
  const int64_t base = (unit == 0 ? 1000 : 1024);
140
0
  if (unit>0) n *= unit;
141
142
0
  const int64_t pos = (n < 0 ? -n : n);
143
0
  if (pos < base) {
144
0
    if (n!=1 || suffix[0] != 'B') {  // skip printing 1 B for the unit column
145
0
      _mi_snprintf(buf, len, "%lld   %-3s", (long long)n, (n==0 ? "" : suffix));
146
0
    }
147
0
  }
148
0
  else {
149
0
    int64_t divider = base;
150
0
    const char* magnitude = "K";
151
0
    if (pos >= divider*base) { divider *= base; magnitude = "M"; }
152
0
    if (pos >= divider*base) { divider *= base; magnitude = "G"; }
153
0
    const int64_t tens = (n / (divider/10));
154
0
    const long whole = (long)(tens/10);
155
0
    const long frac1 = (long)(tens%10);
156
0
    char unitdesc[8];
157
0
    _mi_snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix);
158
0
    _mi_snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);
159
0
  }
160
0
  if (limitwidth) {
161
0
    _mi_fprintf(out, arg, "%12s", buf);
162
0
  }
163
0
  else {
164
0
    _mi_fprintf(out, arg, "%s", buf);
165
0
  }
166
0
}
167
168
169
0
static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
170
0
  mi_printf_amount(n,unit,out,arg,true);
171
0
}
172
173
0
static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
174
0
  if (unit==1) _mi_fprintf(out, arg, "%12s"," ");
175
0
          else mi_print_amount(n,0,out,arg);
176
0
}
177
178
0
static 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 ) {
179
0
  _mi_fprintf(out, arg,"%10s:", msg);
180
0
  if (unit != 0) {
181
0
    if (unit > 0) {
182
0
      mi_print_amount(stat->peak, unit, out, arg);
183
0
      mi_print_amount(stat->total, unit, out, arg);
184
      // mi_print_amount(stat->freed, unit, out, arg);
185
0
      mi_print_amount(stat->current, unit, out, arg);
186
0
      mi_print_amount(unit, 1, out, arg);
187
0
      mi_print_count(stat->total, unit, out, arg);
188
0
    }
189
0
    else {
190
0
      mi_print_amount(stat->peak, -1, out, arg);
191
0
      mi_print_amount(stat->total, -1, out, arg);
192
      // mi_print_amount(stat->freed, -1, out, arg);
193
0
      mi_print_amount(stat->current, -1, out, arg);
194
0
      if (unit == -1) {
195
0
        _mi_fprintf(out, arg, "%24s", "");
196
0
      }
197
0
      else {
198
0
        mi_print_amount(-unit, 1, out, arg);
199
0
        mi_print_count((stat->total / -unit), 0, out, arg);
200
0
      }
201
0
    }
202
0
    if (stat->current != 0) {
203
0
      _mi_fprintf(out, arg, "  ");
204
0
      _mi_fprintf(out, arg, "%s", (notok == NULL ? "not all freed" : notok));
205
0
      _mi_fprintf(out, arg, "\n");
206
0
    }
207
0
    else {
208
0
      _mi_fprintf(out, arg, "  ok\n");
209
0
    }
210
0
  }
211
0
  else {
212
0
    mi_print_amount(stat->peak, 0, out, arg);
213
0
    mi_print_amount(stat->total, 0, out, arg);
214
0
    mi_print_amount(stat->current, 0, out, arg);
215
0
    _mi_fprintf(out, arg, "\n");
216
0
  }
217
0
}
218
219
0
static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
220
0
  mi_stat_print_ex(stat, msg, unit, out, arg, NULL);
221
0
}
222
223
#if MI_STAT>1
224
static void mi_stat_total_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
225
  _mi_fprintf(out, arg, "%10s:", msg);
226
  _mi_fprintf(out, arg, "%12s", " ");  // no peak
227
  mi_print_amount(stat->total, unit, out, arg);
228
  _mi_fprintf(out, arg, "\n");
229
}
230
#endif
231
232
0
static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
233
0
  _mi_fprintf(out, arg, "%10s:", msg);
234
0
  mi_print_amount(stat->total, 0, out, arg);
235
0
  _mi_fprintf(out, arg, "\n");
236
0
}
237
238
0
static void mi_stat_counter_print_size(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
239
0
  _mi_fprintf(out, arg, "%10s:", msg);
240
0
  mi_print_amount(stat->total, 1, out, arg);
241
0
  _mi_fprintf(out, arg, "\n");
242
0
}
243
244
0
static void mi_stat_average_print(int64_t count, int64_t total, const char* msg, mi_output_fun* out, void* arg) {
245
0
  const int64_t avg_tens = (count == 0 ? 0 : (total*10 / count));
246
0
  const int64_t avg_whole = avg_tens/10;
247
0
  const int64_t avg_frac1 = avg_tens%10;
248
0
  _mi_fprintf(out, arg, "%10s: %5lld.%lld avg\n", msg, avg_whole, avg_frac1);
249
0
}
250
251
252
0
static void mi_print_header(mi_output_fun* out, void* arg ) {
253
0
  _mi_fprintf(out, arg, "%10s: %11s %11s %11s %11s %11s\n", "heap stats", "peak   ", "total   ", "current   ", "block   ", "total#   ");
254
0
}
255
256
#if MI_STAT>1
257
static void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) {
258
  bool found = false;
259
  char buf[64];
260
  for (size_t i = 0; i <= max; i++) {
261
    if (bins[i].total > 0) {
262
      found = true;
263
      int64_t unit = _mi_bin_size((uint8_t)i);
264
      _mi_snprintf(buf, 64, "%s %3lu", fmt, (long)i);
265
      mi_stat_print(&bins[i], buf, unit, out, arg);
266
    }
267
  }
268
  if (found) {
269
    _mi_fprintf(out, arg, "\n");
270
    mi_print_header(out, arg);
271
  }
272
}
273
#endif
274
275
276
277
//------------------------------------------------------------
278
// Use an output wrapper for line-buffered output
279
// (which is nice when using loggers etc.)
280
//------------------------------------------------------------
281
typedef struct buffered_s {
282
  mi_output_fun* out;   // original output function
283
  void*          arg;   // and state
284
  char*          buf;   // local buffer of at least size `count+1`
285
  size_t         used;  // currently used chars `used <= count`
286
  size_t         count; // total chars available for output
287
} buffered_t;
288
289
0
static void mi_buffered_flush(buffered_t* buf) {
290
0
  buf->buf[buf->used] = 0;
291
0
  _mi_fputs(buf->out, buf->arg, NULL, buf->buf);
292
0
  buf->used = 0;
293
0
}
294
295
0
static void mi_cdecl mi_buffered_out(const char* msg, void* arg) {
296
0
  buffered_t* buf = (buffered_t*)arg;
297
0
  if (msg==NULL || buf==NULL) return;
298
0
  for (const char* src = msg; *src != 0; src++) {
299
0
    char c = *src;
300
0
    if (buf->used >= buf->count) mi_buffered_flush(buf);
301
0
    mi_assert_internal(buf->used < buf->count);
302
0
    buf->buf[buf->used++] = c;
303
0
    if (c == '\n') mi_buffered_flush(buf);
304
0
  }
305
0
}
306
307
//------------------------------------------------------------
308
// Print statistics
309
//------------------------------------------------------------
310
311
0
static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept {
312
  // wrap the output function to be line buffered
313
0
  char buf[256];
314
0
  buffered_t buffer = { out0, arg0, NULL, 0, 255 };
315
0
  buffer.buf = buf;
316
0
  mi_output_fun* out = &mi_buffered_out;
317
0
  void* arg = &buffer;
318
319
  // and print using that
320
0
  mi_print_header(out,arg);
321
  #if MI_STAT>1
322
  mi_stats_print_bins(stats->malloc_bins, MI_BIN_HUGE, "bin",out,arg);
323
  #endif
324
  #if MI_STAT
325
  mi_stat_print(&stats->malloc_normal, "binned", (stats->malloc_normal_count.total == 0 ? -1 : 1), out, arg);
326
  mi_stat_print(&stats->malloc_huge, "huge", (stats->malloc_huge_count.total == 0 ? -1 : 1), out, arg);
327
  mi_stat_count_t total = { 0,0,0 };
328
  mi_stat_count_add_mt(&total, &stats->malloc_normal);
329
  // mi_stat_count_add(&total, &stats->malloc_large);
330
  mi_stat_count_add_mt(&total, &stats->malloc_huge);
331
  mi_stat_print_ex(&total, "total", 1, out, arg, "");
332
  #endif
333
  #if MI_STAT>1
334
  mi_stat_total_print(&stats->malloc_requested, "malloc req", 1, out, arg);
335
  _mi_fprintf(out, arg, "\n");
336
  #endif
337
0
  mi_stat_print_ex(&stats->reserved, "reserved", 1, out, arg, "");
338
0
  mi_stat_print_ex(&stats->committed, "committed", 1, out, arg, "");
339
0
  mi_stat_counter_print_size(&stats->reset, "reset", out, arg );
340
0
  mi_stat_counter_print_size(&stats->purged, "purged", out, arg );
341
0
  mi_stat_print_ex(&stats->page_committed, "touched", 1, out, arg, "");
342
0
  mi_stat_print(&stats->segments, "segments", 0, out, arg);
343
0
  mi_stat_print(&stats->segments_abandoned, "-abandoned", 0, out, arg);
344
0
  mi_stat_print(&stats->segments_cache, "-cached", 0, out, arg);
345
0
  mi_stat_print(&stats->pages, "pages", 0, out, arg);
346
0
  mi_stat_print(&stats->pages_abandoned, "-abandoned", 0, out, arg);
347
0
  mi_stat_counter_print(&stats->pages_extended, "-extended", out, arg);
348
0
  mi_stat_counter_print(&stats->pages_retire, "-retire", out, arg);
349
0
  mi_stat_counter_print(&stats->arena_count, "arenas", out, arg);
350
  // mi_stat_counter_print(&stats->arena_crossover_count, "-crossover", out, arg);
351
0
  mi_stat_counter_print(&stats->arena_rollback_count, "-rollback", out, arg);
352
0
  mi_stat_counter_print(&stats->mmap_calls, "mmaps", out, arg);
353
0
  mi_stat_counter_print(&stats->commit_calls, "commits", out, arg);
354
0
  mi_stat_counter_print(&stats->reset_calls, "resets", out, arg);
355
0
  mi_stat_counter_print(&stats->purge_calls, "purges", out, arg);
356
0
  mi_stat_counter_print(&stats->malloc_guarded_count, "guarded", out, arg);
357
0
  mi_stat_print(&stats->threads, "threads", 0, out, arg);
358
0
  mi_stat_average_print(stats->page_searches_count.total, stats->page_searches.total, "searches", out, arg);
359
0
  _mi_fprintf(out, arg, "%10s: %5i\n", "numa nodes", _mi_os_numa_node_count());
360
361
0
  size_t elapsed;
362
0
  size_t user_time;
363
0
  size_t sys_time;
364
0
  size_t current_rss;
365
0
  size_t peak_rss;
366
0
  size_t current_commit;
367
0
  size_t peak_commit;
368
0
  size_t page_faults;
369
0
  mi_process_info(&elapsed, &user_time, &sys_time, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);
370
0
  _mi_fprintf(out, arg, "%10s: %5zu.%03zu s\n", "elapsed", elapsed/1000, elapsed%1000);
371
0
  _mi_fprintf(out, arg, "%10s: user: %zu.%03zu s, system: %zu.%03zu s, faults: %zu, peak rss: ", "process",
372
0
              user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, page_faults );
373
0
  mi_printf_amount((int64_t)peak_rss, 1, out, arg, false);
374
0
  if (peak_commit > 0) {
375
0
    _mi_fprintf(out, arg, ", peak commit: ");
376
0
    mi_printf_amount((int64_t)peak_commit, 1, out, arg, false);
377
0
  }
378
0
  _mi_fprintf(out, arg, "\n");
379
0
}
380
381
static mi_msecs_t mi_process_start; // = 0
382
383
37
static mi_stats_t* mi_stats_get_default(void) {
384
37
  mi_heap_t* heap = mi_heap_get_default();
385
37
  return &heap->tld->stats;
386
37
}
387
388
0
static void mi_stats_merge_from(mi_stats_t* stats) {
389
0
  if (stats != &_mi_stats_main) {
390
0
    mi_stats_add(&_mi_stats_main, stats);
391
0
    memset(stats, 0, sizeof(mi_stats_t));
392
0
  }
393
0
}
394
395
37
void mi_stats_reset(void) mi_attr_noexcept {
396
37
  mi_stats_t* stats = mi_stats_get_default();
397
37
  if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); }
398
37
  memset(&_mi_stats_main, 0, sizeof(mi_stats_t));
399
37
  if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); };
400
37
}
401
402
0
void mi_stats_merge(void) mi_attr_noexcept {
403
0
  mi_stats_merge_from( mi_stats_get_default() );
404
0
}
405
406
0
void _mi_stats_merge_thread(mi_tld_t* tld) {
407
0
  mi_stats_merge_from( &tld->stats );
408
0
}
409
410
0
void _mi_stats_done(mi_stats_t* stats) {  // called from `mi_thread_done`
411
0
  mi_stats_merge_from(stats);
412
0
}
413
414
0
void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {
415
0
  mi_stats_merge_from(mi_stats_get_default());
416
0
  _mi_stats_print(&_mi_stats_main, out, arg);
417
0
}
418
419
0
void mi_stats_print(void* out) mi_attr_noexcept {
420
  // for compatibility there is an `out` parameter (which can be `stdout` or `stderr`)
421
0
  mi_stats_print_out((mi_output_fun*)out, NULL);
422
0
}
423
424
0
void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {
425
0
  _mi_stats_print(mi_stats_get_default(), out, arg);
426
0
}
427
428
429
// ----------------------------------------------------------------
430
// Basic timer for convenience; use milli-seconds to avoid doubles
431
// ----------------------------------------------------------------
432
433
static mi_msecs_t mi_clock_diff;
434
435
111
mi_msecs_t _mi_clock_now(void) {
436
111
  return _mi_prim_clock_now();
437
111
}
438
439
37
mi_msecs_t _mi_clock_start(void) {
440
37
  if (mi_clock_diff == 0.0) {
441
37
    mi_msecs_t t0 = _mi_clock_now();
442
37
    mi_clock_diff = _mi_clock_now() - t0;
443
37
  }
444
37
  return _mi_clock_now();
445
37
}
446
447
0
mi_msecs_t _mi_clock_end(mi_msecs_t start) {
448
0
  mi_msecs_t end = _mi_clock_now();
449
0
  return (end - start - mi_clock_diff);
450
0
}
451
452
453
// --------------------------------------------------------
454
// Basic process statistics
455
// --------------------------------------------------------
456
457
mi_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_noexcept
458
0
{
459
0
  mi_process_info_t pinfo;
460
0
  _mi_memzero_var(pinfo);
461
0
  pinfo.elapsed        = _mi_clock_end(mi_process_start);
462
0
  pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current));
463
0
  pinfo.peak_commit    = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak));
464
0
  pinfo.current_rss    = pinfo.current_commit;
465
0
  pinfo.peak_rss       = pinfo.peak_commit;
466
0
  pinfo.utime          = 0;
467
0
  pinfo.stime          = 0;
468
0
  pinfo.page_faults    = 0;
469
470
0
  _mi_prim_process_info(&pinfo);
471
472
0
  if (elapsed_msecs!=NULL)  *elapsed_msecs  = (pinfo.elapsed < 0 ? 0 : (pinfo.elapsed < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.elapsed : PTRDIFF_MAX));
473
0
  if (user_msecs!=NULL)     *user_msecs     = (pinfo.utime < 0 ? 0 : (pinfo.utime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.utime : PTRDIFF_MAX));
474
0
  if (system_msecs!=NULL)   *system_msecs   = (pinfo.stime < 0 ? 0 : (pinfo.stime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.stime : PTRDIFF_MAX));
475
0
  if (current_rss!=NULL)    *current_rss    = pinfo.current_rss;
476
0
  if (peak_rss!=NULL)       *peak_rss       = pinfo.peak_rss;
477
0
  if (current_commit!=NULL) *current_commit = pinfo.current_commit;
478
0
  if (peak_commit!=NULL)    *peak_commit    = pinfo.peak_commit;
479
0
  if (page_faults!=NULL)    *page_faults    = pinfo.page_faults;
480
0
}
481
482
483
// --------------------------------------------------------
484
// Return statistics
485
// --------------------------------------------------------
486
487
0
bool mi_stats_get(mi_stats_t* stats) mi_attr_noexcept {
488
0
  if (stats == NULL || stats->size != sizeof(mi_stats_t) || stats->version != MI_STAT_VERSION) return false;
489
0
  _mi_memzero(stats,stats->size);
490
0
  _mi_memcpy(stats, &_mi_stats_main, sizeof(mi_stats_t));
491
0
  return true;
492
0
}
493
494
495
// --------------------------------------------------------
496
// Statics in json format
497
// --------------------------------------------------------
498
499
typedef struct mi_heap_buf_s {
500
  char*   buf;
501
  size_t  size;
502
  size_t  used;
503
  bool    can_realloc;
504
} mi_heap_buf_t;
505
506
0
static bool mi_heap_buf_expand(mi_heap_buf_t* hbuf) {
507
0
  if (hbuf==NULL) return false;
508
0
  if (hbuf->buf != NULL && hbuf->size>0) {
509
0
    hbuf->buf[hbuf->size-1] = 0;
510
0
  }
511
0
  if (hbuf->size > SIZE_MAX/2 || !hbuf->can_realloc) return false;
512
0
  const size_t newsize = (hbuf->size == 0 ? mi_good_size(12*MI_KiB) : 2*hbuf->size);
513
0
  char* const  newbuf  = (char*)mi_rezalloc(hbuf->buf, newsize);
514
0
  if (newbuf == NULL) return false;
515
0
  hbuf->buf = newbuf;
516
0
  hbuf->size = newsize;
517
0
  return true;
518
0
}
519
520
0
static void mi_heap_buf_print(mi_heap_buf_t* hbuf, const char* msg) {
521
0
  if (msg==NULL || hbuf==NULL) return;
522
0
  if (hbuf->used + 1 >= hbuf->size && !hbuf->can_realloc) return;
523
0
  for (const char* src = msg; *src != 0; src++) {
524
0
    char c = *src;
525
0
    if (hbuf->used + 1 >= hbuf->size) {
526
0
      if (!mi_heap_buf_expand(hbuf)) return;
527
0
    }
528
0
    mi_assert_internal(hbuf->used < hbuf->size);
529
0
    hbuf->buf[hbuf->used++] = c;
530
0
  }
531
0
  mi_assert_internal(hbuf->used < hbuf->size);
532
0
  hbuf->buf[hbuf->used] = 0;
533
0
}
534
535
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) {
536
0
  const size_t binsize = _mi_bin_size(bin);
537
0
  const size_t pagesize = (binsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_SMALL_PAGE_SIZE :
538
0
                            (binsize <= MI_MEDIUM_OBJ_SIZE_MAX ? MI_MEDIUM_PAGE_SIZE :
539
                              #if MI_LARGE_PAGE_SIZE
540
                              (binsize <= MI_LARGE_OBJ_SIZE_MAX ? MI_LARGE_PAGE_SIZE : 0)
541
                              #else
542
0
                              0
543
0
                              #endif
544
0
                              ));
545
0
  char buf[128];
546
0
  _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 ? "," : ""));
547
0
  buf[127] = 0;
548
0
  mi_heap_buf_print(hbuf, buf);
549
0
}
550
551
0
static void mi_heap_buf_print_count(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, bool add_comma) {
552
0
  char buf[128];
553
0
  _mi_snprintf(buf, 128, "%s{ \"total\": %lld, \"peak\": %lld, \"current\": %lld }%s\n", prefix, stat->total, stat->peak, stat->current, (add_comma ? "," : ""));
554
0
  buf[127] = 0;
555
0
  mi_heap_buf_print(hbuf, buf);
556
0
}
557
558
0
static void mi_heap_buf_print_count_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_count_t* stat) {
559
0
  char buf[128];
560
0
  _mi_snprintf(buf, 128, "  \"%s\": ", name);
561
0
  buf[127] = 0;
562
0
  mi_heap_buf_print(hbuf, buf);
563
0
  mi_heap_buf_print_count(hbuf, "", stat, true);
564
0
}
565
566
0
static void mi_heap_buf_print_value(mi_heap_buf_t* hbuf, const char* name, int64_t val) {
567
0
  char buf[128];
568
0
  _mi_snprintf(buf, 128, "  \"%s\": %lld,\n", name, val);
569
0
  buf[127] = 0;
570
0
  mi_heap_buf_print(hbuf, buf);
571
0
}
572
573
0
static void mi_heap_buf_print_size(mi_heap_buf_t* hbuf, const char* name, size_t val, bool add_comma) {
574
0
  char buf[128];
575
0
  _mi_snprintf(buf, 128, "    \"%s\": %zu%s\n", name, val, (add_comma ? "," : ""));
576
0
  buf[127] = 0;
577
0
  mi_heap_buf_print(hbuf, buf);
578
0
}
579
580
0
static void mi_heap_buf_print_counter_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_counter_t* stat) {
581
0
  mi_heap_buf_print_value(hbuf, name, stat->total);
582
0
}
583
584
0
#define MI_STAT_COUNT(stat)    mi_heap_buf_print_count_value(&hbuf, #stat, &stats->stat);
585
0
#define MI_STAT_COUNTER(stat)  mi_heap_buf_print_counter_value(&hbuf, #stat, &stats->stat);
586
587
0
char* mi_stats_get_json(size_t output_size, char* output_buf) mi_attr_noexcept {
588
0
  mi_heap_buf_t hbuf = { NULL, 0, 0, true };
589
0
  if (output_size > 0 && output_buf != NULL) {
590
0
    _mi_memzero(output_buf, output_size);
591
0
    hbuf.buf = output_buf;
592
0
    hbuf.size = output_size;
593
0
    hbuf.can_realloc = false;
594
0
  }
595
0
  else {
596
0
    if (!mi_heap_buf_expand(&hbuf)) return NULL;
597
0
  }
598
0
  mi_heap_buf_print(&hbuf, "{\n");
599
0
  mi_heap_buf_print_value(&hbuf, "stat_version", MI_STAT_VERSION);
600
0
  mi_heap_buf_print_value(&hbuf, "mimalloc_version", MI_MALLOC_VERSION);
601
602
  // process info
603
0
  mi_heap_buf_print(&hbuf, "  \"process\": {\n");
604
0
  size_t elapsed;
605
0
  size_t user_time;
606
0
  size_t sys_time;
607
0
  size_t current_rss;
608
0
  size_t peak_rss;
609
0
  size_t current_commit;
610
0
  size_t peak_commit;
611
0
  size_t page_faults;
612
0
  mi_process_info(&elapsed, &user_time, &sys_time, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);
613
0
  mi_heap_buf_print_size(&hbuf, "elapsed_msecs", elapsed, true);
614
0
  mi_heap_buf_print_size(&hbuf, "user_msecs", user_time, true);
615
0
  mi_heap_buf_print_size(&hbuf, "system_msecs", sys_time, true);
616
0
  mi_heap_buf_print_size(&hbuf, "page_faults", page_faults, true);
617
0
  mi_heap_buf_print_size(&hbuf, "rss_current", current_rss, true);
618
0
  mi_heap_buf_print_size(&hbuf, "rss_peak", peak_rss, true);
619
0
  mi_heap_buf_print_size(&hbuf, "commit_current", current_commit, true);
620
0
  mi_heap_buf_print_size(&hbuf, "commit_peak", peak_commit, false);
621
0
  mi_heap_buf_print(&hbuf, "  },\n");
622
623
  // statistics
624
0
  mi_stats_t* stats = &_mi_stats_main;
625
0
  MI_STAT_FIELDS()
626
627
  // size bins
628
0
  mi_heap_buf_print(&hbuf, "  \"malloc_bins\": [\n");
629
0
  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
630
0
    mi_heap_buf_print_count_bin(&hbuf, "    ", &stats->malloc_bins[i], i, i!=MI_BIN_HUGE);
631
0
  }
632
0
  mi_heap_buf_print(&hbuf, "  ],\n");
633
0
  mi_heap_buf_print(&hbuf, "  \"page_bins\": [\n");
634
0
  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
635
0
    mi_heap_buf_print_count_bin(&hbuf, "    ", &stats->page_bins[i], i, i!=MI_BIN_HUGE);
636
0
  }
637
0
  mi_heap_buf_print(&hbuf, "  ]\n");
638
0
  mi_heap_buf_print(&hbuf, "}\n");
639
0
  if (hbuf.used >= hbuf.size) {
640
    // failed
641
0
    if (hbuf.can_realloc) { mi_free(hbuf.buf); }
642
0
    return NULL;
643
0
  }
644
0
  else {
645
0
    return hbuf.buf;
646
0
  }
647
0
}