Hey guys,

Stephan Huber wrote:
However, running IBM Purify on the application reveals no massive leaks,
only a few false positives. Are there any tools or techniques that
someone could recommend to make finding and fixing memory leaks easy,
and which work well with OSG? I tried Visual Leak Detector which is open
source, but it reported so many false positives related to variables
stored in ref_ptrs (over 400) that I gave up on it.

For that type of debugging I am using a custom singleton-class called
AllocationObserver which observes the refcount for osg::Referenced. You
That gave me an idea. You can't determine the actual type being constructed in either the Referenced constructor or destructor, but you can set a delete handler on Referenced that extracts this info before the destructor is called. See attached small diff to src/osg/Referenced.cpp (against the 2.6 branch, includes end-of-line whitespace trimming as my editor is set up like that and I'm too lazy to turn it off). This forces a line of output to be written to stdout every time a Referenced instance is allocated or deallocated, together with the instance's address. By using the deallocations you can figure out the allocations (which don't have type info), which is what the alloc.py script does. It filters the original output of a program run and replaces the unknown allocated instance types with the actual ones gotten from deallocations.

This gives quite nice statistics on the number of objects created, but unfortunately there seems to be a group of objects that is never deallocated. Their type can therefore not be determined. See below for example (processed by alloc.py) output when running osgviewer on the cow, and doing some viewpoint interaction and stuff.

What I don't understand is why osg::Matrix ends up in the list (at a count of a few thousand in this case), as that isn't a Referenced subclass.

In J-S's original simple test case I get around 250 Referenced instances that are never deallocated when stopping at breakpoint 2, but sadly can't tell where they come from. Perhaps moving class/libraryName() up into Referenced might help here?

Regards,
Paul

P.S. Stephan, you say "AllocationObserver which observes the refcount for osg::Referenced", but it only observes the refcount going to 0 right? I don't seen any other 'events' for observers.


