Compteur de performance – System.InvalidOperationException: la catégorie n’existe pas

J’ai la classe suivante qui renvoie le nombre de requêtes en cours par seconde d’IIS. J’appelle RefreshCounters toutes les minutes pour que la valeur Demandes par seconde soit actualisée (car elle est moyenne et si je la conserve trop longtemps, l’ancienne valeur influence trop le résultat) … et lorsque j’ai besoin d’afficher RequestsPerSecond actuelle, j’appelle cette propriété.

public class Counters { private static PerformanceCounter pcReqsPerSec; private const ssortingng counterKey = "Requests_Sec"; public static object RequestsPerSecond { get { lock (counterKey) { if (pcReqsPerSec != null) return pcReqsPerSec.NextValue().ToSsortingng("N2"); // EXCEPTION else return "0"; } } } internal static ssortingng RefreshCounters() { lock (counterKey) { try { if (pcReqsPerSec != null) { pcReqsPerSec.Dispose(); pcReqsPerSec = null; } pcReqsPerSec = new PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true); pcReqsPerSec.NextValue(); PerformanceCounter.CloseSharedResources(); return null; } catch (Exception ex) { return ex.ToSsortingng(); } } } } 

Le problème est que l’exception suivante est parfois levée:

 System.InvalidOperationException: Category does not exist. at System.Diagnostics.PerformanceCounterLib.GetCategorySample(Ssortingng machine,\ Ssortingng category) at System.Diagnostics.PerformanceCounter.NextSample() at System.Diagnostics.PerformanceCounter.NextValue() at BidBop.Admin.PerfCounter.Counters.get_RequestsPerSecond() in [[[pcReqsPerSec.NextValue().ToSsortingng("N2");]]] 

Est-ce que je ne ferme pas correctement les instances précédentes de PerformanceCounter? Qu’est-ce que je fais de mal pour que je finisse parfois avec cette exception?

EDIT: Et pour le compte rendu, j’héberge cette classe sur le site Web IIS (logé, bien sûr, dans App Pool, qui dispose de privilèges administratifs) et j’appelle des méthodes du service ASMX. Le site qui utilise les valeurs de compteur (les affiche) appelle RefreshCounters toutes les minutes et RequestsPerSecond toutes les 5 secondes. RequestPerSecond sont mis en cache entre les appels.

J’appelle RefreshCounters toutes les minutes car les valeurs ont tendance à devenir “obsolètes” – elles sont trop influencées par les valeurs plus anciennes (qui étaient réelles il y a une minute, par exemple).

Antenka vous a conduit dans la bonne direction ici. Vous ne devez pas supprimer et recréer le compteur de performance à chaque mise à jour / demande de valeur. Il y a un coût pour l’instanciation des compteurs de performance et la première lecture peut être inexacte, comme indiqué dans le devis ci-dessous. De plus, vos déclarations lock() { ... } sont très larges (elles couvrent de nombreuses déclarations) et seront lentes. Il vaut mieux avoir vos serrures aussi petites que possible. Je donne à Antenka un vote pour la référence de qualité et de bons conseils!

Cependant, je pense pouvoir vous fournir une meilleure réponse. J’ai une bonne expérience de la surveillance des performances du serveur et je comprends exactement ce dont vous avez besoin. Un problème que votre code ne prend pas en compte est que le code qui affiche votre compteur de performance (.aspx, .asmx, application console, application Winform, etc.) peut demander cette statistique à tout moment. il pourrait être demandé une fois toutes les 10 secondes, peut-être 5 fois par seconde, vous ne savez pas et ne devriez pas vous en soucier. Vous devez donc séparer le code de la collection PerformanceCounter de celui qui effectue la surveillance du code qui rapporte réellement la valeur Requests / Second actuelle. Et pour des raisons de performance, je vais également vous montrer comment configurer le compteur de performance à la première demande, puis le laisser fonctionner jusqu’à ce que personne ne l’ait faite pendant 5 secondes, puis fermer / éliminer correctement le PerformanceCounter.

