Production debugging : 100% CPU dans une application ASP.NET

Le but de cet article est de donner deux méthodes (la simple, l’avancée) permettant de trouver la cause d’un 100% CPU qui ne se produit que sur un environnement de production sur lequel nous n’avons pas toute la panoplie d’outils nous permettant de trouver rapidement la cause.

(Les ecrans qui suivent sont sur Windows 2008 R2)

Diagnostiques de base

Avant de passer dans les diagnostiques avancés, nous allons commencer par essayer d’isoler une URL qui génère ce probleme.

A l’aide de l’outil d’administration de IIS, au niveau serveur, on peut afficher la liste des processus généré par IIS.

Pour chaque process, il est possible de voir la liste des Requetes.

Et de trouver ainsi celle qui pose probleme dans un cas particulier.

A l’aide de ces requetes on a un déjà une bonne idée d’ou peut se situer le probleme.

Diagnostiques avancés

Connaitre la liste des requetes ne résoud pas pour autant le probleme dans tous les cas (cas ou cela ne depend pas de la requete mais d’un evenement exterieur…). Et il faudrait au minimum savoir quel bout de code génère ce 100% CPU. S’il n’est pas facile de le connaitre de prime abord, il est possible à l’aide de la stack trace des thread en cours d’execution de savoir approximativement ou se trouve le probleme.

Le principe qui va etre appliqué pour ce diagnostique avancé, sera de prendre des images du processus en cours et de regarder dans quel partie du code se trouve le programme. A l’aide de plusieurs de ces images prises a des moments différents, on va pouvoir grossierement déterminer ou se trouve le bout de code en cause.

Prérequis

Il faut commencer par télécharger les debugging tools pour windows et les installer sur le serveur ou le problème se produit : (Voir plus bas une méthode par le gestionnaire de tache ne nécessitant pas ce prérequis sur le serveur)

http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx

Une fois installé dans le dossier de destination vous devez avoir l’outils adplus.exe :

Récupération du Process ID

Repérer le PID du processus ASP.NET qui pose problème à l’aide du gestionnaire de taches (Menu View > Select columns) :

Enregistrement d’images mémoires (Dumps)

Nous allons utiliser adplus.exe pour générer un Dump complet du processus que nous pourrons analyser à l’aide de l’outil WinDbg.

Prenons comme exemple ou le processus w3wp.exe utilisant la majorité du CPU est le PID : 3376, et nous crérons un dossier c:\dumps qui contiendra l’ensemble des dumps du processus.

adplus.exe -hang -p 3376 -quiet -o c:\dumps

Patienter environ 10s après que le programme vous ait rendu la main.

Il vous faudra reproduire cette actions plusieurs fois lorsque le probleme se produit. Attention le process est mis en pause (freezer) pendant les quelques secondes du Dump mémoire et ne répondra donc plus aux requetes utilisateurs pendant cette durée.

Dump sans ADPlus

Si vous n’avez pas le temps d’installer ADPlus, vous pouvez tout de même effectué des dumps directement depuis le gestionnaire des taches (TaskManager). Pour cela, dans l’onglet process, faites un clic droit sur le process en question, puis Create Dump File.

Analyse d’une image mémoire (dump)

En plus de AdPlus, les debugging tools incluent l’outils WinDbg que vous trouverez dans votre menu démarrer.

Lancez cet outil. (attention à executer celui correspondant à votre architecture à debugger : x64 ou x86)

Ouvrez le Dump en utilisant : File > Open Crash Dump et en allant chercher le dump qui a été généré dans le dossier que vous avez fournit à adplus lors de l’étape précédente.

Pour qu’il soit capable de transformer des adresses dans des DLLs en Nom de méthode, vous allez devoir charger les symbols. Le plus simple est de laisser WinDbg le faire tout seul en lui indiquant le serveur de Microsoft adapté.

SRV*c:\temp*http://msdl.microsoft.com/download/symbols

Adapter WinDbg aux diagnostiques de code .NET :

dans la fenêtre command :

.loadby sos clr

