Its easy to create a graph of memory use in Excel
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>