I have an assembly which monitors the performance statistics of a number of our micro services running on a machine. The code is using performance counters to obtain and publish the data as below:
_counters = new Dictionary<string, PerformanceCounter>
{
["System Memory Available Bytes"] = new PerformanceCounter { CategoryName = "Memory", CounterName = "Available Bytes", ReadOnly = true },
["System CPU Usage %"] = new PerformanceCounter { CategoryName = "Processor", CounterName = "% Processor Time", InstanceName = "_Total", ReadOnly = true },
["Process CPU Usage %"] = new PerformanceCounter { CategoryName = "Process", CounterName = "% Processor Time", InstanceName = processName, ReadOnly = true },
["Process Thread Count"] = new PerformanceCounter { CategoryName = "Process", CounterName = "Thread Count", InstanceName = processName, ReadOnly = true },
["Process Private Bytes"] = new PerformanceCounter { CategoryName = "Process", CounterName = "Private Bytes", InstanceName = processName, ReadOnly = true },
["Process Working Set Bytes"] = new PerformanceCounter { CategoryName = "Process", CounterName = "Working Set", InstanceName = processName, ReadOnly = true },
["Process Virtual Bytes"] = new PerformanceCounter { CategoryName = "Process", CounterName = "Virtual Bytes", InstanceName = processName, ReadOnly = true },
["Contention Rate/Sec"] = new PerformanceCounter { CategoryName = ".NET CLR LocksAndThreads", CounterName = "Contention Rate / Sec", InstanceName = processName, ReadOnly = true },
["Contention Queue Peak Size"] = new PerformanceCounter { CategoryName = ".NET CLR LocksAndThreads", CounterName = "Queue Length Peak", InstanceName = processName, ReadOnly = true },
["Contention Total Count"] = new PerformanceCounter { CategoryName = ".NET CLR LocksAndThreads", CounterName = "Total # of Contentions", InstanceName = processName, ReadOnly = true },
["Induced GC Count"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "# Induced GC", InstanceName = processName, ReadOnly = true },
["GC Handle Count"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "# GC Handles", InstanceName = processName, ReadOnly = true },
["Finalization Survivors"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "Finalization Survivors", InstanceName = processName, ReadOnly = true },
["LOB Size Bytes"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "Large Object Heap size", InstanceName = processName, ReadOnly = true },
["Total Bytes in all Heaps"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "# Bytes in all Heaps", InstanceName = processName, ReadOnly = true },
["Total Bytes Committed"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "# Total committed Bytes", InstanceName = processName, ReadOnly = true },
["Total Bytes Reserved"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "# Total reserved Bytes", InstanceName = processName, ReadOnly = true },
["Time in GC %"] = new PerformanceCounter { CategoryName = ".NET CLR Memory", CounterName = "% Time in GC", InstanceName = processName, ReadOnly = true },
["Time in JIT %"] = new PerformanceCounter { CategoryName = ".NET CLR Jit", CounterName = "% Time in Jit", InstanceName = processName, ReadOnly = true },
};
// Make sure we read each counter once, this is to set the sample start for
// some of the counters that need a start, end invocation to return a value.
foreach (var counter in _counters.Values) { counter.NextValue(); }
I am then starting a timer which at every 1 second publishes the data (I am only storing them in a variable here for brevity):
_timer = new Timer(1000);
_timer.Elapsed += OnTimerElapsed;
_timer.Start();
private void OnTimerElapsed(object sender, ElapsedEventArgs eArgs)
{
var a = _counters["System CPU Usage %"].NextValue();
var b = _counters["System Memory Available Bytes"].NextValue();
//var c = _counters["Process CPU Usage %"].NextValue()/Environment.ProcessorCount;
//var d = _counters["Process Thread Count"].NextValue();
//var e = _counters["Process Private Bytes"].NextValue();
//var f = _counters["Process Working Set Bytes"].NextValue();
//var g = _counters["Process Virtual Bytes"].NextValue();
var h = _counters["Contention Rate/Sec"].NextValue();
var i = _counters["Contention Queue Peak Size"].NextValue();
var j = _counters["Contention Total Count"].NextValue();
var k = _counters["Induced GC Count"].NextValue();
var l = _counters["GC Handle Count"].NextValue();
var m = _counters["Finalization Survivors"].NextValue();
var n = _counters["LOB Size Bytes"].NextValue();
var o = _counters["Total Bytes in all Heaps"].NextValue();
var p = _counters["Total Bytes Committed"].NextValue();
var q = _counters["Total Bytes Reserved"].NextValue();
var r = _counters["Time in GC %"].NextValue();
var s = _counters["Time in JIT %"].NextValue();
}
Running the assembly with the commented counters results in expected GC
behaviour with only gen 0
collections every few seconds. However if I uncomment any of the counters in c
to g
I then get a full (gen 0
to 2
) collection every 1 second. I cannot explain this behaviour, what is going on?