 public class RequestsPerSecondCollector { #region General Declaration //Static Stuff for the polling timer private static System.Threading.Timer pollingTimer; private static int stateCounter = 0; private static int lockTimerCounter = 0; //Instance Stuff for our performance counter private static System.Diagnostics.PerformanceCounter pcReqsPerSec; private readonly static object threadLock = new object(); private static decimal CurrentRequestsPerSecondValue; private static int LastRequestTicks; #endregion #region Singleton Implementation ///  /// Static members are 'eagerly initialized', that is, /// immediately when class is loaded for the first time. /// .NET guarantees thread safety for static initialization. ///  private static readonly RequestsPerSecondCollector _instance = new RequestsPerSecondCollector(); #endregion #region Constructor/Finalizer ///  /// Private constructor for static singleton instance construction, you won't be able to instantiate this class outside of itself. ///  private RequestsPerSecondCollector() { LastRequestTicks = System.Environment.TickCount; // Start things up by making the first request. GetRequestsPerSecond(); } #endregion #region Getter for current requests per second measure public static decimal GetRequestsPerSecond() { if (pollingTimer == null) { Console.WriteLine("Starting Poll Timer"); // Let's check the performance counter every 1 second, and don't do the first time until after 1 second. pollingTimer = new System.Threading.Timer(OnTimerCallback, null, 1000, 1000); // The first read from a performance counter is notoriously inaccurate, so OnTimerCallback(null); } LastRequestTicks = System.Environment.TickCount; lock (threadLock) { return CurrentRequestsPerSecondValue; } } #endregion #region Polling Timer static void OnTimerCallback(object state) { if (System.Threading.Interlocked.CompareExchange(ref lockTimerCounter, 1, 0) == 0) { if (pcReqsPerSec == null) pcReqsPerSec = new System.Diagnostics.PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true); if (pcReqsPerSec != null) { try { lock (threadLock) { CurrentRequestsPerSecondValue = Convert.ToDecimal(pcReqsPerSec.NextValue().ToSsortingng("N2")); } } catch (Exception) { // We had problem, just get rid of the performance counter and we'll rebuild it next revision if (pcReqsPerSec != null) { pcReqsPerSec.Close(); pcReqsPerSec.Dispose(); pcReqsPerSec = null; } } } stateCounter++; //Check every 5 seconds or so if anybody is still monitoring the server PerformanceCounter, if not shut down our PerformanceCounter if (stateCounter % 5 == 0) { if (System.Environment.TickCount - LastRequestTicks > 5000) { Console.WriteLine("Stopping Poll Timer"); pollingTimer.Dispose(); pollingTimer = null; if (pcReqsPerSec != null) { pcReqsPerSec.Close(); pcReqsPerSec.Dispose(); pcReqsPerSec = null; } } } System.Threading.Interlocked.Add(ref lockTimerCounter, -1); } } #endregion } 

Ok maintenant pour quelques explications.

  1. Vous remarquerez d’abord que cette classe est conçue pour être un singleton statique. Vous ne pouvez pas en charger plusieurs copies, il a un constructeur privé et une instance interne initialisée avec impatience. Cela garantit que vous ne créez pas accidentellement plusieurs copies du même PerformanceCounter .
  2. Ensuite, vous remarquerez que dans le constructeur privé (il ne sera exécuté qu’une seule fois lors du premier access à la classe), nous créons le PerformanceCounter et une timer qui sera utilisée pour interroger le PerformanceCounter .
  3. La méthode de rappel du minuteur créera le PerformanceCounter si nécessaire et obtiendra sa prochaine valeur disponible. De plus, toutes les 5 itérations, nous verrons depuis combien de temps sa dernière demande de valeur pour PerformanceCounter . Si plus de 5 secondes se sont écastings, nous arrêterons le minuteur d’interrogation, car ce n’est pas nécessaire pour le moment. Nous pouvons toujours le redémarrer plus tard si nous en avons besoin à nouveau.
  4. Nous avons maintenant une méthode statique appelée GetRequestsPerSecond() que vous appellerez, qui renverra la valeur actuelle du RequestsPerSecond PerformanceCounter .

Les avantages de cette implémentation sont que vous créez le compteur de performance une seule fois, puis continuez à l’utiliser jusqu’à ce que vous en ayez fini. Il est facile à utiliser, car vous appelez simplement RequestsPerSecondCollector.GetRequestsPerSecond() de partout où vous en avez besoin (.aspx, .asmx, application console, application Winforms, etc.). Il y aura toujours un seul PerformanceCounter et il sera toujours interrogé exactement 1 fois par seconde, quelle que soit la rapidité avec laquelle vous appelez RequestsPerSecondCollector.GetRequestsPerSecond() . Il ferme également automatiquement le PerformanceCounter le PerformanceCounter si vous n’avez pas demandé sa valeur depuis plus de 5 secondes. Bien entendu, vous pouvez régler l’intervalle de timer et le délai d’expiration en millisecondes en fonction de vos besoins. Vous pouvez interroger plus rapidement et expirer en 60 secondes au lieu de 5 secondes. J’ai choisi 5 secondes, ce qui prouve que cela fonctionne très rapidement lors du débogage dans Visual Studio. Une fois que vous l’avez testé et que vous savez qu’il fonctionne, vous souhaiterez peut-être un délai plus long.

J’espère que cela vous aidera non seulement à mieux utiliser PerformanceCounters, mais également à réutiliser cette classe, qui est distincte de ce que vous voulez afficher les statistiques. Le code réutilisable est toujours un avantage!

EDIT: Comme question de suivi, que se passe-t-il si vous souhaitez exécuter une tâche de nettoyage ou de garde toutes les 60 secondes pendant que ce compteur de performances est en cours d’exécution? Nous avons déjà le minuteur en marche toutes les secondes et une variable qui suit nos itérations de boucle appelée stateCounter qui est incrémentée à chaque rappel du minuteur. Vous pouvez donc append du code comme celui-ci:

