In this post Examine .Net Memory Leaks I showed how to find a .Net managed memory leak.

 

Now let’s create a graph of memory and resource use over time.

 

 

Start Visual Studio 2008, File->New->Visual Basic (or C#) Windows, WPF application. Dbl click the WPF form to get to the Xaml.cs or Xaml.vb file

Paste in the appropriate version from below.

Hit F5 to run the code for about 2 minutes. Then Excel starts (if you have Excel installed)

 

 

When Excel starts, type these keystrokes exactly (we can automate Excel with a macro or use Automation, but that’s another story)

                Right Arrow (to skip the iteration column)

                Shift-Ctrl-End (to select the entire table)

                Alt (to activate the menu shortcuts

                N (to choose Insert)

                N (to choose Line Graph)

                Enter (to choose the first kind of line graph

 

See how easy it is to create a picture?

 

Try experimenting with the garbage collection and how it behaves. Try commenting out the UnSubscribe call.

 

Open the log.csv file from within VS. As the file changes, VS will automatically detect and reload the text, so you can watch as it runs.

 

See also

Create your own Test Host using XAML to run your unit tests

Excel's new gradient Data Bar feature is cool: you can do it too!

Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net

 

<VB Code>

Imports System.IO

 

Class Window1

    Public Shared logFile = "log.csv"    ' Excel can read/create graph easily

 

    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), logFile)

 

        Dim nLoops = 100

        Dim nItersPerLoop = 100

        Dim dummy(nItersPerLoop) As MyWatcher

        For i = 1 To nLoops

            For j = 1 To nItersPerLoop

                Dim oWatcher = New MyWatcher

                dummy(i) = oWatcher

                dummy(i).UnSubscribe()

            Next

            GarbageCollect(0)

        Next

 

        Process.Start(logFile) ' start Excel 2007

        End ' end the program

    End Sub

 

    Private Shared _nIter As Integer

    Sub GarbageCollect(ByVal nGarbageCollect As Integer)

        For i = 1 To nGarbageCollect

            GC.Collect()

            GC.WaitForPendingFinalizers()

            Dim start = DateTime.Now

            ' give time to other threads to finish (WPF managed objects on other threads)

            Do Until (DateTime.Now - start).Duration > TimeSpan.FromSeconds(1)

                Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Background, Function() Nothing)

            Loop

        Next

        If _nIter = 0 AndAlso File.Exists(logFile) Then

            File.Delete(logFile)

        End If

        Dim devenv = Process.GetCurrentProcess

        Dim sFmt = "{0,5}, [{1,22}], {2,10}, {3,5}, {4,5}, {5,5}"

        Using writer = If(File.Exists(logFile), File.AppendText(logFile), File.CreateText(logFile))

            _nIter += 1

            Dim sOutput As String

            If _nIter = 1 Then

                sOutput = String.Format(sFmt, "Iter", "When", "Priv Mb", "Hndle", "GDI", "User")

                writer.WriteLine(sOutput)

                Debug.WriteLine(sOutput)

            End If

 

            sOutput = String.Format(sFmt, _nIter, _

                    DateTime.Now, (devenv.PrivateMemorySize64 / 1000000.0).ToString("f6"), devenv.HandleCount, GetGuiResources(devenv.Handle, 0), GetGuiResources(devenv.Handle, 1))

            writer.WriteLine(sOutput)

            Debug.WriteLine(sOutput)

            writer.Close()

        End Using

 

    End Sub

    Declare Function GetGuiResources Lib "user32" (ByVal hHandle As IntPtr, ByVal uiFlags As Integer) As Integer

 

 

    Class MyWatcher

        Dim MyLargeMemoryEater(1000000) As String ' make the instance bigger to magnify issue: 4 bytes per array item on x86

        Dim fsw As IO.FileSystemWatcher

        Sub New()

            fsw = New IO.FileSystemWatcher

            fsw.Path = Path.GetDirectoryName(logFile)

            fsw.Filter = "*.*"

            AddHandler fsw.Created, AddressOf OnWatcherFileCreated

 

            fsw.EnableRaisingEvents = True

 

        End Sub

        Sub UnSubscribe()

            fsw.EnableRaisingEvents = False

            RemoveHandler fsw.Created, AddressOf OnWatcherFileCreated

        End Sub

        Sub OnWatcherFileCreated(ByVal sender As Object, ByVal args As System.IO.FileSystemEventArgs)

            Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + " " + args.FullPath)

        End Sub

        Protected Overrides Sub Finalize()  ' called when garbage collector collects on the GC Finalizer thread.

            MyBase.Finalize()

            '            Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + _nIter.ToString + " Thread= " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString)

        End Sub

 

    End Class

 

