Why is obtaining Process related data using Perfor

2019-09-09 00:45发布

问题:

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?