├── .gitattributes ├── .gitignore ├── GCBench ├── App.config ├── GCBench.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── GCVisualisation.sln ├── GCVisualisation ├── App.config ├── GCVisualisation.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TraceEvent.ReadMe.txt ├── TraceEvent.ReleaseNotes.txt ├── _TraceEventProgrammersGuide.docx └── packages.config ├── README.md └── SampleApp ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── SampleApp.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | 50 | bin/ 51 | obj/ 52 | packages/ 53 | *.suo 54 | *.user 55 | -------------------------------------------------------------------------------- /GCBench/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /GCBench/GCBench.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {55A6DD7B-02E9-412F-B268-481A5068EA11} 8 | Exe 9 | Properties 10 | GCBench 11 | GCBench 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /GCBench/Program.cs: -------------------------------------------------------------------------------- 1 | // This benchmark has been modified (by Will Clinger): 2 | // 3 | // The name of the main class has been changed to GCBench. 4 | // The name of the main method has been changed to originalMain. 5 | // The benchmark's parameters are now variables instead of constants. 6 | // A new main method allows the number of iterations and the 7 | // benchmark's main size parameter to be specified on the 8 | // command line. The new main method computes the other 9 | // parameters from the main parameter, times the iterated 10 | // benchmark, and reports that iterated timing in addition 11 | // to timings for each iteration (which are still reported 12 | // by the originalMain method). 13 | // 14 | // Usage: 15 | // 16 | // java GCBench N K 17 | // 18 | // where 19 | // N is the number of iterations (defaulting to its original value, 1) 20 | // K is the size parameter (defaulting to its original value, 18) 21 | // 22 | // The original comment follows: 23 | 24 | // This is adapted from a benchmark written by John Ellis and Pete Kovac 25 | // of Post Communications. 26 | // It was modified by Hans Boehm of Silicon Graphics. 27 | // 28 | // This is no substitute for real applications. No actual application 29 | // is likely to behave in exactly this way. However, this benchmark was 30 | // designed to be more representative of real applications than other 31 | // Java GC benchmarks of which we are aware. 32 | // It attempts to model those properties of allocation requests that 33 | // are important to current GC techniques. 34 | // It is designed to be used either to obtain a single overall performance 35 | // number, or to give a more detailed estimate of how collector 36 | // performance varies with object lifetimes. It prints the time 37 | // required to allocate and collect balanced binary trees of various 38 | // sizes. Smaller trees result in shorter object lifetimes. Each cycle 39 | // allocates roughly the same amount of memory. 40 | // Two data structures are kept around during the entire process, so 41 | // that the measured performance is representative of applications 42 | // that maintain some live in-memory data. One of these is a tree 43 | // containing many pointers. The other is a large array containing 44 | // double precision floating point numbers. Both should be of comparable 45 | // size. 46 | // 47 | // The results are only really meaningful together with a specification 48 | // of how much memory was used. It is possible to trade memory for 49 | // better time performance. This benchmark should be run in a 32 MB 50 | // heap, though we don't currently know how to enforce that uniformly. 51 | // 52 | // Unlike the original Ellis and Kovac benchmark, we do not attempt 53 | // measure pause times. This facility should eventually be added back 54 | // in. There are several reasons for omitting it for now. The original 55 | // implementation depended on assumptions about the thread scheduler 56 | // that don't hold uniformly. The results really measure both the 57 | // scheduler and GC. Pause time measurements tend to not fit well with 58 | // current benchmark suites. As far as we know, none of the current 59 | // commercial Java implementations seriously attempt to minimize GC pause 60 | // times. 61 | 62 | // Simple conversion to C# - leppie 63 | using System; 64 | using System.Threading.Tasks; 65 | 66 | class Node 67 | { 68 | public Node left, right; 69 | 70 | public Node(Node l, Node r) : this() 71 | { 72 | left = l; 73 | right = r; 74 | } 75 | public Node() {} 76 | } 77 | 78 | class GCBench 79 | { 80 | public static void Main(string[] args) 81 | { 82 | int n = Environment.ProcessorCount; // number of iterations 83 | 84 | if (args.Length > 0) 85 | n = int.Parse(args[0]); 86 | if (args.Length > 1) 87 | { 88 | kStretchTreeDepth = int.Parse(args[1]); 89 | kLongLivedTreeDepth = kStretchTreeDepth - 2; 90 | kArraySize = 4 * TreeSize(kLongLivedTreeDepth); 91 | kMaxTreeDepth = kLongLivedTreeDepth; 92 | } 93 | if (n == 1) 94 | originalMain(0); 95 | else 96 | { 97 | long tStart, tFinish; 98 | tStart = DateTime.UtcNow.Ticks; 99 | 100 | Parallel.For(0, n, new ParallelOptions { MaxDegreeOfParallelism = Math.Min(Environment.ProcessorCount, Math.Max(1, n >> 1)) }, originalMain); 101 | tFinish = DateTime.UtcNow.Ticks; 102 | PrintDiagnostics(); 103 | Console.WriteLine($"{n} gcbench:{kStretchTreeDepth} took {new TimeSpan(tFinish - tStart).TotalMilliseconds:F0} ms."); 104 | } 105 | } 106 | 107 | public static int kStretchTreeDepth = 20; // about 16Mb 108 | public static int kLongLivedTreeDepth = 18; // about 16Mb 109 | public static int kArraySize = 4000000; // about 16Mb 110 | public const int kMinTreeDepth = 10; 111 | public static int kMaxTreeDepth = 16; 112 | 113 | // Nodes used by a tree of a given size 114 | static int TreeSize(int i) 115 | { 116 | return ((1 << (i + 1)) - 1); 117 | } 118 | 119 | // Number of iterations to use for a given tree depth 120 | static int NumIters(int i) 121 | { 122 | return 2 * TreeSize(kStretchTreeDepth) / TreeSize(i); 123 | } 124 | 125 | // Build tree top down, assigning to older objects. 126 | static void Populate(int iDepth, Node thisNode) 127 | { 128 | if (iDepth <= 0) 129 | { 130 | return; 131 | } 132 | else 133 | { 134 | iDepth--; 135 | thisNode.left = new Node(); 136 | thisNode.right = new Node(); 137 | Populate(iDepth, thisNode.left); 138 | Populate(iDepth, thisNode.right); 139 | } 140 | } 141 | 142 | // Build tree bottom-up 143 | static Node MakeTree(int iDepth) 144 | { 145 | if (iDepth <= 0) 146 | { 147 | return new Node(); 148 | } 149 | else 150 | { 151 | return new Node(MakeTree(iDepth - 1), 152 | MakeTree(iDepth - 1)); 153 | } 154 | } 155 | 156 | static void PrintDiagnostics() 157 | { 158 | GC.Collect(2, GCCollectionMode.Optimized, false); 159 | Console.WriteLine($" Working set={Environment.WorkingSet:N0} bytes"); 160 | } 161 | 162 | static void TimeConstruction(int depth) 163 | { 164 | long tStart, tFinish; 165 | int iNumIters = NumIters(depth); 166 | Node tempTree; 167 | 168 | Console.WriteLine("Creating " + iNumIters + 169 | " trees of depth " + depth); 170 | tStart = DateTime.UtcNow.Ticks; 171 | for (int i = 0; i < iNumIters; ++i) 172 | { 173 | tempTree = new Node(); 174 | Populate(depth, tempTree); 175 | tempTree = null; 176 | } 177 | tFinish = DateTime.UtcNow.Ticks; 178 | Console.WriteLine($"\tTop down construction took {new TimeSpan(tFinish - tStart).TotalMilliseconds:F0} ms"); 179 | tStart = DateTime.UtcNow.Ticks; 180 | for (int i = 0; i < iNumIters; ++i) 181 | { 182 | tempTree = MakeTree(depth); 183 | tempTree = null; 184 | } 185 | tFinish = DateTime.UtcNow.Ticks; 186 | Console.WriteLine($"\tBottom up construction took {new TimeSpan(tFinish - tStart).TotalMilliseconds:F0} ms"); 187 | } 188 | 189 | public static void originalMain(int cpu) 190 | { 191 | Node longLivedTree; 192 | Node tempTree; 193 | long tStart, tFinish; 194 | 195 | Console.WriteLine("Garbage Collector Test"); 196 | Console.WriteLine( 197 | " Stretching memory with a binary tree of depth " 198 | + kStretchTreeDepth); 199 | PrintDiagnostics(); 200 | tStart = DateTime.UtcNow.Ticks; 201 | 202 | // Stretch the memory space quickly 203 | tempTree = MakeTree(kStretchTreeDepth); 204 | tempTree = null; 205 | 206 | // Create a long lived object 207 | Console.WriteLine( 208 | " Creating a long-lived binary tree of depth " + 209 | kLongLivedTreeDepth); 210 | longLivedTree = new Node(); 211 | Populate(kLongLivedTreeDepth, longLivedTree); 212 | 213 | // Create long-lived array, filling half of it 214 | Console.WriteLine( 215 | " Creating a long-lived array of " 216 | + kArraySize + " doubles"); 217 | double[] array = new double[kArraySize]; 218 | for (int i = 0; i < kArraySize / 2; ++i) 219 | { 220 | array[i] = 1.0 / i; 221 | } 222 | PrintDiagnostics(); 223 | 224 | for (int d = kMinTreeDepth; d <= kMaxTreeDepth; d += 2) 225 | { 226 | TimeConstruction(d); 227 | } 228 | 229 | if (longLivedTree == null || array[1000] != 1.0 / 1000) 230 | Console.WriteLine("Failed"); 231 | // fake reference to LongLivedTree 232 | // and array 233 | // to keep them from being optimized away 234 | longLivedTree = null; 235 | tFinish = DateTime.UtcNow.Ticks; 236 | PrintDiagnostics(); 237 | Console.WriteLine($"Completed in {new TimeSpan(tFinish - tStart).TotalMilliseconds:F0} ms."); 238 | 239 | } 240 | } // class JavaGC -------------------------------------------------------------------------------- /GCBench/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("GCBench")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("GCBench")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("55a6dd7b-02e9-412f-b268-481a5068ea11")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /GCVisualisation.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCVisualisation", "GCVisualisation\GCVisualisation.csproj", "{274B102E-42A8-4FBF-9F13-415B11E3DF80}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD} = {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD} 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "SampleApp\SampleApp.csproj", "{059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCBench", "GCBench\GCBench.csproj", "{55A6DD7B-02E9-412F-B268-481A5068EA11}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {274B102E-42A8-4FBF-9F13-415B11E3DF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {274B102E-42A8-4FBF-9F13-415B11E3DF80}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {274B102E-42A8-4FBF-9F13-415B11E3DF80}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {274B102E-42A8-4FBF-9F13-415B11E3DF80}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {55A6DD7B-02E9-412F-B268-481A5068EA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {55A6DD7B-02E9-412F-B268-481A5068EA11}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {55A6DD7B-02E9-412F-B268-481A5068EA11}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {55A6DD7B-02E9-412F-B268-481A5068EA11}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /GCVisualisation/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /GCVisualisation/GCVisualisation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {274B102E-42A8-4FBF-9F13-415B11E3DF80} 8 | Exe 9 | Properties 10 | GCVisualisation 11 | GCVisualisation 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll 39 | True 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /GCVisualisation/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Diagnostics.Tracing; 2 | using Microsoft.Diagnostics.Tracing.Parsers; 3 | using Microsoft.Diagnostics.Tracing.Parsers.Clr; 4 | using Microsoft.Diagnostics.Tracing.Session; 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Collections.Generic; 13 | using System.IO; 14 | 15 | namespace GCVisualisation 16 | { 17 | class Program 18 | { 19 | private static TraceEventSession session; 20 | private static ConcurrentBag ProcessIdsUsedInRuns = new ConcurrentBag(); 21 | private static long totalBytesAllocated, gen0, gen1, gen2, gen2Background, gen3; 22 | private static double timeInGc, totalGcPauseTime, largestGcPause, startTime, stopTime; 23 | private static List[] binning = new List[4]; // just in case of LOH, but not printed 24 | 25 | private static object ConsoleLock = new object(); 26 | private static object BinningLock = new object(); 27 | 28 | class ProcessComparer : IEqualityComparer 29 | { 30 | public bool Equals(Process x, Process y) => x.Id == y.Id; 31 | public int GetHashCode(Process obj) => obj.Id; 32 | } 33 | 34 | static void Main(string[] args) 35 | { 36 | // TODO 37 | // - allow to specify PID of running process (DON'T kill it at the end!!) 38 | // - put some stats in a "margin" and display the output on the RH 80% of the screen 39 | // - GC pauses, for info see 40 | // - https://blogs.msdn.microsoft.com/maoni/2014/12/22/gc-etw-events-1/ 41 | // - https://blogs.msdn.microsoft.com/maoni/2014/12/25/gc-etw-events-3/ 42 | // - https://blogs.msdn.microsoft.com/maoni/2015/11/20/are-you-glad/ 43 | // - https://blogs.msdn.microsoft.com/maoni/2006/06/07/suspending-and-resuming-threads-for-gc/ 44 | 45 | ResetStats(); 46 | 47 | var sessionName = "GCVisualiser"; 48 | session = new TraceEventSession(sessionName); 49 | session.EnableProvider(ClrTraceEventParser.ProviderGuid, 50 | TraceEventLevel.Verbose, 51 | (ulong)(ClrTraceEventParser.Keywords.GC)); 52 | 53 | // The ETW collection thread starts receiving all events immediately, but we filter on the Process Id 54 | var processingTask = Task.Factory.StartNew(StartProcessingEvents, TaskCreationOptions.LongRunning); 55 | 56 | var exename = args[0]; 57 | 58 | var procname = Path.GetFileNameWithoutExtension(exename); 59 | var existingProcs = Process.GetProcessesByName(procname); 60 | 61 | var process = Process.Start(exename); 62 | 63 | // check for some shell executing the process, eg comemu/cmder 64 | if (!process.ProcessName.Equals(procname, StringComparison.OrdinalIgnoreCase)) 65 | { 66 | var cmp = new ProcessComparer(); 67 | Process subprocess = null; 68 | while (!process.HasExited) 69 | { 70 | Thread.Sleep(100); 71 | Console.WriteLine($"Trying to find '{procname}' process..."); 72 | subprocess = Process.GetProcessesByName(procname).Except(existingProcs, cmp).FirstOrDefault(); 73 | if (subprocess != null) 74 | { 75 | break; 76 | } 77 | } 78 | 79 | // one last time 80 | if (subprocess == null) 81 | { 82 | Thread.Sleep(100); 83 | Console.WriteLine($"Last attempt to find '{procname}' process"); 84 | subprocess = Process.GetProcessesByName(procname).Except(existingProcs, cmp).FirstOrDefault(); 85 | } 86 | 87 | if (subprocess == null) 88 | { 89 | Console.WriteLine($"'{procname}' process not found, goodbye"); 90 | return; 91 | } 92 | else 93 | { 94 | process = subprocess; 95 | } 96 | } 97 | 98 | ProcessIdsUsedInRuns.Add(process.Id); 99 | Console.CancelKeyPress += (sender, e) => 100 | { 101 | Console.WriteLine("\nConsole being killed, tidying up\n"); 102 | session.Dispose(); 103 | if (process.HasExited == false) 104 | process.Kill(); 105 | Console.WriteLine(); 106 | }; 107 | 108 | PrintSymbolInformation(); 109 | 110 | Console.WriteLine("Visualising GC Events, press , or 'q' to exit"); 111 | Console.WriteLine("You can also push 's' at any time and the current summary will be displayed"); 112 | Console.WriteLine("You can also push 'r' at any time to reset the counters"); 113 | 114 | ConsoleKeyInfo cki; 115 | while (process.HasExited == false) 116 | { 117 | if (Console.KeyAvailable == false) 118 | { 119 | Thread.Sleep(250); 120 | continue; 121 | } 122 | 123 | cki = Console.ReadKey(); 124 | if (cki.Key == ConsoleKey.Enter || 125 | cki.Key == ConsoleKey.Escape || 126 | cki.Key == ConsoleKey.Q) 127 | { 128 | break; 129 | } 130 | 131 | if (cki.Key == ConsoleKey.S) 132 | { 133 | lock (ConsoleLock) 134 | { 135 | PrintSummaryInfo(); 136 | } 137 | } 138 | else if (cki.Key == ConsoleKey.R) 139 | { 140 | ResetStats(); 141 | } 142 | } 143 | 144 | if (process.HasExited == false) 145 | process.Kill(); 146 | 147 | // Flush the session before we finish, so that we get all the events possible 148 | session.Flush(); 149 | // wait a little while for all events to come through (Flush() doesn't seem to do this?) 150 | Thread.Sleep(3000); 151 | // Now kill the session completely 152 | session.Dispose(); 153 | 154 | var completed = processingTask.Wait(millisecondsTimeout: 3000); 155 | if (!completed) 156 | Console.WriteLine("\nWait timed out, the Processing Task is still running"); 157 | 158 | PrintSummaryInfo(); 159 | Console.WriteLine(); 160 | } 161 | 162 | private static void ResetStats() 163 | { 164 | lock (BinningLock) 165 | { 166 | for (int i = 0; i < binning.Length; i++) 167 | { 168 | binning[i] = new List(); 169 | } 170 | } 171 | 172 | totalBytesAllocated = gen0 = gen1 = gen2 = gen2Background = gen3 = 0; 173 | timeInGc = totalGcPauseTime = largestGcPause = startTime = stopTime = 0; 174 | } 175 | 176 | private static void PrintSymbolInformation() 177 | { 178 | Console.WriteLine("Key to symbols:"); 179 | Console.WriteLine(" - '.' represents ~100K of memory ALLOCATIONS"); 180 | Console.WriteLine(" - 'o' represents >200K of memory ALLOCATIONS"); 181 | 182 | Console.Write(" - "); 183 | Console.ForegroundColor = GetColourForGC(0); Console.Write("0"); 184 | Console.ResetColor(); Console.Write("/"); 185 | Console.ForegroundColor = GetColourForGC(1); Console.Write("1"); 186 | Console.ResetColor(); Console.Write("/"); 187 | Console.ForegroundColor = GetColourForGC(2); Console.Write("2"); 188 | Console.ResetColor(); 189 | Console.WriteLine(" indicates a FOREGROUND GC Collection, for the given generation (0, 1 or 2)"); 190 | 191 | Console.Write(" - "); 192 | Console.BackgroundColor = GetColourForGC(2); Console.ForegroundColor = ConsoleColor.Black; Console.Write("2"); 193 | Console.ResetColor(); 194 | Console.WriteLine(" indicates a BACKGROUND GC Collection (Gen 2 only)"); 195 | 196 | Console.WriteLine(" - ░/▒/▓/█ indicates a PAUSE due to a GC Collection"); 197 | Console.WriteLine(" - ░ up to 25 msecs"); 198 | Console.WriteLine(" - ▒ 25 to 50 msecs"); 199 | Console.WriteLine(" - ▓ 50 to 75 msecs"); 200 | Console.WriteLine(" - █ 75 msecs or longer"); 201 | 202 | Console.WriteLine(new string('#', 25) + "\n"); 203 | } 204 | 205 | private static string Statistics(List bin) 206 | { 207 | if (bin.Count == 0) 208 | { 209 | return "no data"; 210 | } 211 | 212 | var max = bin.Max(); 213 | var avg = bin.Average(); 214 | var total = bin.Sum(); 215 | // this seems a bit useless 216 | //var stdev = bin.Count == 1 ? 0 : Math.Sqrt(bin.Sum(x => Math.Pow(x - avg, 2)) / (bin.Count - 1)); 217 | 218 | const int DIVISIONS = 10; 219 | var d = max/DIVISIONS; 220 | 221 | var lu = bin.ToLookup(x => (int)( x / d)); 222 | 223 | var maxlen = lu.Max(x => x.Count()); 224 | 225 | var output = new StringBuilder(new string(' ', DIVISIONS)); 226 | 227 | for (int i = 0; i < DIVISIONS; i++) 228 | { 229 | var len = lu[i].Count(); 230 | // seems you need a special font for this... #sadpanda 231 | output[i] = len == 0 ? ' ' : (char)(9601 + ((len * 7.99999) / maxlen)); 232 | // alternative output, too confusing :( 233 | //output[i] = ((len * 9) / maxlen).ToString()[0]; 234 | } 235 | 236 | return $"max: {max,6:F2} ms avg: {avg,6:F2} ms total: {total,8:F2} ms";// {output} {d,4:F2} ms per division"; 237 | } 238 | 239 | private static void PrintSummaryInfo() 240 | { 241 | session.Flush(); 242 | Console.ForegroundColor = ConsoleColor.DarkYellow; 243 | 244 | Console.WriteLine("\nMemory Allocations:"); 245 | // 100GB = 107,374,182,400 bytes, so min-width = 15 246 | Console.WriteLine(" {0,15:N0} bytes currently allocated", GC.GetTotalMemory(forceFullCollection: false)); 247 | Console.WriteLine(" {0,15:N0} bytes have been allocated in total", totalBytesAllocated); 248 | 249 | var totalGC = gen0 + gen1 + gen2 + gen3; 250 | var testTime = stopTime - startTime; 251 | Console.WriteLine("GC Collections:\n {0,5:N0} in total ({1:N0} excluding B/G)", totalGC + gen2Background, totalGC); 252 | Console.WriteLine(" {0,5:N0} - generation 0 - {1}", gen0, Statistics(binning[0])); 253 | Console.WriteLine(" {0,5:N0} - generation 1 - {1}", gen1, Statistics(binning[1])); 254 | Console.WriteLine(" {0,5:N0} - generation 2 - {1}", gen2, Statistics(binning[2])); 255 | Console.WriteLine(" {0,5:N0} - generation 2 (B/G)", gen2Background); 256 | if (gen3 > 0) 257 | Console.WriteLine(" {0,5:N0} - generation 3 (LOH)", gen3); 258 | 259 | Console.WriteLine("Time in GC : {0,12:N2} ms ({1:N2} ms avg per/GC) ", timeInGc, timeInGc / totalGC); 260 | Console.WriteLine("Time in test: {0,12:N2} ms ({1:P2} spent in GC)", testTime, timeInGc / testTime); 261 | Console.WriteLine("Total GC Pause time : {0,12:N2} ms", totalGcPauseTime); 262 | Console.WriteLine("Largest GC Pause time: {0,12:N2} ms", largestGcPause); 263 | 264 | Console.ResetColor(); 265 | } 266 | 267 | private static void StartProcessingEvents() 268 | { 269 | // See https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/pal/prebuilt/inc/mscoree.h#L294-L315 270 | session.Source.Clr.RuntimeStart += runtimeData => 271 | { 272 | if (ProcessIdsUsedInRuns.Contains(runtimeData.ProcessID) == false) 273 | return; 274 | 275 | lock (ConsoleLock) 276 | { 277 | Console.WriteLine("\nCONCURRENT_GC = {0}, SERVER_GC = {1}", 278 | (runtimeData.StartupFlags & StartupFlags.CONCURRENT_GC) == StartupFlags.CONCURRENT_GC, 279 | (runtimeData.StartupFlags & StartupFlags.SERVER_GC) == StartupFlags.SERVER_GC); 280 | } 281 | }; 282 | 283 | session.Source.Clr.GCAllocationTick += allocationData => 284 | { 285 | if (ProcessIdsUsedInRuns.Contains(allocationData.ProcessID) == false) 286 | return; 287 | 288 | if (startTime == 0) 289 | startTime = allocationData.TimeStampRelativeMSec; 290 | 291 | stopTime = allocationData.TimeStampRelativeMSec; 292 | 293 | totalBytesAllocated += allocationData.AllocationAmount; 294 | 295 | lock (ConsoleLock) 296 | { 297 | if (allocationData.AllocationAmount > 200000) 298 | { 299 | Console.Write("o"); 300 | } 301 | else 302 | { 303 | Console.Write("."); 304 | } 305 | } 306 | }; 307 | 308 | GCType lastGCType = 0; 309 | int lastGen = 0; 310 | double gcStart = 0; 311 | session.Source.Clr.GCStart += startData => 312 | { 313 | if (ProcessIdsUsedInRuns.Contains(startData.ProcessID) == false) 314 | return; 315 | 316 | if (startTime == 0) 317 | startTime = startData.TimeStampRelativeMSec; 318 | 319 | lastGCType = startData.Type; 320 | lastGen = startData.Depth; 321 | gcStart = startData.TimeStampRelativeMSec; 322 | IncrementGCCollectionCounts(startData); 323 | 324 | var colourToUse = GetColourForGC(startData.Depth); 325 | lock (ConsoleLock) 326 | { 327 | if (startData.Type == GCType.ForegroundGC || startData.Type == GCType.NonConcurrentGC) 328 | { 329 | // Make the FG coloured/highlighted 330 | Console.ForegroundColor = colourToUse; 331 | Console.BackgroundColor = ConsoleColor.Black; 332 | } 333 | else // GCType.BackgroundGC 334 | { 335 | // Make the BG coloured/highlighted 336 | Console.ForegroundColor = ConsoleColor.Black; 337 | Console.BackgroundColor = colourToUse; 338 | } 339 | 340 | Console.Write(startData.Depth); 341 | Console.ResetColor(); 342 | } 343 | }; 344 | 345 | session.Source.Clr.GCStop += stopData => 346 | { 347 | if (ProcessIdsUsedInRuns.Contains(stopData.ProcessID) == false) 348 | return; 349 | 350 | stopTime = stopData.TimeStampRelativeMSec; 351 | 352 | // If we don't have a matching start event, don't calculate the GC time 353 | if (gcStart == 0) 354 | return; 355 | 356 | var gcTime = stopData.TimeStampRelativeMSec - gcStart; 357 | timeInGc += gcTime; 358 | 359 | lock (BinningLock) 360 | { 361 | binning[lastGen].Add(gcTime); 362 | } 363 | }; 364 | 365 | //In a typical blocking GC (this means all ephemeral GCs and full blocking GCs) the event sequence is very simple: 366 | //GCSuspendEE_V1 Event 367 | //GCSuspendEEEnd_V1 Event <– suspension is done 368 | //GCStart_V1 Event 369 | //GCEnd_V1 Event <– actual GC is done 370 | //GCRestartEEBegin_V1 Event 371 | //GCRestartEEEnd_V1 Event <– resumption is done. 372 | 373 | double pauseStart = 0; 374 | session.Source.Clr.GCSuspendEEStop += suspendData => 375 | { 376 | if (ProcessIdsUsedInRuns.Contains(suspendData.ProcessID) == false) 377 | return; 378 | 379 | pauseStart = suspendData.TimeStampRelativeMSec; 380 | }; 381 | 382 | session.Source.Clr.GCRestartEEStop += restartData => 383 | { 384 | if (ProcessIdsUsedInRuns.Contains(restartData.ProcessID) == false) 385 | return; 386 | 387 | stopTime = restartData.TimeStampRelativeMSec; 388 | 389 | // Only display this if the GC Type is Foreground, (Background is different!!) 390 | // 0x0 - NonConcurrentGC - Blocking garbage collection occurred outside background garbage collection. 391 | // 0x1 - BackgroundGC - Background garbage collection. 392 | // 0x2 - ForegroundGC - Blocking garbage collection occurred during background garbage collection. 393 | if (lastGCType == GCType.BackgroundGC) 394 | return; 395 | 396 | // If we don't have a matching start event, don't calculate the "pause" time 397 | if (pauseStart == 0) 398 | return; 399 | 400 | var pauseDurationMSec = restartData.TimeStampRelativeMSec - pauseStart; 401 | var pauseText = new StringBuilder(); 402 | while (pauseDurationMSec > 100) 403 | { 404 | pauseText.Append('█'); 405 | pauseDurationMSec -= 100; 406 | } 407 | if (pauseDurationMSec > 75) 408 | pauseText.Append('█'); 409 | else if (pauseDurationMSec > 50) 410 | pauseText.Append('▓'); 411 | else if (pauseDurationMSec > 25) 412 | pauseText.Append('▒'); 413 | else 414 | pauseText.Append("░"); 415 | 416 | totalGcPauseTime += pauseDurationMSec; 417 | if (pauseDurationMSec > largestGcPause) 418 | largestGcPause = pauseDurationMSec; 419 | 420 | lock (ConsoleLock) 421 | { 422 | Console.ResetColor(); 423 | Console.Write(pauseText.ToString()); 424 | } 425 | }; 426 | 427 | session.Source.Process(); 428 | } 429 | 430 | private static void IncrementGCCollectionCounts(GCStartTraceData gcData) 431 | { 432 | if (gcData.Type == GCType.BackgroundGC && gcData.Depth == 2) 433 | gen2Background++; 434 | else if (gcData.Depth == 0) 435 | gen0++; 436 | else if (gcData.Depth == 1) 437 | gen1++; 438 | else if (gcData.Depth == 2) 439 | gen2++; 440 | else if (gcData.Depth == 3) 441 | gen3++; // L.O.H 442 | } 443 | 444 | private static ConsoleColor GetColourForGC(int depth) 445 | { 446 | var colourToUse = ConsoleColor.White; 447 | if (depth == 0) 448 | colourToUse = ConsoleColor.Yellow; 449 | else if (depth == 1) 450 | colourToUse = ConsoleColor.Blue; 451 | else if (depth == 2) 452 | colourToUse = ConsoleColor.Red; 453 | else 454 | colourToUse = ConsoleColor.Green; 455 | 456 | return colourToUse; 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /GCVisualisation/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("GCVisualisation")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("CA, Inc.")] 12 | [assembly: AssemblyProduct("GCVisualisation")] 13 | [assembly: AssemblyCopyright("Copyright © CA, Inc. 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("274b102e-42a8-4fbf-9f13-415b11e3df80")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /GCVisualisation/TraceEvent.ReadMe.txt: -------------------------------------------------------------------------------- 1 |  2 | ************* Welcome to the Microsoft.Diagnostics.Tracing.TraceEvent library! *************** 3 | 4 | This library is designed to make controlling and parsing Event Tracing for Windows (ETW) events easy. 5 | In particular if you are generating events with System.Diagnostics.Tracing.EventSource, this library 6 | makes it easy to process that data. 7 | 8 | ******** PROGRAMMERS GUIDE ******** 9 | 10 | If you are new to TraceEvent, see the _TraceEventProgammersGuide.docx that was installed as part of 11 | your solution when this NuGet package was installed. 12 | 13 | ************ FEEDBACK ************* 14 | 15 | If you have problems, wish to report a bug, or have a suggestion please log your comments on the 16 | .NET Runtime Framework Blog http://blogs.msdn.com/b/dotnet/ under the TraceEvent announcement. 17 | 18 | ********** RELEASE NOTES *********** 19 | 20 | If you are interested what particular features/bug fixes are in this particular version please 21 | see the TraceEvent.RelaseNotes.txt file that is part of this package. It also contains 22 | information about breaking changes (If you use the bcl.codeplex version in the past). 23 | 24 | ************* SAMPLES ************* 25 | 26 | There is a companion NUGET package called Microsoft.Diagnostics.Tracing.TraceEvent.Samples. These 27 | are simple but well commented examples of how to use this library. To get the samples, it is best 28 | to simply create a new Console application, and then reference the Samples package from that App. 29 | The package's README.TXT file tell you how to run the samples. 30 | 31 | ************** BLOGS ************** 32 | 33 | See http://blogs.msdn.com/b/vancem/archive/tags/traceevent/ for useful blog entries on using this 34 | package. 35 | 36 | *********** QUICK STARTS *********** 37 | 38 | The quick-starts below will get you going in a minimum of typing, but please see the WELL COMMENTED 39 | samples in the Samples NUGET package that describe important background and other common scenarios. 40 | 41 | ************************************************************************************************** 42 | ******* Quick Start: Turning on the 'MyEventSource' EventSource and log to MyEventsFile.etl: 43 | 44 | using (var session = new TraceEventSession("SimpleMontitorSession", "MyEventsFile.etl")) // Sessions collect and control event providers. Here we send data to a file 45 | { 46 | var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("MyEventSource"); // Get the unique ID for the eventSouce. 47 | session.EnableProvider(eventSourceGuid); // Turn it on. 48 | Thread.Sleep(10000); // Collect for 10 seconds then stop. 49 | } 50 | 51 | ************************************************************************************************** 52 | ******** Quick Start: Reading MyEventsFile.etl file and printing the events. 53 | 54 | using (var source = new ETWTraceEventSource("MyEtlFile.etl")) // Open the file 55 | { 56 | var parser = new DynamicTraceEventParser(source); // DynamicTraceEventParser knows about EventSourceEvents 57 | parser.All += delegate(TraceEvent data) // Set up a callback for every event that prints the event 58 | { 59 | Console.WriteLine("GOT EVENT: " + data.ToString()); // Print the event. 60 | }; 61 | source.Process(); // Read the file, processing the callbacks. 62 | } // Close the file. 63 | 64 | 65 | ************************************************************************************************************* 66 | ******** Quick Start: Turning on the 'MyEventSource', get callbacks in real time (no files involved). 67 | 68 | using (var session = new TraceEventSession("MyRealTimeSession")) // Create a session to listen for events 69 | { 70 | session.Source.Dynamic.All += delegate(TraceEvent data) // Set Source (stream of events) from session. 71 | { // Get dynamic parser (knows about EventSources) 72 | // Subscribe to all EventSource events 73 | Console.WriteLine("GOT Event " + data); // Print each message as it comes in 74 | }; 75 | 76 | var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("MyEventSource"); // Get the unique ID for the eventSouce. 77 | session.EnableProvider(eventSourceGuid); // Enable MyEventSource. 78 | session.Source.Process(); // Wait for incoming events (forever). 79 | } 80 | -------------------------------------------------------------------------------- /GCVisualisation/TraceEvent.ReleaseNotes.txt: -------------------------------------------------------------------------------- 1 | Version 1.0.0.3 - Initial release to NuGet, pre-release. 2 | 3 | TraceEvent has been available from the site http://bcl.codeplex.com/wikipage?title=TraceEvent for some time now 4 | this NuGet Version of the library supersedes that one. WHile the 'core' part of the library is unchanged, 5 | we did change lesser used features, and change the namespace and DLL name, which will cause break. We anticipate 6 | it will take an hour or so to 'port' to this version from the old one. Below are specific details on what 7 | has changed to help in this port. 8 | 9 | * The DLL has been renamed from TraceEvent.dll to Microsoft.Diagnostics.Tracing.TraceEvent.dll 10 | * The name spaces for all classes have been changed. The easiest way to port is to simply place 11 | the following using clauses at the top of any file that uses TraceEvent classes 12 | using Microsoft.Diagnostics.Symbols; 13 | using Microsoft.Diagnostics.Tracing; 14 | using Microsoft.Diagnostics.Tracing.Etlx; 15 | using Microsoft.Diagnostics.Tracing.Parsers.Clr; 16 | using Microsoft.Diagnostics.Tracing.Parsers.Kernel; 17 | using Microsoft.Diagnostics.Tracing.Session; 18 | using Microsoft.Diagnostics.Tracing.Stacks; 19 | * Any method with the name RelMSec in it has been changed to be RelativeMSec. The easiest port is to 20 | simply globally rename RelMSec to RelativeMSec 21 | * Any property in the Trace* classes that has the form Max*Index has been renamed to Count. 22 | * A number of methods have been declared obsolete, these are mostly renames and the warning will tell you 23 | how to update them. 24 | * The following classes have been rename 25 | SymPath -> SymbolPath 26 | SymPathElement -> SymbolPathElement 27 | SymbolReaderFlags -> SymbolReaderOptions 28 | * TraceEventSession is now StopOnDispose (it will stop the session when TraceEventSesssion dies), by default 29 | If you were relying on the kernel session living past the process that started it, you must now set 30 | the StopOnDispose explicitly 31 | * There used to be XmlAttrib extensions methods on StringBuilder for use in manifest generated TraceEventParsers 32 | These have been moved to protected members of TraceEvent. The result is that in stead of writing 33 | sb.XmlAttrib(...) you write XmlAttrib(sb, ...) 34 | * References to Pdb in names have been replaced with 'Symbol' to conform to naming guidelines. 35 | 36 | *********************************************************************************************** 37 | Version 1.0.0.4 - Initial stable release 38 | 39 | Mostly this was insuring that the library was cleaned up in preparation 40 | for release the TraceParserGen tool 41 | 42 | Improved the docs, removed old code, fixed some naming convention stuff 43 | 44 | * Additional changes from the PreRelease copy to the first Stable release 45 | 46 | * The arguments to AddCallbackForProviderEvent were reversed!!!! (now provider than event) 47 | * The arguments to Observe(string, string)!!!! (now provider than event) 48 | * Event names for these APIs must include a / between the Task and Opcode names 49 | 50 | * Many Events in KernelTraceEventParser were harmonized to be consistent with other conventions 51 | * Events of the form PageFault* were typically renamed to Memory* 52 | * The 'End' suffix was renamed to 'Stop' (its official name) 53 | * PerfInfoSampleProf -> PerfInfoSample 54 | * PerfInfoSampleProf -> PerfInfoSample 55 | * ReadyThread -> DispatcherReadyThread 56 | * StackWalkTraceData -> StackWalkStackTraceData 57 | * FileIo -> FileIO 58 | * DiskIo -> DiskIO 59 | 60 | * Many Events in SymbolTraceEventParser were harmonized to be consistent with other conventions 61 | * names with Symbol -> ImageID 62 | -------------------------------------------------------------------------------- /GCVisualisation/_TraceEventProgrammersGuide.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwarren/GCVisualisation/aa9d2f542264c102bd293818c61aefa15b5f15eb/GCVisualisation/_TraceEventProgrammersGuide.docx -------------------------------------------------------------------------------- /GCVisualisation/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GC Visualisation 2 | 3 | Use ETW events to show what the .NET GC is doing, see [Visualising the .NET Garbage Collector](http://mattwarren.org/2016/06/20/Visualising-the-dotNET-Garbage-Collector/) for more info 4 | 5 | Sample output: 6 | ![GC Visualisation.gif](http://mattwarren.org/images/2016/06/GC%20Visualisation.gif) 7 | -------------------------------------------------------------------------------- /SampleApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SampleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Collections.Concurrent; 4 | using System.Linq; 5 | 6 | namespace SampleApp 7 | { 8 | class Program 9 | { 10 | private static byte[] staticArray; 11 | static void Main(string[] args) 12 | { 13 | GCBenchmark(); 14 | Console.WriteLine("Sample App has completed"); 15 | return; 16 | 17 | var random = new Random(12345); 18 | for (int i = 0; i < 10000; i++) 19 | { 20 | Console.WriteLine("Iteration {0,10:N0}", i); 21 | var temp = new byte[10000]; 22 | if (i % 10 == 0) 23 | staticArray = temp; 24 | Thread.Sleep(random.Next(5, 20)); 25 | //Thread.Sleep(random.Next(50, 500)); 26 | } 27 | Console.WriteLine("Sample App has completed"); 28 | } 29 | 30 | private static void GCBenchmark() 31 | { 32 | // From http://prl.ccs.neu.edu/blog/2016/05/24/measuring-gc-latencies-in-haskell-ocaml-racket/ 33 | // also see https://blog.pusher.com/latency-working-set-ghc-gc-pick-two/ 34 | // This is the Haskell code we want to replicate in C# 35 | // Is uses an immutable associative Map data structure 36 | // So we'll use the .NET ConcurrentDictionary instead 37 | 38 | // type Msg = ByteString.ByteString 39 | // type Chan = Map.Map Int Msg 40 | 41 | // windowSize = 200000 42 | // msgCount = 1000000 43 | 44 | // message::Int->Msg 45 | // message n = ByteString.replicate 1024(fromIntegral n) 46 | 47 | // pushMsg::Chan->Int->IO Chan 48 | // pushMsg chan highId = 49 | // Exception.evaluate $ 50 | // let lowId = highId - windowSize in 51 | // let inserted = Map.insert highId(message highId) chan in 52 | // if lowId < 0 then inserted 53 | // else Map.delete lowId inserted 54 | 55 | // main ::IO() 56 | // main = Monad.foldM_ pushMsg Map.empty[0..msgCount] 57 | 58 | var windowSize = 200000; 59 | var msgCount = 1000000; 60 | var map = new ConcurrentDictionary(); // could we pre-size? 61 | //var map = new ConcurrentDictionary(2, capacity: windowSize); 62 | 63 | foreach (var highId in Enumerable.Range(0, msgCount)) 64 | { 65 | var lowId = highId - windowSize; 66 | var msg = new byte[1024]; 67 | // replicate n x is a ByteString of length n with x the value of every element. 68 | byte data = (byte)(highId % 256); 69 | //for (int i = 0; i < msg.Length; i++) 70 | // msg[i] = data; 71 | MemSet(msg, data); 72 | 73 | var inserted = map.AddOrUpdate(highId, msg, (key, value) => msg); 74 | if (lowId >= 0) 75 | { 76 | byte[] removed; 77 | map.TryRemove(lowId, out removed); 78 | } 79 | } 80 | 81 | Console.WriteLine("Concurrent Dictionary contains {0:N0} items", map.Count); 82 | Thread.Sleep(2500); // So we can see the msg, before the Console closes 83 | } 84 | 85 | public static void MemSet(byte[] array, byte value) 86 | { 87 | if (array == null) 88 | { 89 | throw new ArgumentNullException("array"); 90 | } 91 | 92 | int block = 32, index = 0; 93 | int length = Math.Min(block, array.Length); 94 | 95 | //Fill the initial array 96 | while (index < length) 97 | { 98 | array[index++] = value; 99 | } 100 | 101 | length = array.Length; 102 | while (index < length) 103 | { 104 | Buffer.BlockCopy(array, 0, array, index, Math.Min(block, length - index)); 105 | index += block; 106 | block *= 2; 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SampleApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SampleApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("CA, Inc.")] 12 | [assembly: AssemblyProduct("SampleApp")] 13 | [assembly: AssemblyCopyright("Copyright © CA, Inc. 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("059e1ce3-e6ce-4fe4-95a6-c3dbb2a487fd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SampleApp/SampleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {059E1CE3-E6CE-4FE4-95A6-C3DBB2A487FD} 8 | Exe 9 | Properties 10 | SampleApp 11 | SampleApp 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | ..\packages\System.Collections.Immutable.1.2.0-rc2-24027\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64 | -------------------------------------------------------------------------------- /SampleApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------