End Class

</VB Code>

 

<C# Code>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.Diagnostics;

using System.IO;

 

namespace WpfApplication1

{

    /// <summary>

    /// Interaction logic for Window1.xaml

    /// </summary>

    public partial class Window1 : Window

    {

        public static string logFile = "log.csv"; // Excel can read/create graph easily

        private static int _nIter;

 

        public Window1()

        {

            InitializeComponent();

        }

 

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            logFile = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), logFile);

            int nLoops = 100;

            int nItersPerLoop = 100;

            MyWatcher[] dummy = new MyWatcher[nItersPerLoop];

            for (int i = 0; i < nLoops; i++)

            {

                for (int j = 0; j < nItersPerLoop; j++)

                {

                    var oWatcher = new MyWatcher();

                    dummy[i] = oWatcher;

                    dummy[i].UnSubscribe();

                }

                GarbageCollect(01);

            }

 

            Process.Start(logFile);// start Excel 2007

            Process.GetCurrentProcess().CloseMainWindow();

 

        }

        private void GarbageCollect(int nGarbageCollect)

        {

            for (int i = 0; i < nGarbageCollect; i++)

            {

                GC.Collect();

                GC.WaitForPendingFinalizers();

                var start = DateTime.Now;

                // give time to other threads to finish (WPF managed objects on other threads)

                var dd = TimeSpan.FromSeconds(1);

                var yy = dd > TimeSpan.FromSeconds(2);

 

 

                while (((DateTime.Now - start)) < TimeSpan.FromSeconds(1))

                {

                    Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,

                        new System.Windows.Threading.DispatcherOperationCallback(delegate

                            {

                                return null;

                            }

                            ),null);

 

                }

 

            }

            if (_nIter == 0 && System.IO.File.Exists(logFile))

            {

                System.IO.File.Delete(logFile);

            }

            var devenv = Process.GetCurrentProcess();

            var sFmt = "{0,5}, [{1,22}], {2,10}, {3,5}, {4,5}, {5,5}";

            using (TextWriter writer = File.Exists(logFile) ? File.AppendText(logFile) :  File.CreateText(logFile))

            {

                _nIter += 1;

                var sOutput = "";

                if (_nIter == 1)

                {

                    sOutput = String.Format(sFmt, "Iter", "When", "Priv Mb", "Hndle", "GDI", "User");

                    writer.WriteLine(sOutput);

                    Debug.WriteLine(sOutput);

                }

                    sOutput = String.Format(sFmt, _nIter,

                            DateTime.Now, (devenv.PrivateMemorySize64 / 1000000.0).ToString("f6"), devenv.HandleCount, GetGuiResources(devenv.Handle, 0), GetGuiResources(devenv.Handle, 1));

                    writer.WriteLine(sOutput);

                    Debug.WriteLine(sOutput);

                    writer.Close();

            }

        }

 

        [System.Runtime.InteropServices.DllImport("User32")]

        extern public static int GetGuiResources(IntPtr hProcess, int uiFlags);

        public class MyWatcher

        {

            string[] MyLargeMemoryEater = new string[1000000]; //' make the instance bigger to magnify issue: 4 bytes per array item on x86

            System.IO.FileSystemWatcher fsw;

            public MyWatcher()

            {

                fsw = new System.IO.FileSystemWatcher();

                fsw.Path = System.IO.Path.GetDirectoryName(logFile);

                fsw.Filter = "*.*";

 

                fsw.EnableRaisingEvents = true;

                fsw.Created += OnWatcherFileCreated;

 

            }

            public void UnSubscribe()

            {

                fsw.EnableRaisingEvents = false;

                fsw.Created -= OnWatcherFileCreated;

            }

            void OnWatcherFileCreated(Object sender, System.IO.FileSystemEventArgs args)

            {

                Debug.WriteLine(((new StackTrace()).GetFrames()).FirstOrDefault().GetMethod().Name + " " + args.FullPath);

            }

        }

    }

}

 

</C# Code>