 // Every 60 seconds I want to close/dispose my PerformanceCounter if (stateCounter % 60 == 0) { if (pcReqsPerSec != null) { pcReqsPerSec.Close(); pcReqsPerSec.Dispose(); pcReqsPerSec = null; } } 

Je devrais souligner que ce compteur de performance dans l’exemple ne doit pas “disparaître”. Je crois que “Demande / Sec” devrait être une statistique moyenne et non une moyenne mobile . Mais cet exemple illustre simplement une manière de procéder à tout type de nettoyage ou de “surveillance” de votre PerformanceCounter selon un intervalle de temps régulier. Dans ce cas, nous sums la fermeture et la suppression du compteur de performance qui le recréera lors du prochain rappel du minuteur. Vous pouvez le modifier en fonction de votre cas d’utilisation et en fonction du PerformanceCounter que vous utilisez. La plupart des lecteurs de cette question / réponse n’auront pas besoin de le faire. la documentation de votre PerformanceCounter souhaité pour voir s’il s’agit d’un nombre continu, d’une moyenne, d’une moyenne mobile, etc., et ajuster votre implémentation de manière appropriée.

Je ne sais pas si cela vous réussit .. J’ai lu l’article PerformanceCounter.NextValue, méthode

Et il y avait un commentaire:

 // If the category does not exist, create the category and exit. // Performance counters should not be created and immediately used. // There is a latency time to enable the counters, they should be created // prior to executing the application that uses the counters. // Execute this sample a second time to use the category. 

J’ai donc une question qui peut amener à répondre: l’appel d’une méthode RequestsPerSecond ne se produit-il pas trop tôt? De plus, je vous suggérerais d’essayer de vérifier si la catégorie n’existe pas et de consigner les informations quelque part afin que nous puissions l’parsingr et déterminer quelles conditions nous avons et à quelle fréquence.

Je viens de résoudre ce type d’erreur ou d’exception avec:

En utilisant,

 new PerformanceCounter("Processor Information", "% Processor Time", "_Total"); 

Au lieu de,

 new PerformanceCounter("Processor", "% Processor Time", "_Total"); 

J’ai eu un problème d’extraction de requêtes par seconde sur IIS à l’aide d’un code similaire à celui-ci.

 var pc = new PerformanceCounter(); pc.CategoryName = @"W3SVC_W3WP"; pc.InstanceName = @"_Total"; pc.CounterName = @"Requests / Sec"; Console.WriteLine(pc.NextValue()); 

Cela lançait parfois InvalidOperationException et j’ai pu reproduire l’exception en redémarrant IIS. Si j’exécute avec un IIS non réchauffé, par exemple après un redémarrage d’ordinateur portable ou un redémarrage d’IIS, j’obtiens cette exception. Allez d’abord sur le site Web, faites une demande http au préalable, et attendez une seconde ou deux et je ne reçois pas l’exception. Cela sent que les compteurs de performance sont mis en cache et lorsqu’ils sont inactifs, ils sont vidés et prennent un certain temps à se remettre en cache? (ou similaire).

Update1 : lorsque je navigue manuellement sur le site Web et le réchauffe, le problème est résolu. J’ai essayé de réchauffer le serveur par programme avec le new WebClient().DownloadSsortingng(); Thread.Sleep() new WebClient().DownloadSsortingng(); Thread.Sleep() jusqu’à 3000 ms et cela n’a pas fonctionné? Ainsi, mes résultats de réchauffement manuel du serveur pourraient en quelque sorte être un faux positif. Je laisse ma réponse ici, parce que cela pourrait en être la cause (par exemple, un échauffement manuel), et peut-être que quelqu’un d’autre pourra élaborer davantage?

Update2 : Ah, ok, voici quelques tests unitaires qui résument certaines leçons tirées d’expérimentations plus poussées que j’ai faites hier. (Il n’y a pas beaucoup sur Google à ce sujet d’ailleurs.)

Autant que je puisse raisonner, les affirmations suivantes pourraient être vraies; (et je soumets les tests unitaires ci-dessous à titre de preuve.) J’ai peut-être mal interprété les résultats, veuillez donc vérifier à deux resockets ;-D

