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 | |
11 | #include <stdio.h> // fputs, stderr |
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->page_committed, &src->page_committed, 1); |
99 | |
100 | mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned, 1); |
101 | mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned, 1); |
102 | mi_stat_add(&stats->threads, &src->threads, 1); |
103 | |
104 | mi_stat_add(&stats->malloc, &src->malloc, 1); |
105 | mi_stat_add(&stats->segments_cache, &src->segments_cache, 1); |
106 | mi_stat_add(&stats->normal, &src->normal, 1); |
107 | mi_stat_add(&stats->huge, &src->huge, 1); |
108 | mi_stat_add(&stats->large, &src->large, 1); |
109 | |
110 | mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1); |
111 | mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1); |
112 | mi_stat_counter_add(&stats->commit_calls, &src->commit_calls, 1); |
113 | |
114 | mi_stat_counter_add(&stats->page_no_retire, &src->page_no_retire, 1); |
115 | mi_stat_counter_add(&stats->searches, &src->searches, 1); |
116 | mi_stat_counter_add(&stats->normal_count, &src->normal_count, 1); |
117 | mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1); |
118 | mi_stat_counter_add(&stats->large_count, &src->large_count, 1); |
119 | #if MI_STAT>1 |
120 | for (size_t i = 0; i <= MI_BIN_HUGE; i++) { |
121 | if (src->normal_bins[i].allocated > 0 || src->normal_bins[i].freed > 0) { |
122 | mi_stat_add(&stats->normal_bins[i], &src->normal_bins[i], 1); |
123 | } |
124 | } |
125 | #endif |
126 | } |
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 | static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) { |
136 | char buf[32]; buf[0] = 0; |
137 | int len = 32; |
138 | const char* suffix = (unit <= 0 ? " " : "B" ); |
139 | const int64_t base = (unit == 0 ? 1000 : 1024); |
140 | if (unit>0) n *= unit; |
141 | |
142 | const int64_t pos = (n < 0 ? -n : n); |
143 | if (pos < base) { |
144 | if (n!=1 || suffix[0] != 'B') { // skip printing 1 B for the unit column |
145 | snprintf(buf, len, "%d %-3s" , (int)n, (n==0 ? "" : suffix)); |
146 | } |
147 | } |
148 | else { |
149 | int64_t divider = base; |
150 | const char* magnitude = "K" ; |
151 | if (pos >= divider*base) { divider *= base; magnitude = "M" ; } |
152 | if (pos >= divider*base) { divider *= base; magnitude = "G" ; } |
153 | const int64_t tens = (n / (divider/10)); |
154 | const long whole = (long)(tens/10); |
155 | const long frac1 = (long)(tens%10); |
156 | char unitdesc[8]; |
157 | snprintf(unitdesc, 8, "%s%s%s" , magnitude, (base==1024 ? "i" : "" ), suffix); |
158 | snprintf(buf, len, "%ld.%ld %-3s" , whole, (frac1 < 0 ? -frac1 : frac1), unitdesc); |
159 | } |
160 | _mi_fprintf(out, arg, (fmt==NULL ? "%11s" : fmt), buf); |
161 | } |
162 | |
163 | |
164 | static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) { |
165 | mi_printf_amount(n,unit,out,arg,NULL); |
166 | } |
167 | |
168 | static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) { |
169 | if (unit==1) _mi_fprintf(out, arg, "%11s" ," " ); |
170 | else mi_print_amount(n,0,out,arg); |
171 | } |
172 | |
173 | static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg ) { |
174 | _mi_fprintf(out, arg,"%10s:" , msg); |
175 | if (unit>0) { |
176 | mi_print_amount(stat->peak, unit, out, arg); |
177 | mi_print_amount(stat->allocated, unit, out, arg); |
178 | mi_print_amount(stat->freed, unit, out, arg); |
179 | mi_print_amount(stat->current, unit, out, arg); |
180 | mi_print_amount(unit, 1, out, arg); |
181 | mi_print_count(stat->allocated, unit, out, arg); |
182 | if (stat->allocated > stat->freed) |
183 | _mi_fprintf(out, arg, " not all freed!\n" ); |
184 | else |
185 | _mi_fprintf(out, arg, " ok\n" ); |
186 | } |
187 | else if (unit<0) { |
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, "%22s" , "" ); |
194 | } |
195 | else { |
196 | mi_print_amount(-unit, 1, out, arg); |
197 | mi_print_count((stat->allocated / -unit), 0, out, arg); |
198 | } |
199 | if (stat->allocated > stat->freed) |
200 | _mi_fprintf(out, arg, " not all freed!\n" ); |
201 | else |
202 | _mi_fprintf(out, arg, " ok\n" ); |
203 | } |
204 | else { |
205 | mi_print_amount(stat->peak, 1, out, arg); |
206 | mi_print_amount(stat->allocated, 1, out, arg); |
207 | _mi_fprintf(out, arg, "%11s" , " " ); // no freed |
208 | mi_print_amount(stat->current, 1, out, arg); |
209 | _mi_fprintf(out, arg, "\n" ); |
210 | } |
211 | } |
212 | |
213 | static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) { |
214 | _mi_fprintf(out, arg, "%10s:" , msg); |
215 | mi_print_amount(stat->total, -1, out, arg); |
216 | _mi_fprintf(out, arg, "\n" ); |
217 | } |
218 | |
219 | static void mi_stat_counter_print_avg(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) { |
220 | const int64_t avg_tens = (stat->count == 0 ? 0 : (stat->total*10 / stat->count)); |
221 | const long avg_whole = (long)(avg_tens/10); |
222 | const long avg_frac1 = (long)(avg_tens%10); |
223 | _mi_fprintf(out, arg, "%10s: %5ld.%ld avg\n" , msg, avg_whole, avg_frac1); |
224 | } |
225 | |
226 | |
227 | static void (mi_output_fun* out, void* arg ) { |
228 | _mi_fprintf(out, arg, "%10s: %10s %10s %10s %10s %10s %10s\n" , "heap stats" , "peak " , "total " , "freed " , "current " , "unit " , "count " ); |
229 | } |
230 | |
231 | #if MI_STAT>1 |
232 | static void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) { |
233 | bool found = false; |
234 | char buf[64]; |
235 | for (size_t i = 0; i <= max; i++) { |
236 | if (bins[i].allocated > 0) { |
237 | found = true; |
238 | int64_t unit = _mi_bin_size((uint8_t)i); |
239 | snprintf(buf, 64, "%s %3lu" , fmt, (long)i); |
240 | mi_stat_print(&bins[i], buf, unit, out, arg); |
241 | } |
242 | } |
243 | if (found) { |
244 | _mi_fprintf(out, arg, "\n" ); |
245 | mi_print_header(out, arg); |
246 | } |
247 | } |
248 | #endif |
249 | |
250 | |
251 | |
252 | //------------------------------------------------------------ |
253 | // Use an output wrapper for line-buffered output |
254 | // (which is nice when using loggers etc.) |
255 | //------------------------------------------------------------ |
256 | typedef struct buffered_s { |
257 | mi_output_fun* out; // original output function |
258 | void* arg; // and state |
259 | char* buf; // local buffer of at least size `count+1` |
260 | size_t used; // currently used chars `used <= count` |
261 | size_t count; // total chars available for output |
262 | } buffered_t; |
263 | |
264 | static void mi_buffered_flush(buffered_t* buf) { |
265 | buf->buf[buf->used] = 0; |
266 | _mi_fputs(buf->out, buf->arg, NULL, buf->buf); |
267 | buf->used = 0; |
268 | } |
269 | |
270 | static void mi_cdecl mi_buffered_out(const char* msg, void* arg) { |
271 | buffered_t* buf = (buffered_t*)arg; |
272 | if (msg==NULL || buf==NULL) return; |
273 | for (const char* src = msg; *src != 0; src++) { |
274 | char c = *src; |
275 | if (buf->used >= buf->count) mi_buffered_flush(buf); |
276 | mi_assert_internal(buf->used < buf->count); |
277 | buf->buf[buf->used++] = c; |
278 | if (c == '\n') mi_buffered_flush(buf); |
279 | } |
280 | } |
281 | |
282 | //------------------------------------------------------------ |
283 | // Print statistics |
284 | //------------------------------------------------------------ |
285 | |
286 | static void mi_stat_process_info(mi_msecs_t* elapsed, mi_msecs_t* utime, mi_msecs_t* stime, size_t* , size_t* , size_t* current_commit, size_t* peak_commit, size_t* page_faults); |
287 | |
288 | static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept { |
289 | // wrap the output function to be line buffered |
290 | char buf[256]; |
291 | buffered_t buffer = { out0, arg0, NULL, 0, 255 }; |
292 | buffer.buf = buf; |
293 | mi_output_fun* out = &mi_buffered_out; |
294 | void* arg = &buffer; |
295 | |
296 | // and print using that |
297 | mi_print_header(out,arg); |
298 | #if MI_STAT>1 |
299 | mi_stats_print_bins(stats->normal_bins, MI_BIN_HUGE, "normal" ,out,arg); |
300 | #endif |
301 | #if MI_STAT |
302 | mi_stat_print(&stats->normal, "normal" , (stats->normal_count.count == 0 ? 1 : -(stats->normal.allocated / stats->normal_count.count)), out, arg); |
303 | mi_stat_print(&stats->large, "large" , (stats->large_count.count == 0 ? 1 : -(stats->large.allocated / stats->large_count.count)), out, arg); |
304 | mi_stat_print(&stats->huge, "huge" , (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg); |
305 | mi_stat_count_t total = { 0,0,0,0 }; |
306 | mi_stat_add(&total, &stats->normal, 1); |
307 | mi_stat_add(&total, &stats->large, 1); |
308 | mi_stat_add(&total, &stats->huge, 1); |
309 | mi_stat_print(&total, "total" , 1, out, arg); |
310 | #endif |
311 | #if MI_STAT>1 |
312 | mi_stat_print(&stats->malloc, "malloc req" , 1, out, arg); |
313 | _mi_fprintf(out, arg, "\n" ); |
314 | #endif |
315 | mi_stat_print(&stats->reserved, "reserved" , 1, out, arg); |
316 | mi_stat_print(&stats->committed, "committed" , 1, out, arg); |
317 | mi_stat_print(&stats->reset, "reset" , 1, out, arg); |
318 | mi_stat_print(&stats->page_committed, "touched" , 1, out, arg); |
319 | mi_stat_print(&stats->segments, "segments" , -1, out, arg); |
320 | mi_stat_print(&stats->segments_abandoned, "-abandoned" , -1, out, arg); |
321 | mi_stat_print(&stats->segments_cache, "-cached" , -1, out, arg); |
322 | mi_stat_print(&stats->pages, "pages" , -1, out, arg); |
323 | mi_stat_print(&stats->pages_abandoned, "-abandoned" , -1, out, arg); |
324 | mi_stat_counter_print(&stats->pages_extended, "-extended" , out, arg); |
325 | mi_stat_counter_print(&stats->page_no_retire, "-noretire" , out, arg); |
326 | mi_stat_counter_print(&stats->mmap_calls, "mmaps" , out, arg); |
327 | mi_stat_counter_print(&stats->commit_calls, "commits" , out, arg); |
328 | mi_stat_print(&stats->threads, "threads" , -1, out, arg); |
329 | mi_stat_counter_print_avg(&stats->searches, "searches" , out, arg); |
330 | _mi_fprintf(out, arg, "%10s: %7zu\n" , "numa nodes" , _mi_os_numa_node_count()); |
331 | |
332 | mi_msecs_t elapsed; |
333 | mi_msecs_t user_time; |
334 | mi_msecs_t sys_time; |
335 | size_t ; |
336 | size_t ; |
337 | size_t current_commit; |
338 | size_t peak_commit; |
339 | size_t page_faults; |
340 | mi_stat_process_info(&elapsed, &user_time, &sys_time, ¤t_rss, &peak_rss, ¤t_commit, &peak_commit, &page_faults); |
341 | _mi_fprintf(out, arg, "%10s: %7ld.%03ld s\n" , "elapsed" , elapsed/1000, elapsed%1000); |
342 | _mi_fprintf(out, arg, "%10s: user: %ld.%03ld s, system: %ld.%03ld s, faults: %lu, rss: " , "process" , |
343 | user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, (unsigned long)page_faults ); |
344 | mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s" ); |
345 | if (peak_commit > 0) { |
346 | _mi_fprintf(out, arg, ", commit: " ); |
347 | mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s" ); |
348 | } |
349 | _mi_fprintf(out, arg, "\n" ); |
350 | } |
351 | |
352 | static mi_msecs_t mi_process_start; // = 0 |
353 | |
354 | static mi_stats_t* mi_stats_get_default(void) { |
355 | mi_heap_t* heap = mi_heap_get_default(); |
356 | return &heap->tld->stats; |
357 | } |
358 | |
359 | static void mi_stats_merge_from(mi_stats_t* stats) { |
360 | if (stats != &_mi_stats_main) { |
361 | mi_stats_add(&_mi_stats_main, stats); |
362 | memset(stats, 0, sizeof(mi_stats_t)); |
363 | } |
364 | } |
365 | |
366 | void mi_stats_reset(void) mi_attr_noexcept { |
367 | mi_stats_t* stats = mi_stats_get_default(); |
368 | if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); } |
369 | memset(&_mi_stats_main, 0, sizeof(mi_stats_t)); |
370 | if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); }; |
371 | } |
372 | |
373 | void mi_stats_merge(void) mi_attr_noexcept { |
374 | mi_stats_merge_from( mi_stats_get_default() ); |
375 | } |
376 | |
377 | void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done` |
378 | mi_stats_merge_from(stats); |
379 | } |
380 | |
381 | void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { |
382 | mi_stats_merge_from(mi_stats_get_default()); |
383 | _mi_stats_print(&_mi_stats_main, out, arg); |
384 | } |
385 | |
386 | void mi_stats_print(void* out) mi_attr_noexcept { |
387 | // for compatibility there is an `out` parameter (which can be `stdout` or `stderr`) |
388 | mi_stats_print_out((mi_output_fun*)out, NULL); |
389 | } |
390 | |
391 | void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { |
392 | _mi_stats_print(mi_stats_get_default(), out, arg); |
393 | } |
394 | |
395 | |
396 | // ---------------------------------------------------------------- |
397 | // Basic timer for convenience; use milli-seconds to avoid doubles |
398 | // ---------------------------------------------------------------- |
399 | #ifdef _WIN32 |
400 | #include <windows.h> |
401 | static mi_msecs_t mi_to_msecs(LARGE_INTEGER t) { |
402 | static LARGE_INTEGER mfreq; // = 0 |
403 | if (mfreq.QuadPart == 0LL) { |
404 | LARGE_INTEGER f; |
405 | QueryPerformanceFrequency(&f); |
406 | mfreq.QuadPart = f.QuadPart/1000LL; |
407 | if (mfreq.QuadPart == 0) mfreq.QuadPart = 1; |
408 | } |
409 | return (mi_msecs_t)(t.QuadPart / mfreq.QuadPart); |
410 | } |
411 | |
412 | mi_msecs_t _mi_clock_now(void) { |
413 | LARGE_INTEGER t; |
414 | QueryPerformanceCounter(&t); |
415 | return mi_to_msecs(t); |
416 | } |
417 | #else |
418 | #include <time.h> |
419 | #if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC) |
420 | mi_msecs_t _mi_clock_now(void) { |
421 | struct timespec t; |
422 | #ifdef CLOCK_MONOTONIC |
423 | clock_gettime(CLOCK_MONOTONIC, &t); |
424 | #else |
425 | clock_gettime(CLOCK_REALTIME, &t); |
426 | #endif |
427 | return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000); |
428 | } |
429 | #else |
430 | // low resolution timer |
431 | mi_msecs_t _mi_clock_now(void) { |
432 | return ((mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000)); |
433 | } |
434 | #endif |
435 | #endif |
436 | |
437 | |
438 | static mi_msecs_t mi_clock_diff; |
439 | |
440 | mi_msecs_t _mi_clock_start(void) { |
441 | if (mi_clock_diff == 0.0) { |
442 | mi_msecs_t t0 = _mi_clock_now(); |
443 | mi_clock_diff = _mi_clock_now() - t0; |
444 | } |
445 | return _mi_clock_now(); |
446 | } |
447 | |
448 | mi_msecs_t _mi_clock_end(mi_msecs_t start) { |
449 | mi_msecs_t end = _mi_clock_now(); |
450 | return (end - start - mi_clock_diff); |
451 | } |
452 | |
453 | |
454 | // -------------------------------------------------------- |
455 | // Basic process statistics |
456 | // -------------------------------------------------------- |
457 | |
458 | #if defined(_WIN32) |
459 | #include <windows.h> |
460 | #include <psapi.h> |
461 | #pragma comment(lib,"psapi.lib") |
462 | |
463 | static mi_msecs_t filetime_msecs(const FILETIME* ftime) { |
464 | ULARGE_INTEGER i; |
465 | i.LowPart = ftime->dwLowDateTime; |
466 | i.HighPart = ftime->dwHighDateTime; |
467 | mi_msecs_t msecs = (i.QuadPart / 10000); // FILETIME is in 100 nano seconds |
468 | return msecs; |
469 | } |
470 | |
471 | static void mi_stat_process_info(mi_msecs_t* elapsed, mi_msecs_t* utime, mi_msecs_t* stime, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) |
472 | { |
473 | *elapsed = _mi_clock_end(mi_process_start); |
474 | FILETIME ct; |
475 | FILETIME ut; |
476 | FILETIME st; |
477 | FILETIME et; |
478 | GetProcessTimes(GetCurrentProcess(), &ct, &et, &st, &ut); |
479 | *utime = filetime_msecs(&ut); |
480 | *stime = filetime_msecs(&st); |
481 | PROCESS_MEMORY_COUNTERS info; |
482 | GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info)); |
483 | *current_rss = (size_t)info.WorkingSetSize; |
484 | *peak_rss = (size_t)info.PeakWorkingSetSize; |
485 | *current_commit = (size_t)info.PagefileUsage; |
486 | *peak_commit = (size_t)info.PeakPagefileUsage; |
487 | *page_faults = (size_t)info.PageFaultCount; |
488 | } |
489 | |
490 | #elif !defined(__wasi__) && (defined(__unix__) || defined(__unix) || defined(unix) || defined(__APPLE__) || defined(__HAIKU__)) |
491 | #include <stdio.h> |
492 | #include <unistd.h> |
493 | #include <sys/resource.h> |
494 | |
495 | #if defined(__APPLE__) |
496 | #include <mach/mach.h> |
497 | #endif |
498 | |
499 | #if defined(__HAIKU__) |
500 | #include <kernel/OS.h> |
501 | #endif |
502 | |
503 | static mi_msecs_t timeval_secs(const struct timeval* tv) { |
504 | return ((mi_msecs_t)tv->tv_sec * 1000L) + ((mi_msecs_t)tv->tv_usec / 1000L); |
505 | } |
506 | |
507 | static void mi_stat_process_info(mi_msecs_t* elapsed, mi_msecs_t* utime, mi_msecs_t* stime, size_t* , size_t* , size_t* current_commit, size_t* peak_commit, size_t* page_faults) |
508 | { |
509 | *elapsed = _mi_clock_end(mi_process_start); |
510 | struct rusage rusage; |
511 | getrusage(RUSAGE_SELF, &rusage); |
512 | *utime = timeval_secs(&rusage.ru_utime); |
513 | *stime = timeval_secs(&rusage.ru_stime); |
514 | #if !defined(__HAIKU__) |
515 | *page_faults = rusage.ru_majflt; |
516 | #endif |
517 | // estimate commit using our stats |
518 | *peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak)); |
519 | *current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current)); |
520 | *current_rss = *current_commit; // estimate |
521 | #if defined(__HAIKU__) |
522 | // Haiku does not have (yet?) a way to |
523 | // get these stats per process |
524 | thread_info tid; |
525 | area_info mem; |
526 | ssize_t c; |
527 | get_thread_info(find_thread(0), &tid); |
528 | while (get_next_area_info(tid.team, &c, &mem) == B_OK) { |
529 | *peak_rss += mem.ram_size; |
530 | } |
531 | *page_faults = 0; |
532 | #elif defined(__APPLE__) |
533 | *peak_rss = rusage.ru_maxrss; // BSD reports in bytes |
534 | struct mach_task_basic_info info; |
535 | mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; |
536 | if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) { |
537 | *current_rss = (size_t)info.resident_size; |
538 | } |
539 | #else |
540 | *peak_rss = rusage.ru_maxrss * 1024; // Linux reports in KiB |
541 | #endif |
542 | } |
543 | |
544 | #else |
545 | #ifndef __wasi__ |
546 | // WebAssembly instances are not processes |
547 | #pragma message("define a way to get process info") |
548 | #endif |
549 | |
550 | static void mi_stat_process_info(mi_msecs_t* elapsed, mi_msecs_t* utime, mi_msecs_t* stime, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) |
551 | { |
552 | *elapsed = _mi_clock_end(mi_process_start); |
553 | *peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak)); |
554 | *current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current)); |
555 | *peak_rss = *peak_commit; |
556 | *current_rss = *current_commit; |
557 | *page_faults = 0; |
558 | *utime = 0; |
559 | *stime = 0; |
560 | } |
561 | #endif |
562 | |
563 | |
564 | mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* , size_t* , size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept |
565 | { |
566 | mi_msecs_t elapsed = 0; |
567 | mi_msecs_t utime = 0; |
568 | mi_msecs_t stime = 0; |
569 | size_t = 0; |
570 | size_t = 0; |
571 | size_t current_commit0 = 0; |
572 | size_t peak_commit0 = 0; |
573 | size_t page_faults0 = 0; |
574 | mi_stat_process_info(&elapsed,&utime, &stime, ¤t_rss0, &peak_rss0, ¤t_commit0, &peak_commit0, &page_faults0); |
575 | if (elapsed_msecs!=NULL) *elapsed_msecs = (elapsed < 0 ? 0 : (elapsed < (mi_msecs_t)PTRDIFF_MAX ? (size_t)elapsed : PTRDIFF_MAX)); |
576 | if (user_msecs!=NULL) *user_msecs = (utime < 0 ? 0 : (utime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)utime : PTRDIFF_MAX)); |
577 | if (system_msecs!=NULL) *system_msecs = (stime < 0 ? 0 : (stime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)stime : PTRDIFF_MAX)); |
578 | if (current_rss!=NULL) *current_rss = current_rss0; |
579 | if (peak_rss!=NULL) *peak_rss = peak_rss0; |
580 | if (current_commit!=NULL) *current_commit = current_commit0; |
581 | if (peak_commit!=NULL) *peak_commit = peak_commit0; |
582 | if (page_faults!=NULL) *page_faults = page_faults0; |
583 | } |
584 | |
585 | |