Maintenant il ne reste plus qu’a lister la pile des appels (call stack) de l’ensemble des threads pour récupérer l’emplacement d’execution à l’instant de ce dump mémoire.

Pour cela la commande ci-dessous a exécuter dans la fenêtre de commande va lister l’ensemble des piles d’appels dans les processus et threads en cours d’execution dans ce dump :

~* e !clrstack

Maintenant, il reste a partir de ces informations a en déduire le problème.

plus d’informations :

Sharepoint 2010 : WebPart Générique pour charger des Contrôles ASP.NET

Le modèle de création de WebPart Sharepoint poussé par Microsoft est de faire contenir dans une WebPart un contrôle ASP.NET. Le contenu du WebPart étant assez simple, on peut en faire un générique dont un paramètre permettra de définir le contrôle ASP.NET à charger.

L’intérêt, bien que limité, permet de créer des interfaces qui ne seront pas disponible directement à travers l’ajout de WebPart mais en connaissant le chemin du contrôle ASP.NET uniquement. Cela peut être pratique lorsqu’on a quelques contrôles ASP.NET de debug qu’on ne souhaite pas faire apparaitre dans la liste des WebPart disponibles.

Elle permettra aussi de déployer des contrôles (dépourvus de code behind) en uploadant des contrôles dans Sharepoint.

Il conviendra de vérifier que seuls les administrateurs aient le droit de modifier le chemin car dans le cas contraire cela pourrait entrainer une faille de sécurité.

[ToolboxItemAttribute(false)]
 public class LoadUserControl : Microsoft.SharePoint.WebPartPages.WebPart
 {
 [WebBrowsable(true),
 Category("WebPart Configuration"),
 WebPartStorage(Storage.Shared),
 FriendlyName("ASCX"),
 Description("Chemin du controle ASP.NET."),
 Personalizable(true)]
 public string ascx { get; set; }

 protected override void CreateChildControls()
 {
 if (!string.IsNullOrEmpty(ascx))
 {
 Control control = Page.LoadControl(ascx);
 Controls.Add(control);
 }
 }
 }

Exemple d’exploitation pour charger un contrôle :

<webParts>
 <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
 <metaData>
 <type name="MyNameSpace.LoadUserControl.LoadUserControl, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0000000000000000" />
 <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage>
 </metaData>
 <data>
 <properties>
 <property name="Title" type="string">Titre publique de la WebPart</property>
 <property name="Description" type="string">My WebPart</property>
 <property name="ascx" type="string">~/_CONTROLTEMPLATES/Exemple/Exemple.ascx</property>
 </properties>
 </data>
 </webPart>
</webParts>

Solution élegante pour le probleme du OnClick OnChange sur une checkbox

Lorsque vous souhaitez faire évoluer le contenu de votre page Web lorsque qu’un utilisateur clic sur une case à cocher, On est rapidement confronté au problème du OnChange qui agit différemment en fonction des navigateurs.

Mais lequel des navigateurs a raison d’après les standards ?

D’après mes recherche, il semblerait que les standards ne soit pas assez précis sur un point : La définition exacte de la perte du focus.

Si vous consultez les standards sur l’évènement onchange http://www.w3.org/TR/REC-html40/interact/scripts.html#h-18.2.3, vous verrez qu’il y est clairement explicité que le onchange doit être appelé lorsque le contrôle perd le focus et que la valeur a changé depuis qu’il a reçu le focus.

Il est explicité ici quand est ce qu’un contrôle reçoit le focus : http://www.w3.org/TR/html401/interact/forms.html#h-17.11, mais il ne permet de déterminer précisément à quel moment un contrôle perd le focus.

La solution la plus simple pour remédier à ce problème est de ne pas utiliser le onchange sur une checkbox, mais le onclick.

Toutefois, cette solution ne fonctionne pas dans tous les cas. En particulier lorsque la méthode appelée en javascript est complexe et appelle des fonctions d’annulation d’évènements. Dans ce dernier cas, l’action est réalisée, mais la case à cocher ne se décoche par sur Internet Explorer.

