Panda3D
Loading...
Searching...
No Matches
memoryUsage.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file memoryUsage.cxx
10 * @author drose
11 * @date 2000-05-25
12 */
13
14#include "memoryUsage.h"
15#include "memoryUsagePointers.h"
16#include "trueClock.h"
17#include "typedReferenceCount.h"
18#include "mutexImpl.h"
19#include "interrogate_request.h"
20
21#if (defined(WIN32_VC) || defined (WIN64_VC)) && defined(_DEBUG)
22#include <crtdbg.h>
23#endif
24
25#include "config_express.h"
26#include "configVariableInt64.h"
27#include <algorithm>
28#include <iterator>
29
30using std::pair;
31
32MemoryUsage *MemoryUsage::_global_ptr;
33
34// This flag is used to protect the operator newdelete handlers against
35// recursive entry.
36bool MemoryUsage::_recursion_protect = false;
37
38// The cutoff ages, in seconds, for the various buckets in the AgeHistogram.
39double MemoryUsage::AgeHistogram::_cutoff[MemoryUsage::AgeHistogram::num_buckets] = {
40 0.0,
41 0.1,
42 1.0,
43 10.0,
44 60.0,
45};
46
47/**
48 * Adds a single entry to the histogram.
49 */
50void MemoryUsage::TypeHistogram::
51add_info(TypeHandle type, MemoryInfo *info) {
52#ifdef DO_MEMORY_USAGE
53 _counts[type].add_info(info);
54#endif
55}
56
57#ifdef DO_MEMORY_USAGE
58// This class is a temporary class used only in TypeHistogram::show(), below,
59// to sort the types in descending order by counts.
60class TypeHistogramCountSorter {
61public:
62 TypeHistogramCountSorter(const MemoryUsagePointerCounts &count,
63 TypeHandle type) :
64 _count(count),
65 _type(type)
66 {
67 }
68 bool operator < (const TypeHistogramCountSorter &other) const {
69 return other._count < _count;
70 }
72 TypeHandle _type;
73};
74#endif
75
76/**
77 * Shows the contents of the histogram to nout.
78 */
79void MemoryUsage::TypeHistogram::
80show() const {
81#ifdef DO_MEMORY_USAGE
82 // First, copy the relevant information to a vector so we can sort by
83 // counts. Don't use a pvector.
84 typedef std::vector<TypeHistogramCountSorter> CountSorter;
85 CountSorter count_sorter;
86 Counts::const_iterator ci;
87 for (ci = _counts.begin(); ci != _counts.end(); ++ci) {
88 count_sorter.push_back
89 (TypeHistogramCountSorter((*ci).second, (*ci).first));
90 }
91
92 sort(count_sorter.begin(), count_sorter.end());
93
94 CountSorter::const_iterator vi;
95 for (vi = count_sorter.begin(); vi != count_sorter.end(); ++vi) {
96 TypeHandle type = (*vi)._type;
97 if (type == TypeHandle::none()) {
98 nout << "unknown";
99 } else {
100 nout << type;
101 }
102 nout << " : " << (*vi)._count << "\n";
103 }
104#endif
105}
106
107/**
108 * Resets the histogram in preparation for new data.
109 */
110void MemoryUsage::TypeHistogram::
111clear() {
112 _counts.clear();
113}
114
115/**
116 *
117 */
118MemoryUsage::AgeHistogram::
119AgeHistogram() {
120 clear();
121}
122
123/**
124 * Adds a single entry to the histogram.
125 */
126void MemoryUsage::AgeHistogram::
127add_info(double age, MemoryInfo *info) {
128#ifdef DO_MEMORY_USAGE
129 int bucket = choose_bucket(age);
130 nassertv(bucket >= 0 && bucket < num_buckets);
131 _counts[bucket].add_info(info);
132#endif
133}
134
135/**
136 * Shows the contents of the histogram to nout.
137 */
138void MemoryUsage::AgeHistogram::
139show() const {
140#ifdef DO_MEMORY_USAGE
141 for (int i = 0; i < num_buckets - 1; i++) {
142 nout << _cutoff[i] << " to " << _cutoff[i + 1] << " seconds old : ";
143 _counts[i].output(nout);
144 nout << "\n";
145 }
146 nout << _cutoff[num_buckets - 1] << " seconds old and up : ";
147 _counts[num_buckets - 1].output(nout);
148 nout << "\n";
149#endif
150}
151
152/**
153 * Resets the histogram in preparation for new data.
154 */
155void MemoryUsage::AgeHistogram::
156clear() {
157#ifdef DO_MEMORY_USAGE
158 for (int i = 0; i < num_buckets; i++) {
159 _counts[i].clear();
160 }
161#endif
162}
163
164/**
165 *
166 */
167int MemoryUsage::AgeHistogram::
168choose_bucket(double age) const {
169#ifdef DO_MEMORY_USAGE
170 for (int i = num_buckets - 1; i >= 0; i--) {
171 if (age >= _cutoff[i]) {
172 return i;
173 }
174 }
175 express_cat.error()
176 << "No suitable bucket for age " << age << "\n";
177#endif
178 return 0;
179}
180
181/**
182 * Allocates a block of memory from the heap, similar to malloc(). This will
183 * never return NULL; it will abort instead if memory is not available.
184 */
185void *MemoryUsage::
186heap_alloc_single(size_t size) {
187#ifdef DO_MEMORY_USAGE
188 void *ptr;
189
190 if (_recursion_protect) {
192 if (express_cat.is_spam()) {
193 express_cat.spam()
194 << "Allocating pointer " << (void *)ptr
195 << " during recursion protect.\n";
196 }
197
198 } else {
199 if (_track_memory_usage) {
201 /*
202 if (express_cat.is_spam()) {
203 express_cat.spam()
204 << "Allocating pointer " << (void *)ptr
205 << " of size " << size << ".\n";
206 }
207 */
208
209 get_global_ptr()->ns_record_void_pointer(ptr, size);
210
211 } else {
213 }
214 }
215
216 return ptr;
217#else
219#endif
220}
221
222/**
223 * Releases a block of memory previously allocated via heap_alloc_single.
224 */
225void MemoryUsage::
226heap_free_single(void *ptr) {
227#ifdef DO_MEMORY_USAGE
228 if (_recursion_protect) {
229 if (express_cat.is_spam()) {
230 express_cat.spam()
231 << "Deleting pointer " << (void *)ptr
232 << " during recursion protect.\n";
233 }
235
236 } else {
237 if (_track_memory_usage) {
238 /*
239 if (express_cat.is_spam()) {
240 express_cat.spam()
241 << "Removing pointer " << (void *)ptr << "\n";
242 }
243 */
244 ns_remove_void_pointer(ptr);
246 } else {
248 }
249 }
250#else
252#endif
253}
254
255/**
256 * Allocates a block of memory from the heap, similar to malloc(). This will
257 * never return NULL; it will abort instead if memory is not available.
258 */
259void *MemoryUsage::
260heap_alloc_array(size_t size) {
261#ifdef DO_MEMORY_USAGE
262 void *ptr;
263
264 if (_recursion_protect) {
266 if (express_cat.is_spam()) {
267 express_cat.spam()
268 << "Allocating array pointer " << (void *)ptr
269 << " during recursion protect.\n";
270 }
271
272 } else {
273 if (_track_memory_usage) {
275 /*
276 if (express_cat.is_spam()) {
277 express_cat.spam()
278 << "Allocating array pointer " << (void *)ptr
279 << " of size " << size << ".\n";
280 }
281 */
282
283 get_global_ptr()->ns_record_void_pointer(ptr, size);
284
285 } else {
287 }
288 }
289
290 return ptr;
291#else
292 return MemoryHook::heap_alloc_array(size);
293#endif
294}
295
296/**
297 * Resizes a block of memory previously returned from heap_alloc_array.
298 */
299void *MemoryUsage::
300heap_realloc_array(void *ptr, size_t size) {
301#ifdef DO_MEMORY_USAGE
302 if (_recursion_protect) {
303 ptr = MemoryHook::heap_realloc_array(ptr, size);
304 if (express_cat.is_spam()) {
305 express_cat.spam()
306 << "Reallocating array pointer " << (void *)ptr
307 << " during recursion protect.\n";
308 }
309
310 } else {
311 if (_track_memory_usage) {
312 get_global_ptr()->ns_remove_void_pointer(ptr);
313 ptr = MemoryHook::heap_realloc_array(ptr, size);
314 /*
315 if (express_cat.is_spam()) {
316 express_cat.spam()
317 << "Reallocating array pointer " << (void *)ptr
318 << " to size " << size << ".\n";
319 }
320 */
321
322 get_global_ptr()->ns_record_void_pointer(ptr, size);
323
324 } else {
325 ptr = MemoryHook::heap_realloc_array(ptr, size);
326 }
327 }
328
329 return ptr;
330#else
331 return MemoryHook::heap_realloc_array(ptr, size);
332#endif
333}
334
335/**
336 * Releases a block of memory previously allocated via heap_alloc_array.
337 */
338void MemoryUsage::
339heap_free_array(void *ptr) {
340#ifdef DO_MEMORY_USAGE
341 if (_recursion_protect) {
342 if (express_cat.is_spam()) {
343 express_cat.spam()
344 << "Deleting pointer " << (void *)ptr
345 << " during recursion protect.\n";
346 }
348
349 } else {
350 if (_track_memory_usage) {
351 /*
352 if (express_cat.is_spam()) {
353 express_cat.spam()
354 << "Removing pointer " << (void *)ptr << "\n";
355 }
356 */
357 ns_remove_void_pointer(ptr);
359 } else {
361 }
362 }
363#else
365#endif
366}
367
368/**
369 * This special method exists only to provide a callback hook into
370 * MemoryUsage. It indicates that the indicated pointer, allocated from
371 * somewhere other than a call to heap_alloc(), now contains a pointer to the
372 * indicated ReferenceCount object. If orig_size is 0, it indicates that the
373 * ReferenceCount object has been destroyed.
374 */
375void MemoryUsage::
376mark_pointer(void *ptr, size_t size, ReferenceCount *ref_ptr) {
377#ifdef DO_MEMORY_USAGE
378 if (_recursion_protect || !_track_memory_usage) {
379 return;
380 }
381
382 if (express_cat.is_spam()) {
383 express_cat.spam()
384 << "Marking pointer " << ptr << ", size " << size
385 << ", ref_ptr = " << ref_ptr << "\n";
386 }
387
388 if (size != 0) {
389 // We're recording this pointer as now in use.
390 ns_record_void_pointer(ptr, size);
391
392 if (ref_ptr != nullptr) {
393 // Make the pointer typed. This is particularly necessary in case the
394 // ref_ptr is a different value than the base void pointer; this may be
395 // our only opportunity to associate the two pointers.
396 Table::iterator ti;
397 ti = _table.find(ptr);
398 nassertv(ti != _table.end());
399 MemoryInfo *info = (*ti).second;
400
401 info->_ref_ptr = ref_ptr;
402 info->_static_type = ReferenceCount::get_class_type();
403 info->_dynamic_type = ReferenceCount::get_class_type();
404 info->_flags |= MemoryInfo::F_reconsider_dynamic_type;
405
406 if (ref_ptr != ptr) {
407 _recursion_protect = true;
408
409 pair<Table::iterator, bool> insert_result =
410 _table.insert(Table::value_type((void *)ref_ptr, info));
411 assert(insert_result.first != _table.end());
412 if (!insert_result.second) {
413 express_cat.warning()
414 << "Attempt to mark pointer " << ptr << " as ReferenceCount "
415 << ref_ptr << ", which was already allocated.\n";
416 }
417
418 _recursion_protect = false;
419 }
420 }
421
422 } else {
423 // We're removing this pointer from use.
424 ns_remove_void_pointer(ptr);
425 }
426#endif
427}
428
429#if (defined(WIN32_VC) || defined (WIN64_VC))&& defined(_DEBUG)
430/**
431 * This callback is attached to the Win32 debug malloc system to be called
432 * whenever a pointer is allocated, reallocated, or freed. It's used to track
433 * the total memory allocated via calls to malloc().
434 */
435int MemoryUsage::
436win32_malloc_hook(int alloc_type, void *ptr,
437 size_t size, int block_use, long request,
438 const unsigned char *filename, int line) {
439 MemoryUsage *mu = get_global_ptr();
440 int increment = 0;
441 switch (alloc_type) {
442 case _HOOK_ALLOC:
443 increment = size;
444 break;
445
446 case _HOOK_REALLOC:
447 increment = size - _msize(ptr);
448 break;
449
450 case _HOOK_FREE:
451 increment = - ((int)_msize(ptr));
452 break;
453 }
454
455 mu->_total_size += increment;
456 return true;
457}
458#endif // WIN32_VC && _DEBUG
459
460
461
462/**
463 *
464 */
465MemoryUsage::
466MemoryUsage(const MemoryHook &copy) :
467 MemoryHook(copy),
468 _info_set_dirty(false),
469 _freeze_index(0),
470 _count(0),
471 _current_cpp_size(0),
472 _total_cpp_size(0),
473 _total_size(0),
474
475 _track_memory_usage(false),
476 _startup_track_memory_usage(false),
477 _count_memory_usage(false),
478 _report_memory_usage(false),
479 _report_memory_interval(0.0),
480 _last_report_time(0.0) {
481
482#ifdef DO_MEMORY_USAGE
483 // We must get these variables here instead of in config_express.cxx,
484 // because we need to know it at static init time, and who knows when the
485 // code in config_express will be executed.
486
487 _track_memory_usage = ConfigVariableBool
488 ("track-memory-usage", false,
489 PRC_DESC("Set this to true to enable full-force tracking of C++ allocations "
490 "and recordkeeping by type. It's quite expensive."));
491
492 // Since enabling this after startup might cause bogus errors, we'd like to
493 // know if this happened, so we can squelch those error messages.
494 _startup_track_memory_usage = _track_memory_usage;
495
496 // Make sure the express category has been instantiated.
497 express_cat->is_info();
498
499 _report_memory_usage = ConfigVariableBool
500 ("report-memory-usage", false,
501 PRC_DESC("Set this true to enable automatic reporting of allocated objects "
502 "at the interval specified by report-memory-interval. This also "
503 "requires track-memory-usage."));
504 _report_memory_interval = ConfigVariableDouble
505 ("report-memory-interval", 5.0,
506 PRC_DESC("This is the interval, in seconds, for reports of currently allocated "
507 "memory, when report-memory-usage is true."));
508
509 int64_t max_heap_size = ConfigVariableInt64
510 ("max-heap-size", 0,
511 PRC_DESC("If this is nonzero, it is the maximum number of bytes expected "
512 "to be allocated on the heap before we enter report-memory-usage "
513 "mode automatically. The assumption is that once this limit "
514 "has been crossed, we must be leaking."));
515 if (max_heap_size != 0) {
516 _max_heap_size = (size_t)max_heap_size;
517 }
518
519#ifdef USE_MEMORY_NOWRAPPERS
520#error Cannot compile MemoryUsage without malloc wrappers!
521#endif
522
523#if (defined(WIN32_VC) || defined(WIN64_VC)) && defined(_DEBUG)
524 // On a debug Windows build, we can set this malloc hook which allows
525 // tracking every malloc call, even from subordinate libraries.
526 _CrtSetAllocHook(&win32_malloc_hook);
527 _count_memory_usage = true;
528#endif
529#endif // DO_MEMORY_USAGE
530}
531
532/**
533 * Initializes the global MemoryUsage pointer.
534 */
535void MemoryUsage::
536init_memory_usage() {
537#ifdef DO_MEMORY_USAGE
539 _global_ptr = new MemoryUsage(*memory_hook);
540 memory_hook = _global_ptr;
541#else
542 // If this gets called, we still need to initialize the global_ptr with a
543 // stub even if we don't compile with memory usage tracking enabled, for ABI
544 // stability. However, we won't replace the memory hook.
545 _global_ptr = new MemoryUsage(*memory_hook);
546#endif
547}
548
549/**
550 * This callback method is called whenever the total allocated heap size
551 * exceeds _max_heap_size. It's mainly intended for reporting memory leaks,
552 * on the assumption that once we cross some specified threshold, we're just
553 * leaking memory.
554 */
555void MemoryUsage::
556overflow_heap_size() {
557#ifdef DO_MEMORY_USAGE
558 MemoryHook::overflow_heap_size();
559
560 express_cat.error()
561 << "Total allocated memory has reached "
563 << " bytes."
564 << "\n heap single: " << get_panda_heap_single_size()
565 << "\n heap array: " << get_panda_heap_array_size()
566 << "\n heap overhead: " << get_panda_heap_overhead()
567 << "\n mmap: " << get_panda_mmap_size()
568 << "\n external: " << get_external_size()
569 << "\n total: " << get_total_size()
570 << "\n";
571
572 // Turn on spamful debugging.
573 _track_memory_usage = true;
574 _report_memory_usage = true;
575#endif
576}
577
578/**
579 * Indicates that the given pointer has been recently allocated.
580 */
581void MemoryUsage::
582ns_record_pointer(ReferenceCount *ptr) {
583#ifdef DO_MEMORY_USAGE
584 if (_track_memory_usage) {
585 // We have to protect modifications to the table from recursive calls by
586 // toggling _recursion_protect while we adjust it.
587 _recursion_protect = true;
588 pair<Table::iterator, bool> insert_result =
589 _table.insert(Table::value_type((void *)ptr, nullptr));
590
591 // This shouldn't fail.
592 assert(insert_result.first != _table.end());
593
594 if (insert_result.second) {
595 (*insert_result.first).second = new MemoryInfo;
596 _info_set_dirty = true;
597 ++_count;
598 }
599
600 MemoryInfo *info = (*insert_result.first).second;
601
602 // We might already have a ReferenceCount pointer, thanks to a previous
603 // call to mark_pointer().
604 nassertv(info->_ref_ptr == nullptr || info->_ref_ptr == ptr);
605
606 info->_ref_ptr = ptr;
607 info->_static_type = ReferenceCount::get_class_type();
608 info->_dynamic_type = ReferenceCount::get_class_type();
609 info->_time = TrueClock::get_global_ptr()->get_long_time();
610 info->_freeze_index = _freeze_index;
611 info->_flags |= MemoryInfo::F_reconsider_dynamic_type;
612
613 // We close the recursion_protect flag all the way down here, so that we
614 // also protect ourselves against a possible recursive call in
615 // TrueClock::get_global_ptr().
616 _recursion_protect = false;
617
618 if (_report_memory_usage) {
619 double now = TrueClock::get_global_ptr()->get_long_time();
620 if (now - _last_report_time > _report_memory_interval) {
621 _last_report_time = now;
622 express_cat.info()
623 << "*** Current memory usage: " << get_total_size() << "\n";
625 }
626 }
627 }
628#endif
629}
630
631
632/**
633 * Indicates that the given pointer has been recently allocated.
634 */
635void MemoryUsage::
636ns_record_pointer(void *ptr, TypeHandle type) {
637#ifdef DO_MEMORY_USAGE
638 if (_track_memory_usage) {
639 // We have to protect modifications to the table from recursive calls by
640 // toggling _recursion_protect while we adjust it.
641 _recursion_protect = true;
642 pair<Table::iterator, bool> insert_result =
643 _table.insert(Table::value_type(ptr, nullptr));
644
645 // This shouldn't fail.
646 assert(insert_result.first != _table.end());
647
648 if (insert_result.second) {
649 (*insert_result.first).second = new MemoryInfo;
650 _info_set_dirty = true;
651 ++_count;
652 }
653
654 MemoryInfo *info = (*insert_result.first).second;
655
656 // We should already have a pointer, thanks to a previous call to
657 // mark_pointer().
658 nassertv(info->_void_ptr == ptr && info->_ref_ptr == nullptr);
659
660 info->_void_ptr = ptr;
661 info->_static_type = type;
662 info->_dynamic_type = type;
663 info->_time = TrueClock::get_global_ptr()->get_long_time();
664 info->_freeze_index = _freeze_index;
665 info->_flags |= MemoryInfo::F_reconsider_dynamic_type;
666
667 // We close the recursion_protect flag all the way down here, so that we
668 // also protect ourselves against a possible recursive call in
669 // TrueClock::get_global_ptr().
670 _recursion_protect = false;
671
672 if (_report_memory_usage) {
673 double now = TrueClock::get_global_ptr()->get_long_time();
674 if (now - _last_report_time > _report_memory_interval) {
675 _last_report_time = now;
676 express_cat.info()
677 << "*** Current memory usage: " << get_total_size() << "\n";
679 }
680 }
681 }
682#endif
683}
684
685/**
686 * Associates the indicated type with the given pointer. This should be
687 * called by functions (e.g. the constructor) that know more specifically
688 * what type of thing we've got; otherwise, the MemoryUsage database will know
689 * only that it's a "ReferenceCount".
690 */
691void MemoryUsage::
692ns_update_type(void *ptr, TypeHandle type) {
693#ifdef DO_MEMORY_USAGE
694 if (_track_memory_usage) {
695 Table::iterator ti;
696 ti = _table.find(ptr);
697 if (ti == _table.end()) {
698 if (_startup_track_memory_usage) {
699 express_cat.error()
700 << "Attempt to update type to " << type << " for unrecorded pointer "
701 << ptr << "!\n";
702 nassertv(false);
703 }
704 return;
705 }
706
707 MemoryInfo *info = (*ti).second;
708
709 info->update_type_handle(info->_static_type, type);
710 info->determine_dynamic_type();
711
712 consolidate_void_ptr(info);
713 }
714#endif
715}
716
717/**
718 * Associates the indicated type with the given pointer. This flavor of
719 * update_type() also passes in the pointer as a TypedObject, and useful for
720 * objects that are, in fact, TypedObjects. Once the MemoryUsage database has
721 * the pointer as a TypedObject it doesn't need any more help.
722 */
723void MemoryUsage::
724ns_update_type(void *ptr, TypedObject *typed_ptr) {
725#ifdef DO_MEMORY_USAGE
726 if (_track_memory_usage) {
727 Table::iterator ti;
728 ti = _table.find(ptr);
729 if (ti == _table.end()) {
730 if (_startup_track_memory_usage) {
731 express_cat.error()
732 << "Attempt to update type to " << typed_ptr->get_type()
733 << " for unrecorded pointer "
734 << ptr << "!\n";
735 }
736 return;
737 }
738
739 MemoryInfo *info = (*ti).second;
740 info->_typed_ptr = typed_ptr;
741 info->determine_dynamic_type();
742
743 consolidate_void_ptr(info);
744 }
745#endif
746}
747
748/**
749 * Indicates that the given pointer has been recently freed.
750 */
751void MemoryUsage::
752ns_remove_pointer(ReferenceCount *ptr) {
753#ifdef DO_MEMORY_USAGE
754 if (_track_memory_usage) {
755 Table::iterator ti;
756 ti = _table.find(ptr);
757 if (ti == _table.end()) {
758 if (_startup_track_memory_usage) {
759 express_cat.error()
760 << "Attempt to remove pointer " << (void *)ptr
761 << ", not in table.\n"
762 << "Possibly a double-destruction.\n";
763 nassertv(false);
764 }
765 return;
766 }
767
768 MemoryInfo *info = (*ti).second;
769
770 if (info->_ref_ptr == nullptr) {
771 express_cat.error()
772 << "Pointer " << (void *)ptr << " deleted twice!\n";
773 return;
774 }
775 nassertv(info->_ref_ptr == ptr);
776
777 if (express_cat.is_spam()) {
778 express_cat.spam()
779 << "Removing ReferenceCount pointer " << (void *)ptr << "\n";
780 }
781
782 info->_ref_ptr = nullptr;
783 info->_typed_ptr = nullptr;
784
785 if (info->_freeze_index == _freeze_index) {
786 double now = TrueClock::get_global_ptr()->get_long_time();
787
788 // We have to protect modifications to the table from recursive calls by
789 // toggling _recursion_protect while we adjust it.
790 _recursion_protect = true;
791 _trend_types.add_info(info->get_type(), info);
792 _trend_ages.add_info(now - info->_time, info);
793 _recursion_protect = false;
794 }
795
796 if (ptr != info->_void_ptr || info->_void_ptr == nullptr) {
797 // Remove the entry from the table.
798
799 // We have to protect modifications to the table from recursive calls by
800 // toggling _recursion_protect while we adjust it.
801 _recursion_protect = true;
802 _table.erase(ti);
803 _recursion_protect = false;
804
805 if (info->_void_ptr == nullptr) {
806 // That was the last entry. Remove it altogether.
807 _total_cpp_size -= info->_size;
808 if (info->_freeze_index == _freeze_index) {
809 _current_cpp_size -= info->_size;
810 _count--;
811 }
812
813 _info_set_dirty = true;
814 delete info;
815 }
816 }
817 }
818#endif
819}
820
821/**
822 * Records a pointer that's not even necessarily a ReferenceCount object (but
823 * for which we know the size of the allocated structure).
824 */
825void MemoryUsage::
826ns_record_void_pointer(void *ptr, size_t size) {
827#ifdef DO_MEMORY_USAGE
828 if (_track_memory_usage) {
829 if (express_cat.is_spam()) {
830 express_cat.spam()
831 << "Recording void pointer " << (void *)ptr << "\n";
832 }
833
834 // We have to protect modifications to the table from recursive calls by
835 // toggling _recursion_protect while we adjust it.
836
837 _recursion_protect = true;
838 pair<Table::iterator, bool> insert_result =
839 _table.insert(Table::value_type((void *)ptr, nullptr));
840
841 assert(insert_result.first != _table.end());
842
843 if (insert_result.second) {
844 (*insert_result.first).second = new MemoryInfo;
845 _info_set_dirty = true;
846 ++_count;
847 }
848
849 MemoryInfo *info = (*insert_result.first).second;
850
851 // We shouldn't already have a void pointer.
852 if (info->_void_ptr != nullptr) {
853 express_cat.error()
854 << "Void pointer " << (void *)ptr << " recorded twice!\n";
855 nassertv(false);
856 }
857
858 if (info->_freeze_index == _freeze_index) {
859 _current_cpp_size += size - info->_size;
860 } else {
861 _current_cpp_size += size;
862 }
863 _total_cpp_size += size - info->_size;
864
865 info->_void_ptr = ptr;
866 info->_size = size;
867 info->_time = TrueClock::get_global_ptr()->get_long_time();
868 info->_freeze_index = _freeze_index;
869 info->_flags |= MemoryInfo::F_size_known;
870
871 // We close the recursion_protect flag all the way down here, so that we
872 // also protect ourselves against a possible recursive call in
873 // TrueClock::get_global_ptr().
874 _recursion_protect = false;
875 }
876#endif
877}
878
879/**
880 * Removes a pointer previously recorded via record_void_pointer.
881 */
882void MemoryUsage::
883ns_remove_void_pointer(void *ptr) {
884#ifdef DO_MEMORY_USAGE
885 if (_track_memory_usage) {
886 if (express_cat.is_spam()) {
887 express_cat.spam()
888 << "Removing void pointer " << (void *)ptr << "\n";
889 }
890
891 Table::iterator ti;
892 ti = _table.find(ptr);
893 if (ti == _table.end()) {
894 // The pointer we tried to delete was not recorded in the table.
895
896 // We can't report this as an error, because (a) we might have removed
897 // the void pointer entry already when we consolidated, and (b) a few
898 // objects might have been created during static init time, before we
899 // grabbed the operator newdelete function handlers.
900 return;
901 }
902
903 MemoryInfo *info = (*ti).second;
904
905 if (info->_void_ptr == nullptr) {
906 express_cat.error()
907 << "Pointer " << (void *)ptr << " deleted twice!\n";
908 return;
909 }
910 nassertv(info->_void_ptr == ptr);
911
912 if (info->_ref_ptr != nullptr) {
913 express_cat.error()
914 << "Pointer " << (void *)ptr
915 << " did not destruct before being deleted!\n";
916 if (info->_ref_ptr != ptr) {
917 remove_pointer(info->_ref_ptr);
918 }
919 }
920
921 info->_void_ptr = nullptr;
922
923 // Remove it from the table.
924
925 // We have to protect modifications to the table from recursive calls by
926 // toggling _recursion_protect while we adjust it.
927 _recursion_protect = true;
928 _table.erase(ti);
929 _recursion_protect = false;
930
931 _total_cpp_size -= info->_size;
932 if (info->_freeze_index == _freeze_index) {
933 --_count;
934 _current_cpp_size -= info->_size;
935 }
936
937 _info_set_dirty = true;
938 delete info;
939 }
940#endif
941}
942
943/**
944 * Returns the number of pointers currently active.
945 */
946int MemoryUsage::
947ns_get_num_pointers() {
948#ifdef DO_MEMORY_USAGE
949 nassertr(_track_memory_usage, 0);
950 return _count;
951#else
952 return 0;
953#endif
954}
955
956/**
957 * Fills the indicated MemoryUsagePointers with the set of all pointers
958 * currently active.
959 */
960void MemoryUsage::
961ns_get_pointers(MemoryUsagePointers &result) {
962#ifdef DO_MEMORY_USAGE
963 nassertv(_track_memory_usage);
964 result.clear();
965
966 if (_info_set_dirty) {
967 refresh_info_set();
968 }
969
970 double now = TrueClock::get_global_ptr()->get_long_time();
971 InfoSet::iterator si;
972 for (si = _info_set.begin(); si != _info_set.end(); ++si) {
973 MemoryInfo *info = (*si);
974 if (info->_freeze_index == _freeze_index &&
975 info->_ref_ptr != nullptr) {
976 result.add_entry(info->_ref_ptr, info->_typed_ptr, info->get_type(),
977 now - info->_time);
978 }
979 }
980#endif
981}
982
983/**
984 * Fills the indicated MemoryUsagePointers with the set of all pointers of the
985 * indicated type currently active.
986 */
987void MemoryUsage::
988ns_get_pointers_of_type(MemoryUsagePointers &result, TypeHandle type) {
989#ifdef DO_MEMORY_USAGE
990 nassertv(_track_memory_usage);
991 result.clear();
992
993 if (_info_set_dirty) {
994 refresh_info_set();
995 }
996
997 double now = TrueClock::get_global_ptr()->get_long_time();
998 InfoSet::iterator si;
999 for (si = _info_set.begin(); si != _info_set.end(); ++si) {
1000 MemoryInfo *info = (*si);
1001 if (info->_freeze_index == _freeze_index &&
1002 info->_ref_ptr != nullptr) {
1003 TypeHandle info_type = info->get_type();
1004 if (info_type != TypeHandle::none() &&
1005 info_type.is_derived_from(type)) {
1006 result.add_entry(info->_ref_ptr, info->_typed_ptr, info_type,
1007 now - info->_time);
1008 }
1009 }
1010 }
1011#endif
1012}
1013
1014/**
1015 * Fills the indicated MemoryUsagePointers with the set of all pointers that
1016 * were allocated within the range of the indicated number of seconds ago.
1017 */
1018void MemoryUsage::
1019ns_get_pointers_of_age(MemoryUsagePointers &result,
1020 double from, double to) {
1021#ifdef DO_MEMORY_USAGE
1022 nassertv(_track_memory_usage);
1023 result.clear();
1024
1025 if (_info_set_dirty) {
1026 refresh_info_set();
1027 }
1028
1029 double now = TrueClock::get_global_ptr()->get_long_time();
1030 InfoSet::iterator si;
1031 for (si = _info_set.begin(); si != _info_set.end(); ++si) {
1032 MemoryInfo *info = (*si);
1033 if (info->_freeze_index == _freeze_index &&
1034 info->_ref_ptr != nullptr) {
1035 double age = now - info->_time;
1036 if ((age >= from && age <= to) ||
1037 (age >= to && age <= from)) {
1038 result.add_entry(info->_ref_ptr, info->_typed_ptr, info->get_type(), age);
1039 }
1040 }
1041 }
1042#endif
1043}
1044
1045/**
1046 * Fills the indicated MemoryUsagePointers with the set of all currently
1047 * active pointers (that is, pointers allocated since the last call to
1048 * freeze(), and not yet freed) that have a zero reference count.
1049 *
1050 * Generally, an undeleted pointer with a zero reference count means its
1051 * reference count has never been incremented beyond zero (since once it has
1052 * been incremented, the only way it can return to zero would free the
1053 * pointer). This may include objects that are allocated statically or on the
1054 * stack, which are never intended to be deleted. Or, it might represent a
1055 * programmer or compiler error.
1056 *
1057 * This function has the side-effect of incrementing each of their reference
1058 * counts by one, thus preventing them from ever being freed--but since they
1059 * hadn't been freed anyway, probably no additional harm is done.
1060 */
1061void MemoryUsage::
1062ns_get_pointers_with_zero_count(MemoryUsagePointers &result) {
1063#ifdef DO_MEMORY_USAGE
1064 nassertv(_track_memory_usage);
1065 result.clear();
1066
1067 if (_info_set_dirty) {
1068 refresh_info_set();
1069 }
1070
1071 double now = TrueClock::get_global_ptr()->get_long_time();
1072 InfoSet::iterator si;
1073 for (si = _info_set.begin(); si != _info_set.end(); ++si) {
1074 MemoryInfo *info = (*si);
1075 if (info->_freeze_index == _freeze_index &&
1076 info->_ref_ptr != nullptr) {
1077 if (info->_ref_ptr->get_ref_count() == 0) {
1078 info->_ref_ptr->ref();
1079 result.add_entry(info->_ref_ptr, info->_typed_ptr, info->get_type(),
1080 now - info->_time);
1081 }
1082 }
1083 }
1084#endif
1085}
1086
1087/**
1088 * 'Freezes' all pointers currently stored so that they are no longer
1089 * reported; only newly allocate pointers from this point on will appear in
1090 * future information requests. This makes it easier to differentiate between
1091 * continuous leaks and one-time memory allocations.
1092 */
1093void MemoryUsage::
1094ns_freeze() {
1095#ifdef DO_MEMORY_USAGE
1096 _count = 0;
1097 _current_cpp_size = 0;
1098 _trend_types.clear();
1099 _trend_ages.clear();
1100 _freeze_index++;
1101#endif
1102}
1103
1104/**
1105 * Shows the breakdown of types of all of the active pointers.
1106 */
1107void MemoryUsage::
1108ns_show_current_types() {
1109#ifdef DO_MEMORY_USAGE
1110 nassertv(_track_memory_usage);
1111 TypeHistogram hist;
1112
1113 if (_info_set_dirty) {
1114 refresh_info_set();
1115 }
1116
1117 _recursion_protect = true;
1118 InfoSet::iterator si;
1119 for (si = _info_set.begin(); si != _info_set.end(); ++si) {
1120 MemoryInfo *info = (*si);
1121 if (info->_freeze_index == _freeze_index) {
1122 hist.add_info(info->get_type(), info);
1123 }
1124 }
1125 hist.show();
1126 _recursion_protect = false;
1127#endif
1128}
1129
1130/**
1131 * Shows the breakdown of types of all of the pointers allocated and freed
1132 * since the last call to freeze().
1133 */
1134void MemoryUsage::
1135ns_show_trend_types() {
1136#ifdef DO_MEMORY_USAGE
1137 _trend_types.show();
1138#endif
1139}
1140
1141/**
1142 * Shows the breakdown of ages of all of the active pointers.
1143 */
1144void MemoryUsage::
1145ns_show_current_ages() {
1146#ifdef DO_MEMORY_USAGE
1147 nassertv(_track_memory_usage);
1148
1149 AgeHistogram hist;
1150 double now = TrueClock::get_global_ptr()->get_long_time();
1151
1152 _recursion_protect = true;
1153 InfoSet::iterator si;
1154 for (si = _info_set.begin(); si != _info_set.end(); ++si) {
1155 MemoryInfo *info = (*si);
1156 if (info->_freeze_index == _freeze_index) {
1157 hist.add_info(now - info->_time, info);
1158 }
1159 }
1160
1161 hist.show();
1162 _recursion_protect = false;
1163#endif
1164}
1165
1166/**
1167 * Shows the breakdown of ages of all of the pointers allocated and freed
1168 * since the last call to freeze().
1169 */
1170void MemoryUsage::
1171ns_show_trend_ages() {
1172 _trend_ages.show();
1173}
1174
1175#ifdef DO_MEMORY_USAGE
1176
1177/**
1178 * If the size information has not yet been determined for this pointer,
1179 * checks to see if it has possibly been recorded under the TypedObject
1180 * pointer (this will happen when the class inherits from TypedObject before
1181 * ReferenceCount, e.g. TypedReferenceCount).
1182 */
1183void MemoryUsage::
1184consolidate_void_ptr(MemoryInfo *info) {
1185 if (info->is_size_known()) {
1186 // We already know the size, so no sweat.
1187 return;
1188 }
1189
1190 if (info->_typed_ptr == nullptr) {
1191 // We don't have a typed pointer for this thing yet.
1192 return;
1193 }
1194
1195 TypedObject *typed_ptr = info->_typed_ptr;
1196
1197 if ((void *)typed_ptr == (void *)info->_ref_ptr) {
1198 // The TypedObject pointer is the same pointer as the ReferenceCount
1199 // pointer, so there's no point in looking it up separately. Actually,
1200 // this really shouldn't even be possible.
1201 return;
1202 }
1203
1204 nassertv(info->_void_ptr == nullptr);
1205
1206 Table::iterator ti;
1207 ti = _table.find(typed_ptr);
1208 if (ti == _table.end()) {
1209 // No entry for the typed pointer, either.
1210 return;
1211 }
1212
1213 // We do have an entry! Copy over the relevant pieces.
1214 MemoryInfo *typed_info = (*ti).second;
1215
1216 nassertv(typed_info->_void_ptr == typed_ptr &&
1217 typed_info->_ref_ptr == nullptr);
1218
1219 info->_void_ptr = typed_info->_void_ptr;
1220 if (typed_info->is_size_known()) {
1221 info->_size = typed_info->get_size();
1222 info->_flags |= MemoryInfo::F_size_known;
1223 if (typed_info->_freeze_index == _freeze_index) {
1224 _current_cpp_size += info->_size;
1225 }
1226 }
1227
1228 // Now that we've consolidated the pointers, remove the entry for the typed
1229 // pointer.
1230 if (info->_freeze_index == _freeze_index) {
1231 _count--;
1232 _current_cpp_size -= info->_size;
1233 }
1234
1235 _info_set_dirty = true;
1236 delete typed_info;
1237
1238 (*ti).second = info;
1239}
1240
1241/**
1242 * Recomputes the _info_set table, if necessary. This table stores a unique
1243 * entry for each MemoryInfo object in _table.
1244 */
1245void MemoryUsage::
1246refresh_info_set() {
1247 if (!_info_set_dirty) {
1248 return;
1249 }
1250
1251 // We have to protect modifications to the table from recursive calls by
1252 // toggling _recursion_protect while we adjust it.
1253 _recursion_protect = true;
1254
1255 _info_set.clear();
1256 Table::iterator ti;
1257 for (ti = _table.begin(); ti != _table.end(); ++ti) {
1258 _info_set.insert((*ti).second);
1259 }
1260
1261 _recursion_protect = false;
1262
1263 _info_set_dirty = false;
1264}
1265
1266#endif // DO_MEMORY_USAGE
This is a convenience class to specialize ConfigVariable as a boolean type.
This is a convenience class to specialize ConfigVariable as a floating- point type.
This is a convenience class to specialize ConfigVariable as a 64-bit integer type.
This class provides a wrapper around the various possible malloc schemes Panda might employ.
Definition memoryHook.h:37
virtual void * heap_alloc_array(size_t size)
Allocates a block of memory from the heap, similar to malloc().
virtual void heap_free_array(void *ptr)
Releases a block of memory previously allocated via heap_alloc_array.
virtual void * heap_alloc_single(size_t size)
Allocates a block of memory from the heap, similar to malloc().
virtual void heap_free_single(void *ptr)
Releases a block of memory previously allocated via heap_alloc_single.
virtual void * heap_realloc_array(void *ptr, size_t size)
Resizes a block of memory previously returned from heap_alloc_array.
This is a supporting class for MemoryUsage.
This is a list of pointers returned by a MemoryUsage object in response to some query.
void clear()
Empties the set of pointers.
This class is used strictly for debugging purposes, specifically for tracking memory leaks of referen...
Definition memoryUsage.h:35
static void show_current_types()
Shows the breakdown of types of all of the active pointers.
get_external_size
Returns the total number of bytes of allocated memory in the heap that Panda didn't seem to be respon...
get_panda_mmap_size
Returns the total number of bytes allocated from the virtual memory pool from code within Panda.
get_panda_heap_array_size
Returns the total number of bytes allocated from the heap from code within Panda, for arrays.
Definition memoryUsage.h:99
get_total_size
Returns the total size of allocated memory consumed by the process, as nearly as can be determined.
get_panda_heap_single_size
Returns the total number of bytes allocated from the heap from code within Panda, for individual obje...
Definition memoryUsage.h:98
static void remove_pointer(ReferenceCount *ptr)
Indicates that the given pointer has been recently freed.
Definition memoryUsage.I:90
get_panda_heap_overhead
Returns the extra bytes allocated from the system that are not immediately used for holding allocated...
A base class for all things that want to be reference-counted.
static TrueClock * get_global_ptr()
Returns a pointer to the one TrueClock object in the world.
Definition trueClock.I:68
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
bool is_derived_from(TypeHandle parent, TypedObject *object=nullptr) const
Returns true if this type is derived from the indicated type, false otherwise.
Definition typeHandle.I:105
This is an abstract class that all classes which use TypeHandle, and also provide virtual functions t...
Definition typedObject.h:88
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void init_memory_hook()
Any code that might need to use PANDA_MALLOC or PANDA_FREE, or any methods of the global memory_hook ...
Definition dtoolbase.cxx:38
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.