SUMMARY
osg::AlphaFunc 3 A 3 D (0) osg::AnimationPath 2 A 2 D (0) osg::AnimationPathCallback 1 A 1 D (0) osg::AutoTransform 1 A 1 D (0) osg::Billboard 1 A 1 D (0) osg::BlendColor 1 A 1 D (0) osg::BlendEquation 1 A 1 D (0) osg::BlendFunc 12 A 12 D (0) osg::Box 1 A 1 D (0) osg::Camera 13 A 13 D (0) osg::CameraView 1 A 1 D (0) osg::Capsule 1 A 1 D (0) osg::ClearNode 2 A 2 D (0) osg::ClipNode 1 A 1 D (0) osg::ClipPlane 1 A 1 D (0) osg::ClusterCullingCallback 1 A 1 D (0) osg::ColorMask 5 A 5 D (0) osg::ColorMatrix 1 A 1 D (0) osg::CompositeShape 1 A 1 D (0) osg::Cone 1 A 1 D (0) osg::ConvexPlanarOccluder 1 A 1 D (0) osg::CoordinateSystemNode 1 A 1 D (0) osg::CullFace 1 A 1 D (0) osg::Cylinder 1 A 1 D (0) osg::Depth 2 A 2 D (0) osg::DrawArrayLengths 1 A 1 D (0) osg::DrawArrays 7 A 7 D (0) osg::DrawCallback 13 A 13 D (0) osg::DrawElementsUShort 2 A 2 D (0) osg::EllipsoidModel 1 A 1 D (0) osg::FloatArray 14 A 14 D (0) osg::Fog 1 A 1 D (0) osg::FragmentProgram 1 A 1 D (0) osg::FrontFace 1 A 1 D (0) osg::Geode 8 A 8 D (0) osg::Geometry 11 A 11 D (0) osg::GlyphTexture 2 A 2 D (0) osg::Group 4 A 4 D (0) osg::HeightField 2 A 2 D (0) osg::Image 137 A 137 D (0) osg::ImageSequence 1 A 1 D (0) osg::IntArray 2 A 2 D (0) osg::KdTree 1 A 1 D (0) osg::LOD 1 A 1 D (0) osg::Light 11 A 11 D (0) osg::LightModel 10 A 10 D (0) osg::LightSource 1 A 1 D (0) osg::LineStipple 1 A 1 D (0) osg::LineWidth 1 A 1 D (0) osg::Material 12 A 12 D (0) osg::Matrix 8628 A 8628 D (0) osg::MatrixTransform 2 A 2 D (0) osg::Node 1 A 1 D (0) osg::NodeCallback 2 A 2 D (0) osg::OccluderNode 1 A 1 D (0) osg::OcclusionQueryNode 1 A 1 D (0) osg::PagedLOD 1 A 1 D (0) osg::PixelBufferObject 1 A 1 D (0) osg::Point 1 A 1 D (0) osg::PointSprite 1 A 1 D (0) osg::PolygonMode 6 A 6 D (0) osg::PolygonOffset 3 A 3 D (0) osg::PositionAttitudeTransform 1 A 1 D (0) osg::Program 1 A 1 D (0) osg::Projection 1 A 1 D (0) osg::ProxyNode 1 A 1 D (0) osg::Scissor 1 A 1 D (0) osg::Sequence 1 A 1 D (0) osg::ShadeModel 1 A 1 D (0) osg::Shader 1 A 1 D (0) osg::ShapeDrawable 1 A 1 D (0) osg::Sphere 1 A 1 D (0) osg::StateSet 34 A 34 D (0) osg::Stencil 1 A 1 D (0) osg::Switch 2 A 2 D (0) osg::TessellationHints 1 A 1 D (0) osg::TexEnv 12 A 12 D (0) osg::TexEnvCombine 1 A 1 D (0) osg::TexEnvFilter 1 A 1 D (0) osg::TexGen 4 A 4 D (0) osg::TexGenNode 1 A 1 D (0) osg::TexMat 1 A 1 D (0) osg::Texture1D 1 A 1 D (0) osg::Texture2D 4 A 4 D (0) osg::Texture3D 1 A 1 D (0) osg::TextureCubeMap 1 A 1 D (0) osg::TextureRectangle 1 A 1 D (0) osg::Transform 1 A 1 D (0) osg::Uniform 15 A 15 D (0) osg::Vec2Array 1 A 1 D (0) osg::Vec3Array 9 A 9 D (0) osg::Vec4Array 9 A 9 D (0) osg::VertexProgram 1 A 1 D (0) osg::Viewport 12 A 12 D (0) osgDB::FreeType Font Reader/Writer 1 A 1 D (0) osgDB::OSG Reader/Writer 1 A 1 D (0) osgDB::Options 14 A 14 D (0) osgDB::RGB Image Reader/Writer 1 A 1 D (0) osgGA::Drive 1 A 1 D (0) osgGA::Flight 1 A 1 D (0) osgGA::GUIEventAdapter 1969 A 1969 D (0) osgGA::GUIEventHandler 7 A 7 D (0) osgGA::KeySwitchMatrixManipulator 1 A 1 D (0) osgGA::StateSetManipulator 1 A 1 D (0) osgGA::Terrain 1 A 1 D (0) osgGA::Trackball 1 A 1 D (0) osgText::Font 2 A 2 D (0) osgText::Text 13 A 13 D (0) osgUtil::PositionalStateContainer 2 A 2 D (0) osgUtil::RenderBin 1176 A 1176 D (0) osgUtil::RenderStage 8 A 8 D (0) osgUtil::SceneView 8 A 8 D (0) osgViewer::GraphicsWindowX11 1 A 1 D (0) typeid(12FreeTypeFont) 1 A 1 D (0) typeid(15FreeTypeLibrary) 1 A 1 D (0) typeid(22RenderBinPrototypeList) 1 A 1 D (0) typeid(27X11WindowingSystemInterface) 1 A 1 D (0) typeid(29ViewerCoordinateFrameCallback) 1 A 1 D (0) typeid(N3osg10FrameStampE) 1 A 1 D (0) typeid(N3osg12BufferObject10ExtensionsE) 1 A 1 D (0) typeid(N3osg13FBOExtensionsE) 1 A 1 D (0) typeid(N3osg13GL2ExtensionsE) 1 A 1 D (0) typeid(N3osg13KdTreeBuilderE) 1 A 1 D (0) typeid(N3osg15FragmentProgram10ExtensionsE) 1 A 1 D (0) typeid(N3osg15GraphicsContext6TraitsE) 1 A 1 D (0) typeid(N3osg30FlushDeletedGLObjectsOperationE) 4 A 4 D (0) typeid(N3osg5StateE) 9 A 9 D (0) typeid(N3osg5StatsE) 2 A 2 D (0) typeid(N3osg7Texture13TextureObjectE) 2 A 2 D (0) typeid(N3osg8Drawable10ExtensionsE) 1 A 1 D (0) typeid(N3osg8RefBlockE) 6 A 6 D (0) typeid(N5osgDB10ImagePager11ImageThreadE) 3 A 3 D (0) typeid(N5osgDB10ImagePager12RequestQueueE) 1 A 1 D (0) typeid(N5osgDB10ImagePager9ReadQueueE) 1 A 1 D (0) typeid(N5osgDB10ImagePagerE) 1 A 1 D (0) typeid(N5osgDB13DatabasePager12RequestQueueE) 4 A 4 D (0) typeid(N5osgDB13DatabasePager14DatabaseThreadE) 4 A 4 D (0) typeid(N5osgDB13DatabasePager9ReadQueueE) 4 A 4 D (0) typeid(N5osgDB13DatabasePagerE) 2 A 2 D (0) typeid(N5osgDB13DotOsgWrapperE) 91 A 91 D (0) typeid(N5osgDB14DynamicLibraryE) 3 A 3 D (0) typeid(N5osgDB8RegistryE) 1 A 1 D (0) typeid(N5osgGA10EventQueueE) 2 A 2 D (0) typeid(N5osgGA12EventVisitorE) 1 A 1 D (0) typeid(N7osgUtil10RenderLeafE) 21 A 21 D (0) typeid(N7osgUtil10StateGraphE) 24 A 24 D (0) typeid(N7osgUtil11CullVisitorE) 9 A 9 D (0) typeid(N7osgUtil13UpdateVisitorE) 9 A 9 D (0) typeid(N7osgUtil22LineSegmentIntersectorE) 1 A 1 D (0) typeid(N9osgViewer20ScreenCaptureHandler11WriteToFileE) 1 A 1 D (0) typeid(N9osgViewer5SceneE) 1 A 1 D (0) typeid(N9osgViewer8RendererE) 4 A 4 D (0)
Only allocated:
UNKNOWN (no deallocation found)           5775 allocs
Only deallocated:

