├── .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 | 
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 |
--------------------------------------------------------------------------------