C#: Accessing PerformanceCounters for the “.NET CL

2019-06-16 06:51发布

问题:

I'm trying to access the performance counters located in ".NET CLR Memory category" through C# using the PerformanceCounter class. However a cannot instantiate the categories with what I would expect was the correct category/counter name

new PerformanceCounter(".NET CLR Memory", "# bytes in all heaps", Process.GetCurrentProcess().ProcessName);

I tried looping through categories and counters using the following code

string[] categories = PerformanceCounterCategory.GetCategories().Select(c => c.CategoryName).OrderBy(s => s).ToArray();
string toInspect = string.Join(",\r\n", categories);

System.Text.StringBuilder interestingToInspect = new System.Text.StringBuilder();
string[] interestingCategories = categories.Where(s => s.StartsWith(".NET") || s.Contains("Memory")).ToArray();
foreach (string interestingCategory in interestingCategories)
{
    PerformanceCounterCategory cat = new PerformanceCounterCategory(interestingCategory);
    foreach (PerformanceCounter counter in cat.GetCounters())
    {
        interestingToInspect.AppendLine(interestingCategory + ":" + counter.CounterName);
    }
}
toInspect = interestingToInspect.ToString();

But could not find anything that seems to match. Is it not possible to observe these values from within the CLR or am I doing something wrong.

The environment, should it matter, is .NET 4.0 running on a 64-bit windows 7 box.

回答1:

You need to run the program as Administrator to access .NET CLR Memory category.

Try this simple test, using powershell:

When running as Administrator

[Diagnostics.PerformanceCounterCategory]::Exists(".NET CLR Memory")

True

When running without administrative privileges:

[Diagnostics.PerformanceCounterCategory]::Exists(".NET CLR Memory")

False



回答2:

It should work. Note that as others have already set, the CLR counters are per instance counters, thus you need to specify the instance name for the process you wish to query the counters for.

So the statement you specified at the top of your post should work. However, you should also use the constructor overload that allows you to specify that you wish to access the instance in "read-only" mode:

new PerformanceCounter(".NET CLR Memory", "# bytes in all heaps", Process.GetCurrentProcess().ProcessName, true);

The second code fragment you posted does not work, as you didn't specify an instance name to the GetCounters() operation. Use the GetCounters(string instanceName) overload instead and it should work.

Finally, note that the instance name is not necessarily the same as Process.ProcessName (or Process.GetCurrentProcess().ProcessName for that matter). If there are multiple instances of a process, i.e. executable, the process name is created by appending a #<number>. To figure out the actual instance name of a process you should query the .NET CLR Memory\Process ID counter.

Example:

    public static string GetInstanceNameForProcessId(int pid)
    {
        var cat = new PerformanceCounterCategory(".NET CLR Memory");
        foreach (var instanceName in cat.GetInstanceNames())
        {
            try
            {
                 using (var pcPid = new PerformanceCounter(cat.CategoryName, "Process ID", instanceName))
                 {
                     if ((int)pcPid.NextValue() == pid)
                     {
                         return instanceName;
                     }
                 }
            }
            catch (InvalidOperationException)
            {
                // This may happen, if the PC-instance no longer exists between the
                // time we called GetInstanceNames() and the time we come around actually
                // try and use the instance. 
                // In this situation that is not an error, so ignore it.
            }
        }

        throw new ArgumentException(
            string.Format("No performance counter instance found for process id '{0}'", pid),
            "pid");
    }

The instance name you gather by this method is good for performance counters in other ".NET CLR" categories as well.

Update: Added mitigation for potential race condition between the time we gather the potential instance names and the time we peruse them. A .NET process (for which we've seen an instance name) might no longer be there (and such the instance is also gone) when we try to use it.



回答3:

Counters are per instance. Try adding:

foreach (var instance in cat.GetInstanceNames())
{
    foreach (PerformanceCounter counter in cat.GetCounters(instance))
    {
        interestingToInspect.AppendLine(interestingCategory + ":" + counter.CounterName); 
    } 
}