Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/mimalloc/src/stats.c
6175 views
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
static bool mi_is_in_main(void* stat) {
23
return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main
24
&& (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t)));
25
}
26
27
static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {
28
if (amount == 0) return;
29
if (mi_is_in_main(stat))
30
{
31
// add atomically (for abandoned pages)
32
int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount);
33
mi_atomic_maxi64_relaxed(&stat->peak, current + amount);
34
if (amount > 0) {
35
mi_atomic_addi64_relaxed(&stat->allocated,amount);
36
}
37
else {
38
mi_atomic_addi64_relaxed(&stat->freed, -amount);
39
}
40
}
41
else {
42
// add thread local
43
stat->current += amount;
44
if (stat->current > stat->peak) stat->peak = stat->current;
45
if (amount > 0) {
46
stat->allocated += amount;
47
}
48
else {
49
stat->freed += -amount;
50
}
51
}
52
}
53
54
void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {
55
if (mi_is_in_main(stat)) {
56
mi_atomic_addi64_relaxed( &stat->count, 1 );
57
mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount );
58
}
59
else {
60
stat->count++;
61
stat->total += amount;
62
}
63
}
64
65
void _mi_stat_increase(mi_stat_count_t* stat, size_t amount) {
66
mi_stat_update(stat, (int64_t)amount);
67
}
68
69
void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
70
mi_stat_update(stat, -((int64_t)amount));
71
}
72
73
// must be thread safe as it is called from stats_merge
74
static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) {
75
if (stat==src) return;
76
if (src->allocated==0 && src->freed==0) return;
77
mi_atomic_addi64_relaxed( &stat->allocated, src->allocated * unit);
78
mi_atomic_addi64_relaxed( &stat->current, src->current * unit);
79
mi_atomic_addi64_relaxed( &stat->freed, src->freed * unit);
80
// peak scores do not work across threads..
81
mi_atomic_addi64_relaxed( &stat->peak, src->peak * unit);
82
}
83
84
static void mi_stat_counter_add(mi_stat_counter_t* stat, const mi_stat_counter_t* src, int64_t unit) {
85
if (stat==src) return;
86
mi_atomic_addi64_relaxed( &stat->total, src->total * unit);
87
mi_atomic_addi64_relaxed( &stat->count, src->count * unit);
88
}
89
90
// must be thread safe as it is called from stats_merge
91
static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
92
if (stats==src) return;
93
mi_stat_add(&stats->segments, &src->segments,1);
94
mi_stat_add(&stats->pages, &src->pages,1);
95
mi_stat_add(&stats->reserved, &src->reserved, 1);
96
mi_stat_add(&stats->committed, &src->committed, 1);
97
mi_stat_add(&stats->reset, &src->reset, 1);
98
mi_stat_add(&stats->purged, &src->purged, 1);
99
mi_stat_add(&stats->page_committed, &src->page_committed, 1);
100
101
mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned, 1);
102
mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned, 1);
103
mi_stat_add(&stats->threads, &src->threads, 1);
104
105
mi_stat_add(&stats->malloc, &src->malloc, 1);
106
mi_stat_add(&stats->segments_cache, &src->segments_cache, 1);
107
mi_stat_add(&stats->normal, &src->normal, 1);
108
mi_stat_add(&stats->huge, &src->huge, 1);
109
mi_stat_add(&stats->large, &src->large, 1);
110
111
mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1);
112
mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1);
113
mi_stat_counter_add(&stats->commit_calls, &src->commit_calls, 1);
114
mi_stat_counter_add(&stats->reset_calls, &src->reset_calls, 1);
115
mi_stat_counter_add(&stats->purge_calls, &src->purge_calls, 1);
116
117
mi_stat_counter_add(&stats->page_no_retire, &src->page_no_retire, 1);
118
mi_stat_counter_add(&stats->searches, &src->searches, 1);
119
mi_stat_counter_add(&stats->normal_count, &src->normal_count, 1);
120
mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1);
121
mi_stat_counter_add(&stats->large_count, &src->large_count, 1);
122
#if MI_STAT>1
123
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
124
if (src->normal_bins[i].allocated > 0 || src->normal_bins[i].freed > 0) {
125
mi_stat_add(&stats->normal_bins[i], &src->normal_bins[i], 1);
126
}
127
}
128
#endif
129
}
130
131
/* -----------------------------------------------------------
132
Display statistics
133
----------------------------------------------------------- */
134
135
// unit > 0 : size in binary bytes
136
// unit == 0: count as decimal
137
// unit < 0 : count in binary
138
static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) {
139
char buf[32]; buf[0] = 0;
140
int len = 32;
141
const char* suffix = (unit <= 0 ? " " : "B");
142
const int64_t base = (unit == 0 ? 1000 : 1024);
143
if (unit>0) n *= unit;
144
145
const int64_t pos = (n < 0 ? -n : n);
146
if (pos < base) {
147
if (n!=1 || suffix[0] != 'B') { // skip printing 1 B for the unit column
148
_mi_snprintf(buf, len, "%lld %-3s", (long long)n, (n==0 ? "" : suffix));
149
}
150
}
151
else {
152
int64_t divider = base;
153
const char* magnitude = "K";
154
if (pos >= divider*base) { divider *= base; magnitude = "M"; }
155
if (pos >= divider*base) { divider *= base; magnitude = "G"; }
156
const int64_t tens = (n / (divider/10));
157
const long whole = (long)(tens/10);
158
const long frac1 = (long)(tens%10);
159
char unitdesc[8];
160
_mi_snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix);
161
_mi_snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);
162
}
163
_mi_fprintf(out, arg, (fmt==NULL ? "%12s" : fmt), buf);
164
}
165
166
167
static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
168
mi_printf_amount(n,unit,out,arg,NULL);
169
}
170
171
static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
172
if (unit==1) _mi_fprintf(out, arg, "%12s"," ");
173
else mi_print_amount(n,0,out,arg);
174
}
175
176
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 ) {
177
_mi_fprintf(out, arg,"%10s:", msg);
178
if (unit != 0) {
179
if (unit > 0) {
180
mi_print_amount(stat->peak, unit, out, arg);
181
mi_print_amount(stat->allocated, unit, out, arg);
182
mi_print_amount(stat->freed, unit, out, arg);
183
mi_print_amount(stat->current, unit, out, arg);
184
mi_print_amount(unit, 1, out, arg);
185
mi_print_count(stat->allocated, unit, out, arg);
186
}
187
else {
188
mi_print_amount(stat->peak, -1, out, arg);
189
mi_print_amount(stat->allocated, -1, out, arg);
190
mi_print_amount(stat->freed, -1, out, arg);
191
mi_print_amount(stat->current, -1, out, arg);
192
if (unit == -1) {
193
_mi_fprintf(out, arg, "%24s", "");
194
}
195
else {
196
mi_print_amount(-unit, 1, out, arg);
197
mi_print_count((stat->allocated / -unit), 0, out, arg);
198
}
199
}
200
if (stat->allocated > stat->freed) {
201
_mi_fprintf(out, arg, " ");
202
_mi_fprintf(out, arg, (notok == NULL ? "not all freed" : notok));
203
_mi_fprintf(out, arg, "\n");
204
}
205
else {
206
_mi_fprintf(out, arg, " ok\n");
207
}
208
}
209
else {
210
mi_print_amount(stat->peak, 1, out, arg);
211
mi_print_amount(stat->allocated, 1, out, arg);
212
_mi_fprintf(out, arg, "%11s", " "); // no freed
213
mi_print_amount(stat->current, 1, out, arg);
214
_mi_fprintf(out, arg, "\n");
215
}
216
}
217
218
static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
219
mi_stat_print_ex(stat, msg, unit, out, arg, NULL);
220
}
221
222
static void mi_stat_peak_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
223
_mi_fprintf(out, arg, "%10s:", msg);
224
mi_print_amount(stat->peak, unit, out, arg);
225
_mi_fprintf(out, arg, "\n");
226
}
227
228
static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
229
_mi_fprintf(out, arg, "%10s:", msg);
230
mi_print_amount(stat->total, -1, out, arg);
231
_mi_fprintf(out, arg, "\n");
232
}
233
234
235
static void mi_stat_counter_print_avg(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) {
236
const int64_t avg_tens = (stat->count == 0 ? 0 : (stat->total*10 / stat->count));
237
const long avg_whole = (long)(avg_tens/10);
238
const long avg_frac1 = (long)(avg_tens%10);
239
_mi_fprintf(out, arg, "%10s: %5ld.%ld avg\n", msg, avg_whole, avg_frac1);
240
}
241
242
243
static void mi_print_header(mi_output_fun* out, void* arg ) {
244
_mi_fprintf(out, arg, "%10s: %11s %11s %11s %11s %11s %11s\n", "heap stats", "peak ", "total ", "freed ", "current ", "unit ", "count ");
245
}
246
247
#if MI_STAT>1
248
static void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) {
249
bool found = false;
250
char buf[64];
251
for (size_t i = 0; i <= max; i++) {
252
if (bins[i].allocated > 0) {
253
found = true;
254
int64_t unit = _mi_bin_size((uint8_t)i);
255
_mi_snprintf(buf, 64, "%s %3lu", fmt, (long)i);
256
mi_stat_print(&bins[i], buf, unit, out, arg);
257
}
258
}
259
if (found) {
260
_mi_fprintf(out, arg, "\n");
261
mi_print_header(out, arg);
262
}
263
}
264
#endif
265
266
267
268
//------------------------------------------------------------
269
// Use an output wrapper for line-buffered output
270
// (which is nice when using loggers etc.)
271
//------------------------------------------------------------
272
typedef struct buffered_s {
273
mi_output_fun* out; // original output function
274
void* arg; // and state
275
char* buf; // local buffer of at least size `count+1`
276
size_t used; // currently used chars `used <= count`
277
size_t count; // total chars available for output
278
} buffered_t;
279
280
static void mi_buffered_flush(buffered_t* buf) {
281
buf->buf[buf->used] = 0;
282
_mi_fputs(buf->out, buf->arg, NULL, buf->buf);
283
buf->used = 0;
284
}
285
286
static void mi_cdecl mi_buffered_out(const char* msg, void* arg) {
287
buffered_t* buf = (buffered_t*)arg;
288
if (msg==NULL || buf==NULL) return;
289
for (const char* src = msg; *src != 0; src++) {
290
char c = *src;
291
if (buf->used >= buf->count) mi_buffered_flush(buf);
292
mi_assert_internal(buf->used < buf->count);
293
buf->buf[buf->used++] = c;
294
if (c == '\n') mi_buffered_flush(buf);
295
}
296
}
297
298
//------------------------------------------------------------
299
// Print statistics
300
//------------------------------------------------------------
301
302
static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept {
303
// wrap the output function to be line buffered
304
char buf[256];
305
buffered_t buffer = { out0, arg0, NULL, 0, 255 };
306
buffer.buf = buf;
307
mi_output_fun* out = &mi_buffered_out;
308
void* arg = &buffer;
309
310
// and print using that
311
mi_print_header(out,arg);
312
#if MI_STAT>1
313
mi_stats_print_bins(stats->normal_bins, MI_BIN_HUGE, "normal",out,arg);
314
#endif
315
#if MI_STAT
316
mi_stat_print(&stats->normal, "normal", (stats->normal_count.count == 0 ? 1 : -(stats->normal.allocated / stats->normal_count.count)), out, arg);
317
mi_stat_print(&stats->large, "large", (stats->large_count.count == 0 ? 1 : -(stats->large.allocated / stats->large_count.count)), out, arg);
318
mi_stat_print(&stats->huge, "huge", (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg);
319
mi_stat_count_t total = { 0,0,0,0 };
320
mi_stat_add(&total, &stats->normal, 1);
321
mi_stat_add(&total, &stats->large, 1);
322
mi_stat_add(&total, &stats->huge, 1);
323
mi_stat_print(&total, "total", 1, out, arg);
324
#endif
325
#if MI_STAT>1
326
mi_stat_print(&stats->malloc, "malloc req", 1, out, arg);
327
_mi_fprintf(out, arg, "\n");
328
#endif
329
mi_stat_print_ex(&stats->reserved, "reserved", 1, out, arg, "");
330
mi_stat_print_ex(&stats->committed, "committed", 1, out, arg, "");
331
mi_stat_peak_print(&stats->reset, "reset", 1, out, arg );
332
mi_stat_peak_print(&stats->purged, "purged", 1, out, arg );
333
mi_stat_print(&stats->page_committed, "touched", 1, out, arg);
334
mi_stat_print(&stats->segments, "segments", -1, out, arg);
335
mi_stat_print(&stats->segments_abandoned, "-abandoned", -1, out, arg);
336
mi_stat_print(&stats->segments_cache, "-cached", -1, out, arg);
337
mi_stat_print(&stats->pages, "pages", -1, out, arg);
338
mi_stat_print(&stats->pages_abandoned, "-abandoned", -1, out, arg);
339
mi_stat_counter_print(&stats->pages_extended, "-extended", out, arg);
340
mi_stat_counter_print(&stats->page_no_retire, "-noretire", out, arg);
341
mi_stat_counter_print(&stats->arena_count, "arenas", out, arg);
342
mi_stat_counter_print(&stats->arena_crossover_count, "-crossover", out, arg);
343
mi_stat_counter_print(&stats->arena_rollback_count, "-rollback", out, arg);
344
mi_stat_counter_print(&stats->mmap_calls, "mmaps", out, arg);
345
mi_stat_counter_print(&stats->commit_calls, "commits", out, arg);
346
mi_stat_counter_print(&stats->reset_calls, "resets", out, arg);
347
mi_stat_counter_print(&stats->purge_calls, "purges", out, arg);
348
mi_stat_print(&stats->threads, "threads", -1, out, arg);
349
mi_stat_counter_print_avg(&stats->searches, "searches", out, arg);
350
_mi_fprintf(out, arg, "%10s: %5zu\n", "numa nodes", _mi_os_numa_node_count());
351
352
size_t elapsed;
353
size_t user_time;
354
size_t sys_time;
355
size_t current_rss;
356
size_t peak_rss;
357
size_t current_commit;
358
size_t peak_commit;
359
size_t page_faults;
360
mi_process_info(&elapsed, &user_time, &sys_time, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);
361
_mi_fprintf(out, arg, "%10s: %5ld.%03ld s\n", "elapsed", elapsed/1000, elapsed%1000);
362
_mi_fprintf(out, arg, "%10s: user: %ld.%03ld s, system: %ld.%03ld s, faults: %lu, rss: ", "process",
363
user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, (unsigned long)page_faults );
364
mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s");
365
if (peak_commit > 0) {
366
_mi_fprintf(out, arg, ", commit: ");
367
mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s");
368
}
369
_mi_fprintf(out, arg, "\n");
370
}
371
372
static mi_msecs_t mi_process_start; // = 0
373
374
static mi_stats_t* mi_stats_get_default(void) {
375
mi_heap_t* heap = mi_heap_get_default();
376
return &heap->tld->stats;
377
}
378
379
static void mi_stats_merge_from(mi_stats_t* stats) {
380
if (stats != &_mi_stats_main) {
381
mi_stats_add(&_mi_stats_main, stats);
382
memset(stats, 0, sizeof(mi_stats_t));
383
}
384
}
385
386
void mi_stats_reset(void) mi_attr_noexcept {
387
mi_stats_t* stats = mi_stats_get_default();
388
if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); }
389
memset(&_mi_stats_main, 0, sizeof(mi_stats_t));
390
if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); };
391
}
392
393
void mi_stats_merge(void) mi_attr_noexcept {
394
mi_stats_merge_from( mi_stats_get_default() );
395
}
396
397
void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done`
398
mi_stats_merge_from(stats);
399
}
400
401
void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {
402
mi_stats_merge_from(mi_stats_get_default());
403
_mi_stats_print(&_mi_stats_main, out, arg);
404
}
405
406
void mi_stats_print(void* out) mi_attr_noexcept {
407
// for compatibility there is an `out` parameter (which can be `stdout` or `stderr`)
408
mi_stats_print_out((mi_output_fun*)out, NULL);
409
}
410
411
void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {
412
_mi_stats_print(mi_stats_get_default(), out, arg);
413
}
414
415
416
// ----------------------------------------------------------------
417
// Basic timer for convenience; use milli-seconds to avoid doubles
418
// ----------------------------------------------------------------
419
420
static mi_msecs_t mi_clock_diff;
421
422
mi_msecs_t _mi_clock_now(void) {
423
return _mi_prim_clock_now();
424
}
425
426
mi_msecs_t _mi_clock_start(void) {
427
if (mi_clock_diff == 0.0) {
428
mi_msecs_t t0 = _mi_clock_now();
429
mi_clock_diff = _mi_clock_now() - t0;
430
}
431
return _mi_clock_now();
432
}
433
434
mi_msecs_t _mi_clock_end(mi_msecs_t start) {
435
mi_msecs_t end = _mi_clock_now();
436
return (end - start - mi_clock_diff);
437
}
438
439
440
// --------------------------------------------------------
441
// Basic process statistics
442
// --------------------------------------------------------
443
444
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
445
{
446
mi_process_info_t pinfo;
447
_mi_memzero_var(pinfo);
448
pinfo.elapsed = _mi_clock_end(mi_process_start);
449
pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current));
450
pinfo.peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak));
451
pinfo.current_rss = pinfo.current_commit;
452
pinfo.peak_rss = pinfo.peak_commit;
453
pinfo.utime = 0;
454
pinfo.stime = 0;
455
pinfo.page_faults = 0;
456
457
_mi_prim_process_info(&pinfo);
458
459
if (elapsed_msecs!=NULL) *elapsed_msecs = (pinfo.elapsed < 0 ? 0 : (pinfo.elapsed < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.elapsed : PTRDIFF_MAX));
460
if (user_msecs!=NULL) *user_msecs = (pinfo.utime < 0 ? 0 : (pinfo.utime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.utime : PTRDIFF_MAX));
461
if (system_msecs!=NULL) *system_msecs = (pinfo.stime < 0 ? 0 : (pinfo.stime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.stime : PTRDIFF_MAX));
462
if (current_rss!=NULL) *current_rss = pinfo.current_rss;
463
if (peak_rss!=NULL) *peak_rss = pinfo.peak_rss;
464
if (current_commit!=NULL) *current_commit = pinfo.current_commit;
465
if (peak_commit!=NULL) *peak_commit = pinfo.peak_commit;
466
if (page_faults!=NULL) *page_faults = pinfo.page_faults;
467
}
468
469