性能计数器 - 实例;创建/更新没有错误,但在 PerfMon 中不可见

本文关键字:PerfMon 但在 有错误 实例 创建 更新 性能计数器 | 更新日期: 2023-09-27 18:35:24

调用更新性能计数器时:在此更新程序中,类别和实例计数器的所有计数器名称都是相同的 - 它们始终派生自枚举。更新程序将传递一个"配置文件",通常包含以下内容:

{saTrilogy.Core.Instrumentation.PerformanceCounterProfile}
    _disposed: false
    CategoryDescription: "Timed function for a Data Access process"
    CategoryName: "saTrilogy<Core> DataAccess Span"
    Duration: 405414
    EndTicks: 212442328815
    InstanceName: "saTrilogy.Core.DataAccess.ParameterCatalogue..ctor::[dbo].[sp_KernelProcedures]"
    LogFormattedEntry: "{'"CategoryName'":'"saTrilogy<Core> DataAccess ... 
    StartTicks: 212441923401

请注意实例名称的"复杂性"。

验证计数器存在方法的 toUpdate.AddRange() 始终成功并生成"预期"输出,因此 UpdatePerformanceCounters 方法继续到计数器的"成功"递增。

尽管有"捕获",但这永远不会"失败" - 除了在 PerfMon 中查看类别时,它不显示任何实例,因此不显示实例计数器的任何"成功"更新。

怀疑我的问题可能是我的实例名称被拒绝了,无一例外,因为它的"复杂性" - 当我通过 PerfView 通过控制台测试器运行它时,它不会显示任何异常堆栈,并且与计数器更新关联的 ETW 事件已成功记录在进程外接收器中。此外,Windows 日志中没有条目。

这一切都是通过VS2012在带有NET 4.5的Windows 2008R2服务器上"本地"运行的。

有没有人对我如何尝试这个有任何想法 - 甚至测试PerfMon是否接受"更新"?

public sealed class Performance {
    private enum ProcessCounterNames {
        [Description("Total Process Invocation Count")]
        TotalProcessInvocationCount,
        [Description("Average Process Invocation Rate per second")]
        AverageProcessInvocationRate,
        [Description("Average Duration per Process Invocation")]
        AverageProcessInvocationDuration,
        [Description("Average Time per Process Invocation - Base")]
        AverageProcessTimeBase
        }
    private readonly static CounterCreationDataCollection ProcessCounterCollection = new CounterCreationDataCollection{
                new CounterCreationData(
                    Enum<ProcessCounterNames>.GetName(ProcessCounterNames.TotalProcessInvocationCount),
                    Enum<ProcessCounterNames>.GetDescription(ProcessCounterNames.TotalProcessInvocationCount),
                    PerformanceCounterType.NumberOfItems32),
                new CounterCreationData(
                    Enum<ProcessCounterNames>.GetName(ProcessCounterNames.AverageProcessInvocationRate),
                    Enum<ProcessCounterNames>.GetDescription(ProcessCounterNames.AverageProcessInvocationRate),
                    PerformanceCounterType.RateOfCountsPerSecond32),
                new CounterCreationData(
                    Enum<ProcessCounterNames>.GetName(ProcessCounterNames.AverageProcessInvocationDuration),
                    Enum<ProcessCounterNames>.GetDescription(ProcessCounterNames.AverageProcessInvocationDuration),
                    PerformanceCounterType.AverageTimer32),
                new CounterCreationData(
                    Enum<ProcessCounterNames>.GetName(ProcessCounterNames.AverageProcessTimeBase),
                    Enum<ProcessCounterNames>.GetDescription(ProcessCounterNames.AverageProcessTimeBase),
                    PerformanceCounterType.AverageBase),
            };
    private static bool VerifyCounterExistence(PerformanceCounterProfile profile, out List<PerformanceCounter> toUpdate) {
        toUpdate = new List<PerformanceCounter>();
        bool willUpdate = true;
        try {
            if (!PerformanceCounterCategory.Exists(profile.CategoryName)) {
                PerformanceCounterCategory.Create(profile.CategoryName, profile.CategoryDescription, PerformanceCounterCategoryType.MultiInstance, ProcessCounterCollection);
                }
            toUpdate.AddRange(Enum<ProcessCounterNames>.GetNames().Select(counterName => new PerformanceCounter(profile.CategoryName, counterName, profile.InstanceName, false) { MachineName = "." }));
            }
        catch (Exception error) {
            Kernel.Log.Trace(Reflector.ResolveCaller<Performance>(), EventSourceMethods.Kernel_Error, new PacketUpdater {
                Message = StandardMessage.PerformanceCounterError,
                Data = new Dictionary<string, object> { { "Instance", profile.LogFormattedEntry } },
                Error = error
            });
            willUpdate = false;
            }
        return willUpdate;
        }
    public static void UpdatePerformanceCounters(PerformanceCounterProfile profile) {
        List<PerformanceCounter> toUpdate;
        if (profile.Duration <= 0 || !VerifyCounterExistence(profile, out toUpdate)) {
            return;
            }
        foreach (PerformanceCounter counter in toUpdate) {
            if (Equals(PerformanceCounterType.RateOfCountsPerSecond32, counter.CounterType)) {
                counter.IncrementBy(profile.Duration);
                }
            else {
                counter.Increment();
                }
            }
        }
    }

