Get PerformanceCounter by Index

安稳与你 提交于 2019-12-22 04:14:20

问题


I want to access the "Processor Time %" counter in an application which runs on systems with different localizations.

To do so, I want to access the counter by its index, which is guaranteed to be unique (see https://support.microsoft.com/en-us/kb/287159).

The following code works and gives me the correct result for the current locale, but to open the performance counter I also need the counter's category name (see constructors for the PerformanceCounter class) as well as the instance name:

[DllImport("pdh.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern UInt32 PdhLookupPerfNameByIndex(string szMachineName, uint dwNameIndex, StringBuilder szNameBuffer, ref uint pcchNameBufferSize); 

void Main()
{
    var buffer = new StringBuilder(1024);
    var bufSize = (uint)buffer.Capacity;
    PdhLookupPerfNameByIndex(null, 6, buffer, ref bufSize);
    Console.WriteLine(buffer.ToString());

    var counter = new PerformanceCounter(/* category??? */, buffer.ToString(), /* instance??? */);
}

How can I get that category and instance name?

See also: Retrieve performance counter value in a language-independent way, which describes the same problem but does not provide a solution.


回答1:


You misinterpret how PdhLookupPerfNameByIndex() works. Its job is not to map a performance counter but to map a string. It should be used both for the counter's category as well as its name. Not for the counter's instance, if applicable, it is not localized.

Best way to see what it does is by using Regedit.exe. Navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib. Note the "009" key, its Counter value has the index to English string mapping. Double-click Counter and copy-paste the content of the box into a text editor to have a better look-see. The "CurrentLanguage" key is the same mapping but uses the localized names.

So PdhLookupPerfNameByIndex() uses the CurrentLanguage key, use the list you obtained in the previous step to know the index number of the string. The other way to do it as noted (confusingly) at the bottom of the KB article is by first looking up the index number from the "009" registry key. This lets you translate from the English string to the localized string. Do note that the KB article documents the registry key location wrong, no idea why.

Keep in mind that it is less than perfect, as pointed out in the KB article, these mappings only exist for the "base" counters and the "009" key is ambiguous because some indices map to the same string. Testing on a localized Windows version is very important.

Some code that does it both ways:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class PerfMapper {
    private static Dictionary<string, int> English;
    private static Dictionary<int, string> Localized;

    public static PerformanceCounter FromEnglish(string category, string name, string instance = null) {
        return new PerformanceCounter(Map(category), Map(name), instance);
    }

    public static PerformanceCounter FromIndices(int category, int name, string instance = null) {
        return new PerformanceCounter(PdhMap(category), PdhMap(name), instance);
    }

    public static bool HasName(string name) {
        if (English == null) LoadNames();
        if (!English.ContainsKey(name)) return false;
        var index = English[name];
        return !Localized.ContainsKey(index);
    }

    public static string Map(string text) {
        if (HasName(text)) return Localized[English[text]];
        else return text;
    }

    private static string PdhMap(int index) {
        int size = 0;
        uint ret = PdhLookupPerfNameByIndex(null, index, null, ref size);
        if (ret == 0x800007D2) {
            var buffer = new StringBuilder(size);
            ret = PdhLookupPerfNameByIndex(null, index, buffer, ref size);
            if (ret == 0) return buffer.ToString();
        }
        throw new System.ComponentModel.Win32Exception((int)ret, "PDH lookup failed");
    }

    private static void LoadNames() {
        string[] english;
        string[] local;
        // Retrieve English and localized strings
        using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) {
            using (var key = hklm.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009")) {
                english = (string[])key.GetValue("Counter");
            }
            using (var key = hklm.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage")) {
                local = (string[])key.GetValue("Counter");
            }
        }
        // Create English lookup table
        English = new Dictionary<string, int>(english.Length / 2, StringComparer.InvariantCultureIgnoreCase);
        for (int ix = 0; ix < english.Length - 1; ix += 2) {
            int index = int.Parse(english[ix]);
            if (!English.ContainsKey(english[ix + 1])) English.Add(english[ix + 1], index);
        }
        // Create localized lookup table
        Localized = new Dictionary<int, string>(local.Length / 2);
        for (int ix = 0; ix < local.Length - 1; ix += 2) {
            int index = int.Parse(local[ix]);
            Localized.Add(index, local[ix + 1]);
        }
    }

    [DllImport("pdh.dll", CharSet = CharSet.Auto)]
    private static extern uint PdhLookupPerfNameByIndex(string machine, int index, StringBuilder buffer, ref int bufsize);
}

Sample usage:

class Program {
    static void Main(string[] args) {
        var ctr1 = PerfMapper.FromEnglish("Processor", "% Processor Time");
        var ctr2 = PerfMapper.FromIndices(238, 6);
    }
}

I only have access to an English version of Windows so can't vouch for accuracy on a localized version. Please correct any bugs you encounter by editing this post.




回答2:


Try this one:

var counter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); 

It works for me on the PC with german localization.

UPDATE

This is an example you could use to understand how these categories, instances, and counters are organized. Don't forget to check Performance Monitor under Administrative Tools in the Control panel where you can add a counter or find existing counters.

string counterName = buffer.ToString();
PerformanceCounter counter = null;            
foreach (var category in PerformanceCounterCategory.GetCategories())
{
    // Get all possible instances for the current category
    var instanceNames = category.GetInstanceNames();
    if (instanceNames.Length == 0)
        continue;
    // Get all counters in the category. 
    // We want to find an instance with underscores first, for example, "_Total"             
    var counters = category.GetCounters(
        category.GetInstanceNames().OrderBy(i => i).First());
    foreach (var currentCounter in counters)
    {                                        
        if (currentCounter.CounterName == counterName)
        {
            // Hurray! Here it is!
            counter = currentCounter;
        }
    }
}


来源:https://stackoverflow.com/questions/33804402/get-performancecounter-by-index

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!