Alors une idée m’est venue, puisque le problème  sous-jacent est un problème de moment ou le contrôle perd le focus, pourquoi ne pas nous même décider du moment ou le focus est perdu ?

exemple :

<input type="checkbox" onclick="$(this).blur();" onchange="alert('modifié');"/>

(utilise le framework jQuery pour l’appel du blur).

Cette exemple fonctionne de la même façon sur tous les Navigateurs et si vous devez appeler des fonctions d’annulations d’évènements dans le onchange, on pourra toujours cocher et décocher la case sous Internet Explorer.

Exemple concret du problème sous Internet Explorer : http://webbugtrack.blogspot.com/2007/11/bug-193-onchange-does-not-fire-properly.html

IIS Application WarmUP

Lorsque dans votre application ASP.NET vous souhaitez effectuer des actions a heures précises, vous ne pouviez jusqu’à présent le faire de façon fiable qu’en utilisant un Service Windows ou en utilisant le gestionnaire de taches planifiées.

La complexité de la solution “service windows” est la communication entre ce service et l’application ASP.NET. La solution que j’ai trouvé la plus fiable jusque la est l’utilisation du Remoting.

Mais, la solution la plus simple, était de mettre dans l’application ASP.NET un thread qui fournissait cette fonctionnalité. Je n’ai pas utilisé jusque là cette solution car lorsque l’application était recyclée et qu’aucun utilisateur ne se connectait au site internet l’action planifiée n’était pas déclenchée.

Récemment, je suis tombé sur l’Application WarmUP (Béta 1) de Microsoft. Ce produit a comme fonctionnalité principale de précharger l’application permettant ainsi une meilleure réactivité pour le premier utilisateur.

Il se pourrait bien que cette application me permette aussi de m’affranchir de la lourdeur de déployer un service Windows ou une tache planifiée !

Update 25/10/2010 : Après une série de tests, c’est concluant !

Ajax et l’historique du navigateur

Un problème récurent lorsque l’on développe une application << Web 2.0 >> est l’utilisation du bouton back par l’utilisateur.
Dans la plupart des cas, les appels AJAX ne sont pas historisés amenant a une page qui ne correspond plus à la dernière action de l’utilisateur.
Ramener l’utilisateur à la visu après son dernier appel Ajax ne résoudrait pas tous les problèmes, mais amènerait déjà une amélioration.

Au moins deux approches pour corriger ce problème existent.
– Utiliser un framework gérant un historique au niveau du serveur et ramener une information cohérente. Cette approche bien que la plus fiable entraine une complexité au niveau du développement, des temps de chargements allongés, une surcharge en trafic réseau qui ne saurait en faire une alternative raisonnable dans la plupart des développements “Agile”.
– Utiliser un framework simulant un changement de page apres une activité AJAX permettant de “forcer” le navigateur a ajouter un evenement affectant l’historique. Le coup d’intégration de cette solution est variable en fonction de la méthode utilisée mais reste inférieure a une solution serveur.

La deuxième approche est dans la plupart des cas retenue, elle peut elle même se décliner en plusieurs sous approche :
– Intégration simple : intégration au niveau des liens et bouton d’un handler gardant l’état précédent.
– Intégration spécifique : Au retour des appels Ajax appeler une méthode permettant d’historiser le changement.

Même si l’intégration spécifique est un peu plus couteuse, personnellement je préfère utiliser cette dernière.

Liens :
http://code.google.com/p/reallysimplehistory/

http://www.balupton.com/projects/jquery-history
http://www.balupton.com/#/projects/jquery-ajaxy

Mais techniquement quel est le principe de cette seconde approche ?
C’est on ne peu plus simple, elle utilise une des fonctions existant depuis la nuit des temps en HTML, les liens inter-ancre :
<a href=”#madeuxiemeancre”>goto</a> <a name=”madeuxiemeancre”></a>
le navigateur historise un evenement lors de cette navigation. Il ne reste plus qu’a ramener en javascript le contenu correspondant au #madeuxiemeancre

AJAX – Introducing AJAX Navigations