Hi Robert, I have work on a small tool to detect how a program evolve during time, It tracks the numer of instance of each object inherited from osg::Referenced. I use this to detect 'leak' but release when quitting the program so not really a leak but to detect consumption of too much memory during runtime. The tool to works need modification in osg::Referenced. because there is already a mecanism to set a custom DeleteHandler, I thought we could do the same to intercept instanciation of osg::Referenced with a custom InstanceHandler ?
In my use case, I have used a NodeCallBack that dump every second the current instance of Referenced and Object, then I have used a function to dump the gnuplot script from the data store each seconds. Some screenshoot will help to understand how I use it: http://plopbyte.net/tmp/combined.png http://plopbyte.net/tmp/individual.png here the data generated to build the graph http://plopbyte.net/tmp/data.dat http://plopbyte.net/tmp/plot.script What do you think ? I have attached my modified Referenced and Referenced.cpp to work with my tool, but It would need to be more generic like the DeleteHandler to enter in OSG. Cheers, Cedric -- Provide OpenGL, WebGL and OpenSceneGraph services +33 659 598 614 Cedric Pinson mailto:cedric.pin...@plopbyte.net http://www.plopbyte.net
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include <stdlib.h> #include <osg/Referenced> #include <osg/Notify> #include <osg/ApplicationUsage> #include <osg/Observer> #include <typeinfo> #include <memory> #include <set> #include <OpenThreads/ScopedLock> #include <OpenThreads/Mutex> #include <osg/DeleteHandler> #include "ReferencedExtra.cpp" namespace osg { //#define ENFORCE_THREADSAFE //#define DEBUG_OBJECT_ALLOCATION_DESTRUCTION // specialized smart pointer, used to get round auto_ptr<>'s lack of the destructor reseting itself to 0. template<typename T> struct ResetPointer { ResetPointer(): _ptr(0) {} ResetPointer(T* ptr): _ptr(ptr) {} ~ResetPointer() { delete _ptr; _ptr = 0; } inline ResetPointer& operator = (T* ptr) { if (_ptr==ptr) return *this; delete _ptr; _ptr = ptr; return *this; } void reset(T* ptr) { if (_ptr==ptr) return; delete _ptr; _ptr = ptr; } inline T& operator*() { return *_ptr; } inline const T& operator*() const { return *_ptr; } inline T* operator->() { return _ptr; } inline const T* operator->() const { return _ptr; } T* get() { return _ptr; } const T* get() const { return _ptr; } T* _ptr; }; typedef ResetPointer<DeleteHandler> DeleteHandlerPointer; typedef ResetPointer<OpenThreads::Mutex> GlobalMutexPointer; OpenThreads::Mutex* Referenced::getGlobalReferencedMutex() { static GlobalMutexPointer s_ReferencedGlobalMutext = new OpenThreads::Mutex; return s_ReferencedGlobalMutext.get(); } // helper class for forcing the global mutex to be constructed when the library is loaded. struct InitGlobalMutexes { InitGlobalMutexes() { Referenced::getGlobalReferencedMutex(); } }; static InitGlobalMutexes s_initGlobalMutexes; #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) static bool s_useThreadSafeReferenceCounting = getenv("OSG_THREAD_SAFE_REF_UNREF")!=0; #endif // static std::auto_ptr<DeleteHandler> s_deleteHandler(0); static DeleteHandlerPointer s_deleteHandler(0); static ApplicationUsageProxy Referenced_e0(ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_THREAD_SAFE_REF_UNREF",""); void Referenced::setThreadSafeReferenceCounting(bool enableThreadSafeReferenceCounting) { #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) s_useThreadSafeReferenceCounting = enableThreadSafeReferenceCounting; #endif } bool Referenced::getThreadSafeReferenceCounting() { #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) return true; #else return s_useThreadSafeReferenceCounting; #endif } void Referenced::setDeleteHandler(DeleteHandler* handler) { s_deleteHandler.reset(handler); } DeleteHandler* Referenced::getDeleteHandler() { return s_deleteHandler.get(); } #ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION OpenThreads::Mutex& getNumObjectMutex() { static OpenThreads::Mutex s_numObjectMutex; return s_numObjectMutex; } static int s_numObjects = 0; #endif Referenced::Referenced(int dontlog): #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) _observerSet(0), _refCount(0) #else _refMutex(0), _refCount(0), _observerSet(0) #endif { #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) #ifndef ENFORCE_THREADSAFE if (s_useThreadSafeReferenceCounting) #endif _refMutex = new OpenThreads::Mutex; #endif #ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex()); ++s_numObjects; printf("Object created, total num=%d\n",s_numObjects); } #endif _logged = !dontlog; if (!dontlog) { log(this); } } Referenced::Referenced(bool threadSafeRefUnref, int dontlog): #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) _observerSet(0), _refCount(0) #else _refMutex(0), _refCount(0), _observerSet(0) #endif { #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) #ifndef ENFORCE_THREADSAFE if (threadSafeRefUnref) #endif _refMutex = new OpenThreads::Mutex; #endif #ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex()); ++s_numObjects; printf("Object created, total num=%d\n",s_numObjects); } #endif _logged = !dontlog; if (!dontlog) log(this); } Referenced::Referenced(const Referenced&, int dontlog): #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) _observerSet(0), _refCount(0) #else _refMutex(0), _refCount(0), _observerSet(0) #endif { #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) #ifndef ENFORCE_THREADSAFE if (s_useThreadSafeReferenceCounting) #endif _refMutex = new OpenThreads::Mutex; #endif #ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex()); ++s_numObjects; printf("Object created, total num=%d\n",s_numObjects); } #endif _logged = !dontlog; if (!dontlog) log(this); } Referenced::~Referenced() { #ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex()); --s_numObjects; printf("Object created, total num=%d\n",s_numObjects); } #endif if (_refCount>0) { OSG_WARN<<"Warning: deleting still referenced object "<<this<<" of type '"<<typeid(this).name()<<"'"<<std::endl; OSG_WARN<<" the final reference count was "<<_refCount<<", memory corruption possible."<<std::endl; } // signal observers that we are being deleted. signalObserversAndDelete(true, false); // delete the ObserverSet #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) if (_observerSet.get()) static_cast<ObserverSet*>(_observerSet.get())->unref(); #else if (_observerSet) static_cast<ObserverSet*>(_observerSet)->unref(); #endif #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) if (_refMutex) delete _refMutex; #endif if (_logged) { unlog(this); } } ObserverSet* Referenced::getOrCreateObserverSet() const { #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) ObserverSet* observerSet = static_cast<ObserverSet*>(_observerSet.get()); while (0 == observerSet) { ObserverSet* newObserverSet = new ObserverSet(this); newObserverSet->ref(); if (!_observerSet.assign(newObserverSet, 0)) { newObserverSet->unref(); } observerSet = static_cast<ObserverSet*>(_observerSet.get()); } return observerSet; #else if (_refMutex) { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); if (!_observerSet) { _observerSet = new ObserverSet(this); static_cast<ObserverSet*>(_observerSet)->ref(); } return static_cast<ObserverSet*>(_observerSet); } else { if (!_observerSet) _observerSet = new ObserverSet(this); return static_cast<ObserverSet*>(_observerSet); } #endif } void Referenced::addObserver(Observer* observer) const { getOrCreateObserverSet()->addObserver(observer); } void Referenced::removeObserver(Observer* observer) const { getOrCreateObserverSet()->removeObserver(observer); } void Referenced::signalObserversAndDelete(bool signalDelete, bool doDelete) const { #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) ObserverSet* observerSet = static_cast<ObserverSet*>(_observerSet.get()); #else ObserverSet* observerSet = static_cast<ObserverSet*>(_observerSet); #endif if (observerSet && signalDelete) { observerSet->signalObjectDeleted(const_cast<Referenced*>(this)); } if (doDelete) { if (_refCount!=0) OSG_NOTICE<<"Warning Referenced::signalObserversAndDelete(,,) doing delete with _refCount="<<_refCount<<std::endl; if (getDeleteHandler()) deleteUsingDeleteHandler(); else delete this; } } void Referenced::setThreadSafeRefUnref(bool threadSafe) { #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) if (threadSafe) { if (!_refMutex) { // we want thread safe ref()/unref() so assign a mutex _refMutex = new OpenThreads::Mutex; } } else { if (_refMutex) { // we don't want thread safe ref()/unref() so remove any assigned mutex OpenThreads::Mutex* tmpMutexPtr = _refMutex; _refMutex = 0; delete tmpMutexPtr; } } #endif } int Referenced::unref_nodelete() const { #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) return --_refCount; #else if (_refMutex) { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); return --_refCount; } else { return --_refCount; } #endif } void Referenced::deleteUsingDeleteHandler() const { getDeleteHandler()->requestDelete(this); } } // end of namespace osg
#include <set> #include <osg/observer_ptr> #include <osg/Object> #include <iostream> namespace osg { static bool readEnv() { const char* ptr = getenv("OSG_LOG_OBJECT"); if (ptr) { std::cout << "will log creation of object" << std::endl; return true; } return false; } static bool readEnvDumpOnConsole() { if (!readEnv()) return false; const char* ptr = getenv("OSG_MEMORY_DUMP"); if (ptr) { std::cout << "will dump on console" << std::endl; return true; } std::cout << "will not dump on console" << std::endl; return false; } static bool s_log = readEnv(); static bool s_dump = readEnvDumpOnConsole(); OpenThreads::Mutex& getLogObjectMutex() { static OpenThreads::Mutex my_numObjectMutex; return my_numObjectMutex; } typedef std::set<osg::Referenced*> ObjectObserver; typedef std::map<std::string, int> TypeInstance; static ObjectObserver s_allObjects; static std::map<int, TypeInstance > s_toPlot; void Referenced::reportCurrentMemoryObject() { if (!s_log) return; { static int nbCall = 0; OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getLogObjectMutex()); if (s_dump) std::cout << "Total Num Objects allocated " << s_allObjects.size() << std::endl; TypeInstance& types = s_toPlot[nbCall]; for (ObjectObserver::iterator it = s_allObjects.begin(); it != s_allObjects.end(); ++it) { osg::Referenced* obj = (*it); if (obj && obj->referenceCount() >= 1) { osg::Object* o = dynamic_cast<osg::Object*>(obj); if (o) { std::string type = std::string(o->libraryName()) + ":" + std::string(o->className()); types[type]++; } else { types[typeid(obj).name()]++; } } } if (s_dump) { for (TypeInstance::iterator it = types.begin(); it != types.end(); ++it) std::cout << it->first << " nb instances " << it->second << std::endl; } nbCall++; } } void Referenced::clearLog() { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getLogObjectMutex()); std::cout << "Clear log " << s_allObjects.size() << " entries" << std::endl; s_allObjects.clear(); } static void gnuPlot(std::map<int, TypeInstance >& data) { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getLogObjectMutex()); // collect all type TypeInstance alltype; for (std::map<int, TypeInstance >::iterator it = data.begin(); it != data.end(); ++it) { TypeInstance& types = it->second; for (TypeInstance::iterator it2 = types.begin(); it2 != types.end(); ++it2) alltype[it2->first] = 0; } // need to finish here std::cout << "# sample "; for (TypeInstance::iterator it = alltype.begin(); it != alltype.end(); ++it) std::cout << "\t" << it->first; std::cout << std::endl; for (std::map<int, TypeInstance >::iterator it = data.begin(); it != data.end(); ++it) { TypeInstance& t = it->second; std::cout << it->first; for (TypeInstance::iterator it2 = alltype.begin(); it2 != alltype.end(); ++it2) { const std::string& key = it2->first; std::cout << "\t" << t[key]; } std::cout << std::endl; } std::cout << "set term png size 1280,1024" << std::endl; std::cout << "set output \"combined.png\"" << std::endl; std::cout << "plot "; int i = 1; for (TypeInstance::iterator it = alltype.begin(); it != alltype.end(); ++it) { if (i == 1) std::cout << "\\" << std::endl; else std::cout << ",\\" << std::endl; std::cout << "\"data.dat\" u 1:" << ++i << " t \'" << it->first << "\' with lines"; } std::cout << std::endl << std::endl; // draw each graph std::cout << "set term png size 1280,8192" << std::endl; std::cout << "set output \"individual.png\"" << std::endl; std::cout << "set tmargin 2" << std::endl << "set bmargin 2" << std::endl << "set lmargin 9" << std::endl << "set rmargin 2" << std::endl; std::cout << "set multiplot" << std::endl; int nbColumn = 2; double size = 1.0/(alltype.size()/nbColumn); std::cout << "set size " << 1.0/nbColumn << "," << size << std::endl; i = 1; int currentIndex = 0; for (TypeInstance::iterator it = alltype.begin(); it != alltype.end(); ++it) { int col = currentIndex/(alltype.size()/nbColumn); std::cout << "set origin " << col * 1.0 / nbColumn << "," << (currentIndex%(alltype.size()/nbColumn)) * 1.0 * size << std::endl; std::cout << "plot \"data.dat\" u 1:" << ++i << " t \'" << it->first << "\' with lines" << std::endl; currentIndex++; } } static void log(Referenced* obj) { if (!s_log) return; { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getLogObjectMutex()); s_allObjects.insert(obj); } } static void unlog(Referenced* obj) { if (!s_log) return; { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getLogObjectMutex()); s_allObjects.erase(obj); } } void Referenced::dumpStats() { gnuPlot(s_toPlot); } }
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #ifndef OSG_REFERENCED #define OSG_REFERENCED 1 #include <osg/Export> #include <OpenThreads/ScopedLock> #include <OpenThreads/Mutex> #include <OpenThreads/Atomic> #if !defined(_OPENTHREADS_ATOMIC_USE_MUTEX) # define _OSG_REFERENCED_USE_ATOMIC_OPERATIONS #endif namespace osg { // forward declare, declared after Referenced below. class DeleteHandler; class Observer; class ObserverSet; /** template class to help enforce static initialization order. */ template <typename T, T M()> struct depends_on { depends_on() { M(); } }; /** Base class from providing referencing counted objects.*/ class OSG_EXPORT Referenced { public: static void reportCurrentMemoryObject(); static void clearLog(); static void dumpStats(); Referenced(int dontlog = 0); explicit Referenced(bool threadSafeRefUnref, int dontlog = 0); Referenced(const Referenced&, int dontlog = 0); inline Referenced& operator = (const Referenced&) { return *this; } /** Set whether to use a mutex to ensure ref() and unref() are thread safe.*/ virtual void setThreadSafeRefUnref(bool threadSafe); /** Get whether a mutex is used to ensure ref() and unref() are thread safe.*/ #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) bool getThreadSafeRefUnref() const { return true; } #else bool getThreadSafeRefUnref() const { return _refMutex!=0; } #endif /** Get the mutex used to ensure thread safety of ref()/unref(). */ #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) OpenThreads::Mutex* getRefMutex() const { return getGlobalReferencedMutex(); } #else OpenThreads::Mutex* getRefMutex() const { return _refMutex; } #endif /** Get the optional global Referenced mutex, this can be shared between all osg::Referenced.*/ static OpenThreads::Mutex* getGlobalReferencedMutex(); /** Increment the reference count by one, indicating that this object has another pointer which is referencing it.*/ inline int ref() const; /** Decrement the reference count by one, indicating that a pointer to this object is referencing it. If the reference count goes to zero, it is assumed that this object is no longer referenced and is automatically deleted.*/ inline int unref() const; /** Decrement the reference count by one, indicating that a pointer to this object is referencing it. However, do not delete it, even if ref count goes to 0. Warning, unref_nodelete() should only be called if the user knows exactly who will be responsible for, one should prefer unref() over unref_nodelete() as the later can lead to memory leaks.*/ int unref_nodelete() const; /** Return the number pointers currently referencing this object. */ inline int referenceCount() const { return _refCount; } /** Get the ObserverSet if one is attached, otherwise return NULL.*/ ObserverSet* getObserverSet() const { #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) return static_cast<ObserverSet*>(_observerSet.get()); #else return static_cast<ObserverSet*>(_observerSet); #endif } /** Get the ObserverSet if one is attached, otherwise create an ObserverSet, attach it, then return this newly created ObserverSet.*/ ObserverSet* getOrCreateObserverSet() const; /** Add a Observer that is observing this object, notify the Observer when this object gets deleted.*/ void addObserver(Observer* observer) const; /** remove Observer that is observing this object.*/ void removeObserver(Observer* observer) const; public: /** Set whether reference counting should be use a mutex to create thread reference counting.*/ static void setThreadSafeReferenceCounting(bool enableThreadSafeReferenceCounting); /** Get whether reference counting is active.*/ static bool getThreadSafeReferenceCounting(); friend class DeleteHandler; /** Set a DeleteHandler to which deletion of all referenced counted objects * will be delegated to.*/ static void setDeleteHandler(DeleteHandler* handler); /** Get a DeleteHandler.*/ static DeleteHandler* getDeleteHandler(); protected: virtual ~Referenced(); void signalObserversAndDelete(bool signalDelete, bool doDelete) const; void deleteUsingDeleteHandler() const; bool _logged; #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) mutable OpenThreads::AtomicPtr _observerSet; mutable OpenThreads::Atomic _refCount; #else mutable OpenThreads::Mutex* _refMutex; mutable int _refCount; mutable void* _observerSet; #endif }; inline int Referenced::ref() const { #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) return ++_refCount; #else if (_refMutex) { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); return ++_refCount; } else { return ++_refCount; } #endif } inline int Referenced::unref() const { int newRef; #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS) newRef = --_refCount; bool needDelete = (newRef == 0); #else bool needDelete = false; if (_refMutex) { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); newRef = --_refCount; needDelete = newRef==0; } else { newRef = --_refCount; needDelete = newRef==0; } #endif if (needDelete) { signalObserversAndDelete(true,true); } return newRef; } // intrusive_ptr_add_ref and intrusive_ptr_release allow // use of osg Referenced classes with boost::intrusive_ptr inline void intrusive_ptr_add_ref(Referenced* p) { p->ref(); } inline void intrusive_ptr_release(Referenced* p) { p->unref(); } } #endif
signature.asc
Description: This is a digitally signed message part
_______________________________________________ osg-users mailing list osg-users@lists.openscenegraph.org http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org