1

调用 UpdatePerformanceCounters 时:在此更新程序中,类别和实例计数器的所有计数器名称都是相同的 - 它们总是从 Enum 派生的。更新程序被传递一个“配置文件”,通常包含以下内容:

{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

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

VerifyCounterExistence 方法的 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 属性(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 个字符的子字符串 - 以防万一,以及一个普通的“测试”字符串都无济于事。

变化...

除了 Enum 和 ProcessCounterCollection 我已经用以下内容替换了类主体:

    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));
        }
    }

在 PerformanceCounter 配置文件中,我添加了:

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

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

ETW EventSource 现在对“预定义”性能类别进行初始化,同时还创建一个名为“Total”的实例。

    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 中我仍然看不到任何实例 - 即使是“Total”实例和更新,显然也总是运行没有错误。

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

4

1 回答 1

1

这些是结论和“答案”,据我所知,尽我所能,我相信正在发生的事情并由我自己发布 - 鉴于我最近对 ​​Stack Overflow 的有用使用,我希望这将有助于其他...

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

这是我的“问题”的症结所在——或者更确切地说,是我对架构的不完整理解:实例计数器是瞬态的——实例在程序/过程终止后不会持续存在。一旦我意识到这一点,这是“显而易见的” - 例如,尝试使用 PerfMon 查看您的 IIS AppPool 之一的实例计数器 - 然后停止 AppPool,您将在 PerfMon 中看到已停止的实例AppPool 不再可见。

鉴于这个关于实例计数器的公理,上面的代码还有另一个完全不相关的部分:当尝试使用 UpdateDefinedInstanceCounterDictionary 方法时,从现有计数器集中分配列表是没有意义的。首先,显示的“else”代码将失败,因为我们试图返回此方法不起作用的(实例)计数器集合,其次,GetCategories() 后跟 GetCounters() 或/和 GetInstanceNames() 是一个非常昂贵和耗时的过程——即使它可以工作。使用的适当方法是前面提到的方法 - PerformanceCounterCategory(categoryKey).ReadCategory()。但是,这会返回一个 InstanceDataCollectionCollection,它实际上是只读的,因此,作为计数器的提供者(而不是消费者),它是没有意义的。事实上,它不

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

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

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

于 2013-10-17T15:55:45.997 回答