来自 MSDN .Net 4.5 PerformanceCounter.InstanceName Property (http://msdn.microsoft.com/en-us/library/system.diagnostics.performancecounter.instancename.aspx)...

Note: Instance names must be shorter than 128 characters in length.
Note: Do not use the characters "(", ")", "#", "'", or "/" in the instance name. If any of these characters are used, the Performance Console (see Runtime Profiling) may not correctly display the instance values.

我上面使用的 79 个字符的实例名称满足这些条件,因此,除非"."、":"、"["和"]"也被"保留",否则名称似乎不是问题所在。我还尝试了实例名称的 64 个字符的子字符串 - 以防万一,以及一个普通的"测试"字符串都无济于事。

变化。。。

除了枚举和进程计数器集合之外,我用以下内容替换了类主体:

    private static readonly Dictionary<string, List<PerformanceCounter>> definedInstanceCounters = new Dictionary<string, List<PerformanceCounter>>();
    private static void UpdateDefinedInstanceCounterDictionary(string dictionaryKey, string categoryName, string instanceName = null) {
        definedInstanceCounters.Add(
            dictionaryKey,
            !PerformanceCounterCategory.InstanceExists(instanceName ?? "Total", categoryName)
                ? Enum<ProcessCounterNames>.GetNames().Select(counterName => new PerformanceCounter(categoryName, counterName, instanceName ?? "Total", false) { RawValue = 0, MachineName = "." }).ToList()
                : PerformanceCounterCategory.GetCategories().First(category => category.CategoryName == categoryName).GetCounters().Where(counter => counter.InstanceName == (instanceName ?? "Total")).ToList());
        }

    public static void InitialisationCategoryVerify(IReadOnlyCollection<PerformanceCounterProfile> etwProfiles){
        foreach (PerformanceCounterProfile profile in etwProfiles){
            if (!PerformanceCounterCategory.Exists(profile.CategoryName)){
                PerformanceCounterCategory.Create(profile.CategoryName, profile.CategoryDescription, PerformanceCounterCategoryType.MultiInstance, ProcessCounterCollection);
            }
            UpdateDefinedInstanceCounterDictionary(profile.DictionaryKey, profile.CategoryName);
        }
    }
    public static void UpdatePerformanceCounters(PerformanceCounterProfile profile) {
        if (!definedInstanceCounters.ContainsKey(profile.DictionaryKey)) {
            UpdateDefinedInstanceCounterDictionary(profile.DictionaryKey, profile.CategoryName, profile.InstanceName);
            }
        definedInstanceCounters[profile.DictionaryKey].ForEach(c => c.IncrementBy(c.CounterType == PerformanceCounterType.AverageTimer32 ? profile.Duration : 1));
        definedInstanceCounters[profile.TotalInstanceKey].ForEach(c => c.IncrementBy(c.CounterType == PerformanceCounterType.AverageTimer32 ? profile.Duration : 1));
        }
    }

在性能计数器配置文件中,我添加了:

    internal string DictionaryKey {
        get {
            return String.Concat(CategoryName, " - ", InstanceName ?? "Total");
            }
        }
    internal string TotalInstanceKey {
        get {
            return String.Concat(CategoryName, " - Total");
            }
        }