Index: src/osg/Referenced.cpp
===================================================================
--- src/osg/Referenced.cpp	(revision 9401)
+++ src/osg/Referenced.cpp	(working copy)
@@ -1,18 +1,19 @@
-/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
+/* -*-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 
+ * 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 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * OpenSceneGraph Public License for more details.
 */
 #include <stdlib.h>
 
 #include <osg/Referenced>
+#include <osg/Object>
 #include <osg/Notify>
 #include <osg/ApplicationUsage>
 #include <osg/observer_ptr>
@@ -26,13 +27,32 @@
 
 #include <osg/DeleteHandler>
 
-#ifndef OSG_JAVA_BUILD       
+#ifndef OSG_JAVA_BUILD
 
+
+class ReferencedDeleteHandler : public osg::DeleteHandler
+{
+    virtual void requestDelete(const osg::Referenced* r)
+    {
+        printf("DEALLOCATE[%08x] ", r);
+        const osg::Object *o = dynamic_cast<const osg::Object*>(r);
+        if (o)
+            printf("%s::%s", o->libraryName(), o->className());
+        else
+            // Unknown osg::Referenced subclass, try typeid
+            printf("typeid(%s)", typeid(*r).name());
+        printf("\n");
+
+        // Call original method
+        osg::DeleteHandler::requestDelete(r);
+    }
+};
+
 namespace osg
 {
 
 //#define ENFORCE_THREADSAFE
-//#define DEBUG_OBJECT_ALLOCATION_DESTRUCTION
+#define DEBUG_OBJECT_ALLOCATION_DESTRUCTION
 
 // specialized smart pointer, used to get round auto_ptr<>'s lack of the destructor reseting itself to 0.
 struct DeleteHandlerPointer
@@ -149,12 +169,17 @@
 #endif
         _refMutex = new OpenThreads::Mutex;
 #endif
-        
+
 #ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION
     {
         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
         ++s_numObjects;
+
+        printf("ALLOCATE[%08x] ???\n", this);
         osg::notify(osg::NOTICE)<<"Object created, total num="<<s_numObjects<<std::endl;
+
+        if (getDeleteHandler() == NULL)
+            setDeleteHandler(new ReferencedDeleteHandler());
     }
 #endif
 
@@ -181,7 +206,12 @@
     {
         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
         ++s_numObjects;
+
+        printf("ALLOCATE[%08x] ???\n", this);
         osg::notify(osg::NOTICE)<<"Object created, total num="<<s_numObjects<<std::endl;
+
+        if (getDeleteHandler() == NULL)
+            setDeleteHandler(new ReferencedDeleteHandler());
     }
 #endif
 }
@@ -207,7 +237,12 @@
     {
         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
         ++s_numObjects;
+
+        printf("ALLOCATE[%08x] ???\n", this);
         osg::notify(osg::NOTICE)<<"Object created, total num="<<s_numObjects<<std::endl;
+
+        if (getDeleteHandler() == NULL)
+            setDeleteHandler(new ReferencedDeleteHandler());
     }
 #endif
 }
