按索引获取PerformanceCounter

tho*_*mai 8 c# winapi perfmon

我想访问在具有不同本地化的系统上运行的应用程序中的"处理器时间%"计数器.

为此,我想通过其索引访问计数器,该索引保证是唯一的(请参阅https://support.microsoft.com/en-us/kb/287159).

下面的代码工作并为我提供了当前语言环境的正确结果,但是要打开性能计数器,我还需要计数器的类别名称(请参阅PerformanceCounter类的构造函数)以及实例名称:

[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??? */);
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能获得该类别和实例名称?

另请参阅: 以与语言无关的方式检索性能计数器值,该方法描述了相同的问题,但未提供解决方案.

Han*_*ant 8

你误解了PdhLookupPerfNameByIndex()的工作原理.它的工作不是映射性能计数器,而是映射字符串.它既可以用于计数器的类别,也可以用于名称.不适用于计数器的实例,如果适用,则不会进行本地化.

查看它的作用的最佳方法是使用Regedit.exe.导航到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib.注意"009"键,其Counter值具有英文字符串映射的索引.双击计数器并将框的内容复制粘贴到文本编辑器中以获得更好的外观."CurrentLanguage"键是相同的映射,但使用本地化的名称.

所以PdhLookupPerfNameByIndex()使用CurrentLanguage键,使用您在上一步中获得的列表来知道字符串的索引号.另一种在知识库文章底部注意到的(令人困惑的)方法是先从"009"注册表项中查找索引号.这使您可以从英语字符串转换为本地化字符串.请注意,知识库文章记录了错误的注册表项位置,不明白为什么.

请记住,它不是完美的,正如知识库文章中指出的那样,这些映射仅存在于"基本"计数器中,而"009"键是不明确的,因为一些索引映射到相同的字符串.在本地化Windows版本上进行测试非常重要.

一些代码可以两种方式完成:

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);
}
Run Code Online (Sandbox Code Playgroud)

样品用法:

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

我只能访问英文版的Windows,因此无法保证本地化版本的准确性.请通过编辑此帖来更正您遇到的任何错误.