Arcane  4.1.12.0
User documentation
Loading...
Searching...
No Matches
MemoryInfo.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* MemoryInfo.cc (C) 2000-2022 */
9/* */
10/* Memory usage information collector. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arcane/utils/ArcanePrecomp.h"
15
16#include "arcane/utils/String.h"
17#include "arcane/utils/Iostream.h"
18#include "arcane/utils/FatalErrorException.h"
19#include "arcane/utils/Iterator.h"
20#include "arcane/utils/MemoryInfo.h"
21#include "arcane/utils/ITraceMng.h"
22#include "arcane/utils/IStackTraceService.h"
23#include "arcane/utils/PlatformUtils.h"
24#include "arcane/utils/ValueConvert.h"
25
26#include <vector>
27#include <algorithm>
28
29/*---------------------------------------------------------------------------*/
30/*---------------------------------------------------------------------------*/
31
32// TODO: the hooks are obsolete because they are not thread-safe.
33// We should use LD_PRELOAD
34// from a library that overrides malloc(), realloc(), ...,
35// then perform a dlopen on libc and call in our
36// library the libc allocation routines.
37
38// This macro ARCANE_CHECK_MEMORY_USE_MALLOC_HOOK is now defined
39// during configuration
40// #if defined(ARCANE_OS_LINUX)
41// #define ARCANE_CHECK_MEMORY_USE_MALLOC_HOOK
42// #endif
43
44/*---------------------------------------------------------------------------*/
45/*---------------------------------------------------------------------------*/
46
47#ifdef ARCANE_CHECK_MEMORY_USE_MALLOC_HOOK
48#include <malloc.h>
49#endif
50
51/*---------------------------------------------------------------------------*/
52/*---------------------------------------------------------------------------*/
53
54namespace Arcane
55{
56
57/*---------------------------------------------------------------------------*/
58/*---------------------------------------------------------------------------*/
59
60extern "C++" MemoryInfo*
61arcaneGlobalTrueMemoryInfo()
62{
63 static MemoryInfo mem_info;
64 return &mem_info;
65}
66
67/*---------------------------------------------------------------------------*/
68/*---------------------------------------------------------------------------*/
69
70#ifdef ARCANE_CHECK_MEMORY_USE_MALLOC_HOOK
71static void* (*old_malloc_hook)(size_t, const void*);
72static void (*old_free_hook)(void*, const void*);
73static void* (*old_realloc_hook)(void* __ptr,
74 size_t __size,
75 __const void*);
76
77static Arcane::Int64 global_nb_malloc = 1;
78static void* my_malloc_hook(size_t size, const void* caller);
79static void my_free_hook(void* ptr, const void* caller);
80static void* my_realloc_hook(void* __ptr,
81 size_t __size,
82 __const void*);
83static void _pushHook()
84{
85 __malloc_hook = old_malloc_hook;
86 __free_hook = old_free_hook;
87 __realloc_hook = old_realloc_hook;
88}
89static void _popHook()
90{
91 __malloc_hook = my_malloc_hook;
92 __free_hook = my_free_hook;
93 __realloc_hook = my_realloc_hook;
94}
95
96static void* my_malloc_hook(size_t size, const void* /*caller*/)
97{
98 _pushHook();
99 void* r = malloc(size);
100 ++global_nb_malloc;
101 //std::cerr << "*ALLOC = " << r << " s=" << size << '\n';
102 arcaneGlobalTrueMemoryInfo()->addInfo(0, r, size);
103 arcaneGlobalTrueMemoryInfo()->checkMemory(0, size);
104 _popHook();
105 return r;
106}
107static void my_free_hook(void* ptr, const void* /*caller*/)
108{
109 _pushHook();
110 arcaneGlobalTrueMemoryInfo()->removeInfo(0, ptr, true);
111 //std::cerr << "*FREE = " << ptr << '\n';
112 ++global_nb_malloc;
113 free(ptr);
114 _popHook();
115}
116static void* my_realloc_hook(void* ptr, size_t size, const void* /*caller*/)
117{
118 _pushHook();
119 //free(ptr);
120 ++global_nb_malloc;
121 //std::cerr << "*REFREE = " << ptr << '\n';
122 arcaneGlobalTrueMemoryInfo()->removeInfo(0, ptr, true);
123 void* r = realloc(ptr, size);
124 ++global_nb_malloc;
125 //std::cerr << "*REALLOC = " << r << " s=" << size << '\n';
126 arcaneGlobalTrueMemoryInfo()->addInfo(0, r, size);
127 arcaneGlobalTrueMemoryInfo()->checkMemory(0, size);
128 _popHook();
129 return r;
130}
131
132static void _initMallocHook()
133{
134 old_malloc_hook = __malloc_hook;
135 __malloc_hook = my_malloc_hook;
136 old_free_hook = __free_hook;
137 __free_hook = my_free_hook;
138 old_realloc_hook = __realloc_hook;
139 __realloc_hook = my_realloc_hook;
140}
141
142static void _restoreMallocHook()
143{
144 __free_hook = old_free_hook;
145 __malloc_hook = old_malloc_hook;
146 __realloc_hook = old_realloc_hook;
147}
148//void (*__malloc_initialize_hook)(void) = my_init_hook;
149#else
150static void _initMallocHook()
151{
152}
153static void _restoreMallocHook()
154{
155}
156static void _pushHook()
157{
158}
159static void _popHook()
160{
161}
162#endif
163
164static bool global_check_memory = false;
165extern "C++" void
166arcaneInitCheckMemory()
167{
168 String s = platform::getEnvironmentVariable("ARCANE_CHECK_MEMORY");
169 if (!s.null()) {
170 global_check_memory = true;
171 arcaneGlobalMemoryInfo()->beginCollect();
172 }
173}
174
175extern "C++" void
176arcaneExitCheckMemory()
177{
178 if (global_check_memory)
179 arcaneGlobalMemoryInfo()->endCollect();
180 global_check_memory = false;
181}
182
183/*---------------------------------------------------------------------------*/
184/*---------------------------------------------------------------------------*/
185
186MemoryInfo::
187MemoryInfo()
188: m_alloc_id(0)
189, m_max_allocated(0)
190, m_current_allocated(0)
191, m_biggest_allocated(0)
192, m_info_big_alloc(1000000)
193, m_info_biggest_minimal(2000000)
194, m_info_peak_minimal(10000000)
195, m_iteration(0)
196, m_trace(0)
197, m_display_max_alloc(true)
198, m_in_display(false)
199, m_is_first_collect(true)
200, m_is_stack_trace_active(true)
201{
202}
203
204/*---------------------------------------------------------------------------*/
205/*---------------------------------------------------------------------------*/
206
207MemoryInfo::
208~MemoryInfo()
209{
210}
211
212/*---------------------------------------------------------------------------*/
213/*---------------------------------------------------------------------------*/
214
215void MemoryInfo::
216setOwner(const void* owner, const TraceInfo& ti)
217{
218 MemoryTraceInfoMap::iterator i = m_owner_infos.find(owner);
219 if (i != m_owner_infos.end()) {
220 i->second = ti;
221 }
222}
223
224/*---------------------------------------------------------------------------*/
225/*---------------------------------------------------------------------------*/
226
227void MemoryInfo::
228addInfo(const void* owner, const void* ptr, Int64 size)
229{
230 //NOTE: This method must be reentrant.
231 //TODO: verify owner present.
232 MemoryInfoMap::const_iterator i = m_infos.find(ptr);
233 String stack_value;
234 //cout << "** ADD: " << ptr << '\n';
235 if (i == m_infos.end()) {
236 MemoryInfoChunk c(owner, size, m_alloc_id, m_iteration);
237 //if (size>5000)
238 //std::cout << " ALLOC size=" << size << " ptr=" << ptr << '\n';
239 if (size >= m_info_big_alloc && m_is_stack_trace_active) {
241 if (s) {
242 stack_value = s->stackTrace(2).toString();
243 c.setStackTrace(stack_value);
244 }
245 }
246 m_infos.insert(MemoryInfoMap::value_type(ptr, c));
247 }
248 else {
249 //cout << "** OLD VALUE file=" << i->second->m_file << " line=" << i->second->m_line;
250 //if (i->second->m_name)
251 //cout << " name=" << i->second->m_name << '\n';
252 cout << "** addInfo() ALREADY IN MAP VALUE=" << ptr << " size=" << size << '\n';
253 //throw FatalErrorException("MemoryInfo::addInfo() pointer already in map");
254 }
255 m_current_allocated += size;
256 ++m_alloc_id;
257}
258
259/*---------------------------------------------------------------------------*/
260/*---------------------------------------------------------------------------*/
261
262void MemoryInfo::
263createOwner(const void* owner, const TraceInfo& trace_info)
264{
265 MemoryTraceInfoMap::iterator i = m_owner_infos.find(owner);
266 if (i == m_owner_infos.end()) {
267 //cout << "** CREATE OWNER " << owner << "\n";
268 m_owner_infos.insert(MemoryTraceInfoMap::value_type(owner, trace_info));
269 }
270 else {
271 cout << "** createOwner() ALREADY IN MAP VALUE=" << owner << '\n';
272 //throw FatalErrorException("MemoryInfo::createOwner() owner already in map");
273 }
274}
275
276/*---------------------------------------------------------------------------*/
277/*---------------------------------------------------------------------------*/
278
279void MemoryInfo::
280addInfo(const void* owner, const void* ptr, Int64 size, const void* /*old_ptr*/)
281{
282 addInfo(owner, ptr, size);
283}
284
285/*---------------------------------------------------------------------------*/
286/*---------------------------------------------------------------------------*/
287
288void MemoryInfo::
289changeOwner(const void* new_owner, const void* ptr)
290{
291 MemoryTraceInfoMap::iterator i_owner = m_owner_infos.find(new_owner);
292 if (i_owner == m_owner_infos.end()) {
293 cerr << "** UNKNOWN NEW OWNER " << new_owner << '\n';
294 throw FatalErrorException("MemoryInfo::changeOwner() unknown new owner");
295 }
296 if (ptr) {
297 MemoryInfoMap::iterator i = m_infos.find(ptr);
298 if (i == m_infos.end()) {
299 cout << "** BAD VALUE=" << ptr << '\n';
300 throw FatalErrorException("MemoryInfo::changeOwner() pointer not in map");
301 }
302 else {
303 i->second.setOwner(i_owner->first);
304 }
305 }
306}
307
308/*---------------------------------------------------------------------------*/
309/*---------------------------------------------------------------------------*/
310
311void MemoryInfo::
312_removeOwner(const void* owner)
313{
314 MemoryTraceInfoMap::iterator i = m_owner_infos.find(owner);
315 if (i != m_owner_infos.end()) {
316 //cout << "** REMOVE OWNER " << owner << "\n";
317 m_owner_infos.erase(i);
318 }
319}
320
321/*---------------------------------------------------------------------------*/
322/*---------------------------------------------------------------------------*/
323
324void MemoryInfo::
325removeOwner(const void* owner)
326{
327 _removeOwner(owner);
328}
329
330/*---------------------------------------------------------------------------*/
331/*---------------------------------------------------------------------------*/
332
333void MemoryInfo::
334removeInfo(const void* owner, const void* ptr, bool can_fail)
335{
336 if (!ptr)
337 return;
338 MemoryInfoMap::iterator i = m_infos.find(ptr);
339 //cout << "** REMOVE: " << ptr << '\n';
340 if (i == m_infos.end()) {
341 if (can_fail)
342 return;
343 cout << "MemoryInfo::removeInfo() pointer not in map";
344 //throw FatalErrorException("MemoryInfo::removeInfo() pointer not in map");
345 }
346 else {
347 MemoryInfoChunk& chunk = i->second;
348 Int64 size = chunk.size();
349 //if (size>5000)
350 //std::cout << " FREE size=" << size << " ptr=" << ptr << '\n';
351 _removeMemory(owner, size);
352 m_infos.erase(i);
353 }
354}
355
356/*---------------------------------------------------------------------------*/
357/*---------------------------------------------------------------------------*/
358
360{
361 public:
362
363 MemoryInfoSorter()
364 : m_size(0)
365 , m_alloc_id(-1)
366 , m_iteration(0)
367 , m_ptr(0)
368 , m_owner(0)
369 {}
370 MemoryInfoSorter(Int64 size, Int64 alloc_id, Integer iteration, const void* ptr,
371 const void* owner, const String& stack_trace)
372 : m_size(size)
373 , m_alloc_id(alloc_id)
374 , m_iteration(iteration)
375 , m_ptr(ptr)
376 , m_owner(owner)
377 , m_stack_trace(stack_trace)
378 {}
379
380 public:
381
382 Int64 m_size;
383 Int64 m_alloc_id;
384 Integer m_iteration;
385 const void* m_ptr;
386 const void* m_owner;
387 String m_stack_trace;
388
389 public:
390
391 bool operator<(const MemoryInfoSorter& rhs) const
392 {
393 return m_size > rhs.m_size;
394 }
395};
396
398{
399 public:
400
401 TracePrinter(const TraceInfo* ti)
402 : m_trace_info(ti)
403 {}
404 void print(std::ostream& o) const
405 {
406 if (m_trace_info) {
407 o << " name=" << m_trace_info->name()
408 << " file=" << m_trace_info->file()
409 << " line=" << m_trace_info->line();
410 }
411 }
412
413 private:
414
415 const TraceInfo* m_trace_info;
416};
417
418std::ostream&
419operator<<(std::ostream& o, const MemoryInfo::TracePrinter& tp)
420{
421 tp.print(o);
422 return o;
423}
424
425/*---------------------------------------------------------------------------*/
426/*---------------------------------------------------------------------------*/
427
428void MemoryInfo::
429printInfos(std::ostream& ostr)
430{
431 bool is_collecting = global_check_memory;
432 // Since _printInfos() uses m_infos and may allocate memory, which
433 // would cause a modification of m_infos while we are in
434 // collection, we disable the hooks for the duration of the call.
435 if (is_collecting)
436 _pushHook();
437 try {
438 _printInfos(ostr);
439 }
440 catch (...) {
441 if (is_collecting)
442 _popHook();
443 throw;
444 }
445 if (is_collecting)
446 _popHook();
447}
448
449/*---------------------------------------------------------------------------*/
450/*---------------------------------------------------------------------------*/
451
452void MemoryInfo::
453_printInfos(std::ostream& ostr)
454{
455 Int64 total_size = 0;
456 ostr << "MemoryInfos: " << m_infos.size() << '\n';
457
458 size_t nb_chunk = m_infos.size();
459 std::vector<MemoryInfoSorter> sorted_chunk;
460 sorted_chunk.reserve(nb_chunk);
461 for (const auto& i : m_infos) {
462 sorted_chunk.push_back(MemoryInfoSorter(i.second.size(), i.second.allocId(),
463 i.second.iteration(),
464 i.first, i.second.owner(), i.second.stackTrace()));
465 }
466 std::sort(sorted_chunk.begin(), sorted_chunk.end());
467
468 for (const auto& i : sorted_chunk) {
469 const void* v = i.m_ptr;
470 const void* owner = i.m_owner;
471 Int64 size = i.m_size;
472 const TraceInfo* ti = 0;
473 {
474 MemoryTraceInfoMap::iterator i_owner = m_owner_infos.find(owner);
475 if (i_owner != m_owner_infos.end()) {
476 ti = &i_owner->second;
477 }
478 }
479 total_size += size;
480 if (size >= m_info_big_alloc) {
481 ostr << " Remaining: " << v << " size=" << size << " id=" << i.m_alloc_id
482 << " iteration=" << i.m_iteration
483 << TracePrinter(ti) << " trace=" << i.m_stack_trace << '\n';
484 }
485 }
486 ostr << "Total size=" << total_size;
487}
488
489/*---------------------------------------------------------------------------*/
490/*---------------------------------------------------------------------------*/
491
492void MemoryInfo::
493printAllocatedMemory(std::ostream& ostr, Integer iteration)
494{
495 ostr << " INFO_ALLOCATION: current= " << m_current_allocated
496 << " ITERATION= " << iteration
497 << " NB_CHUNK=" << m_infos.size()
498 << " ID=" << m_alloc_id
499 << '\n';
500 for (ConstIterT<MemoryInfoMap> i(m_infos); i(); ++i) {
501 const MemoryInfoChunk& mi = i->second;
502 if (mi.iteration() != iteration)
503 continue;
504 Int64 size = mi.size();
505 if (size >= m_info_big_alloc) {
506 ostr << " Allocated: " << " iteration=" << iteration
507 << " size=" << size << " id=" << mi.allocId()
508 << " trace=" << mi.stackTrace() << '\n';
509 }
510 }
511 for (ConstIterT<MemoryInfoMap> i(m_infos); i(); ++i) {
512 const MemoryInfoChunk& mi = i->second;
513 if (mi.iteration() != iteration)
514 continue;
515 Int64 size = mi.size();
516 if (size < m_info_big_alloc) {
517 ostr << " Allocated: " << " iteration=" << iteration
518 << " size=" << size << " id=" << mi.allocId() << '\n';
519 }
520 }
521}
522
523/*---------------------------------------------------------------------------*/
524/*---------------------------------------------------------------------------*/
525
526void MemoryInfo::
527setTraceMng(ITraceMng* trace)
528{
529 m_trace = trace;
530}
531
532/*---------------------------------------------------------------------------*/
533/*---------------------------------------------------------------------------*/
534
535void MemoryInfo::
536beginCollect()
537{
538 if (m_is_first_collect) {
539 String s = platform::getEnvironmentVariable("ARCANE_CHECK_MEMORY_BLOCK_SIZE");
540 if (!s.null()) {
541 Int64 block_size = 0;
542 bool is_bad = builtInGetValue(block_size, s);
543 if (!is_bad && block_size > 2) {
544 m_info_big_alloc = block_size;
545 m_info_biggest_minimal = block_size * 2;
546 m_info_peak_minimal = block_size * 10;
547 }
548 if (m_trace)
549 m_trace->info() << " BLOCK SIZE '" << s;
550 }
551 m_is_first_collect = false;
552 }
553 _initMallocHook();
554}
555
556/*---------------------------------------------------------------------------*/
557/*---------------------------------------------------------------------------*/
558
559void MemoryInfo::
560endCollect()
561{
562 _restoreMallocHook();
563}
564
565/*---------------------------------------------------------------------------*/
566/*---------------------------------------------------------------------------*/
567
568bool MemoryInfo::
569isCollecting() const
570{
571 return global_check_memory;
572}
573
574/*---------------------------------------------------------------------------*/
575/*---------------------------------------------------------------------------*/
576
577void MemoryInfo::
578checkMemory(const void* owner, Int64 size)
579{
580 if (m_current_allocated > m_max_allocated) {
581 m_max_allocated = m_current_allocated;
582 if (m_display_max_alloc && m_max_allocated > m_info_peak_minimal && size > 5000 && m_trace && !m_in_display) {
583 m_in_display = true;
584 m_trace->info() << "Memory:PEAK_MEM: iteration=" << m_iteration
585 << " max allocation reached: max="
586 << m_max_allocated << " size=" << size
587 << " id=" << m_alloc_id << " "
588 << TracePrinter(_getTraceInfo(owner));
589 m_in_display = false;
590 }
591 }
592 if (size > m_biggest_allocated) {
593 m_biggest_allocated = size;
594 if (m_display_max_alloc && m_biggest_allocated > m_info_biggest_minimal && m_trace && !m_in_display) {
595 m_in_display = true;
596 m_trace->info() << "Memory:PEAK_ALLOC: biggest allocation : " << size << " "
597 << " id=" << m_alloc_id << " "
598 << TracePrinter(_getTraceInfo(owner));
599 m_in_display = false;
600 }
601 }
602 if (m_info_big_alloc > 0 && size > m_info_big_alloc) {
603 if (m_display_max_alloc && m_trace && !m_in_display) {
604 m_in_display = true;
605 String stack_value;
606 IStackTraceService* s = platform::getStackTraceService();
607 if (s) {
608 stack_value = s->stackTrace(2).toString();
609 }
610#if 0
611 m_trace->info() << "Memory:BIG_ALLOC: iteration=" << m_iteration
612 << " big alloc= " << size
613 << " id=" << m_alloc_id
614 << " current=" << m_current_allocated
615 //<< " " << TracePrinter(_getTraceInfo(owner))
616 << " stack=" << stack_value;
617#endif
618 m_in_display = false;
619 }
620 }
621}
622
623/*---------------------------------------------------------------------------*/
624/*---------------------------------------------------------------------------*/
625
626void MemoryInfo::
627_removeMemory(const void* /*owner*/, Int64 size)
628{
629 m_current_allocated -= size;
630}
631
632/*---------------------------------------------------------------------------*/
633/*---------------------------------------------------------------------------*/
634
635TraceInfo* MemoryInfo::
636_getTraceInfo(const void* owner)
637{
638 MemoryTraceInfoMap::iterator i = m_owner_infos.find(owner);
639 if (i == m_owner_infos.end())
640 return 0;
641 return &i->second;
642}
643
644/*---------------------------------------------------------------------------*/
645/*---------------------------------------------------------------------------*/
646
647void MemoryInfo::
648visitAllocatedBlocks(IFunctorWithArgumentT<const MemoryInfoChunk&>* functor) const
649{
650 if (!functor)
651 return;
652 for (ConstIterT<MemoryInfoMap> i(m_infos); i(); ++i) {
653 const MemoryInfoChunk& mic = i->second;
654 functor->executeFunctor(mic);
655 }
656}
657
658/*---------------------------------------------------------------------------*/
659/*---------------------------------------------------------------------------*/
660
661extern "C++" IMemoryInfo*
662arcaneGlobalMemoryInfo()
663{
664 return arcaneGlobalTrueMemoryInfo();
665}
666
667/*---------------------------------------------------------------------------*/
668/*---------------------------------------------------------------------------*/
669
670} // End namespace Arcane
671
672/*---------------------------------------------------------------------------*/
673/*---------------------------------------------------------------------------*/
Interface of a functor with an argument but without a return value.
virtual StackTrace stackTrace(int first_function=0)=0
Character string indicating the call stack.
Information about an allocated chunk.
Definition IMemoryInfo.h:33
const String & toString() const
String indicating the call stack.
bool null() const
Returns true if the string is null.
Definition String.cc:306
IStackTraceService * getStackTraceService()
Service used to obtain the call stack.
String getEnvironmentVariable(const String &name)
Environment variable named name.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
std::int64_t Int64
Signed integer type of 64 bits.
Int32 Integer
Type representing an integer.
std::ostream & operator<<(std::ostream &ostr, eItemKind item_kind)
Output operator for a stream.