@@ -218,6 +253,8 @@
     {
         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
         --s_numObjects;
+
+        printf("[%08x] ~Referenced()\n", this);
         osg::notify(osg::NOTICE)<<"Object deleted, total num="<<s_numObjects<<std::endl;
     }
 #endif
@@ -294,7 +331,7 @@
 #if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
     if (_refMutex)
     {
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); 
+        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
         --_refCount;
     }
     else
@@ -321,7 +358,7 @@
 #else
     if (_refMutex)
     {
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); 
+        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
 
         if (!_observers) _observers = new ObserverSet;
         if (_observers) static_cast<ObserverSet*>(_observers)->insert(observer);
@@ -340,13 +377,13 @@
     ObserverSetData* observerSetData = static_cast<ObserverSetData*>(_observerSetDataPtr.get());
     if (observerSetData)
     {
-       OpenThreads::ScopedLock<OpenThreads::Mutex> lock(observerSetData->_mutex); 
+       OpenThreads::ScopedLock<OpenThreads::Mutex> lock(observerSetData->_mutex);
        observerSetData->_observers.erase(observer);
     }
 #else
     if (_refMutex)
     {
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex); 
+        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
 
         if (_observers) static_cast<ObserverSet*>(_observers)->erase(observer);
     }
@@ -364,4 +401,4 @@
 
 } // end of namespace osg
 
-#endif //OSG_JAVA_BUILD        
+#endif //OSG_JAVA_BUILD
#!/usr/bin/env python

# Reconstruct allocations with unknown types from deallocations
# with known types.
#
# Paul E.C. Melis <p...@science.uva.nl>, December 2008

import sys, re

if len(sys.argv) != 2:
    print 'usage: %s file' % sys.argv[0]
    sys.exit(0)

alloc_pat = re.compile('^ALLOCATE\[([a-z0-9]+)\]')
dealloc_pat = re.compile('^DEALLOCATE\[([a-z0-9]+)\] (.*)')

f = open(sys.argv[1], 'rt')
lines = f.readlines()
f.close()

addr2object = {}
dealloc_count_by_addr = {}

allocations_by_type = {}
deallocations_by_type = {}

# Use deallocation to determine types of previously allocated instances

for line in lines:
    m = dealloc_pat.search(line)
    if m:
        addr = m.group(1)
        obj = m.group(2)

        # A newly allocated object may end up reusing the address
        # of a previously dellocated one. So we need to keep track
        # of multiple deallocs per address.
        if addr not in dealloc_count_by_addr:
            dealloc_count_by_addr[addr] = 1
        else:
            dealloc_count_by_addr[addr] += 1

        key = '%s %d' % (addr, dealloc_count_by_addr[addr])

        addr2object[key] = obj

        if obj not in deallocations_by_type:
            deallocations_by_type[obj] = 0
        deallocations_by_type[obj] += 1

# Go through allocations and replace addresses with actual class types

alloc_count_by_addr = {}

for line in lines:
    m = alloc_pat.search(line)
    if m:
        addr = m.group(1)

        if addr not in alloc_count_by_addr:
            alloc_count_by_addr[addr] = 1
        else:
            alloc_count_by_addr[addr] += 1

        key = '%s %d' % (addr, alloc_count_by_addr[addr])

        if key not in addr2object:
            obj = "UNKNOWN (no deallocation found)"
        else:
            obj = addr2object[key]
        sys.stdout.write('ALLOCATE[%s] %s\n' % (addr, obj))

        if obj not in allocations_by_type:
            allocations_by_type[obj] = 0
        allocations_by_type[obj] += 1
    else:
        sys.stdout.write(line)

print 'SUMMARY'

allocated_types = set(allocations_by_type.keys())
deallocated_types = set(deallocations_by_type.keys())

for t in sorted(allocated_types & deallocated_types):
    print '%-60s %5d A  %5d D  (%d)' % (t, allocations_by_type[t], deallocations_by_type[t], allocations_by_type[t]-deallocations_by_type[t])

print 'Only allocated:'
only_allocated = allocated_types - deallocated_types
for t in sorted(only_allocated):
    print '%-40s %5d allocs' % (t, allocations_by_type[t])

print 'Only deallocated:'
only_deallocated = deallocated_types - allocated_types
for t in sorted(only_deallocated):
    print '%-40s %5d deallocs' % (t, deallocations_by_type[t])
_______________________________________________
osg-users mailing list
osg-users@lists.openscenegraph.org
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Reply via email to