1062 lines
36 KiB
C++
1062 lines
36 KiB
C++
// Filename: memoryUsage.cxx
|
|
// Created by: drose (25May00)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d-general@lists.sourceforge.net .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef DO_MEMORY_USAGE
|
|
|
|
#include "memoryUsagePointers.h"
|
|
#include "trueClock.h"
|
|
#include "typedReferenceCount.h"
|
|
|
|
#include "memoryUsage.h"
|
|
#include "interrogate_request.h"
|
|
|
|
#if defined(WIN32_VC) && defined(_DEBUG)
|
|
#include <crtdbg.h>
|
|
#endif
|
|
|
|
#include "config_express.h"
|
|
#include <algorithm>
|
|
|
|
// This flag is set true in is_counting() mode to indicate that the
|
|
// malloc operation is coming from C++ operator new or delete.
|
|
static bool _is_cpp_operator = false;
|
|
|
|
// This flag is used to protect the operator new/delete handlers
|
|
// against recursive entry.
|
|
static bool _recursion_protect = false;
|
|
|
|
MemoryUsage *MemoryUsage::_global_ptr = (MemoryUsage *)NULL;
|
|
|
|
// The cutoff ages, in seconds, for the various buckets in the AgeHistogram.
|
|
double MemoryUsage::AgeHistogram::_cutoff[MemoryUsage::AgeHistogram::num_buckets] = {
|
|
0.0,
|
|
0.1,
|
|
1.0,
|
|
10.0,
|
|
60.0,
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::TypeHistogram::add_info
|
|
// Access: Public
|
|
// Description: Adds a single entry to the histogram.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::TypeHistogram::
|
|
add_info(TypeHandle type, MemoryInfo &info) {
|
|
_counts[type].add_info(info);
|
|
}
|
|
|
|
|
|
// This class is a temporary class used only in TypeHistogram::show(),
|
|
// below, to sort the types in descending order by counts.
|
|
class TypeHistogramCountSorter {
|
|
public:
|
|
TypeHistogramCountSorter(const MemoryUsagePointerCounts &count,
|
|
TypeHandle type) :
|
|
_count(count),
|
|
_type(type)
|
|
{
|
|
}
|
|
bool operator < (const TypeHistogramCountSorter &other) const {
|
|
return other._count < _count;
|
|
}
|
|
MemoryUsagePointerCounts _count;
|
|
TypeHandle _type;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::TypeHistogram::show
|
|
// Access: Public
|
|
// Description: Shows the contents of the histogram to nout.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::TypeHistogram::
|
|
show() const {
|
|
// First, copy the relevant information to a vector so we can sort
|
|
// by counts. Don't use a pvector.
|
|
typedef vector<TypeHistogramCountSorter, dallocator<TypeHistogramCountSorter> > CountSorter;
|
|
CountSorter count_sorter;
|
|
Counts::const_iterator ci;
|
|
for (ci = _counts.begin(); ci != _counts.end(); ++ci) {
|
|
count_sorter.push_back
|
|
(TypeHistogramCountSorter((*ci).second, (*ci).first));
|
|
}
|
|
|
|
sort(count_sorter.begin(), count_sorter.end());
|
|
|
|
CountSorter::const_iterator vi;
|
|
for (vi = count_sorter.begin(); vi != count_sorter.end(); ++vi) {
|
|
TypeHandle type = (*vi)._type;
|
|
if (type == TypeHandle::none()) {
|
|
nout << "unknown";
|
|
} else {
|
|
nout << type;
|
|
}
|
|
nout << " : " << (*vi)._count << "\n";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::TypeHistogram::clear
|
|
// Access: Public
|
|
// Description: Resets the histogram in preparation for new data.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::TypeHistogram::
|
|
clear() {
|
|
_counts.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::AgeHistogram::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
MemoryUsage::AgeHistogram::
|
|
AgeHistogram() {
|
|
clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::AgeHistogram::add_info
|
|
// Access: Public
|
|
// Description: Adds a single entry to the histogram.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::AgeHistogram::
|
|
add_info(double age, MemoryInfo &info) {
|
|
int bucket = choose_bucket(age);
|
|
nassertv(bucket >= 0 && bucket < num_buckets);
|
|
_counts[bucket].add_info(info);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::AgeHistogram::show
|
|
// Access: Public
|
|
// Description: Shows the contents of the histogram to nout.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::AgeHistogram::
|
|
show() const {
|
|
for (int i = 0; i < num_buckets - 1; i++) {
|
|
nout << _cutoff[i] << " to " << _cutoff[i + 1] << " seconds old : ";
|
|
_counts[i].output(nout);
|
|
nout << "\n";
|
|
}
|
|
nout << _cutoff[num_buckets - 1] << " seconds old and up : ";
|
|
_counts[num_buckets - 1].output(nout);
|
|
nout << "\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::AgeHistogram::clear
|
|
// Access: Public
|
|
// Description: Resets the histogram in preparation for new data.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::AgeHistogram::
|
|
clear() {
|
|
for (int i = 0; i < num_buckets; i++) {
|
|
_counts[i].clear();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::AgeHistogram::choose_bucket
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
int MemoryUsage::AgeHistogram::
|
|
choose_bucket(double age) const {
|
|
for (int i = num_buckets - 1; i >= 0; i--) {
|
|
if (age >= _cutoff[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
express_cat.error()
|
|
<< "No suitable bucket for age " << age << "\n";
|
|
return 0;
|
|
}
|
|
|
|
#if defined(__GNUC__)
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::record_pointer
|
|
// Access: Public, Static
|
|
// Description: Indicates that the given pointer has been recently
|
|
// allocated.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
record_pointer(ReferenceCount *ptr) {
|
|
get_global_ptr()->ns_record_pointer(ptr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::update_type
|
|
// Access: Public, Static
|
|
// Description: Associates the indicated type with the given pointer.
|
|
// This should be called by functions (e.g. the
|
|
// constructor) that know more specifically what type of
|
|
// thing we've got; otherwise, the MemoryUsage database
|
|
// will know only that it's a "ReferenceCount".
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
update_type(ReferenceCount *ptr, TypeHandle type) {
|
|
get_global_ptr()->ns_update_type(ptr, type);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::update_type
|
|
// Access: Public, Static
|
|
// Description: Associates the indicated type with the given pointer.
|
|
// This flavor of update_type() also passes in the
|
|
// pointer as a TypedObject, and useful for objects that
|
|
// are, in fact, TypedObjects. Once the MemoryUsage
|
|
// database has the pointer as a TypedObject it doesn't
|
|
// need any more help.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
update_type(ReferenceCount *ptr, TypedObject *typed_ptr) {
|
|
get_global_ptr()->ns_update_type(ptr, typed_ptr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::remove_pointer
|
|
// Access: Public, Static
|
|
// Description: Indicates that the given pointer has been recently
|
|
// freed.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
remove_pointer(ReferenceCount *ptr) {
|
|
get_global_ptr()->ns_remove_pointer(ptr);
|
|
}
|
|
|
|
#endif // __GNUC__
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::operator_new_handler
|
|
// Access: Public, Static
|
|
// Description: This is set up as a global handler function (by
|
|
// redefining a function pointer in Dtool) for the
|
|
// operator new function. If track-memory-usage is
|
|
// enabled, this function will be called whenever any
|
|
// new operator within the Panda source is invoked.
|
|
////////////////////////////////////////////////////////////////////
|
|
void *MemoryUsage::
|
|
operator_new_handler(size_t size) {
|
|
void *ptr;
|
|
|
|
if (_recursion_protect) {
|
|
ptr = default_operator_new(size);
|
|
|
|
} else {
|
|
MemoryUsage *mu = get_global_ptr();
|
|
if (mu->_track_memory_usage) {
|
|
ptr = default_operator_new(size);
|
|
get_global_ptr()->ns_record_void_pointer(ptr, size);
|
|
|
|
} else {
|
|
_is_cpp_operator = true;
|
|
ptr = default_operator_new(size);
|
|
_is_cpp_operator = false;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::operator_delete_handler
|
|
// Access: Public, Static
|
|
// Description: This is set up as a global handler function (by
|
|
// redefining a function pointer in Dtool) for the
|
|
// operator delete function. If track-memory-usage is
|
|
// enabled, this function will be called whenever any
|
|
// delete operator within the Panda source is invoked.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
operator_delete_handler(void *ptr) {
|
|
if (_recursion_protect) {
|
|
default_operator_delete(ptr);
|
|
|
|
} else {
|
|
MemoryUsage *mu = get_global_ptr();
|
|
if (mu->_track_memory_usage) {
|
|
mu->ns_remove_void_pointer(ptr);
|
|
default_operator_delete(ptr);
|
|
} else {
|
|
_is_cpp_operator = true;
|
|
default_operator_delete(ptr);
|
|
_is_cpp_operator = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(WIN32_VC) && defined(_DEBUG)
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::win32_malloc_hook
|
|
// Access: Public, Static
|
|
// Description: This callback is attached to the Win32 debug malloc
|
|
// system to be called whenever a pointer is allocated,
|
|
// reallocated, or freed. It's used to track the total
|
|
// memory allocated via calls to malloc().
|
|
////////////////////////////////////////////////////////////////////
|
|
int MemoryUsage::
|
|
win32_malloc_hook(int alloc_type, void *ptr,
|
|
size_t size, int block_use, long request,
|
|
const unsigned char *filename, int line) {
|
|
if (!_recursion_protect) {
|
|
MemoryUsage *mu = get_global_ptr();
|
|
if (mu->_count_memory_usage) {
|
|
int increment = 0;
|
|
switch (alloc_type) {
|
|
case _HOOK_ALLOC:
|
|
increment = size;
|
|
break;
|
|
|
|
case _HOOK_REALLOC:
|
|
increment = size - _msize(ptr);
|
|
break;
|
|
|
|
case _HOOK_FREE:
|
|
increment = - ((int)_msize(ptr));
|
|
break;
|
|
}
|
|
|
|
mu->_total_size += increment;
|
|
|
|
/*
|
|
This isn't working reliably right now.
|
|
if (_is_cpp_operator) {
|
|
mu->_cpp_size += increment;
|
|
}
|
|
*/
|
|
|
|
#ifdef TRACK_IN_INTERPRETER
|
|
if (in_interpreter) {
|
|
mu->_interpreter_size += increment;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif // WIN32_VC && _DEBUG
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::Constructor
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
MemoryUsage::
|
|
MemoryUsage() {
|
|
// We must get these variables here instead of in
|
|
// config_express.cxx, because we need to know it at static init
|
|
// time, and who knows when the code in config_express will be
|
|
// executed.
|
|
|
|
// track-memory-usage should be true to enable full-force tracking
|
|
// of C++ allocations and recordkeeping by type. It's quite
|
|
// expensive.
|
|
_track_memory_usage =
|
|
config_express.GetBool("track-memory-usage", false);
|
|
|
|
// count-memory-usage is a much lighter-weight version, and only
|
|
// tracks the total memory allocation. However, it only works for
|
|
// certain build environments (in particular, only in an Opt1 or
|
|
// Opt2 build on Windows).
|
|
#if defined(WIN32_VC) && defined(_DEBUG)
|
|
_count_memory_usage = config_express.GetBool("count-memory-usage",
|
|
_track_memory_usage);
|
|
#else
|
|
_count_memory_usage = false;
|
|
#endif
|
|
|
|
if (_track_memory_usage) {
|
|
// Redefine the global pointers for operator new and operator
|
|
// delete (these pointers are defined up in DTOOL) to vector into
|
|
// this class.
|
|
global_operator_new = &operator_new_handler;
|
|
global_operator_delete = &operator_delete_handler;
|
|
}
|
|
|
|
#if defined(WIN32_VC) && defined(_DEBUG)
|
|
if (_count_memory_usage) {
|
|
global_operator_new = &operator_new_handler;
|
|
global_operator_delete = &operator_delete_handler;
|
|
_CrtSetAllocHook(&win32_malloc_hook);
|
|
}
|
|
#endif
|
|
|
|
_freeze_index = 0;
|
|
_count = 0;
|
|
_current_cpp_size = 0;
|
|
_cpp_size = 0;
|
|
_interpreter_size = 0;
|
|
_total_size = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::get_global_ptr
|
|
// Access: Private, Static
|
|
// Description: Returns the pointer to the only MemoryUsage object in
|
|
// the world.
|
|
////////////////////////////////////////////////////////////////////
|
|
MemoryUsage *MemoryUsage::
|
|
get_global_ptr() {
|
|
if (_global_ptr == (MemoryUsage *)NULL) {
|
|
_global_ptr = new MemoryUsage;
|
|
}
|
|
|
|
return _global_ptr;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_record_pointer
|
|
// Access: Private
|
|
// Description: Indicates that the given pointer has been recently
|
|
// allocated.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_record_pointer(ReferenceCount *ptr) {
|
|
if (_track_memory_usage) {
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
pair<Table::iterator, bool> insert_result =
|
|
_table.insert(Table::value_type((void *)ptr, MemoryInfo()));
|
|
|
|
// This shouldn't fail.
|
|
assert(insert_result.first != _table.end());
|
|
|
|
if (insert_result.second) {
|
|
_count++;
|
|
}
|
|
|
|
MemoryInfo &info = (*insert_result.first).second;
|
|
|
|
// We shouldn't already have a ReferenceCount pointer.
|
|
if ((info._flags & MemoryInfo::F_got_ref) != 0) {
|
|
express_cat.error()
|
|
<< "ReferenceCount pointer " << (void *)ptr << " recorded twice!\n";
|
|
}
|
|
|
|
info._void_ptr = (void *)ptr;
|
|
info._ref_ptr = ptr;
|
|
info._static_type = ReferenceCount::get_class_type();
|
|
info._dynamic_type = ReferenceCount::get_class_type();
|
|
info._time = TrueClock::get_ptr()->get_long_time();
|
|
info._freeze_index = _freeze_index;
|
|
info._flags |= (MemoryInfo::F_reconsider_dynamic_type | MemoryInfo::F_got_ref);
|
|
|
|
// We close the recursion_protect flag all the way down here, so
|
|
// that we also protect ourselves against a possible recursive
|
|
// call in TrueClock::get_ptr().
|
|
_recursion_protect = false;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_update_type
|
|
// Access: Private
|
|
// Description: Associates the indicated type with the given pointer.
|
|
// This should be called by functions (e.g. the
|
|
// constructor) that know more specifically what type of
|
|
// thing we've got; otherwise, the MemoryUsage database
|
|
// will know only that it's a "ReferenceCount".
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_update_type(ReferenceCount *ptr, TypeHandle type) {
|
|
if (_track_memory_usage) {
|
|
Table::iterator ti;
|
|
ti = _table.find(ptr);
|
|
if (ti == _table.end()) {
|
|
express_cat.error()
|
|
<< "Attempt to update type to " << type << " for unrecorded pointer "
|
|
<< (void *)ptr << "!\n";
|
|
return;
|
|
}
|
|
|
|
MemoryInfo &info = (*ti).second;
|
|
info.update_type_handle(info._static_type, type);
|
|
info.determine_dynamic_type();
|
|
|
|
consolidate_void_ptr(info);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_update_type
|
|
// Access: Private
|
|
// Description: Associates the indicated type with the given pointer.
|
|
// This flavor of update_type() also passes in the
|
|
// pointer as a TypedObject, and useful for objects that
|
|
// are, in fact, TypedObjects. Once the MemoryUsage
|
|
// database has the pointer as a TypedObject it doesn't
|
|
// need any more help.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_update_type(ReferenceCount *ptr, TypedObject *typed_ptr) {
|
|
if (_track_memory_usage) {
|
|
Table::iterator ti;
|
|
ti = _table.find(ptr);
|
|
if (ti == _table.end()) {
|
|
express_cat.error()
|
|
<< "Attempt to update type to " << typed_ptr->get_type()
|
|
<< " for unrecorded pointer "
|
|
<< (void *)ptr << "!\n";
|
|
return;
|
|
}
|
|
|
|
MemoryInfo &info = (*ti).second;
|
|
info._typed_ptr = typed_ptr;
|
|
info.determine_dynamic_type();
|
|
|
|
consolidate_void_ptr(info);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_remove_pointer
|
|
// Access: Private
|
|
// Description: Indicates that the given pointer has been recently
|
|
// freed.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_remove_pointer(ReferenceCount *ptr) {
|
|
if (_track_memory_usage) {
|
|
Table::iterator ti;
|
|
ti = _table.find(ptr);
|
|
if (ti == _table.end()) {
|
|
express_cat.error()
|
|
<< "Attempt to remove pointer " << (void *)ptr
|
|
<< ", not in table.\n"
|
|
<< "Possibly a double-destruction.\n";
|
|
nassertv(false);
|
|
return;
|
|
}
|
|
|
|
MemoryInfo &info = (*ti).second;
|
|
|
|
if ((info._flags & MemoryInfo::F_got_ref) == 0) {
|
|
express_cat.error()
|
|
<< "Pointer " << (void *)ptr << " deleted twice!\n";
|
|
return;
|
|
}
|
|
|
|
info._flags &= ~MemoryInfo::F_got_ref;
|
|
|
|
// Since the pointer has been destructed, we can't safely call its
|
|
// TypedObject virtual methods any more. Better clear out the
|
|
// typed_ptr for good measure.
|
|
info._typed_ptr = (TypedObject *)NULL;
|
|
|
|
if (info._freeze_index == _freeze_index) {
|
|
double now = TrueClock::get_ptr()->get_long_time();
|
|
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
_trend_types.add_info(info.get_type(), info);
|
|
_trend_ages.add_info(now - info._time, info);
|
|
_recursion_protect = false;
|
|
}
|
|
|
|
if ((info._flags & (MemoryInfo::F_got_ref | MemoryInfo::F_got_void)) == 0) {
|
|
// If we don't expect to call any more remove_*_pointer on this
|
|
// pointer, remove it from the table.
|
|
if (info._freeze_index == _freeze_index) {
|
|
_count--;
|
|
_current_cpp_size -= info._size;
|
|
}
|
|
_cpp_size -= info._size;
|
|
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
_table.erase(ti);
|
|
_recursion_protect = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_record_void_pointer
|
|
// Access: Private
|
|
// Description: Records a pointer that's not even necessarily a
|
|
// ReferenceCount object (but for which we know the size
|
|
// of the allocated structure).
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_record_void_pointer(void *ptr, size_t size) {
|
|
if (_track_memory_usage) {
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
pair<Table::iterator, bool> insert_result =
|
|
_table.insert(Table::value_type((void *)ptr, MemoryInfo()));
|
|
|
|
assert(insert_result.first != _table.end());
|
|
|
|
if (insert_result.second) {
|
|
_count++;
|
|
}
|
|
|
|
MemoryInfo &info = (*insert_result.first).second;
|
|
|
|
// We shouldn't already have a void pointer.
|
|
if ((info._flags & MemoryInfo::F_got_void) != 0) {
|
|
express_cat.error()
|
|
<< "Void pointer " << (void *)ptr << " recorded twice!\n";
|
|
}
|
|
|
|
if (info._freeze_index == _freeze_index) {
|
|
_current_cpp_size += size - info._size;
|
|
} else {
|
|
_current_cpp_size += size;
|
|
}
|
|
_cpp_size += size - info._size;
|
|
|
|
info._void_ptr = ptr;
|
|
info._size = size;
|
|
info._time = TrueClock::get_ptr()->get_long_time();
|
|
info._freeze_index = _freeze_index;
|
|
info._flags |= (MemoryInfo::F_got_void | MemoryInfo::F_size_known);
|
|
|
|
// We close the recursion_protect flag all the way down here, so
|
|
// that we also protect ourselves against a possible recursive
|
|
// call in TrueClock::get_ptr().
|
|
_recursion_protect = false;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_remove_void_pointer
|
|
// Access: Private
|
|
// Description: Removes a pointer previously recorded via
|
|
// record_void_pointer.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_remove_void_pointer(void *ptr) {
|
|
if (_track_memory_usage) {
|
|
Table::iterator ti;
|
|
ti = _table.find(ptr);
|
|
if (ti == _table.end()) {
|
|
// The pointer we tried to delete was not recorded in the table.
|
|
|
|
// We can't report this as an error, because (a) we might have
|
|
// removed the void pointer entry already when we consolidated,
|
|
// and (b) a few objects might have been created during static
|
|
// init time, before we grabbed the operator new/delete function
|
|
// handlers.
|
|
return;
|
|
}
|
|
|
|
MemoryInfo &info = (*ti).second;
|
|
|
|
if ((info._flags & MemoryInfo::F_got_void) == 0) {
|
|
express_cat.error()
|
|
<< "Pointer " << (void *)ptr << " deleted twice!\n";
|
|
return;
|
|
}
|
|
|
|
if ((info._flags & MemoryInfo::F_got_ref) != 0) {
|
|
express_cat.error()
|
|
<< "Pointer " << (void *)ptr << " did not destruct before being deleted!\n";
|
|
}
|
|
|
|
info._flags &= ~MemoryInfo::F_got_void;
|
|
|
|
if ((info._flags & (MemoryInfo::F_got_ref | MemoryInfo::F_got_void)) == 0) {
|
|
// If we don't expect to call any more remove_*_pointer on this
|
|
// pointer, remove it from the table.
|
|
|
|
if (info._freeze_index == _freeze_index) {
|
|
_count--;
|
|
_current_cpp_size -= info._size;
|
|
}
|
|
_cpp_size -= info._size;
|
|
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
_table.erase(ti);
|
|
_recursion_protect = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_current_cpp_size
|
|
// Access: Private
|
|
// Description: Returns the total number of bytes of allocated memory
|
|
// via the C++ operators new and delete as counted,
|
|
// not including the memory previously frozen.
|
|
////////////////////////////////////////////////////////////////////
|
|
size_t MemoryUsage::
|
|
ns_get_current_cpp_size() {
|
|
return _current_cpp_size;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_cpp_size
|
|
// Access: Private
|
|
// Description: Returns the total number of bytes of allocated memory
|
|
// via the C++ operators new and delete as counted,
|
|
// including the memory previously frozen.
|
|
////////////////////////////////////////////////////////////////////
|
|
size_t MemoryUsage::
|
|
ns_get_cpp_size() {
|
|
return _cpp_size;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_interpreter_size
|
|
// Access: Private
|
|
// Description: Returns the total number of bytes of allocated memory
|
|
// while the high-level languange code is running. This
|
|
// number is only meaningful if both Panda and the
|
|
// high-level language are single-threaded, and running
|
|
// in the same thread.
|
|
////////////////////////////////////////////////////////////////////
|
|
size_t MemoryUsage::
|
|
ns_get_interpreter_size() {
|
|
return _interpreter_size;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_total_size
|
|
// Access: Private
|
|
// Description: Returns the total size of the dynamic heap, as nearly
|
|
// as can be determined, including all allocated memory
|
|
// if possible, in addition to that tracked by
|
|
// get_cpp_size().
|
|
////////////////////////////////////////////////////////////////////
|
|
size_t MemoryUsage::
|
|
ns_get_total_size() {
|
|
#if defined(WIN32_VC) && defined(_DEBUG)
|
|
return _total_size;
|
|
#else
|
|
// If we aren't tracking _total_size, report _cpp_size as the next
|
|
// best thing.
|
|
return _cpp_size;
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_num_pointers
|
|
// Access: Private
|
|
// Description: Returns the number of pointers currently active.
|
|
////////////////////////////////////////////////////////////////////
|
|
int MemoryUsage::
|
|
ns_get_num_pointers() {
|
|
nassertr(_track_memory_usage, 0);
|
|
return _count;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_pointers
|
|
// Access: Private
|
|
// Description: Fills the indicated MemoryUsagePointers with the set
|
|
// of all pointers currently active.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_get_pointers(MemoryUsagePointers &result) {
|
|
nassertv(_track_memory_usage);
|
|
result.clear();
|
|
|
|
double now = TrueClock::get_ptr()->get_long_time();
|
|
Table::iterator ti;
|
|
for (ti = _table.begin(); ti != _table.end(); ++ti) {
|
|
MemoryInfo &info = (*ti).second;
|
|
if (info._freeze_index == _freeze_index &&
|
|
info._ref_ptr != (ReferenceCount *)NULL) {
|
|
result.add_entry(info._ref_ptr, info._typed_ptr, info.get_type(),
|
|
now - info._time);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_pointers_of_type
|
|
// Access: Private
|
|
// Description: Fills the indicated MemoryUsagePointers with the set
|
|
// of all pointers of the indicated type currently
|
|
// active.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_get_pointers_of_type(MemoryUsagePointers &result, TypeHandle type) {
|
|
nassertv(_track_memory_usage);
|
|
result.clear();
|
|
|
|
double now = TrueClock::get_ptr()->get_long_time();
|
|
Table::iterator ti;
|
|
for (ti = _table.begin(); ti != _table.end(); ++ti) {
|
|
MemoryInfo &info = (*ti).second;
|
|
if (info._freeze_index == _freeze_index &&
|
|
info._ref_ptr != (ReferenceCount *)NULL) {
|
|
TypeHandle info_type = info.get_type();
|
|
if (info_type != TypeHandle::none() &&
|
|
info_type.is_derived_from(type)) {
|
|
result.add_entry(info._ref_ptr, info._typed_ptr, info_type,
|
|
now - info._time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_pointers_of_age
|
|
// Access: Private
|
|
// Description: Fills the indicated MemoryUsagePointers with the set
|
|
// of all pointers that were allocated within the range
|
|
// of the indicated number of seconds ago.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_get_pointers_of_age(MemoryUsagePointers &result,
|
|
double from, double to) {
|
|
nassertv(_track_memory_usage);
|
|
result.clear();
|
|
|
|
double now = TrueClock::get_ptr()->get_long_time();
|
|
Table::iterator ti;
|
|
for (ti = _table.begin(); ti != _table.end(); ++ti) {
|
|
MemoryInfo &info = (*ti).second;
|
|
if (info._freeze_index == _freeze_index &&
|
|
info._ref_ptr != (ReferenceCount *)NULL) {
|
|
double age = now - info._time;
|
|
if ((age >= from && age <= to) ||
|
|
(age >= to && age <= from)) {
|
|
result.add_entry(info._ref_ptr, info._typed_ptr, info.get_type(), age);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_get_pointers_with_zero_count
|
|
// Access: Private
|
|
// Description: Fills the indicated MemoryUsagePointers with the set
|
|
// of all currently active pointers (that is, pointers
|
|
// allocated since the last call to freeze(), and not
|
|
// yet freed) that have a zero reference count.
|
|
//
|
|
// Generally, an undeleted pointer with a zero reference
|
|
// count means its reference count has never been
|
|
// incremented beyond zero (since once it has been
|
|
// incremented, the only way it can return to zero would
|
|
// free the pointer). This may include objects that are
|
|
// allocated statically or on the stack, which are never
|
|
// intended to be deleted. Or, it might represent a
|
|
// programmer or compiler error.
|
|
//
|
|
// This function has the side-effect of incrementing
|
|
// each of their reference counts by one, thus
|
|
// preventing them from ever being freed--but since they
|
|
// hadn't been freed anyway, probably no additional harm
|
|
// is done.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_get_pointers_with_zero_count(MemoryUsagePointers &result) {
|
|
nassertv(_track_memory_usage);
|
|
result.clear();
|
|
|
|
double now = TrueClock::get_ptr()->get_long_time();
|
|
Table::iterator ti;
|
|
for (ti = _table.begin(); ti != _table.end(); ++ti) {
|
|
MemoryInfo &info = (*ti).second;
|
|
if (info._freeze_index == _freeze_index &&
|
|
info._ref_ptr != (ReferenceCount *)NULL) {
|
|
if (info._ref_ptr->get_ref_count() == 0) {
|
|
info._ref_ptr->ref();
|
|
result.add_entry(info._ref_ptr, info._typed_ptr, info.get_type(),
|
|
now - info._time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_freeze
|
|
// Access: Private
|
|
// Description: 'Freezes' all pointers currently stored so that they
|
|
// are no longer reported; only newly allocate pointers
|
|
// from this point on will appear in future information
|
|
// requests. This makes it easier to differentiate
|
|
// between continuous leaks and one-time memory
|
|
// allocations.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_freeze() {
|
|
_count = 0;
|
|
_current_cpp_size = 0;
|
|
_trend_types.clear();
|
|
_trend_ages.clear();
|
|
_freeze_index++;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_show_current_types
|
|
// Access: Private
|
|
// Description: Shows the breakdown of types of all of the
|
|
// active pointers.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_show_current_types() {
|
|
nassertv(_track_memory_usage);
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
|
|
TypeHistogram hist;
|
|
|
|
Table::iterator ti;
|
|
for (ti = _table.begin(); ti != _table.end(); ++ti) {
|
|
MemoryInfo &info = (*ti).second;
|
|
if (info._freeze_index == _freeze_index) {
|
|
hist.add_info(info.get_type(), info);
|
|
}
|
|
}
|
|
|
|
hist.show();
|
|
_recursion_protect = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_show_trend_types
|
|
// Access: Private
|
|
// Description: Shows the breakdown of types of all of the
|
|
// pointers allocated and freed since the last call to
|
|
// freeze().
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_show_trend_types() {
|
|
_trend_types.show();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_show_current_ages
|
|
// Access: Private
|
|
// Description: Shows the breakdown of ages of all of the
|
|
// active pointers.
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_show_current_ages() {
|
|
nassertv(_track_memory_usage);
|
|
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
|
|
AgeHistogram hist;
|
|
double now = TrueClock::get_ptr()->get_long_time();
|
|
|
|
Table::iterator ti;
|
|
for (ti = _table.begin(); ti != _table.end(); ++ti) {
|
|
MemoryInfo &info = (*ti).second;
|
|
if (info._freeze_index == _freeze_index) {
|
|
hist.add_info(now - info._time, info);
|
|
}
|
|
}
|
|
|
|
hist.show();
|
|
|
|
_recursion_protect = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::ns_show_trend_ages
|
|
// Access: Private
|
|
// Description: Shows the breakdown of ages of all of the
|
|
// pointers allocated and freed since the last call to
|
|
// freeze().
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
ns_show_trend_ages() {
|
|
_trend_ages.show();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: MemoryUsage::consolidate_void_ptr
|
|
// Access: Private
|
|
// Description: If the size information has not yet been determined
|
|
// for this pointer, checks to see if it has possible
|
|
// been recorded under the TypedObject pointer (this
|
|
// will happen when the class inherits from TypedObject
|
|
// before ReferenceCount, e.g. TypedReferenceCount).
|
|
////////////////////////////////////////////////////////////////////
|
|
void MemoryUsage::
|
|
consolidate_void_ptr(MemoryInfo &info) {
|
|
if (info.is_size_known()) {
|
|
// We already know the size, so no sweat.
|
|
return;
|
|
}
|
|
|
|
if (info.get_typed_ptr() == (TypedObject *)NULL) {
|
|
// We don't have a typed pointer for this thing yet.
|
|
return;
|
|
}
|
|
|
|
void *typed_ptr = (void *)info.get_typed_ptr();
|
|
|
|
if (typed_ptr == (void *)info.get_ref_ptr()) {
|
|
// The TypedObject pointer is the same pointer as the
|
|
// ReferenceCount pointer, so there's no point in looking it up
|
|
// separately. Actually, this really shouldn't even be possible.
|
|
return;
|
|
}
|
|
|
|
Table::iterator ti;
|
|
ti = _table.find(typed_ptr);
|
|
if (ti == _table.end()) {
|
|
// No entry for the typed pointer, either.
|
|
return;
|
|
}
|
|
|
|
// We do have an entry! Copy over the relevant pieces.
|
|
MemoryInfo &typed_info = (*ti).second;
|
|
|
|
if (typed_info.is_size_known()) {
|
|
info._size = typed_info.get_size();
|
|
info._flags |= MemoryInfo::F_size_known;
|
|
if (typed_info._freeze_index == _freeze_index) {
|
|
_current_cpp_size += info._size;
|
|
}
|
|
}
|
|
|
|
// The typed_ptr is clearly the more accurate pointer to the
|
|
// beginning of the structure.
|
|
info._void_ptr = typed_ptr;
|
|
|
|
// Now that we've consolidated the pointers, remove the void pointer
|
|
// entry.
|
|
if (info._freeze_index == _freeze_index) {
|
|
_count--;
|
|
_current_cpp_size -= info._size;
|
|
}
|
|
|
|
// We have to protect modifications to the table from recursive
|
|
// calls by toggling _recursion_protect while we adjust it.
|
|
_recursion_protect = true;
|
|
_table.erase(ti);
|
|
_recursion_protect = false;
|
|
}
|
|
|
|
|
|
#endif // DO_MEMORY_USAGE
|