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
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().
Definition: memoryHook.cxx:327
virtual void heap_free_array(void *ptr)
Releases a block of memory previously allocated via heap_alloc_array.
Definition: memoryHook.cxx:448
virtual void * heap_alloc_single(size_t size)
Allocates a block of memory from the heap, similar to malloc().
Definition: memoryHook.cxx:250
virtual void heap_free_single(void *ptr)
Releases a block of memory previously allocated via heap_alloc_single.
Definition: memoryHook.cxx:300
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
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.
Definition: memoryUsage.I:347
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
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_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.
Definition: memoryUsage.h:103
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...
Definition: memoryUsage.h:100
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.