Счетчик производительности - Экземпляры; Создать / обновить без ошибок, но не видно в PerfMon

При вызове 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

Обратите внимание на "сложность" имени экземпляра.

Метод toUpdate.AddRange() метода VerifyCounterExistence всегда завершается успешно и выдает "ожидаемый" вывод, поэтому метод UpdatePerformanceCounters продолжается до "успешного" приращения счетчиков.

Несмотря на "catch", это никогда не "завершается сбоем" - за исключением того, что при просмотре Category в PerfMon он не показывает ни одного экземпляра или, следовательно, любого "успешного" обновления счетчика экземпляров.

Я подозреваю, что моя проблема может быть в том, что мое имя экземпляра отклоняется без исключения из-за его "сложности" - когда я запускаю его через консольный тестер через PerfView, он не показывает никакого стека исключений, и события ETW, связанные с обновлениями счетчика, успешно записано в приемнике вне процесса. Кроме того, в журналах Windows нет записей.

Все это выполняется "локально" через VS2012 на сервере Windows 2008R2 с NET 4.5.

У кого-нибудь есть идеи, как еще я могу попробовать это - или даже проверить, принимается ли "обновление" 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" и обновление всегда, по-видимому, выполняются без ошибок.

Я не знаю, что еще я могу "изменить - возможно," слишком близко "к проблеме и был бы признателен за комментарии / исправления.

1 ответ

Это выводы и "ответ" в той мере, в какой это объясняет, насколько я могу, что я думаю, что происходит и опубликовано мной - учитывая мое недавнее полезное использование переполнения стека, это, я надеюсь, будет полезно для другие...

Во-первых, в отображаемом коде нет ничего плохого, кроме одной оговорки, упомянутой ниже. Помещение Console.ReadKey() перед завершением программы и после выполнения PerformanceCounterCategory(categoryKey).ReadCategory() совершенно ясно, что не только правильные записи реестра (для этого ReadCategory получает свои результаты), но и счетчики экземпляров все были увеличены на соответствующие значения. Если посмотреть на PerfMon до завершения программы, то счетчики экземпляров уже есть и они содержат соответствующие необработанные значения.

В этом суть моей "проблемы" - или, скорее, моего неполного понимания архитектуры: СЧЕТЧИКИ ИНСТАНЦИИ НЕ ВРЕМЕННЫ - НЕ ПРИМЕРЯЮТСЯ ЗА ПРЕКРАЩЕНИЕМ ПРОГРАММЫ / ПРОЦЕССА. Это, как только меня осенило, становится "очевидным" - например, попробуйте использовать PerfMon для просмотра счетчика экземпляров одного из ваших IIS AppPools - затем остановите AppPool, и вы увидите, в PerfMon, что экземпляр для остановленного AppPool больше не виден.

Учитывая эту аксиому о счетчиках экземпляров, приведенный выше код имеет еще один совершенно не относящийся к делу раздел: при попытке использования метода UpdateDefinedInstanceCounterDictionary назначение списка из существующего набора счетчиков не имеет смысла. Во-первых, показанный код "else" потерпит неудачу, так как мы пытаемся вернуть коллекцию (экземпляров) счетчиков, для которых этот подход не будет работать, и, во-вторых, GetCategories(), за которым следует GetCounters() или / и GetInstanceNames(), чрезвычайно дорогой и трудоемкий процесс - даже если бы он работал. Подходящий метод для использования - упомянутый ранее - PerformanceCounterCategory(categoryKey).ReadCategory(). Однако это возвращает InstanceDataCollectionCollection, которая фактически доступна только для чтения, так что в качестве поставщика (в отличие от потребителя) счетчиков это бессмысленно. Фактически, это не имеет значения, если вы просто используете сгенерированный Enum новый список PerformanceCounter - он работает независимо от того, существуют ли счетчики или нет.

В любом случае, InstanceDataCollectionCollection (это, по сути, то, что демонстрирует Win32 SDK для.Net 3.5 "Образец счетчика пользовательского режима"), использует счетчик "Образец", который заполняется и возвращается - согласно использованию пространства имен System.Diagnostics.PerformanceData. whichi выглядит как часть использования версии 2.0 - это использование "несовместимо" с показанным использованием System.Diagnostics.PerformanceCounterCategory.

По общему признанию, факт непостоянства может показаться очевидным и вполне может быть заявлен в документации, но, если бы я прочитал всю документацию обо всем, что мне нужно использовать заранее, я, вероятно, в конечном итоге фактически не написал бы никакого кода! Более того, даже если бы такую ​​подходящую документацию было легко найти (в отличие от опыта, размещенного, например, в Stack Overflow), я не уверен, что доверяю всему этому. Например, я отметил выше, что имя экземпляра в документации MSDN имеет ограничение в 128 символов - неправильно; на самом деле это 127, так как основная строка должна заканчиваться нулем. Кроме того, например, для ETW, мне бы хотелось, чтобы стало более очевидно, что значения ключевых слов должны быть степенями 2, а коды операций со значением меньше 12 используются системой - по крайней мере, PerfView смог показать мне это.

В конечном счете, у этого вопроса нет "ответа", кроме лучшего понимания счетчиков экземпляров - особенно их постоянства. Поскольку мой код предназначен для использования в веб-API на основе службы Windows, его постоянство не является проблемой (особенно при ежедневном использовании LogMan и т. Д.) - сбивает с толку то, что эти чертовы вещи не появлялись, пока я не приостановил код и проверил PerfMon, и я мог бы сэкономить много времени и хлопот, если бы знал об этом заранее. В любом случае мой источник событий ETW регистрирует все истекшие времена выполнения и экземпляры того, что счетчики производительности "отслеживают" в любом случае.

Другие вопросы по тегам