Panda3D
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 
30 using std::pair;
31 
32 MemoryUsage *MemoryUsage::_global_ptr;
33 
34 // This flag is used to protect the operator newdelete handlers against
35 // recursive entry.
36 bool MemoryUsage::_recursion_protect = false;
37 
38 // The cutoff ages, in seconds, for the various buckets in the AgeHistogram.
39 double 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  */
50 void MemoryUsage::TypeHistogram::
51 add_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.
60 class TypeHistogramCountSorter {
61 public:
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  */
79 void MemoryUsage::TypeHistogram::
80 show() 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  */
110 void MemoryUsage::TypeHistogram::
111 clear() {
112  _counts.clear();
113 }
114 
115 /**
116  *
117  */
118 MemoryUsage::AgeHistogram::
119 AgeHistogram() {
120  clear();
121 }
122 
123 /**
124  * Adds a single entry to the histogram.
125  */
126 void MemoryUsage::AgeHistogram::
127 add_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  */
138 void MemoryUsage::AgeHistogram::
139 show() 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  */
155 void MemoryUsage::AgeHistogram::
156 clear() {
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  */
167 int MemoryUsage::AgeHistogram::
168 choose_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  */
185 void *MemoryUsage::
186 heap_alloc_single(size_t size) {
187 #ifdef DO_MEMORY_USAGE
188  void *ptr;
189 
190  if (_recursion_protect) {
191  ptr = MemoryHook::heap_alloc_single(size);
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) {
200  ptr = MemoryHook::heap_alloc_single(size);
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 {
212  ptr = MemoryHook::heap_alloc_single(size);
213  }
214  }
215 
216  return ptr;
217 #else
218  return MemoryHook::heap_alloc_single(size);
219 #endif
220 }
221 
222 /**
223  * Releases a block of memory previously allocated via heap_alloc_single.
224  */
225 void MemoryUsage::
226 heap_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  */
259 void *MemoryUsage::
260 heap_alloc_array(size_t size) {
261 #ifdef DO_MEMORY_USAGE
262  void *ptr;
263 
264  if (_recursion_protect) {
265  ptr = MemoryHook::heap_alloc_array(size);
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) {
274  ptr = MemoryHook::heap_alloc_array(size);
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 {
286  ptr = MemoryHook::heap_alloc_array(size);
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  */
299 void *MemoryUsage::
300 heap_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  */
338 void MemoryUsage::
339 heap_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  */
375 void MemoryUsage::
376 mark_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  */
435 int MemoryUsage::
436 win32_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  */
465 MemoryUsage::
466 MemoryUsage(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  */
535 void MemoryUsage::
536 init_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  */
555 void MemoryUsage::
556 overflow_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  */
581 void MemoryUsage::
582 ns_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  */
635 void MemoryUsage::
636 ns_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  */
691 void MemoryUsage::
692 ns_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  */
723 void MemoryUsage::
724 ns_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  */
751 void MemoryUsage::
752 ns_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  */
825 void MemoryUsage::
826 ns_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  */
882 void MemoryUsage::
883 ns_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  */
946 int MemoryUsage::
947 ns_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  */
960 void MemoryUsage::
961 ns_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  */
987 void MemoryUsage::
988 ns_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  */
1018 void MemoryUsage::
1019 ns_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  */
1061 void MemoryUsage::
1062 ns_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  */
1093 void MemoryUsage::
1094 ns_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  */
1107 void MemoryUsage::
1108 ns_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  */
1134 void MemoryUsage::
1135 ns_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  */
1144 void MemoryUsage::
1145 ns_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  */
1170 void MemoryUsage::
1171 ns_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  */
1183 void MemoryUsage::
1184 consolidate_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  */
1245 void MemoryUsage::
1246 refresh_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
static TrueClock * get_global_ptr()
Returns a pointer to the one TrueClock object in the world.
Definition: trueClock.I:68
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
virtual void heap_free_array(void *ptr)
Releases a block of memory previously allocated via heap_alloc_array.
Definition: memoryHook.cxx:448
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_panda_mmap_size
Returns the total number of bytes allocated from the virtual memory pool from code within Panda.
Definition: memoryUsage.h:101
get_external_size
Returns the total number of bytes of allocated memory in the heap that Panda didn't seem to be respon...
Definition: memoryUsage.h:102
virtual void * heap_alloc_single(size_t size)
Allocates a block of memory from the heap, similar to malloc().
Definition: memoryHook.cxx:250
This is a convenience class to specialize ConfigVariable as a boolean type.
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
void clear()
Empties the set of pointers.
This is an abstract class that all classes which use TypeHandle, and also provide virtual functions t...
Definition: typedObject.h:88
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 a convenience class to specialize ConfigVariable as a 64-bit integer type.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_panda_heap_overhead
Returns the extra bytes allocated from the system that are not immediately used for holding allocated...
Definition: memoryUsage.h:100
This is a convenience class to specialize ConfigVariable as a floating- point type.
This is a list of pointers returned by a MemoryUsage object in response to some query.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static void remove_pointer(ReferenceCount *ptr)
Indicates that the given pointer has been recently freed.
Definition: memoryUsage.I:90
virtual void heap_free_single(void *ptr)
Releases a block of memory previously allocated via heap_alloc_single.
Definition: memoryHook.cxx:300
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class is used strictly for debugging purposes, specifically for tracking memory leaks of referen...
Definition: memoryUsage.h:35
A base class for all things that want to be reference-counted.
virtual void * heap_alloc_array(size_t size)
Allocates a block of memory from the heap, similar to malloc().
Definition: memoryHook.cxx:327
get_total_size
Returns the total size of allocated memory consumed by the process, as nearly as can be determined.
Definition: memoryUsage.h:103
This class provides a wrapper around the various possible malloc schemes Panda might employ.
Definition: memoryHook.h:37
static void show_current_types()
Shows the breakdown of types of all of the active pointers.
Definition: memoryUsage.I:347
virtual void * heap_realloc_array(void *ptr, size_t size)
Resizes a block of memory previously returned from heap_alloc_array.
Definition: memoryHook.cxx:377
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a supporting class for MemoryUsage.