  1. Créez un compteur de performance et appelez getValue avant que la catégorie n’existe. Par exemple, interroger un compteur IIS alors qu’IIS est froid et qu’aucun processus n’est en cours d’exécution générera une exception InvalidOperation “la catégorie n’existe pas”. (Je suppose que cela est vrai pour tous les compteurs, et pas seulement pour IIS.)

  2. À partir d’un test unitaire Visual Studio, une fois que votre compteur lève une exception, si vous réchauffez ensuite le serveur après la première exception et créez un nouveau PerformanceCounter, puis relancez une requête, il lève toujours une exception! (Celui-ci était une surprise, je suppose que c’est à cause d’une action singleton. Mes excuses, je n’ai pas eu le temps de décomstackr les sources pour enquêter plus avant avant de poster cette réponse.)

  3. En 2 ci-dessus, si vous marquez le test unitaire avec [STAThread] je suis alors en mesure de créer un nouveau PerformanceCounter après l’échec de celui-ci. (Cela pourrait avoir quelque chose à voir avec le compteur de performances pouvant être des singletons? Des tests supplémentaires sont nécessaires.)

  4. Aucune pause n’était requirejse pour moi avant de créer et d’utiliser un compteur, malgré certains avertissements dans la même documentation de code MSDN, à l’exception du temps nécessaire pour créer un compteur de performance avant d’appeler NextValue (). Dans mon cas, pour réchauffer le compteur et apporter la “catégorie” à l’existence, c’est pour moi de tirer un coup de feu sur l’arc d’IIS, c’est-à-dire faire une seule requête GET, et l’alto ne reçoit plus “InvalidOperationException”, et cela semble être un correctif fiable pour moi, car à présent. Au moins lorsque vous interrogez des compteurs de performance IIS.

CreatingPerformanceCounterBeforeWarmingUpServerThrowsException

 [Test, Ignore("Run manually AFTER restarting IIS with 'iisreset' at cmd prompt.")] public void CreatingPerformanceCounterBeforeWarmingUpServerThrowsException() { Console.WriteLine("Given a webserver that is cold"); Console.WriteLine("When I create a performance counter and read next value"); using (var pc1 = new PerformanceCounter()) { pc1.CategoryName = @"W3SVC_W3WP"; pc1.InstanceName = @"_Total"; pc1.CounterName = @"Requests / Sec"; Action action1 = () => pc1.NextValue(); Console.WriteLine("Then InvalidOperationException will be thrown"); action1.ShouldThrow(); } } [Test, Ignore("Run manually AFTER restarting IIS with 'iisreset' at cmd prompt.")] public void CreatingPerformanceCounterAfterWarmingUpServerDoesNotThrowException() { Console.WriteLine("Given a webserver that has been Warmed up"); using (var client = new WebClient()) { client.DownloadSsortingng("http://localhost:8082/small1.json"); } Console.WriteLine("When I create a performance counter and read next value"); using (var pc2 = new PerformanceCounter()) { pc2.CategoryName = @"W3SVC_W3WP"; pc2.InstanceName = @"_Total"; pc2.CounterName = @"Requests / Sec"; float? result = null; Action action2 = () => result = pc2.NextValue(); Console.WriteLine("Then InvalidOperationException will not be thrown"); action2.ShouldNotThrow(); Console.WriteLine("And the counter value will be returned"); result.HasValue.Should().BeTrue(); } } 

Par simple curiosité, qu’avez-vous défini pour les propriétés dans Visual Studio? Dans VS, allez dans Propriétés du projet, Construire, cible de la plate-forme et remplacez-le par AnyCPU . Je l’ai déjà vu où les compteurs de performance ne sont pas toujours récupérés quand il est défini sur x86 , et le changer en AnyCPU pourrait résoudre le problème.