ETW 事件源现在执行"预定义"性能类别的初始化,同时还创建一个名为"总计"的实例。

    PerformanceCategoryProfile = Enum<EventSourceMethods>.GetValues().ToDictionary(esm => esm, esm => new PerformanceCounterProfile(String.Concat("saTrilogy<Core> ", Enum<EventSourceMethods>.GetName(esm).Replace("_", " ")), Enum<EventSourceMethods>.GetDescription(esm)));
    Performance.InitialisationCategoryVerify(PerformanceCategoryProfile.Values.Where(v => !v.CategoryName.EndsWith("Trace")).ToArray());

正如预期的那样,这将创建所有类别,但在 PerfMon 中我仍然看不到任何实例 - 即使是"总计"实例和更新也总是,显然,运行没有错误。

我不知道我还能"改变什么 - 可能"太接近"问题,并希望评论/更正。

性能计数器 - 实例;创建/更新没有错误,但在 PerfMon 中不可见

这些是结论和"答案",尽我所能解释了我认为正在发生的事情和我自己发布的内容 - 鉴于我最近对 Stack Overflow 的有用使用,我希望这对其他人有用......

首先,显示的代码基本上没有任何问题,除了一个但书 - 稍后提到。在程序终止之前和完成 PerformanceCounterCategory(categoryKey) 之后放置 Console.ReadKey()。ReadCategory() 很明显,不仅注册表项是正确的(因为这是 ReadCategory 获取其结果的地方),而且实例计数器都增加了适当的值。如果在程序终止之前查看 PerfMon,则实例计数器就在那里,并且它们确实包含适当的原始值。

这是我"问题"的症结所在 - 或者更确切地说,我对架构的不完全理解: 实例计数器是暂时的 - 实例不会在程序/进程终止后持久化。一旦我意识到这一点,这是"显而易见的" - 例如,尝试使用 PerfMon 查看其中一个 IIS 应用程序池的实例计数器 - 然后停止 AppPool,您将看到,在 PerfMon 中,已停止的应用程序池的实例不再可见。

鉴于这个关于实例计数器的公理,上面的代码还有另一个完全不相关的部分:当尝试方法UpdateDefinedInstanceCounterDictionary时,从现有计数器集中分配列表是没有意义的。首先,显示的"else"代码将失败,因为我们试图返回一个(实例)计数器的集合,这种方法不起作用,其次,GetCategories()后跟GetCounters()或/和GetInstanceNames()是一个非常昂贵和耗时的过程 - 即使它工作。要使用的适当方法是前面提到的方法 - 性能计数器类别(类别键)。ReadCategory()。但是,这将返回一个实际上是只读的 InstanceDataCollectionCollection,因此,作为计数器的提供者(而不是使用者),它是没有意义的。事实上,如果你只使用枚举生成的新的性能计数器列表并不重要 - 无论计数器是否已经存在,它都可以工作。

无论如何,InstanceDataCollectionCollection(这本质上是由 Win32 SDK for .Net 3.5"用户模式计数器示例"演示的)使用填充并返回的"样本"计数器 - 根据 System.Diagnostics.PerformanceData 命名空间的用法,它看起来像版本 2.0 用法的一部分 - 该用法与显示的 System.Diagnostics.PerformanceCounterCategory 用法"不兼容"。

诚然,非持久性的事实可能看起来很明显,并且很可能在文档中说明,但是,如果我事先阅读有关我需要使用的所有内容的所有文档,我可能最终不会真正编写任何代码!此外,即使这些相关文档很容易找到(而不是发布在例如Stack Overflow上的经验),我不确定我是否信任所有这些文档。例如,我在上面指出,MSDN 文档中的实例名称有 128 个字符的限制 - 错误;它实际上是 127,因为基础字符串必须以 null 结尾。此外,例如,对于 ETW,我希望关键字值必须是 2 的幂并且系统使用值小于 12 的操作码更加明显 - 至少 PerfView 能够向我展示这一点。

最终,这个问题除了更好地理解实例计数器之外没有"答案"——尤其是它们的持久性。由于我的代码旨在用于基于 Windows 服务的 Web API,因此它的持久性不是问题(尤其是在日常使用 LogMan 等时) - 令人困惑的是,直到我暂停代码并检查 PerfMon 之前,该死的东西才出现,如果我事先知道这一点,我可以为自己节省很多时间和麻烦。在任何情况下,我的 ETW 事件源都会记录所有经过的执行时间和性能计数器"监视"的实例。

相关文章: