Get PerformanceCounter by Index

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

To do this, 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 name of the counter category (see the 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 this category and instance name?

See also: Get the value of a performance counter in a language-neutral way that describes the same problem but does not provide a solution.

+5
source share
2 answers

You misunderstand how PdhLookupPerfNameByIndex () works. Its task is not to match the performance counter, but to display a string. It should be used for both the category of the counter and its name. Not for the counter instance, if applicable, it is not localized.

The best way to see what it does is to use Regedit.exe. Go to HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Perflib. Pay attention to the key "009", its value "Counter" has an index for matching strings in English. Double-click "Counter" and copy the contents of the window into a text editor to see better. The key "CurrentLanguage" is the same mapping, but uses localized names.

So, PdhLookupPerfNameByIndex () uses the CurrentLanguage key, use the list obtained in the previous step to find the row index number. Another way to do this, as indicated (in confusion) at the bottom of the KB article, is to first look at the index number from the registry key "009". This allows you to translate from an English string to a localized string. Please note that the database location does not indicate the location of the registry, I do not know why.

Keep in mind that it is less perfect, as indicated in the KB article, these mappings exist only for “base” counters, and the key “009” is ambiguous because some indices refer to the same row. Testing on a localized version of Windows is very important.

Some code that does this in both directions:

 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 the English version of Windows, so I can not vouch for the accuracy in the localized version. Please correct all errors that you encounter by editing this message.

+6
source

Try the following:

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

It works for me on a PC with German localization.

UPDATE

This is an example that you could use to understand how these categories, instances, and counters are organized. Remember to check the performance monitor in the Administration section of 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; } } } 
0
source

Source: https://habr.com/ru/post/1236325/


All Articles